@zoltanradics/async-script-loader 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +216 -0
- package/dist/example-scripts/success.js +3 -0
- package/dist/example-scripts/with-params.js +8 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.es.js +27 -0
- package/dist/index.umd.js +1 -0
- package/package.json +55 -0
package/README.md
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
# Async Script Loader
|
|
2
|
+
|
|
3
|
+
A lightweight TypeScript library for dynamically loading external JavaScript files with promise-based API and automatic timeout handling.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Promise-based API for script loading
|
|
8
|
+
- Automatic timeout handling (2 second default)
|
|
9
|
+
- Proper event listener cleanup to prevent memory leaks
|
|
10
|
+
- URL encoding for query parameters
|
|
11
|
+
- TypeScript support with full type definitions
|
|
12
|
+
- Works in both ES modules and browser environments
|
|
13
|
+
- Small bundle size (~1 KB)
|
|
14
|
+
|
|
15
|
+
## Why not use `import()`?
|
|
16
|
+
|
|
17
|
+
This library is **not** a replacement for the native `import()` function. They serve different purposes:
|
|
18
|
+
|
|
19
|
+
**Use `import()`** when:
|
|
20
|
+
- Loading ES modules with exports you need to access
|
|
21
|
+
- Working with your own modular code
|
|
22
|
+
- You need tree-shaking and modern module features
|
|
23
|
+
|
|
24
|
+
**Use this library** when:
|
|
25
|
+
- Loading third-party scripts that aren't ES modules (analytics, ads, legacy libraries)
|
|
26
|
+
- Scripts that set global variables or have side effects
|
|
27
|
+
- You need to append query parameters to the script URL
|
|
28
|
+
- Loading scripts that don't have exports (just execute code)
|
|
29
|
+
- You need timeout handling for unreliable external scripts
|
|
30
|
+
- Working with scripts designed to be loaded via `<script>` tags
|
|
31
|
+
|
|
32
|
+
**Example:** Google Analytics, payment SDKs, chat widgets, and many third-party services provide scripts meant to be loaded as `<script src="...">` rather than ES modules. This library makes loading such scripts programmatic and promise-based.
|
|
33
|
+
|
|
34
|
+
## Installation
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npm install @zoltanradics/async-script-loader
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Usage
|
|
41
|
+
|
|
42
|
+
### ES Module (Bundlers, Modern JavaScript)
|
|
43
|
+
|
|
44
|
+
```javascript
|
|
45
|
+
import { asyncScriptLoader } from '@zoltanradics/async-script-loader';
|
|
46
|
+
|
|
47
|
+
// Load a script with query parameters
|
|
48
|
+
asyncScriptLoader('https://example.com/script.js', {
|
|
49
|
+
version: '1.0',
|
|
50
|
+
apiKey: 'your-api-key',
|
|
51
|
+
env: 'production'
|
|
52
|
+
})
|
|
53
|
+
.then(() => {
|
|
54
|
+
console.log('Script loaded successfully');
|
|
55
|
+
// Script is now available and can be used
|
|
56
|
+
})
|
|
57
|
+
.catch((error) => {
|
|
58
|
+
console.error('Failed to load script:', error);
|
|
59
|
+
});
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Direct Browser Usage (CDN)
|
|
63
|
+
|
|
64
|
+
```html
|
|
65
|
+
<!DOCTYPE html>
|
|
66
|
+
<html>
|
|
67
|
+
<head>
|
|
68
|
+
<title>Async Script Loader Example</title>
|
|
69
|
+
</head>
|
|
70
|
+
<body>
|
|
71
|
+
<!-- Load from unpkg -->
|
|
72
|
+
<script src="https://unpkg.com/@zoltanradics/async-script-loader"></script>
|
|
73
|
+
|
|
74
|
+
<!-- Or load from jsDelivr -->
|
|
75
|
+
<!-- <script src="https://cdn.jsdelivr.net/npm/@zoltanradics/async-script-loader"></script> -->
|
|
76
|
+
|
|
77
|
+
<script>
|
|
78
|
+
// Access via global AsyncScriptLoader object
|
|
79
|
+
AsyncScriptLoader.asyncScriptLoader(
|
|
80
|
+
'https://example.com/analytics.js',
|
|
81
|
+
{ userId: '12345', tracking: 'enabled' }
|
|
82
|
+
)
|
|
83
|
+
.then(() => console.log('Analytics loaded'))
|
|
84
|
+
.catch((err) => console.error('Failed to load analytics', err));
|
|
85
|
+
</script>
|
|
86
|
+
</body>
|
|
87
|
+
</html>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### TypeScript
|
|
91
|
+
|
|
92
|
+
The library includes TypeScript definitions out of the box:
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
import { asyncScriptLoader } from '@zoltanradics/async-script-loader';
|
|
96
|
+
|
|
97
|
+
const loadScript = async (): Promise<void> => {
|
|
98
|
+
try {
|
|
99
|
+
await asyncScriptLoader('https://api.example.com/sdk.js', {
|
|
100
|
+
version: '2.0',
|
|
101
|
+
debug: 'true'
|
|
102
|
+
});
|
|
103
|
+
// Script loaded successfully
|
|
104
|
+
} catch (error) {
|
|
105
|
+
// Handle error
|
|
106
|
+
console.error(error);
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
loadScript();
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## API
|
|
114
|
+
|
|
115
|
+
### `asyncScriptLoader(baseUrl, queryParamObject)`
|
|
116
|
+
|
|
117
|
+
Dynamically injects a script tag into the document head and returns a Promise.
|
|
118
|
+
|
|
119
|
+
#### Parameters
|
|
120
|
+
|
|
121
|
+
- `baseUrl` (string): The base URL of the script to load
|
|
122
|
+
- `queryParamObject` (object): An object containing query parameters to append to the URL
|
|
123
|
+
- Keys and values will be automatically URL-encoded
|
|
124
|
+
- Example: `{ key: 'value', foo: 'bar' }` becomes `?key=value&foo=bar`
|
|
125
|
+
|
|
126
|
+
#### Returns
|
|
127
|
+
|
|
128
|
+
- `Promise<void>`: Resolves when the script loads successfully, rejects on error or timeout
|
|
129
|
+
|
|
130
|
+
#### Behavior
|
|
131
|
+
|
|
132
|
+
- The script is injected into the `<head>` element with `async` attribute
|
|
133
|
+
- Automatically times out after 2 seconds if the script hasn't loaded
|
|
134
|
+
- On timeout, the promise resolves (not rejects) with a console warning
|
|
135
|
+
- On error, the promise rejects with an error message
|
|
136
|
+
- Event listeners are automatically cleaned up after load/error/timeout
|
|
137
|
+
|
|
138
|
+
## Examples
|
|
139
|
+
|
|
140
|
+
### Loading Google Analytics
|
|
141
|
+
|
|
142
|
+
```javascript
|
|
143
|
+
import { asyncScriptLoader } from '@zoltanradics/async-script-loader';
|
|
144
|
+
|
|
145
|
+
asyncScriptLoader('https://www.googletagmanager.com/gtag/js', {
|
|
146
|
+
id: 'G-XXXXXXXXXX'
|
|
147
|
+
})
|
|
148
|
+
.then(() => {
|
|
149
|
+
window.dataLayer = window.dataLayer || [];
|
|
150
|
+
function gtag() { dataLayer.push(arguments); }
|
|
151
|
+
gtag('js', new Date());
|
|
152
|
+
gtag('config', 'G-XXXXXXXXXX');
|
|
153
|
+
});
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Loading Multiple Scripts Sequentially
|
|
157
|
+
|
|
158
|
+
```javascript
|
|
159
|
+
import { asyncScriptLoader } from '@zoltanradics/async-script-loader';
|
|
160
|
+
|
|
161
|
+
async function loadScripts() {
|
|
162
|
+
try {
|
|
163
|
+
await asyncScriptLoader('https://cdn.example.com/library.js', {});
|
|
164
|
+
console.log('Library loaded');
|
|
165
|
+
|
|
166
|
+
await asyncScriptLoader('https://cdn.example.com/plugin.js', {
|
|
167
|
+
theme: 'dark'
|
|
168
|
+
});
|
|
169
|
+
console.log('Plugin loaded');
|
|
170
|
+
|
|
171
|
+
// Both scripts are now loaded
|
|
172
|
+
} catch (error) {
|
|
173
|
+
console.error('Script loading failed:', error);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
loadScripts();
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Loading Scripts in Parallel
|
|
181
|
+
|
|
182
|
+
```javascript
|
|
183
|
+
import { asyncScriptLoader } from '@zoltanradics/async-script-loader';
|
|
184
|
+
|
|
185
|
+
Promise.all([
|
|
186
|
+
asyncScriptLoader('https://cdn.example.com/script1.js', {}),
|
|
187
|
+
asyncScriptLoader('https://cdn.example.com/script2.js', {}),
|
|
188
|
+
asyncScriptLoader('https://cdn.example.com/script3.js', {})
|
|
189
|
+
])
|
|
190
|
+
.then(() => {
|
|
191
|
+
console.log('All scripts loaded successfully');
|
|
192
|
+
})
|
|
193
|
+
.catch((error) => {
|
|
194
|
+
console.error('One or more scripts failed to load:', error);
|
|
195
|
+
});
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## Development
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
# Install dependencies
|
|
202
|
+
npm install
|
|
203
|
+
|
|
204
|
+
# Start development server
|
|
205
|
+
npm run dev
|
|
206
|
+
|
|
207
|
+
# Build for production
|
|
208
|
+
npm run build
|
|
209
|
+
|
|
210
|
+
# Preview production build
|
|
211
|
+
npm run preview
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
## License
|
|
215
|
+
|
|
216
|
+
ISC
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// This script demonstrates reading query parameters
|
|
2
|
+
const scriptTag = document.currentScript;
|
|
3
|
+
const url = new URL(scriptTag.src);
|
|
4
|
+
const params = Object.fromEntries(url.searchParams);
|
|
5
|
+
|
|
6
|
+
console.log('Script loaded with query parameters:', params);
|
|
7
|
+
window.paramsReceived = params;
|
|
8
|
+
window.paramsScriptLoadTime = new Date().toISOString();
|
package/dist/index.d.ts
ADDED
package/dist/index.es.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
function f(c, t) {
|
|
2
|
+
const n = E(c, t), e = document.createElement("script");
|
|
3
|
+
e.src = n, e.async = !0;
|
|
4
|
+
const o = document.getElementsByTagName("head")[0];
|
|
5
|
+
return o ? (o.insertAdjacentElement("beforeend", e), new Promise(function(r, a) {
|
|
6
|
+
const m = setTimeout(() => {
|
|
7
|
+
d(), r(), console.warn(`${n} was loading for too long time.`);
|
|
8
|
+
}, 2e3), d = () => {
|
|
9
|
+
clearTimeout(m), e.removeEventListener("load", s), e.removeEventListener("error", u);
|
|
10
|
+
}, s = function(l) {
|
|
11
|
+
d(), r();
|
|
12
|
+
}, u = function(l) {
|
|
13
|
+
d(), a(new Error(`Failed to load ${n}.`));
|
|
14
|
+
};
|
|
15
|
+
e.addEventListener("load", s), e.addEventListener("error", u);
|
|
16
|
+
})) : Promise.reject(new Error("No <head> element found in document"));
|
|
17
|
+
}
|
|
18
|
+
function E(c, t) {
|
|
19
|
+
const i = Object.keys(t).reduce((n, e) => {
|
|
20
|
+
const o = encodeURIComponent(e), r = encodeURIComponent(t[e]);
|
|
21
|
+
return `${n}${n === "" ? "?" : "&"}${o}=${r}`;
|
|
22
|
+
}, "");
|
|
23
|
+
return `${c}${i}`;
|
|
24
|
+
}
|
|
25
|
+
export {
|
|
26
|
+
f as asyncScriptLoader
|
|
27
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
(function(n,o){typeof exports=="object"&&typeof module<"u"?o(exports):typeof define=="function"&&define.amd?define(["exports"],o):(n=typeof globalThis<"u"?globalThis:n||self,o(n.AsyncScriptLoader={}))})(this,(function(n){"use strict";function o(d,r){const t=l(d,r),e=document.createElement("script");e.src=t,e.async=!0;const c=document.getElementsByTagName("head")[0];return c?(c.insertAdjacentElement("beforeend",e),new Promise(function(i,f){const p=setTimeout(()=>{s(),i(),console.warn(`${t} was loading for too long time.`)},2e3),s=()=>{clearTimeout(p),e.removeEventListener("load",a),e.removeEventListener("error",m)},a=function(E){s(),i()},m=function(E){s(),f(new Error(`Failed to load ${t}.`))};e.addEventListener("load",a),e.addEventListener("error",m)})):Promise.reject(new Error("No <head> element found in document"))}function l(d,r){const u=Object.keys(r).reduce((t,e)=>{const c=encodeURIComponent(e),i=encodeURIComponent(r[e]);return`${t}${t===""?"?":"&"}${c}=${i}`},"");return`${d}${u}`}n.asyncScriptLoader=o,Object.defineProperty(n,Symbol.toStringTag,{value:"Module"})}));
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@zoltanradics/async-script-loader",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Async javascript loader",
|
|
5
|
+
"main": "./dist/index.umd.js",
|
|
6
|
+
"module": "./dist/index.es.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"unpkg": "./dist/index.umd.js",
|
|
9
|
+
"jsdelivr": "./dist/index.umd.js",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.es.js",
|
|
14
|
+
"require": "./dist/index.umd.js"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"dev": "vite",
|
|
22
|
+
"build": "vite build",
|
|
23
|
+
"preview": "vite preview",
|
|
24
|
+
"prepublishOnly": "npm run build",
|
|
25
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
26
|
+
},
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public"
|
|
29
|
+
},
|
|
30
|
+
"keywords": [
|
|
31
|
+
"javascript",
|
|
32
|
+
"dynamic-script",
|
|
33
|
+
"script-loader",
|
|
34
|
+
"promise",
|
|
35
|
+
"typescript",
|
|
36
|
+
"script-injection",
|
|
37
|
+
"async-script",
|
|
38
|
+
"cdn"
|
|
39
|
+
],
|
|
40
|
+
"author": "Zoltan Radics",
|
|
41
|
+
"license": "ISC",
|
|
42
|
+
"repository": {
|
|
43
|
+
"type": "git",
|
|
44
|
+
"url": "git+https://github.com/zoltanradics/async-script-loader.git"
|
|
45
|
+
},
|
|
46
|
+
"homepage": "https://github.com/zoltanradics/async-script-loader#readme",
|
|
47
|
+
"bugs": {
|
|
48
|
+
"url": "https://github.com/zoltanradics/async-script-loader/issues"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"typescript": "^5.9.3",
|
|
52
|
+
"vite": "^7.3.1",
|
|
53
|
+
"vite-plugin-dts": "^4.5.4"
|
|
54
|
+
}
|
|
55
|
+
}
|