├── .browserslistrc
├── .eslintignore
├── .eslintrc.js
├── .gitattributes
├── .github
└── workflows
│ └── ci.yml
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── api-extractor.json
├── babel.config.js
├── karma.conf.js
├── package-lock.json
├── package.json
├── rollup.config.js
├── src
└── index.ts
├── test
├── index.spec.js
└── scripts
│ ├── bar.js
│ ├── baz.js
│ └── foo.js
└── tsconfig.json
/.browserslistrc:
--------------------------------------------------------------------------------
1 | > 1%
2 | last 2 versions
3 | not ie <= 9
4 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | *.local*
2 | .temp
3 | coverage
4 | dist
5 | node_modules
6 | test/scripts
7 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: {
4 | browser: true,
5 | node: true,
6 | },
7 | extends: [
8 | 'airbnb-base',
9 | 'airbnb-typescript/base',
10 | 'plugin:@typescript-eslint/recommended',
11 | ],
12 | parser: '@typescript-eslint/parser',
13 | parserOptions: {
14 | project: 'tsconfig.json',
15 | sourceType: 'module',
16 | },
17 | plugins: [
18 | '@typescript-eslint',
19 | ],
20 | rules: {
21 | '@typescript-eslint/no-var-requires': 'off',
22 | },
23 | overrides: [
24 | {
25 | files: ['test/**/*.spec.js'],
26 | env: {
27 | mocha: true,
28 | },
29 | globals: {
30 | expect: true,
31 | },
32 | },
33 | ],
34 | };
35 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches: [main]
6 | pull_request:
7 | branches: [main]
8 |
9 | jobs:
10 | build:
11 | runs-on: ubuntu-latest
12 |
13 | steps:
14 | - uses: actions/checkout@v2
15 | - uses: actions/setup-node@v2
16 | with:
17 | node-version: 14
18 | - run: npm install
19 | - run: npm run lint
20 | - run: npm run build
21 | - run: npm test
22 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.local*
2 | *.log
3 | *.map
4 | .DS_Store
5 | .temp
6 | coverage
7 | dist
8 | node_modules
9 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # [2.0.0](https://github.com/fengyuanchen/load-scripts/compare/v1.1.0...v2.0.0) (2021-09-21)
2 |
3 |
4 | * refactor!: migrate to TypeScript ([0550177](https://github.com/fengyuanchen/load-scripts/commit/05501779c109b2a618bd971a5fbab43ef6903a80))
5 |
6 |
7 | ### BREAKING CHANGES
8 |
9 | * drop `dist/load-scripts.common.js` from the package
10 |
11 |
12 |
13 | # [1.1.0](https://github.com/fengyuanchen/load-scripts/compare/v1.0.1...v1.1.0) (2021-09-20)
14 |
15 |
16 | ### Features
17 |
18 | * add type definitions for TypeScript ([700e649](https://github.com/fengyuanchen/load-scripts/commit/700e649c1e9599d3282a6d22a6dbe61f99d5093c))
19 | * improve script url matching ([fd78f8c](https://github.com/fengyuanchen/load-scripts/commit/fd78f8c048c251d62a2938d12719822b7e192b3b))
20 |
21 |
22 |
23 | ## [1.0.1](https://github.com/fengyuanchen/load-scripts/compare/v1.0.0...v1.0.1) (2021-09-19)
24 |
25 |
26 | ### Bug Fixes
27 |
28 | * avoid loading script repeatedly ([626ae03](https://github.com/fengyuanchen/load-scripts/commit/626ae03fd5ad73b84f3be0b0a57b8e11ec4eed2e))
29 |
30 |
31 |
32 | # [1.0.0](https://github.com/fengyuanchen/load-scripts/compare/v0.1.0...v1.0.0) (2018-05-21)
33 |
34 |
35 |
36 | # 0.1.0 (2018-04-11)
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright 2018-present Chen Fengyuan
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # load-scripts
2 |
3 | [](https://www.npmjs.com/package/load-scripts) [](https://www.npmjs.com/package/load-scripts) [](https://unpkg.com/load-scripts/dist/load-scripts.js)
4 |
5 | > Dynamic scripts loading for modern browsers.
6 |
7 | ## Main files
8 |
9 | ```text
10 | dist/
11 | ├── load-scripts.js (UMD, default)
12 | ├── load-scripts.min.js (UMD, compressed)
13 | ├── load-scripts.esm.js (ECMAScript Module)
14 | ├── load-scripts.esm.min.js (ECMAScript Module, compressed)
15 | └── load-scripts.d.ts (TypeScript Declaration File)
16 | ```
17 |
18 | ## Getting started
19 |
20 | ### Installation
21 |
22 | ```shell
23 | npm install load-scripts
24 | ```
25 |
26 | In browser:
27 |
28 | ```html
29 |
30 | ```
31 |
32 | ### Usage
33 |
34 | #### Syntax
35 |
36 | ```js
37 | loadScripts(script1, script2, ..., scriptN)
38 | .then(() => {})
39 | .catch((err) => {})
40 | .finally(() => {});
41 | ```
42 |
43 | #### Example
44 |
45 | ```js
46 | import loadScripts from 'load-scripts';
47 |
48 | loadScripts('foo.js').then(() => {
49 | console.log(window.Foo);
50 | });
51 |
52 | loadScripts('foo.js', 'bar.js').then(() => {
53 | console.log(window.Foo, window.Bar);
54 | });
55 | ```
56 |
57 | In browser:
58 |
59 | ```html
60 |
65 | ```
66 |
67 | ## Browser support
68 |
69 | - Chrome (latest)
70 | - Firefox (latest)
71 | - Safari (latest)
72 | - Opera (latest)
73 | - Edge (latest)
74 | - Internet Explorer 10+ (requires a `Promise` polyfill as [es6-promise](https://github.com/stefanpenner/es6-promise))
75 |
76 | ## Versioning
77 |
78 | Maintained under the [Semantic Versioning guidelines](https://semver.org/).
79 |
80 | ## License
81 |
82 | [MIT](https://opensource.org/licenses/MIT) © [Chen Fengyuan](https://chenfengyuan.com/)
83 |
--------------------------------------------------------------------------------
/api-extractor.json:
--------------------------------------------------------------------------------
1 | {
2 | "mainEntryPointFilePath": "./.temp/index.d.ts",
3 | "apiReport": {
4 | "enabled": false
5 | },
6 | "docModel": {
7 | "enabled": false
8 | },
9 | "dtsRollup": {
10 | "enabled": true,
11 | "publicTrimmedFilePath": "./dist/.d.ts"
12 | },
13 | "tsdocMetadata": {
14 | "enabled": false
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: ['@babel/preset-env'],
3 | };
4 |
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | const puppeteer = require('puppeteer');
2 |
3 | process.env.CHROME_BIN = puppeteer.executablePath();
4 | process.env.NODE_ENV = 'test';
5 |
6 | module.exports = (config) => {
7 | config.set({
8 | autoWatch: false,
9 | browsers: ['ChromeHeadless'],
10 | files: [
11 | 'dist/load-scripts.js',
12 | 'test/index.spec.js',
13 | {
14 | pattern: 'test/scripts/*',
15 | included: false,
16 | },
17 | ],
18 | frameworks: ['mocha', 'chai'],
19 | reporters: ['mocha'],
20 | singleRun: true,
21 | });
22 | };
23 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "load-scripts",
3 | "version": "2.0.0",
4 | "description": "Dynamic scripts loading for modern browsers.",
5 | "main": "dist/load-scripts.js",
6 | "module": "dist/load-scripts.esm.js",
7 | "types": "dist/load-scripts.d.ts",
8 | "files": [
9 | "dist"
10 | ],
11 | "scripts": {
12 | "build": "rollup -c --environment BUILD:production",
13 | "build:api": "api-extractor run --local --verbose",
14 | "build:dts": "tsc --outDir ./.temp --declaration --emitDeclarationOnly",
15 | "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0",
16 | "clean": "del-cli dist .temp",
17 | "lint": "eslint . --ext .js,.ts --fix",
18 | "release": "npm run clean && npm run lint && npm run build:dts && npm run build && npm run build:api && npm test && npm run changelog",
19 | "test": "karma start karma.conf.js"
20 | },
21 | "repository": {
22 | "type": "git",
23 | "url": "git+https://github.com/fengyuanchen/load-scripts.git"
24 | },
25 | "keywords": [
26 | "load",
27 | "script",
28 | "scripts",
29 | "dynamic",
30 | "async",
31 | "promise",
32 | "browser"
33 | ],
34 | "author": {
35 | "name": "Chen Fengyuan",
36 | "url": "https://chenfengyuan.com/"
37 | },
38 | "license": "MIT",
39 | "bugs": {
40 | "url": "https://github.com/fengyuanchen/load-scripts/issues"
41 | },
42 | "homepage": "https://github.com/fengyuanchen/load-scripts",
43 | "devDependencies": {
44 | "@babel/core": "^7.15.5",
45 | "@babel/preset-env": "^7.15.6",
46 | "@microsoft/api-extractor": "^7.18.9",
47 | "@rollup/plugin-typescript": "^8.2.5",
48 | "@typescript-eslint/eslint-plugin": "^4.31.2",
49 | "@typescript-eslint/parser": "^4.31.2",
50 | "chai": "^4.3.4",
51 | "change-case": "^4.1.2",
52 | "conventional-changelog-cli": "^2.1.1",
53 | "create-banner": "^2.0.0",
54 | "del-cli": "^4.0.1",
55 | "eslint": "^7.32.0",
56 | "eslint-config-airbnb-base": "^14.2.1",
57 | "eslint-config-airbnb-typescript": "^14.0.0",
58 | "eslint-plugin-import": "^2.24.2",
59 | "karma": "^6.3.4",
60 | "karma-chai": "^0.1.0",
61 | "karma-chrome-launcher": "^3.1.0",
62 | "karma-mocha": "^2.0.1",
63 | "karma-mocha-reporter": "^2.2.5",
64 | "mocha": "^9.1.1",
65 | "puppeteer": "^10.2.0",
66 | "rollup": "^2.56.3",
67 | "rollup-plugin-terser": "^7.0.2",
68 | "tslib": "^2.3.1",
69 | "typescript": "^4.4.3"
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import createBanner from 'create-banner';
2 | import typescript from '@rollup/plugin-typescript';
3 | import { camelCase } from 'change-case';
4 | import { terser } from 'rollup-plugin-terser';
5 | import pkg from './package.json';
6 |
7 | const name = camelCase(pkg.name);
8 | const banner = createBanner({
9 | data: {
10 | year: '2018-present',
11 | },
12 | template: 'inline',
13 | });
14 |
15 | export default ['umd', 'esm'].map((format) => ({
16 | input: 'src/index.ts',
17 | output: ['development', 'production'].map((mode) => {
18 | const output = {
19 | banner,
20 | format,
21 | name,
22 | file: pkg.main,
23 | };
24 |
25 | if (format === 'esm') {
26 | output.file = pkg.module;
27 | }
28 |
29 | if (mode === 'production') {
30 | output.compact = true;
31 | output.file = output.file.replace(/(\.js)$/, '.min$1');
32 | output.plugins = [
33 | terser(),
34 | ];
35 | }
36 |
37 | return output;
38 | }),
39 | plugins: [
40 | typescript(),
41 | ],
42 | }));
43 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | export default function loadScripts(...urls: string[]): Promise {
2 | return Promise.all(urls.map((url) => new Promise((resolve, reject) => {
3 | const parent = document.head || document.body || document.documentElement;
4 |
5 | // Avoid loading script repeatedly
6 | if (parent.querySelector(`script[src*="${url}"]`)) {
7 | resolve(url);
8 | return;
9 | }
10 |
11 | const script = document.createElement('script');
12 | const loadend = () => {
13 | script.onerror = null;
14 | script.onload = null;
15 | };
16 |
17 | script.onerror = () => {
18 | loadend();
19 | reject(new Error(`Failed to load script: ${url}`));
20 | };
21 | script.onload = () => {
22 | loadend();
23 | resolve(url);
24 | };
25 | script.async = true;
26 | script.src = url;
27 | parent.appendChild(script);
28 | })));
29 | }
30 |
--------------------------------------------------------------------------------
/test/index.spec.js:
--------------------------------------------------------------------------------
1 | describe('load-scripts', () => {
2 | it('should load single script correctly', (done) => {
3 | window.loadScripts('/base/test/scripts/foo.js').then(() => {
4 | expect(window.Foo).to.be.a('function');
5 | done();
6 | });
7 | });
8 |
9 | it('should load multiple scripts correctly', (done) => {
10 | window.loadScripts('/base/test/scripts/bar.js', '/base/test/scripts/baz.js').then(() => {
11 | expect(window.Bar).to.be.a('function');
12 | expect(window.Baz).to.be.a('function');
13 | done();
14 | });
15 | });
16 |
17 | it('should throw error', (done) => {
18 | const url = '/base/test/scripts/qux.js';
19 |
20 | window.loadScripts(url).catch((error) => {
21 | expect(error.message).to.include(url);
22 | done();
23 | });
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/test/scripts/bar.js:
--------------------------------------------------------------------------------
1 | window.Bar = () => {};
2 |
--------------------------------------------------------------------------------
/test/scripts/baz.js:
--------------------------------------------------------------------------------
1 | window.Baz = () => {};
2 |
--------------------------------------------------------------------------------
/test/scripts/foo.js:
--------------------------------------------------------------------------------
1 | window.Foo = () => {};
2 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowSyntheticDefaultImports": true,
4 | "moduleResolution": "node",
5 | "resolveJsonModule": true,
6 | "strict": true,
7 | "target": "esnext",
8 | },
9 | "include": ["*.js", ".*.js", "src", "test"]
10 | }
11 |
--------------------------------------------------------------------------------