├── .eslintignore
├── .prettierignore
├── test
├── constants.ts
├── removeScript.spec.ts
├── attrs.spec.ts
├── array.spec.ts
├── placement.spec.ts
└── basic.spec.ts
├── browser
├── index.ts
└── index.html
├── vite.config.ts
├── .prettierrc.json
├── vitest.config.ts
├── .gitignore
├── tsconfig.json
├── rollup.config.ts
├── .eslintrc.json
├── changelog.md
├── LICENSE
├── .github
└── workflows
│ └── check.yml
├── package.json
├── src
└── index.ts
└── README.md
/.eslintignore:
--------------------------------------------------------------------------------
1 | dist
2 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | dist
2 |
--------------------------------------------------------------------------------
/test/constants.ts:
--------------------------------------------------------------------------------
1 | export const TIMEOUT = 60_000;
2 |
--------------------------------------------------------------------------------
/browser/index.ts:
--------------------------------------------------------------------------------
1 | import simpleLoadScript from '../src/index.js';
2 |
3 | declare global {
4 | interface Window {
5 | simpleLoadScript: typeof simpleLoadScript;
6 | }
7 | }
8 |
9 | window.simpleLoadScript = simpleLoadScript;
10 |
11 | export {};
12 |
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { UserConfigExport, defineConfig } from 'vite';
2 |
3 | export default defineConfig({
4 | root: 'browser',
5 | server: {
6 | port: 3030,
7 | },
8 | preview: {
9 | port: 8080,
10 | },
11 | }) satisfies UserConfigExport;
12 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "arrowParens": "always",
3 | "printWidth": 80,
4 | "singleQuote": true,
5 | "trailingComma": "all",
6 | "semi": true,
7 | "bracketSpacing": true,
8 | "bracketSameLine": false,
9 | "endOfLine": "lf",
10 | "tabWidth": 4
11 | }
12 |
--------------------------------------------------------------------------------
/browser/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Test Page
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/vitest.config.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | import { UserConfigExport, defineConfig } from 'vite';
4 | import { TIMEOUT } from './test/constants';
5 |
6 | export default defineConfig({
7 | test: {
8 | testTimeout: TIMEOUT,
9 | hookTimeout: TIMEOUT,
10 | },
11 | }) satisfies UserConfigExport;
12 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # typical suspects
2 | **/node_modules/
3 | **/tmp/
4 | **/dist/
5 | **/build/
6 | **/out/
7 | **/.next/
8 | **/.vercel/
9 | .vscode
10 |
11 | # testing
12 | /coverage
13 | /test-results
14 |
15 | # misc
16 | .DS_Store
17 | *.pem
18 |
19 | # logs
20 | npm-debug.log*
21 | yarn-debug.log*
22 | yarn-error.log*
23 | pnpm-debug.log*
24 |
25 | # tf
26 | .env*
27 | .vercel
28 | *.tsbuildinfo
29 | stats.html
30 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2015",
4 | "module": "es2015",
5 | "declaration": true,
6 | "outDir": "./dist",
7 | "forceConsistentCasingInFileNames": true,
8 | "strict": true,
9 | "skipLibCheck": true,
10 | "noEmit": true,
11 | "moduleResolution": "node"
12 | },
13 | "include": ["./**/*.ts"],
14 | "exclude": ["./dist", "./browser/dist"]
15 | }
16 |
--------------------------------------------------------------------------------
/rollup.config.ts:
--------------------------------------------------------------------------------
1 | import typescript from '@rollup/plugin-typescript';
2 | import terser from '@rollup/plugin-terser';
3 |
4 | export default {
5 | input: './src/index.ts',
6 | plugins: [terser(), typescript({ include: ['./src/index.ts'] })],
7 | output: [
8 | { dir: './dist', format: 'cjs', exports: 'default' },
9 | { dir: './dist', format: 'es' },
10 | {
11 | dir: './dist',
12 | format: 'umd',
13 | name: 'simpleLoadScript',
14 | },
15 | ],
16 | watch: {
17 | include: './src/**',
18 | },
19 | };
20 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "node": true
5 | },
6 | "extends": [
7 | "eslint:recommended",
8 | "plugin:@typescript-eslint/eslint-recommended",
9 | "plugin:@typescript-eslint/recommended",
10 | "plugin:@typescript-eslint/stylistic",
11 | "prettier"
12 | ],
13 | "parser": "@typescript-eslint/parser",
14 | "parserOptions": {
15 | "ecmaVersion": 2021,
16 | "sourceType": "module"
17 | },
18 | "plugins": ["@typescript-eslint", "prettier"],
19 | "rules": {
20 | "prettier/prettier": "error"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/changelog.md:
--------------------------------------------------------------------------------
1 | # changelog
2 |
3 | ## 2.1.1
4 |
5 | - update changelog
6 |
7 | ## 2.1.0
8 |
9 | - add action status badge
10 | - use oxlint instead of ESLint (leave config file)
11 | - update deps
12 |
13 | ## 2.0.0
14 |
15 | - **BREAKING CHANGE** exported files paths
16 | - **BREAKING CHANGE** `jsonp` removed form Config
17 | - **BREAKING CHANGE** `callBackName` removed form Config
18 | - **BREAKING CHANGE** `removeScript` changes - script is not removed by default on an error
19 | - **BREAKING CHANGE** `callBack` removed form Config - just run code after simpleLoadScript run (eg. `console.log` in examples)
20 | - TS
21 | - rollup
22 | - deps
23 | - tests
24 | - readme
25 |
26 | ## 1.0.3
27 |
28 | - update 2do
29 | - update readme
30 | - update dev deps
31 |
32 | ## 1.0.2
33 |
34 | - added `insertInto` config option
35 |
36 | ## 1.0.1
37 |
38 | - rename index.js
39 | - readme
40 | - changes in `all` method
41 |
42 | ## 1.0.0
43 |
44 | - `dontRemoveCallBack` and `removeScript`
45 | - readme
46 | - removed ES6 version
47 |
48 | ## 0.0.0
49 |
50 | - init version
51 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) Tomek Fijoł
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 all
13 | 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 THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/.github/workflows/check.yml:
--------------------------------------------------------------------------------
1 | on:
2 | push:
3 | branches: ['**']
4 | # branches: [master, feat/**, fix/**]
5 | # branches: [feat/**, fix/**]
6 | # pull_request:
7 | # branches: [master]
8 | workflow_dispatch:
9 |
10 | jobs:
11 | build:
12 | runs-on: ubuntu-latest
13 | steps:
14 | - uses: actions/checkout@v4
15 | - uses: actions/setup-node@v4
16 | with:
17 | node-version: 20
18 | check-latest: true
19 | cache: 'npm'
20 | - run: npm ci
21 | - run: npm run build
22 | lint:
23 | runs-on: ubuntu-latest
24 | steps:
25 | - uses: actions/checkout@v4
26 | - uses: actions/setup-node@v4
27 | with:
28 | node-version: 20
29 | check-latest: true
30 | cache: 'npm'
31 | - run: npm ci
32 | - run: npm run lint
33 | types:
34 | runs-on: ubuntu-latest
35 | steps:
36 | - uses: actions/checkout@v4
37 | - uses: actions/setup-node@v4
38 | with:
39 | node-version: 20
40 | check-latest: true
41 | cache: 'npm'
42 | - run: npm ci
43 | - run: npm run check-types
44 | prettier:
45 | runs-on: ubuntu-latest
46 | steps:
47 | - uses: actions/checkout@v4
48 | - uses: actions/setup-node@v4
49 | with:
50 | node-version: 20
51 | check-latest: true
52 | cache: 'npm'
53 | - run: npm ci
54 | - run: npm run format-check
55 | test:
56 | runs-on: ubuntu-latest
57 | steps:
58 | - uses: actions/checkout@v4
59 | - uses: actions/setup-node@v4
60 | with:
61 | node-version: 20
62 | check-latest: true
63 | cache: 'npm'
64 | - run: npm ci
65 | - run: npx playwright install chromium
66 | - run: npm run test:run
67 |
--------------------------------------------------------------------------------
/test/removeScript.spec.ts:
--------------------------------------------------------------------------------
1 | import { afterAll, beforeAll, expect, test } from 'vitest';
2 | import { preview } from 'vite';
3 | import type { PreviewServer } from 'vite';
4 | import { Browser, Page, chromium } from 'playwright';
5 | import { TIMEOUT } from './constants';
6 |
7 | let browser: Browser;
8 | let server: PreviewServer;
9 | let page: Page;
10 |
11 | beforeAll(async () => {
12 | browser = await chromium.launch({ headless: true });
13 | server = await preview({ preview: { port: 3004 } });
14 | page = await browser.newPage();
15 | });
16 |
17 | afterAll(async () => {
18 | await browser.close();
19 | await new Promise((resolve, reject) => {
20 | server.httpServer.close((error: unknown) =>
21 | error ? reject(error) : resolve(),
22 | );
23 | });
24 | });
25 |
26 | test(
27 | 'removeScript true',
28 | async () => {
29 | try {
30 | await page.goto('http://localhost:3004');
31 | await page.evaluate(async () => {
32 | await window.simpleLoadScript({
33 | url: '//code.jquery.com/jquery-2.2.3.js',
34 | removeScript: true,
35 | attrs: { id: 'jquery' },
36 | });
37 | });
38 | const jquery = await page.$('script#jquery');
39 | const id = await page.evaluate((script) => script?.id, jquery);
40 | const nodeType = await page.evaluate(
41 | (script) => script?.nodeType,
42 | jquery,
43 | );
44 | expect(id).toBeUndefined();
45 | expect(nodeType).toBeUndefined();
46 | } catch (err) {
47 | expect(err).toBeUndefined();
48 | }
49 | },
50 | TIMEOUT,
51 | );
52 |
53 | test(
54 | 'removeScript false',
55 | async () => {
56 | try {
57 | await page.goto('http://localhost:3004');
58 | await page.evaluate(async () => {
59 | await window.simpleLoadScript({
60 | url: '//code.jquery.com/jquery-2.2.3.js',
61 | attrs: { id: 'jquery' },
62 | });
63 | });
64 | const jquery = await page.$('script#jquery');
65 | const id = await page.evaluate((script) => script?.id, jquery);
66 | expect(id).toBe('jquery');
67 | } catch (err) {
68 | expect(err).toBeUndefined();
69 | }
70 | },
71 | TIMEOUT,
72 | );
73 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "simple-load-script",
3 | "version": "2.1.1",
4 | "description": "Very simple promise based script and JSONP",
5 | "repository": "tomek-f/simple-load-script",
6 | "keywords": [
7 | "script",
8 | "promise",
9 | "JSONP"
10 | ],
11 | "type": "module",
12 | "scripts": {
13 | "check-all": "npm run lint && npm run format-check && npm run check-types && npm run test:run",
14 | "prepublishOnly": "npm run check-all && npm run build",
15 | "build": "rollup --c ./rollup.config.ts --configPlugin typescript",
16 | "watch": "npm run build -- -w",
17 | "lint": "NODE_ENV=production oxlint --import-plugin --promise-plugin --node-plugin ./",
18 | "vite:build-and-preview": "vite build && vite preview",
19 | "test:run": "vite build && vitest run --reporter=verbose",
20 | "test:ui": "vite build && vitest --reporter=verbose --ui",
21 | "test:watch": "vite build && vitest --reporter=verbose",
22 | "format-check": "prettier --check ./",
23 | "format-write": "prettier --write ./",
24 | "check-types": "echo 'checking types…' && tsc && echo '…no type problems'"
25 | },
26 | "files": [
27 | "src",
28 | "dist",
29 | "changelog.md"
30 | ],
31 | "main": "./dist/index.umd.js",
32 | "umd:main": "./dist/index.umd.js",
33 | "module": "./dist/index.es.js",
34 | "types": "./dist/index.d.ts",
35 | "exports": {
36 | "./package.json": "./package.json",
37 | ".": {
38 | "types": "./dist/index.d.ts",
39 | "import": "./dist/index.es.js",
40 | "require": "./dist/index.cjs.js",
41 | "default": "./dist/index.umd.js"
42 | }
43 | },
44 | "author": "Tomek Fijoł (http://tomekf.pl/)",
45 | "contributors": [
46 | {
47 | "name": "Martin Jurča",
48 | "email": "martin.jurca@firma.seznam.cz"
49 | },
50 | {
51 | "name": "Tom Conroy",
52 | "url": "https://github.com/tconroy"
53 | }
54 | ],
55 | "license": "MIT",
56 | "readmeFilename": "README.md",
57 | "devDependencies": {
58 | "@rollup/plugin-terser": "^0.4.4",
59 | "@rollup/plugin-typescript": "^12.1.2",
60 | "@vitest/ui": "^3.1.1",
61 | "oxlint": "^0.16.5",
62 | "playwright": "^1.51.1",
63 | "prettier": "^3.5.3",
64 | "rollup": "^4.40.0",
65 | "tslib": "^2.8.1",
66 | "typescript": "^5.8.3",
67 | "vite": "^6.2.0",
68 | "vitest": "^3.1.1"
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/test/attrs.spec.ts:
--------------------------------------------------------------------------------
1 | import { afterAll, beforeAll, expect, test } from 'vitest';
2 | import { preview } from 'vite';
3 | import type { PreviewServer } from 'vite';
4 | import { Browser, Page, chromium } from 'playwright';
5 | import { TIMEOUT } from './constants';
6 |
7 | let browser: Browser;
8 | let server: PreviewServer;
9 | let page: Page;
10 |
11 | beforeAll(async () => {
12 | browser = await chromium.launch({ headless: true });
13 | server = await preview({ preview: { port: 3001 } });
14 | page = await browser.newPage();
15 | });
16 |
17 | afterAll(async () => {
18 | await browser.close();
19 | await new Promise((resolve, reject) => {
20 | server.httpServer.close((error: unknown) =>
21 | error ? reject(error) : resolve(),
22 | );
23 | });
24 | });
25 |
26 | test(
27 | 'add attrs',
28 | async () => {
29 | try {
30 | await page.goto('http://localhost:3001');
31 | await page.evaluate(async () => {
32 | await window.simpleLoadScript({
33 | url: '//code.jquery.com/jquery-2.2.3.js',
34 | attrs: { id: 'jquery', 'data-test': 'test' },
35 | });
36 | });
37 | const jquery = await page.$('script#jquery');
38 | const id = await page.evaluate((script) => script?.id, jquery);
39 | const dataTest = await page.evaluate(
40 | (script) => script?.dataset.test,
41 | jquery,
42 | );
43 |
44 | expect(id).toBe('jquery');
45 | expect(dataTest).toBe('test');
46 | } catch (err) {
47 | expect(err).toBeUndefined();
48 | }
49 | },
50 | TIMEOUT,
51 | );
52 |
53 | test(
54 | 'do not add attrs',
55 | async () => {
56 | try {
57 | await page.goto('http://localhost:3001');
58 | await page.evaluate(async () => {
59 | await window.simpleLoadScript({
60 | url: '//code.jquery.com/jquery-2.2.3.js',
61 | });
62 | });
63 | const jquery = await page.$('script');
64 | const jqueryWithId = await page.$('script#jquery');
65 | const nodeType = await page.evaluate(
66 | (script) => script?.nodeType,
67 | jquery,
68 | );
69 | const nodeTypeWithId = await page.evaluate(
70 | (script) => script?.nodeType,
71 | jqueryWithId,
72 | );
73 |
74 | expect(nodeType).toBe(1);
75 | expect(nodeTypeWithId).toBeUndefined();
76 | } catch (err) {
77 | expect(err).toBeUndefined();
78 | }
79 | },
80 | TIMEOUT,
81 | );
82 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | export interface Config {
2 | url: string;
3 | attrs?: Record;
4 | inBody?: boolean;
5 | insertInto?: string | null;
6 | removeScript?: boolean;
7 | }
8 |
9 | const defaultConfig = {
10 | url: '',
11 | attrs: {},
12 | inBody: false,
13 | insertInto: null,
14 | removeScript: false,
15 | } satisfies Config;
16 |
17 | export default function simpleLoadScript(
18 | config: (Config | string)[],
19 | ): Promise<(HTMLScriptElement | undefined)[]>;
20 | export default function simpleLoadScript(
21 | config: Config | string,
22 | ): Promise;
23 | export default function simpleLoadScript(
24 | config: Config | string | (Config | string)[],
25 | ): Promise {
26 | if (Array.isArray(config)) {
27 | return Promise.all(config.map(simpleLoadScript));
28 | }
29 |
30 | return new Promise((resolve, reject) => {
31 | if (
32 | !(
33 | (typeof config === 'object' && config.url) ||
34 | typeof config === 'string'
35 | )
36 | ) {
37 | console.log({ config });
38 | reject(new Error('Object with url or url string needed'));
39 | return;
40 | }
41 |
42 | const configProcessed: Required = Object.assign(
43 | {},
44 | defaultConfig,
45 | typeof config === 'string' ? { url: config } : config,
46 | );
47 | const { url, attrs, inBody, insertInto, removeScript } =
48 | configProcessed;
49 | const script = document.createElement('script');
50 | const where: HTMLElement | null = insertInto
51 | ? document.querySelector(insertInto)
52 | : inBody
53 | ? document.body
54 | : document.head;
55 |
56 | if (attrs && typeof attrs === 'object') {
57 | for (const attr of Object.keys(attrs)) {
58 | script.setAttribute(attr, attrs[attr]);
59 | }
60 | }
61 |
62 | if (where == null) {
63 | reject(new Error('No DOM element to append script'));
64 | return;
65 | }
66 |
67 | script.addEventListener('load', () => {
68 | if (removeScript) {
69 | where.removeChild(script);
70 | }
71 | resolve(removeScript ? undefined : script);
72 | });
73 | script.addEventListener('error', (/* err */) => {
74 | if (removeScript) {
75 | where.removeChild(script);
76 | }
77 | // TODO ? just return err
78 | // TODO ? re-throw err with changed message
79 | reject(new Error('Loading script error'));
80 | });
81 | script.src = url;
82 | where.appendChild(script);
83 | });
84 | }
85 |
--------------------------------------------------------------------------------
/test/array.spec.ts:
--------------------------------------------------------------------------------
1 | import { afterAll, beforeAll, expect, test } from 'vitest';
2 | import { preview } from 'vite';
3 | import type { PreviewServer } from 'vite';
4 | import { Browser, Page, chromium } from 'playwright';
5 | import { TIMEOUT } from './constants';
6 |
7 | let browser: Browser;
8 | let server: PreviewServer;
9 | let page: Page;
10 |
11 | beforeAll(async () => {
12 | browser = await chromium.launch({ headless: true });
13 | server = await preview({ preview: { port: 3005 } });
14 | page = await browser.newPage();
15 | });
16 |
17 | afterAll(async () => {
18 | await browser.close();
19 | await new Promise((resolve, reject) => {
20 | server.httpServer.close((error: unknown) =>
21 | error ? reject(error) : resolve(),
22 | );
23 | });
24 | });
25 |
26 | test(
27 | 'load array ok',
28 | async () => {
29 | try {
30 | await page.goto('http://localhost:3005');
31 | const scriptRefs = await page.evaluate(async () => {
32 | const [a, b, c] = await window.simpleLoadScript([
33 | '//code.jquery.com/jquery-2.2.3.js',
34 | {
35 | url: '//code.jquery.com/jquery-2.2.2.js',
36 | attrs: { id: 'jquery2' },
37 | },
38 | {
39 | url: '//code.jquery.com/jquery-2.2.1.js',
40 | attrs: { id: 'jquery3' },
41 | },
42 | ]);
43 | return [a, b, c];
44 | });
45 | expect(scriptRefs.length).toBe(3);
46 | const jquery1 = await page.$(
47 | 'head script[src="//code.jquery.com/jquery-2.2.3.js"]',
48 | );
49 | const jquery2 = await page.$('script#jquery2');
50 | const jquery3 = await page.$('script#jquery3');
51 | const src1 = await page.evaluate(
52 | (script) => script?.getAttribute('src'),
53 | jquery1,
54 | );
55 | const id2 = await page.evaluate((script) => script?.id, jquery2);
56 | const id3 = await page.evaluate((script) => script?.id, jquery3);
57 | expect(src1).toBe('//code.jquery.com/jquery-2.2.3.js');
58 | expect(id2).toBe('jquery2');
59 | expect(id3).toBe('jquery3');
60 | } catch (err) {
61 | expect(err).toBeUndefined();
62 | }
63 | },
64 | TIMEOUT,
65 | );
66 |
67 | test(
68 | 'load array error',
69 | async () => {
70 | try {
71 | await page.goto('http://localhost:3005');
72 | await page.evaluate(async () => {
73 | await window.simpleLoadScript([
74 | '//wrong.domain/jquery-2.2.3.js',
75 | {
76 | url: '//code.jquery.com/jquery-2.2.2.js',
77 | attrs: { id: 'jquery2' },
78 | },
79 | {
80 | url: '//code.jquery.com/jquery-2.2.1.js',
81 | attrs: { id: 'jquery3' },
82 | },
83 | ]);
84 | });
85 | } catch (err) {
86 | expect(
87 | (err as Error).message.includes('Error: Loading script error'),
88 | ).toBe(true);
89 | }
90 | },
91 | TIMEOUT,
92 | );
93 |
--------------------------------------------------------------------------------
/test/placement.spec.ts:
--------------------------------------------------------------------------------
1 | import { afterAll, beforeAll, expect, test } from 'vitest';
2 | import { preview } from 'vite';
3 | import type { PreviewServer } from 'vite';
4 | import { Browser, Page, chromium } from 'playwright';
5 | import { TIMEOUT } from './constants';
6 |
7 | let browser: Browser;
8 | let server: PreviewServer;
9 | let page: Page;
10 |
11 | beforeAll(async () => {
12 | browser = await chromium.launch({ headless: true });
13 | server = await preview({ preview: { port: 3003 } });
14 | page = await browser.newPage();
15 | });
16 |
17 | afterAll(async () => {
18 | await browser.close();
19 | await new Promise((resolve, reject) => {
20 | server.httpServer.close((error: unknown) =>
21 | error ? reject(error) : resolve(),
22 | );
23 | });
24 | });
25 |
26 | test(
27 | 'placement head',
28 | async () => {
29 | try {
30 | await page.goto('http://localhost:3003');
31 | await page.evaluate(async () => {
32 | await window.simpleLoadScript({
33 | url: '//code.jquery.com/jquery-2.2.3.js',
34 | attrs: { id: 'jquery' },
35 | });
36 | });
37 | const jquery = await page.$('head script#jquery');
38 | const id = await page.evaluate((script) => script?.id, jquery);
39 | expect(id).toBe('jquery');
40 | } catch (err) {
41 | expect(err).toBeUndefined();
42 | }
43 | },
44 | TIMEOUT,
45 | );
46 |
47 | test(
48 | 'placement body',
49 | async () => {
50 | try {
51 | await page.goto('http://localhost:3003');
52 | await page.evaluate(async () => {
53 | await window.simpleLoadScript({
54 | url: '//code.jquery.com/jquery-2.2.3.js',
55 | attrs: { id: 'jquery' },
56 | inBody: true,
57 | });
58 | });
59 | const jquery = await page.$('body script#jquery');
60 | const id = await page.evaluate((script) => script?.id, jquery);
61 | expect(id).toBe('jquery');
62 | } catch (err) {
63 | expect(err).toBeUndefined();
64 | }
65 | },
66 | TIMEOUT,
67 | );
68 |
69 | test(
70 | 'insertInto ok',
71 | async () => {
72 | try {
73 | await page.goto('http://localhost:3003');
74 | await page.evaluate(async () => {
75 | await window.simpleLoadScript({
76 | url: '//code.jquery.com/jquery-2.2.3.js',
77 | attrs: { id: 'jquery' },
78 | insertInto: '#insert',
79 | });
80 | });
81 | const jquery = await page.$('#insert script#jquery');
82 | const id = await page.evaluate((script) => script?.id, jquery);
83 | expect(id).toBe('jquery');
84 | } catch (err) {
85 | expect(err).toBeUndefined();
86 | }
87 | },
88 | TIMEOUT,
89 | );
90 |
91 | test(
92 | 'insertInto error',
93 | async () => {
94 | try {
95 | await page.goto('http://localhost:3003');
96 | await page.evaluate(async () => {
97 | await window.simpleLoadScript({
98 | url: '//code.jquery.com/jquery-2.2.3.js',
99 | attrs: { id: 'jquery' },
100 | insertInto: '#insert1',
101 | });
102 | });
103 | } catch (err) {
104 | expect(
105 | (err as Error).message.includes(
106 | 'No DOM element to append script',
107 | ),
108 | ).toBe(true);
109 | }
110 | },
111 | TIMEOUT,
112 | );
113 |
--------------------------------------------------------------------------------
/test/basic.spec.ts:
--------------------------------------------------------------------------------
1 | import { afterAll, beforeAll, describe, expect, test } from 'vitest';
2 | import { preview } from 'vite';
3 | import type { PreviewServer } from 'vite';
4 | import { Browser, Page, chromium } from 'playwright';
5 | import { TIMEOUT } from './constants';
6 |
7 | // https://github.com/vitest-dev/vitest/blob/main/examples/puppeteer/test/basic.test.ts
8 | // https://gist.github.com/mizchi/5f67109d0719ef6dd57695e1f528ce8d
9 |
10 | let browser: Browser;
11 | let server: PreviewServer;
12 | let page: Page;
13 |
14 | beforeAll(async () => {
15 | browser = await chromium.launch({ headless: true });
16 | server = await preview({ preview: { port: 3000 } });
17 | page = await browser.newPage();
18 | });
19 |
20 | afterAll(async () => {
21 | await browser.close();
22 | await new Promise((resolve, reject) => {
23 | server.httpServer.close((error: unknown) =>
24 | error ? reject(error) : resolve(),
25 | );
26 | });
27 | });
28 |
29 | test(
30 | 'load url ok',
31 | async () => {
32 | try {
33 | await page.goto('http://localhost:3000');
34 | const ref = await page.evaluate(async () => {
35 | const scriptRef = await window.simpleLoadScript(
36 | '//code.jquery.com/jquery-2.2.3.js',
37 | );
38 | return scriptRef;
39 | });
40 | expect(ref).toBeDefined();
41 | } catch (err) {
42 | expect(err).toBeUndefined();
43 | }
44 | },
45 | TIMEOUT,
46 | );
47 |
48 | test(
49 | 'load config ok',
50 | async () => {
51 | try {
52 | await page.goto('http://localhost:3000');
53 | const ref = await page.evaluate(async () => {
54 | const scriptRef = await window.simpleLoadScript({
55 | url: '//code.jquery.com/jquery-2.2.3.js',
56 | });
57 | return scriptRef;
58 | });
59 | expect(ref).toBeDefined();
60 | } catch (err) {
61 | expect(err).toBeUndefined();
62 | }
63 | },
64 | TIMEOUT,
65 | );
66 |
67 | test(
68 | 'wrong url error',
69 | async () => {
70 | try {
71 | await page.goto('http://localhost:3000');
72 | await page.evaluate(async () => {
73 | await window.simpleLoadScript('//wrong.domain/jquery-2.2.3.js');
74 | });
75 | } catch (err) {
76 | expect(
77 | (err as Error).message.includes('Error: Loading script error'),
78 | ).toBe(true);
79 | }
80 | },
81 | TIMEOUT,
82 | );
83 |
84 | describe('wrong config error', () => {
85 | test(
86 | 'no param',
87 | async () => {
88 | try {
89 | await page.goto('http://localhost:3000');
90 | await page.evaluate(async () => {
91 | // @ts-expect-error Testing wrong config
92 | await window.simpleLoadScript();
93 | });
94 | } catch (err) {
95 | expect(
96 | (err as Error).message.includes(
97 | 'Error: Object with url or url string needed',
98 | ),
99 | ).toBe(true);
100 | }
101 | },
102 | TIMEOUT,
103 | );
104 | test(
105 | 'bad config',
106 | async () => {
107 | try {
108 | await page.goto('http://localhost:3000');
109 | await page.evaluate(async () => {
110 | await window.simpleLoadScript({
111 | // @ts-expect-error Testing wrong config
112 | elo: '//code.jquery.com/jquery-2.2.3.js',
113 | });
114 | });
115 | } catch (err) {
116 | expect(
117 | (err as Error).message.includes(
118 | 'Error: Object with url or url string needed',
119 | ),
120 | ).toBe(true);
121 | }
122 | },
123 | TIMEOUT,
124 | );
125 | });
126 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # simple-load-script
2 |
3 | > Very simple promise based script loader and JSONP
4 |
5 | [](https://github.com/tomek-f/simple-load-script/actions/workflows/check.yml)
6 |
7 | ## Usage
8 |
9 | ### Async/await
10 |
11 | ```js
12 | import simpleLoadScript from 'simple-load-script';
13 |
14 | try {
15 | const scriptRef = await simpleLoadScript(
16 | 'https://code.jquery.com/jquery-3.7.1.min.js',
17 | );
18 |
19 | console.log(scriptRef); // HTMLScriptElement
20 | } catch (err) {
21 | console.log(err);
22 | }
23 | ```
24 |
25 | ### Promise
26 |
27 | ```js
28 | import simpleLoadScript from 'simple-load-script';
29 |
30 | simpleLoadScript('https://code.jquery.com/jquery-3.7.1.min.js')
31 | .then(function (scriptRef) {
32 | console.log(scriptRef); // HTMLScriptElement
33 | })
34 | .catch(function (err) {
35 | console.log(err);
36 | });
37 | ```
38 |
39 | ### Config object
40 |
41 | ```js
42 | import simpleLoadScript from 'simple-load-script';
43 |
44 | try {
45 | const scriptRef = await simpleLoadScript({
46 | url: 'https://code.jquery.com/jquery-3.7.1.min.js',
47 | inBody: true,
48 | attrs: { id: 'one', charset: 'UTF-8' },
49 | });
50 |
51 | console.log(scriptRef); // HTMLScriptElement inBody with attrs present
52 | } catch (err) {
53 | console.log(err);
54 | }
55 | ```
56 |
57 | ### Google Maps API
58 |
59 | Runs global callback (window.gmapiready)
60 |
61 | ```js
62 | import simpleLoadScript from 'simple-load-script';
63 |
64 | try {
65 | const scriptRef = await simpleLoadScript(
66 | '//maps.googleapis.com/maps/api/js?&callback=gmapiready',
67 | );
68 |
69 | console.log(scriptRef); // HTMLScriptElement
70 | } catch (err) {
71 | console.log(err);
72 | }
73 | ```
74 |
75 | ### JSONP
76 |
77 | Runs global callback (window.elo)
78 |
79 | ```js
80 | var simpleLoadScript = require('simple-load-script');
81 |
82 | try {
83 | const scriptRef = await simpleLoadScript({
84 | url: '//api.ipinfodb.com/v3/ip-city/?format=json&callback=elo',
85 | removeScript: true,
86 | });
87 |
88 | console.log(scriptRef); // undefined
89 | } catch (err) {
90 | console.log(err);
91 | }
92 | ```
93 |
94 | ### Array mode - objects and urls, callBackNames must have unique names
95 |
96 | ```js
97 | import simpleLoadScript from 'simple-load-script';
98 |
99 | try {
100 | const scriptRefs = await simpleLoadScript([
101 | '//maps.googleapis.com/maps/api/js?&callback=gmapiready',
102 | {
103 | url: '//api.ipinfodb.com/v3/ip-city/?format=json&callback=elo',
104 | removeScript: true,
105 | },
106 | 'https://code.jquery.com/jquery-3.7.1.min.js',
107 | ]);
108 |
109 | console.log(scriptRefs); // HTMLScriptElement[]
110 | } catch (err) {
111 | console.log(err);
112 | }
113 | ```
114 |
115 | ## Arguments
116 |
117 | `Config | string | (Config | string)[]`
118 |
119 | ## Config
120 |
121 | ### Interface
122 |
123 | ```ts
124 | interface Config {
125 | url: string;
126 | attrs?: Record;
127 | inBody?: boolean;
128 | insertInto?: string | null;
129 | removeScript?: boolean;
130 | }
131 | ```
132 |
133 | ### Default values
134 |
135 | ```js
136 | const defaultConfig = {
137 | url: '',
138 | attrs: {},
139 | inBody: false,
140 | insertInto: null,
141 | removeScript: false,
142 | };
143 | ```
144 |
145 | - `url` - file to append to body
146 | - `attrs` - with attributes to append to script tag (`charset`, `type`, `id`, …)
147 | - `inBody` - append to `document.body` instead of `document.head`
148 | - `insertInto` - [CSS selector (an ID, class name, element name, etc.)](https://developer.mozilla.org/en/docs/Web/API/Document/querySelector) to insert the script into. Overrides `inBody` value.
149 | - `removeScript` - remove script tag after load; it's always removed on an error
150 |
151 | ## Specific import
152 |
153 | [Check files](https://www.npmjs.com/package/simple-load-script?activeTab=code) or package.json
154 |
155 | ## Changelog
156 |
157 | [View on github](https://github.com/tomek-f/simple-load-script/blob/master/changelog.md).
158 |
159 | ## Misc.
160 |
161 | - uses addEventListener, Array.isArray, for…of, destructuring Promise & Promise.all
162 |
--------------------------------------------------------------------------------