├── .github └── workflows │ └── publish.yml ├── .gitignore ├── README.MD ├── index.js └── package.json /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: publish 2 | 3 | on: 4 | push: 5 | branches: 6 | - '*' 7 | tags: 8 | - v* 9 | 10 | jobs: 11 | publish-npm: 12 | if: startsWith(github.ref, 'refs/tags/v') 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v2 16 | with: 17 | ref: master 18 | 19 | - uses: volta-cli/action@v1 20 | 21 | - name: Authorize NPM 22 | run: npm config set //registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }} 23 | 24 | - name: Cache pnpm modules 25 | uses: actions/cache@v2 26 | with: 27 | path: ~/.pnpm-store 28 | key: ${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }} 29 | restore-keys: | 30 | ${{ runner.os }}- 31 | 32 | - uses: pnpm/action-setup@v2.1.0 33 | with: 34 | version: 6.0.2 35 | run_install: true 36 | 37 | - run: pnpm publish 38 | env: 39 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 | 5 |
6 |
7 |
8 | 9 | ## Async Script Loader 10 | 11 | [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg)](http://standardjs.com) 12 | 13 | Allows asynchronous loading of scripts and styles in a Single Page Application (or anything else, in fact): 14 | 15 | * Using a test to ensure that the code is only loaded once 16 | * Running a callback once the script is loaded 17 | * Running a callback if the script is already loaded 18 | * Not blocking the main thread 19 | 20 | ### Reasoning 21 | 22 | Having integrated a multitude of third-party SDKs from large, well known providers, I've come to the conclusion that not having a standard interface turns the whole thing into a minefield of callbacks, timers, random library-specific loader modules, and global objects on the window, resulting in XSS risks and all sort of other undesirable behaviour. This module aims to provide a standard way of loading third-party dependencies. 23 | 24 | ### Usage 25 | 26 | You pass a list of urls to the loader, along with a method for checking that your page is ready, and a callback to call when it is. 27 | 28 | Urls can be scripts or stylesheets. 29 | 30 | ### Script Tags 31 | 32 | You can use the module like so, for a library loaded from example.com, which, when loaded, adds an attribute called PROVIDER to the global window object. 33 | 34 | ```js 35 | 52 | ``` 53 | 54 | You can pass options for script tags. 55 | 56 | ```js 57 | 62 | ``` 63 | 64 | #### Style Tags 65 | 66 | You can include any number of tags, including style tags. 67 | 68 | When the last one has loaded, the callback will be called. 69 | 70 | ```js 71 | 84 | ``` 85 | 86 | No more tears! 87 | 88 | #### Inline scripts / Inline css 89 | 90 | You can use inline content for either type of tag by passing the configuration attribute `content` *instead* of `url`. This will write the content passed into the tag's body rather than setting it as an `href` or `src` attribute `url` will always take prescidence, so leave it out for `content` to work. 91 | 92 | 93 | ```js 94 | 105 | ``` -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | function load (urls, test, callback) { 2 | let remaining = urls.length 3 | 4 | function maybeCallback () { 5 | remaining = --remaining 6 | if (remaining < 1) { 7 | callback() 8 | } 9 | } 10 | 11 | if (test()) { 12 | return callback() 13 | } 14 | 15 | for (const { type, url, content, options = { async: true, defer: true }} of urls) { 16 | const isScript = type === 'script' 17 | const tag = document.createElement(isScript ? 'script': 'link') 18 | const attribute = isScript ? 'src' : 'href' 19 | const hasUrl = Boolean(url).valueOf() 20 | 21 | if (isScript) { 22 | tag.async = options.async 23 | tag.defer = options.defer 24 | } else { 25 | tag.rel = 'stylesheet' 26 | } 27 | 28 | if (hasUrl) { 29 | tag[attribute] = url 30 | } else { 31 | tag.appendChild( 32 | document.createTextNode(content) 33 | ) 34 | } 35 | 36 | tag.onload = maybeCallback 37 | document.body.appendChild(tag) 38 | } 39 | } 40 | 41 | export default load 42 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@beyonk/async-script-loader", 3 | "version": "2.3.0", 4 | "description": "Loads scripts cleanly and asynchronously in SPAs", 5 | "keywords": [ 6 | "async", 7 | "script", 8 | "loader", 9 | "js", 10 | "client" 11 | ], 12 | "author": "Antony Jones", 13 | "license": "MIT", 14 | "type": "module", 15 | "exports": { 16 | "./package.json": "./package.json", 17 | ".": "./index.js" 18 | }, 19 | "volta": { 20 | "node": "16.14.2" 21 | } 22 | } 23 | --------------------------------------------------------------------------------