├── .editorconfig
├── .github
└── workflows
│ └── main.yml
├── .gitignore
├── .nojekyll
├── .npmignore
├── CHANGELOG.md
├── LICENSE.md
├── README.md
├── docs
├── .nojekyll
├── assets
│ ├── highlight.css
│ ├── icons.js
│ ├── icons.svg
│ ├── main.js
│ ├── material-style.css
│ ├── navigation.js
│ ├── search.js
│ └── style.css
├── classes
│ └── index.AsyncPreloader.html
├── enums
│ └── types.LoaderKey.html
├── index.html
├── interfaces
│ ├── types.FontOptions.html
│ ├── types.LoadItem.html
│ └── types.LoaderValue.html
├── modules
│ ├── index.html
│ └── types.html
├── types
│ ├── types.BodyMethod.html
│ ├── types.BodyResolveValue.html
│ ├── types.LoadedValue.html
│ └── types.LoadedXMLValue.html
└── variables
│ └── index.default.html
├── index.html
├── package-lock.json
├── package.json
├── src
├── index.ts
└── types.ts
├── test
├── assets
│ ├── audio.mp3
│ ├── default.unknown
│ ├── font.ttf
│ ├── image.gif
│ ├── image.jpg
│ ├── image.png
│ ├── json.json
│ ├── manifest.json
│ ├── text.txt
│ ├── video.mp4
│ ├── xml.html
│ ├── xml.svg
│ ├── xml.unknown
│ └── xml.xml
├── browser.spec.js
├── data.js
├── global-setup.js
├── global-teardown.js
├── index.spec.js
└── server.js
└── tsconfig.json
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | end_of_line = lf
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 | on:
3 | push:
4 | branches:
5 | - main
6 | pull_request:
7 | branches:
8 | - main
9 | jobs:
10 | test:
11 | name: Node.js ${{ matrix.node-version }}
12 | runs-on: ubuntu-latest
13 | timeout-minutes: 5
14 | strategy:
15 | matrix:
16 | node_version: ["22"]
17 | steps:
18 | - uses: actions/checkout@v4
19 | - uses: actions/setup-node@v4
20 | with:
21 | node-version: ${{ matrix.node_version }}
22 | cache: "npm"
23 | - run: npm ci --verbose
24 | - run: npm test
25 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
3 | /types
4 | /lib
5 | coverage
6 | /web_modules
7 | .nyc_output
8 |
--------------------------------------------------------------------------------
/.nojekyll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dmnsgn/async-preloader/c1a85109afec2a2500fc29044601caef31825333/.nojekyll
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | /web_modules
2 | /examples
3 | /docs
4 | /coverage
5 | /test
6 | /.github
7 | screenshot.*
8 | index.html
9 | tsconfig.json
10 | .editorconfig
11 | .nojekyll
12 | .nyc_output
13 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file. See [commit-and-tag-version](https://github.com/absolute-version/commit-and-tag-version) for commit guidelines.
4 |
5 | ## [8.0.4](https://github.com/dmnsgn/async-preloader/compare/v8.0.3...v8.0.4) (2024-07-08)
6 |
7 |
8 |
9 | ## [8.0.3](https://github.com/dmnsgn/async-preloader/compare/v8.0.2...v8.0.3) (2023-11-02)
10 |
11 |
12 |
13 | ## [8.0.2](https://github.com/dmnsgn/async-preloader/compare/v8.0.1...v8.0.2) (2023-02-09)
14 |
15 |
16 | ### Bug Fixes
17 |
18 | * force @types/css-font-loading-module to 0.07 and update tsconfig target ([4ca26f9](https://github.com/dmnsgn/async-preloader/commit/4ca26f9f51ffb28b025c49c7cee28e5e1f6fc670))
19 |
20 |
21 |
22 | ## [8.0.1](https://github.com/dmnsgn/async-preloader/compare/v8.0.0...v8.0.1) (2023-02-09)
23 |
24 |
25 |
26 | # [8.0.0](https://github.com/dmnsgn/async-preloader/compare/v7.0.0...v8.0.0) (2022-10-25)
27 |
28 |
29 | ### Bug Fixes
30 |
31 | * check for unsupported features in nodes ([5ad3ae6](https://github.com/dmnsgn/async-preloader/commit/5ad3ae6e2c7e613f4c2be6d6d6a6830ec92801b2))
32 | * reject with error event in loadAudio/Video ([5735f9a](https://github.com/dmnsgn/async-preloader/commit/5735f9a6bf040308bbc436a828e5f39d1aa30fa0))
33 |
34 |
35 | ### Features
36 |
37 | * add noDecode ([7394e0d](https://github.com/dmnsgn/async-preloader/commit/7394e0d200a546d1c480866c3d8d5a711d5d1d2a))
38 | * allow string for loadItem(s) ([7025c7d](https://github.com/dmnsgn/async-preloader/commit/7025c7d95f885b01c1121e1e8de877b61aa8ca81))
39 | * use HTMLImageElement.decode() ([2388d40](https://github.com/dmnsgn/async-preloader/commit/2388d40d5fd134efab2ea01a67acb1574d91be64)), closes [#89](https://github.com/dmnsgn/async-preloader/issues/89)
40 |
41 |
42 | ### BREAKING CHANGES
43 |
44 | * loadImage now decodes the image by default
45 |
46 |
47 |
48 | # [7.0.0](https://github.com/dmnsgn/async-preloader/compare/v6.1.2...v7.0.0) (2022-06-13)
49 |
50 |
51 | ### Bug Fixes
52 |
53 | * exclude android and chrome from isSafari regex ([acf8770](https://github.com/dmnsgn/async-preloader/commit/acf87707d3a6cbb099b990b88a1bb4de42d7c4d6))
54 |
55 |
56 | ### BREAKING CHANGES
57 |
58 | * changes audio and video loading in Chrome
59 |
60 |
61 |
62 | ## [6.1.2](https://github.com/dmnsgn/async-preloader/compare/v6.1.1...v6.1.2) (2022-04-02)
63 |
64 |
65 |
66 | ## [6.1.1](https://github.com/dmnsgn/async-preloader/compare/v6.1.0...v6.1.1) (2022-01-14)
67 |
68 |
69 |
70 | # [6.1.0](https://github.com/dmnsgn/async-preloader/compare/v6.0.0...v6.1.0) (2022-01-14)
71 |
72 |
73 | ### Features
74 |
75 | * compute font name from src if no id is present ([6703830](https://github.com/dmnsgn/async-preloader/commit/6703830085a40a360e5ce6539614d2361c1c38ec))
76 |
77 |
78 |
79 | # [6.0.0](https://github.com/dmnsgn/async-preloader/compare/v5.2.1...v6.0.0) (2021-12-04)
80 |
81 |
82 | ### Bug Fixes
83 |
84 | * enhance node support by checking for navigator and DOMParser ([9e4f6c5](https://github.com/dmnsgn/async-preloader/commit/9e4f6c555ea82950d446f4db0146c0ffe99db9c4))
85 | * handle falsy path in getFileExtension ([aadd369](https://github.com/dmnsgn/async-preloader/commit/aadd36949ce2fbec3fa2aa23b782be35e17ad672))
86 | * update returned types for loaders ([fe46654](https://github.com/dmnsgn/async-preloader/commit/fe4665488645f1b3708afaeaf7dfc13d0d416f5c))
87 |
88 |
89 | ### Features
90 |
91 | * add support for FontFace ([3830dd7](https://github.com/dmnsgn/async-preloader/commit/3830dd78a0231e3fc87e452036403f27bf2509e8)), closes [#79](https://github.com/dmnsgn/async-preloader/issues/79)
92 |
93 |
94 | ### BREAKING CHANGES
95 |
96 | * new behaviour for loadFont
97 |
98 |
99 |
100 | ## [5.2.1](https://github.com/dmnsgn/async-preloader/compare/v5.2.0...v5.2.1) (2021-11-12)
101 |
102 |
103 |
104 | # [5.2.0](https://github.com/dmnsgn/async-preloader/compare/v5.1.0...v5.2.0) (2021-10-29)
105 |
106 |
107 | ### Features
108 |
109 | * update dependencies and ts-jest ([6a452ff](https://github.com/dmnsgn/async-preloader/commit/6a452ffd35f11e5085d4b3945c180b81fb933815))
110 |
111 |
112 |
113 | # [5.1.0](https://github.com/dmnsgn/async-preloader/compare/v5.0.2...v5.1.0) (2021-10-02)
114 |
115 |
116 | ### Features
117 |
118 | * add exports field to package.json ([f9689de](https://github.com/dmnsgn/async-preloader/commit/f9689de1dada6c2b0140a1e5821c4eb869ee47d8))
119 |
120 |
121 |
122 | ## [5.0.2](https://github.com/dmnsgn/async-preloader/compare/v5.0.1...v5.0.2) (2021-03-24)
123 |
124 |
125 |
126 | ## [5.0.1](https://github.com/dmnsgn/async-preloader/compare/v5.0.0...v5.0.1) (2021-03-20)
127 |
128 |
129 | ### Bug Fixes
130 |
131 | * update npmignore ([b0d8e64](https://github.com/dmnsgn/async-preloader/commit/b0d8e64d4c4de183c9e61f78feab21731cd8f1e0))
132 |
133 |
134 |
135 | # [5.0.0](https://github.com/dmnsgn/async-preloader/compare/v4.9.2...v5.0.0) (2021-03-20)
136 |
137 |
138 | ### Code Refactoring
139 |
140 | * use ES modules ([c171d01](https://github.com/dmnsgn/async-preloader/commit/c171d0178f27b5e04fff7ea02260517b062e9e24))
141 |
142 |
143 | ### BREAKING CHANGES
144 |
145 | * remove cjs/umd
146 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright (C) 2017 Damien Seguin
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # async-preloader
2 |
3 | [](https://www.npmjs.com/package/async-preloader)
4 | [](https://www.npmjs.com/package/async-preloader)
5 | [](https://bundlephobia.com/package/async-preloader)
6 | [](https://github.com/dmnsgn/async-preloader/blob/main/package.json)
7 | [](https://github.com/microsoft/TypeScript)
8 | [](https://conventionalcommits.org)
9 | [](https://github.com/prettier/prettier)
10 | [](https://github.com/eslint/eslint)
11 | [](https://github.com/facebook/jest)
12 | [](https://github.com/dmnsgn/async-preloader/blob/main/LICENSE.md)
13 |
14 | Assets preloader using async/await and fetch for usage both in the browser and Node.js.
15 |
16 | [](https://paypal.me/dmnsgn)
17 | [](https://commerce.coinbase.com/checkout/56cbdf28-e323-48d8-9c98-7019e72c97f3)
18 | [](https://twitter.com/dmnsgn)
19 |
20 | ## Install
21 |
22 | ```bash
23 | npm install --save async-preloader
24 | ```
25 |
26 | ## Documentation
27 |
28 | - [AsyncPreloader class](https://dmnsgn.github.io/async-preloader/classes/index.AsyncPreloader.html)
29 | - [AsyncPreloader types](https://dmnsgn.github.io/async-preloader/modules/types.html)
30 |
31 | ## Quick start
32 |
33 | This section covers the basic usage of `AsyncPreloader`. For more informations about async/await, see [Async functions - making promises friendly](https://developers.google.com/web/fundamentals/primers/async-functions). Usage in Node.js environment is limited to its capacity to handle `fetch` requests and DOM APIs. Polyfills like [`undici`](https://www.npmjs.com/package/undici/)/[`node-fetch`](https://www.npmjs.com/package/node-fetch) (for Node.js below 18) and [`xmldom`](https://www.npmjs.com/package/xmldom) might come handy .
34 |
35 | ### Preload items and retrieve them
36 |
37 | ```javascript
38 | import AsyncPreloader from "async-preloader";
39 |
40 | const items = [
41 | { id: "myDefaultFile", src: "assets/default" },
42 | { id: "myTextFile", src: "assets/text.txt" },
43 | { id: "myJsonFile", src: "assets/json.json" },
44 | { id: "myImageFile", src: "assets/image.jpg" },
45 | { id: "myVideoFile", src: "assets/video.mp4" },
46 | { id: "myAudioFile", src: "assets/audio.mp3" },
47 | { id: "myXmlFile", src: "assets/xml.xml" },
48 | { id: "mySvgFile", src: "assets/xml.svg" },
49 | { id: "myHtmlFile", src: "assets/xml.html" },
50 | { id: "myDefaultXmlFile", src: "assets/xml", loader: "Xml" },
51 | { id: "myFont", src: `assets/font.ttf` },
52 | { id: "Space Regular", loader: "Font", fontOptions: { timeout: 10000 } },
53 | // Can be retrieved with the src property eg. AsyncPreloader.items.get("assets/fileWithoutId")
54 | { src: "assets/fileWithoutId" },
55 | ];
56 |
57 | // Pass an array of LoadItem
58 | //
59 | // Returns a Promise with an array of LoadedValue
60 | const pItems = AsyncPreloader.loadItems(items);
61 |
62 | pItems
63 | .then((items) => {
64 | const element = AsyncPreloader.items.get("myVideoFile");
65 | document.body.appendChild(element);
66 | })
67 | .catch((error) => console.error("Error loading items", error));
68 | ```
69 |
70 | ---
71 |
72 | Note: Font loader will try to detect the font in the page using [FontFaceObserver](https://github.com/dmnsgn/fontfaceobserver) when no src is specified.
73 |
74 | ### Load items from a manifest file
75 |
76 | It works in a similar fashion as createjs's [PreloadJS](http://www.createjs.com/docs/preloadjs/classes/LoadQueue.html).
77 |
78 | ```javascript
79 | import AsyncPreloader from "async-preloader";
80 |
81 | // Pass the file url and an optional path of the property to get in the JSON file.
82 | // It will load the file using the Json loader and look for the path key expecting an array of `LoadItem`s.
83 | // Default path is "items" eg the default manifest would look like this:
84 | // `{ "items": [ { "src": "assets/file1" }, { "src": "assets/file2" }] }`
85 | //
86 | // Returns a Promise with an array of LoadedValue
87 | const pItems = AsyncPreloader.loadManifest(
88 | "assets/manifest.json",
89 | "data.preloader.items"
90 | );
91 |
92 | pItems
93 | .then((items) => useLoadedItemsFromManifest(items)) // or AsyncPreloader.items.get("src or id")
94 | .catch((error) => console.error("Error loading items", error));
95 | ```
96 |
97 | ## Advanced usage
98 |
99 | This section takes a closer look at the options of `AsyncPreloader`.
100 |
101 | ### Load a single item by using the [loaders](https://github.com/dmnsgn/async-preloader/blob/main/src/index.ts#L60) directly
102 |
103 | ```javascript
104 | import AsyncPreloader from "async-preloader";
105 |
106 | // Pass a LoadItem
107 | //
108 | // Returns a Promise with the LoadedValue
109 | const pItem = AsyncPreloader.loadJson({ src: "assets/json.json" });
110 |
111 | pItem
112 | .then((item) => useLoadedItem(item))
113 | .catch((error) => console.error("Error loading item", error));
114 | ```
115 |
116 | ---
117 |
118 | Note: Using the loaders directly won't add the item to the `items` Map.
119 | Alternatively you could use `AsyncPreloader.loadItem` and rely on the file extension or add `{ loader: "Json"}` to the item.
120 |
121 | ### Load a single item by using a string directly
122 |
123 | ```javascript
124 | import AsyncPreloader from "async-preloader";
125 |
126 | try {
127 | // Pass a string
128 | //
129 | // Returns a Promise with the LoadedValue
130 | const pItem = await AsyncPreloader.loadItem("assets/json.json");
131 | } catch (error) {
132 | console.error(error);
133 | }
134 | ```
135 |
136 | ### Get an `ArrayBuffer` instead of the default `Blob`
137 |
138 | You can specify how the response is handle by using the `body` key in a `LoadItem`.
139 |
140 | Typical use case: get an ArrayBuffer for the WebAudio API to decode the data with `baseAudioContext.decodeAudioData()`.
141 |
142 | ```javascript
143 | import AsyncPreloader from "async-preloader";
144 |
145 | const audioContext = new AudioContext();
146 | const pItem = AsyncPreloader.loadAudio({
147 | src: "assets/audio.mp3",
148 | body: "arrayBuffer",
149 | });
150 |
151 | pItem
152 | .then((item) => audioContext.decodeAudioData(item))
153 | .then((decodedData) => useDecodedData(decodedData))
154 | .catch((error) => console.error("Error decoding audio", error));
155 | ```
156 |
157 | ### Getting the progress
158 |
159 | Since `fetch` doesn't support `Progress events` yet, you might want to get a per file progress.
160 |
161 | ```javascript
162 | import AsyncPreloader from "async-preloader";
163 |
164 | const items = [
165 | { id: "myDefaultFile", src: "assets/default" }, // ...
166 | ];
167 |
168 | let loadedCount = 0;
169 |
170 | async function preload() {
171 | await Promise.all(
172 | items.map(async (item) => {
173 | const data = await AsyncPreloader.loadItem(item);
174 | loadedCount++;
175 | console.log(`Progress: ${(100 * loadedCount) / items.length}%`);
176 | })
177 | );
178 | }
179 |
180 | await preload();
181 | ```
182 |
183 | ### Abort one or more loadItem(s) request(s)
184 |
185 | To abort a loadItem(s) call, you can create an `AbortController` instance and pass its signal to options.
186 |
187 | ```javascript
188 | const controller = new AbortController();
189 |
190 | const timeoutId = setTimeout(() => {
191 | controller.abort();
192 | }, 150);
193 |
194 | try {
195 | await AsyncPreloader.loadItems(
196 | items.map((item) => ({
197 | ...item,
198 | options: { ...(item.options || {}), signal: controller.signal },
199 | }))
200 | );
201 | } catch (error) {
202 | if (error.name === "AbortError") console.log("Request was aborted");
203 | } finally {
204 | clearTimeout(timeoutId);
205 | }
206 | ```
207 |
208 | ## License
209 |
210 | MIT © [Damien Seguin](https://github.com/dmnsgn)
211 |
--------------------------------------------------------------------------------
/docs/.nojekyll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dmnsgn/async-preloader/c1a85109afec2a2500fc29044601caef31825333/docs/.nojekyll
--------------------------------------------------------------------------------
/docs/assets/highlight.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --light-hl-0: #795E26;
3 | --dark-hl-0: #DCDCAA;
4 | --light-hl-1: #000000;
5 | --dark-hl-1: #D4D4D4;
6 | --light-hl-2: #A31515;
7 | --dark-hl-2: #CE9178;
8 | --light-hl-3: #0000FF;
9 | --dark-hl-3: #569CD6;
10 | --light-hl-4: #AF00DB;
11 | --dark-hl-4: #C586C0;
12 | --light-hl-5: #001080;
13 | --dark-hl-5: #9CDCFE;
14 | --light-hl-6: #0070C1;
15 | --dark-hl-6: #4FC1FF;
16 | --light-hl-7: #098658;
17 | --dark-hl-7: #B5CEA8;
18 | --light-hl-8: #008000;
19 | --dark-hl-8: #6A9955;
20 | --light-hl-9: #267F99;
21 | --dark-hl-9: #4EC9B0;
22 | --light-hl-10: #000000FF;
23 | --dark-hl-10: #D4D4D4;
24 | --light-code-background: #FFFFFF;
25 | --dark-code-background: #1E1E1E;
26 | }
27 |
28 | @media (prefers-color-scheme: light) { :root {
29 | --hl-0: var(--light-hl-0);
30 | --hl-1: var(--light-hl-1);
31 | --hl-2: var(--light-hl-2);
32 | --hl-3: var(--light-hl-3);
33 | --hl-4: var(--light-hl-4);
34 | --hl-5: var(--light-hl-5);
35 | --hl-6: var(--light-hl-6);
36 | --hl-7: var(--light-hl-7);
37 | --hl-8: var(--light-hl-8);
38 | --hl-9: var(--light-hl-9);
39 | --hl-10: var(--light-hl-10);
40 | --code-background: var(--light-code-background);
41 | } }
42 |
43 | @media (prefers-color-scheme: dark) { :root {
44 | --hl-0: var(--dark-hl-0);
45 | --hl-1: var(--dark-hl-1);
46 | --hl-2: var(--dark-hl-2);
47 | --hl-3: var(--dark-hl-3);
48 | --hl-4: var(--dark-hl-4);
49 | --hl-5: var(--dark-hl-5);
50 | --hl-6: var(--dark-hl-6);
51 | --hl-7: var(--dark-hl-7);
52 | --hl-8: var(--dark-hl-8);
53 | --hl-9: var(--dark-hl-9);
54 | --hl-10: var(--dark-hl-10);
55 | --code-background: var(--dark-code-background);
56 | } }
57 |
58 | :root[data-theme='light'] {
59 | --hl-0: var(--light-hl-0);
60 | --hl-1: var(--light-hl-1);
61 | --hl-2: var(--light-hl-2);
62 | --hl-3: var(--light-hl-3);
63 | --hl-4: var(--light-hl-4);
64 | --hl-5: var(--light-hl-5);
65 | --hl-6: var(--light-hl-6);
66 | --hl-7: var(--light-hl-7);
67 | --hl-8: var(--light-hl-8);
68 | --hl-9: var(--light-hl-9);
69 | --hl-10: var(--light-hl-10);
70 | --code-background: var(--light-code-background);
71 | }
72 |
73 | :root[data-theme='dark'] {
74 | --hl-0: var(--dark-hl-0);
75 | --hl-1: var(--dark-hl-1);
76 | --hl-2: var(--dark-hl-2);
77 | --hl-3: var(--dark-hl-3);
78 | --hl-4: var(--dark-hl-4);
79 | --hl-5: var(--dark-hl-5);
80 | --hl-6: var(--dark-hl-6);
81 | --hl-7: var(--dark-hl-7);
82 | --hl-8: var(--dark-hl-8);
83 | --hl-9: var(--dark-hl-9);
84 | --hl-10: var(--dark-hl-10);
85 | --code-background: var(--dark-code-background);
86 | }
87 |
88 | .hl-0 { color: var(--hl-0); }
89 | .hl-1 { color: var(--hl-1); }
90 | .hl-2 { color: var(--hl-2); }
91 | .hl-3 { color: var(--hl-3); }
92 | .hl-4 { color: var(--hl-4); }
93 | .hl-5 { color: var(--hl-5); }
94 | .hl-6 { color: var(--hl-6); }
95 | .hl-7 { color: var(--hl-7); }
96 | .hl-8 { color: var(--hl-8); }
97 | .hl-9 { color: var(--hl-9); }
98 | .hl-10 { color: var(--hl-10); }
99 | pre, code { background: var(--code-background); }
100 |
--------------------------------------------------------------------------------
/docs/assets/icons.js:
--------------------------------------------------------------------------------
1 | (function(svg) {
2 | svg.innerHTML = ``;
3 | svg.style.display = 'none';
4 | if (location.protocol === 'file:') {
5 | if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', updateUseElements);
6 | else updateUseElements()
7 | function updateUseElements() {
8 | document.querySelectorAll('use').forEach(el => {
9 | if (el.getAttribute('href').includes('#icon-')) {
10 | el.setAttribute('href', el.getAttribute('href').replace(/.*#/, '#'));
11 | }
12 | });
13 | }
14 | }
15 | })(document.body.appendChild(document.createElementNS('http://www.w3.org/2000/svg', 'svg')))
--------------------------------------------------------------------------------
/docs/assets/icons.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/assets/material-style.css:
--------------------------------------------------------------------------------
1 | @import url("https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&family=Space+Mono:ital,wght@0,400;0,700;1,400;1,700&display=swap");
2 |
3 | :root,
4 | :root[data-theme="light"],
5 | :root[data-theme="dark"] {
6 | --font-sans: "Space Grotesk", sans-serif;
7 | --font-mono: "Space Mono", monospace;
8 |
9 | --color-background: var(--md-sys-color-surface-container);
10 | --color-background-secondary: var(--md-sys-color-surface-container-high);
11 | --color-background-warning: var(--md-sys-color-error-container);
12 | --color-warning-text: var(--md-sys-color-on-error-container);
13 | --color-icon-background: var(--md-sys-color-on-primary);
14 | --color-accent: var(--md-sys-color-secondary-container);
15 | --color-active-menu-item: var(--md-sys-color-surface-container-highest);
16 | --color-text: var(--md-sys-color-on-surface);
17 | --color-text-aside: var(--md-sys-color-on-surface-variant);
18 | --color-link: var(--md-sys-color-primary);
19 |
20 | --color-ts-project: var(--md-sys-color-secondary);
21 | --color-ts-module: var(--color-ts-project);
22 | --color-ts-namespace: var(--color-ts-project);
23 |
24 | --color-ts-enum: var(--md-sys-color-tertiary);
25 | --color-ts-enum-member: var(--color-ts-enum);
26 |
27 | --color-ts-variable: var(--md-sys-color-primary);
28 | --color-ts-function: var(--md-sys-color-secondary);
29 | --color-ts-class: var(--md-sys-color-tertiary);
30 | --color-ts-interface: var(--md-sys-color-tertiary);
31 |
32 | --color-ts-constructor: var(--md-sys-color-inverse-primary);
33 |
34 | --color-ts-property: var(--md-sys-color-on-background);
35 | --color-ts-method: var(--color-ts-function);
36 |
37 | --color-ts-call-signature: var(--color-ts-method);
38 | --color-ts-index-signature: var(--color-ts-property); /* ? */
39 | --color-ts-constructor-signature: var(--color-ts-function);
40 | --color-ts-parameter: var(--md-sys-color-primary);
41 |
42 | --color-ts-type-parameter: var(--md-sys-color-tertiary);
43 | --color-ts-accessor: var(--color-ts-property);
44 | --color-ts-get-signature: var(--color-ts-accessor);
45 | --color-ts-set-signature: var(--color-ts-accessor);
46 | --color-ts-type-alias: var(--md-sys-color-tertiary);
47 |
48 | /* --external-icon: var(--md-sys-external-icon);
49 | --color-scheme: var(--md-sys-color-scheme); */
50 |
51 | --top-app-bar-height: 4.5rem;
52 | --footer-height: 3.5rem;
53 | }
54 |
55 | body {
56 | font-family: var(--font-sans);
57 | }
58 | code,
59 | pre {
60 | font-family: var(--font-mono);
61 | }
62 |
63 | img {
64 | max-width: 100%;
65 | }
66 |
67 | *::-webkit-scrollbar {
68 | width: 8px;
69 | height: 8px;
70 | }
71 | *::-webkit-scrollbar-track {
72 | background: none;
73 | }
74 | *::-webkit-scrollbar-thumb {
75 | border: none;
76 | }
77 |
78 | .container-main {
79 | min-height: calc(100vh - var(--top-app-bar-height) - var(--footer-height));
80 | }
81 | .col-content {
82 | overflow: hidden;
83 | box-sizing: border-box;
84 | padding: 1.75rem;
85 | border-radius: 28px;
86 | background-color: var(--md-sys-color-surface);
87 | }
88 | .page-menu {
89 | height: fit-content;
90 | padding: 0.75rem 1.75rem;
91 | border-radius: 28px;
92 | background-color: var(--md-sys-color-surface);
93 | }
94 | .site-menu > *,
95 | .page-menu > * {
96 | position: relative;
97 | }
98 | .title {
99 | display: block;
100 | max-width: calc(100% - 5rem);
101 | white-space: nowrap;
102 | overflow: hidden;
103 | text-overflow: ellipsis;
104 | font-size: 22px;
105 | }
106 |
107 | .tsd-page-toolbar {
108 | padding: 8px 0;
109 | height: calc(var(--top-app-bar-height) - 16px);
110 | background-color: var(--color-background);
111 | border-bottom: none;
112 | }
113 | .tsd-page-toolbar .tsd-toolbar-contents {
114 | height: 56px;
115 | }
116 | .tsd-page-toolbar .table-cell {
117 | height: 56px;
118 | margin-left: 1.5rem;
119 | }
120 | .tsd-page-toolbar .tsd-toolbar-icon {
121 | padding: 20px 0;
122 | }
123 | #tsd-search {
124 | line-height: 56px;
125 | border-radius: 22px;
126 | }
127 | #tsd-search .results {
128 | z-index: -1;
129 | top: calc(56px - 22px);
130 | padding-top: 22px;
131 | box-shadow: 0px 4px 2px rgba(0, 0, 0, 0.125);
132 | background-color: var(--color-background-secondary);
133 | border-bottom-left-radius: 22px;
134 | border-bottom-right-radius: 22px;
135 | overflow: hidden;
136 | }
137 | #tsd-search .results li {
138 | background: none;
139 | }
140 | #tsd-search .results a {
141 | padding: 1rem 0.25rem;
142 | }
143 | .col-sidebar {
144 | padding-top: 0;
145 | margin-right: 1rem;
146 | }
147 |
148 | .tsd-signature {
149 | padding: 1rem 1.5rem;
150 | border-radius: 24px;
151 | background-color: var(--md-sys-color-surface-container);
152 | }
153 |
154 | .tsd-page-navigation ul {
155 | padding-left: 0.44rem;
156 | }
157 | .tsd-navigation a,
158 | .tsd-navigation summary > span,
159 | .tsd-page-navigation a {
160 | padding: 0.88rem;
161 | border-radius: 24px;
162 | }
163 | .tsd-navigation a:hover,
164 | .tsd-page-navigation a:hover {
165 | text-decoration: none;
166 | background-color: var(--md-sys-color-surface-container-high);
167 | }
168 | .page-menu .tsd-accordion-summary svg {
169 | position: absolute;
170 | right: 0;
171 | }
172 | .site-menu .tsd-navigation .tsd-accordion-summary {
173 | display: flex;
174 | flex-direction: row-reverse;
175 | width: 100%;
176 | }
177 |
178 | .tsd-small-nested-navigation {
179 | margin-left: 1rem;
180 | }
181 | .tsd-nested-navigation {
182 | margin-left: 2.5rem;
183 | }
184 | .tsd-nested-navigation > li > a,
185 | .tsd-nested-navigation > li > span {
186 | width: 100%;
187 | }
188 | .tsd-navigation > a,
189 | .tsd-navigation .tsd-accordion-summary {
190 | width: 100%;
191 | }
192 | .tsd-index-accordion .tsd-accordion-summary > svg {
193 | position: absolute;
194 | right: 1.5rem;
195 | margin-top: 1rem;
196 | }
197 | .tsd-accordion-summary .tsd-kind-icon ~ span {
198 | margin-right: 2.5rem;
199 | }
200 | .tsd-accordion-summary .tsd-nested-navigation > li > a,
201 | .tsd-nested-navigation > li > span {
202 | width: calc(100% - 0.44rem);
203 | }
204 | .tsd-kind-icon ~ span {
205 | white-space: nowrap;
206 | overflow: hidden;
207 | text-overflow: ellipsis;
208 | }
209 | .tsd-generator {
210 | padding: 0;
211 | border-top: none;
212 | height: var(--footer-height);
213 | line-height: var(--footer-height);
214 | }
215 | .tsd-generator > p {
216 | padding: 0 2rem;
217 | }
218 |
219 | @media (max-width: 769px) {
220 | .container {
221 | padding: 1rem;
222 | }
223 | .col-sidebar {
224 | margin-right: 0;
225 | }
226 | }
227 | @media (min-width: 770px) {
228 | .container-main {
229 | margin: 0 auto;
230 | }
231 | .site-menu {
232 | margin-right: 0.5rem;
233 | }
234 | }
235 | @media (min-width: 1200px) {
236 | .page-menu,
237 | .site-menu {
238 | max-height: calc(100vh - var(--footer-height) - var(--top-app-bar-height));
239 | top: var(--top-app-bar-height);
240 | }
241 | .page-menu {
242 | margin-left: 1rem;
243 | }
244 | .col-sidebar {
245 | margin-right: 0;
246 | }
247 | }
248 |
--------------------------------------------------------------------------------
/docs/assets/navigation.js:
--------------------------------------------------------------------------------
1 | window.navigationData = "data:application/octet-stream;base64,H4sIAAAAAAAAE43PzUrEMBQF4He566JOZfzpzlkI4gyKCxGGLmJzhwbTpCS3wxTpu4uhTdIq7WxzD1/O2X8D4YkgA6E4niCBmlEJGVSaNxLtpXu+KKmSkMCXUByyNIGiFJIbVJDtPfBgW1W8GpSacTRBKiSz1kvj1BhepXdd4j2OB9ZICtCRGcE+Q6k+MDau0y6PEGprtH9XueezVm1dz2dsA4KqqQbCn8dYvONRK3qpSWgVFRGK0BxY4btEqUmv9U2E/f73RFjNSENkiUHzzmSDC1KfmsM2mrc7pFLzYDmgZ8J9olzd367W6UR6Q6vlESfdpl6cWlLdDD4DRoHzrI/ddpEbMv+LeZf/ACa24ZB7AwAA"
--------------------------------------------------------------------------------
/docs/assets/search.js:
--------------------------------------------------------------------------------
1 | window.searchData = "data:application/octet-stream;base64,H4sIAAAAAAAAE7Wa227bRhCG32V9y7reEy3prkEbIG3SBm0RBBCMgBZXDluRFEjKtWH43YslaXGGml1xLfnS1vz7D+fbw/DwxKryv5otlk/s36xI2UJErEhywxYsK1LzwCK2qzZswfIy3W1M/WP738vvTb5hEVttkro2NVsw9hy9jMDFbD/GT/VjsfpcmU2ZpKbaD9br+sFwEDF2xLZJZYpmnxQwuxJq79YNUE+3uRgUg11Tpz9k9Q/bKrtPGnNgPhrHlUta5p+Tqg656AuoOUM+4koNJNamWX3/0Jg8IB+oOXc+d6b5XJXbgGwGxRvk8j7bmF8eGlPUWVmEJTWWvlF275La/G7/E5wcUL5Rbq/L6+1y+tgG/GYew5KCsjfI6lOWm78ft4GVAqoz5KT5sMGvyqJuqt2qKUM2Kazy7NITN8qsMXnIlv0Sf7pzatbJbtO8K9PHT6b5XqYhWzWhPVtGHwNPy4ux7lWZoOlqf/0QSAZqzpfBKxI4m/+npMjWpm4CcwCy8+Txt3kIzaGXnMf/1zroUASS8/j/VFXJ47vdeh20Kg6V58nm3aa8DUyjl5zH/31Z5T8nTRKYA5CdaXXmyV3IYQY158ngS5aaMjCDF82ZZuYuzUIzeNGcJ4Ov7d1SiP/XY/dXQXOxCN2Zesmr/KUYn5V78/ukypLb4Ra1/z3sdnIYvnncmvrg7rf9r/fuV1zNr7kexiG6i3aQfqjhZ3+iXTp+mz9NXW7uzZdkszNOMxh0mmXbbaRfP310G+KQc9gd83qNkY6RCWo6sqIx1TpZIRsbEeaBury6Wk0c/6IL9Xjs453tdTrVK3P3sNOsRg95jtjto0+xLLdNVhb1VM8h/BTTdVk0f4QZY8kp5rdl+jjVtY89xS4f37Mescypu9Vw26L82azKdLItiA+0neENBj00MMUuR9uL/TVw4Q97C2qi3UNfeDvnkYJ2otplj+GUHnmSL2qMPYbebniS00EL7HE72vdOckS3YB43733XJCfcVnus/L30JC/cQHu8/F3ztFmJWmXffPT2x5O8YFPscfJ1whPnYTFpVnh7XrcTbE3eTzl1QNAJDUpq6lWVbZuyCrC6wCrfpcJLcaTQdvOgtsftB8WJ1o2pm7+aKivuAtyR6NQEstyUu5BrHxTh1uP211S4xyZP3j7ohDlmXt6YHOmhoNMFEh1fTN2FnNTZIPuA5ubQPNZaDpX+9q15nfflXjkxhct91v6HzgdvJ47ndKicXJabqL/3Xjyxe1O1r84WTFzKyzmL2Dozm9S+le5yjNiqzHM71k3/2xezaveZxbIL+fGKRcurSF5fxrP5zU20fFG0P7T/aMM4i5acCuMoTLBoKagwgcIki5aSCpMoTLFoqagwhcI0i5aaCtMoLGbRMqbCYhR2zaLlNRV2jcJmLFrOqLAZCpuzaDmnwua4vLbanOTARyBaEjQKzILbmnOSBsc4uC07J4FwTITbynOSCcdQuC0+J7FwzIXb+vM4kuJSzziOxGi4RcBJOBzT4ZYCJ/lwDIhbEJxExDEjYUEIkpHAjIQFIUhGYrRe2gVDrxjMSFgQgmQkMCNhQQiSkcCMhAUhSEYCMxIWhCBXj8CMhAUhSEYCMxIWhCAZCcxIWBCCZCQwI2lBSJKRxIykBSFJRhIzkhaEJBnJ0bbW7mv0xoYZSeWc8xIzkhaEJGlKzEhaEJKkKTEjaUFIkqbEjKQFIUmaEjOSFoSckVeEGamWEUlTYUbKglBX1JgKM1IWhCJpKsxIWRCKpKlGp097/JA0FWakLAhFn1SYkbIgFMlIYUbKglAkI4UZKQtCkYwUZqQsCEWuOIUZaQtCkYw0ZqQtCE2uOI0ZaQtCk4w0ZqQtCE0y0piRVs5Zp0dNQtslkDQ1ZqQtCE3S1JiRtiA03XpgRtqC0CRNjRlpC0KTNDVmFFsQmqQZY0axex3FmFHcMiK5x5hRbEHEJPe4Y9R2qvemakz6oetYl8t9+/7EvvVtrJ6/NNRPLBZs8fT8PLSt9i87dGKftN32T9oGrVKDVmmX1r4T2w6fUg5yPqi5S9w9UgHpXg0i7VLdts/pQJ4a5Bm7RO1D50EkQWHUlUeU92/JgBQkKX3Sqnupdd/duIIBQGmk6yrRB02gqrCsLu3+JeSgG75+fWJi5tfRl80lcJb+EYjZACYTV361vTUcT+QYFD12u4NnPWBWgcy1ayKnZb7tv3IFpAAohw7e/ANLUO7YNUfaD1mz9r0emM1gXrp07WM7YCbA9bnytKL9iyZwhSBRObfREdNu4ypP22fWIN8YJHztEN6ZZp1tzG1Sm+7WedBfD/IjajN8zQrmBaizXz42BhfuWg93pp/J/xq0eYC9Y+6WUtOYg2nMXfPizjTb9pNigBgQdqgyvEkBhXRVJuse1QOYoCjKdWn9F/iDClyT65L6zyNBIcCc5a6D6Z8aw1ZgPSrXJLXEHCeaAJkKV6qtfnwwCVBO4SJglePDSYD9Wrj263aSpYfnBNx+XOu60z7km0M5KLF0lfhwp5ZgSUnXkiSXhQKGypsv3p+BzJ/mwSVqsHto13y12vGWKYBQ+C6S2vIETNiX8cH6EoCo8FVofCpwAIXH3TYtfSvgcMGBGcx9M3i86DjY7bivxvn++1IgBnXmvjo37QtKIAQ7EXftz1Z4370CBDUGJ6jwlejBviQAOlBi4doxqT1dgV1F8Y5N7FrqRZn2nwSAAcAeoVzziTq7QXGlq0bttzpAA6ojXdPAvjaq+9dGYLHBQ8jFcsxRgRyVK8f9iyJgBmhop677Gg4wBHNVuObq/pUccANV0S72BzNNATftOk5Gs0wD1ppkfROxbbY1m6wwbLG8eX7+H02euE4NNwAA";
--------------------------------------------------------------------------------
/docs/modules/index.html:
--------------------------------------------------------------------------------
1 |
index | async-preloader
--------------------------------------------------------------------------------
/docs/modules/types.html:
--------------------------------------------------------------------------------
1 | types | async-preloader
--------------------------------------------------------------------------------
/docs/types/types.BodyMethod.html:
--------------------------------------------------------------------------------
1 | BodyMethod | async-preloaderBodyMethod: "arrayBuffer" | "blob" | "formData" | "json" | "text"
--------------------------------------------------------------------------------
/docs/types/types.BodyResolveValue.html:
--------------------------------------------------------------------------------
1 | BodyResolveValue | async-preloaderType alias BodyResolveValue
BodyResolveValue: ArrayBuffer | Blob | FormData | JSON | string
--------------------------------------------------------------------------------
/docs/types/types.LoadedValue.html:
--------------------------------------------------------------------------------
1 | LoadedValue | async-preloader
--------------------------------------------------------------------------------
/docs/types/types.LoadedXMLValue.html:
--------------------------------------------------------------------------------
1 | LoadedXMLValue | async-preloaderType alias LoadedXMLValue
LoadedXMLValue: Document | XMLDocument
--------------------------------------------------------------------------------
/docs/variables/index.default.html:
--------------------------------------------------------------------------------
1 | default | async-preloader
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | async-preloader by Damien Seguin
9 | (https://github.com/dmnsgn/async-preloader)
10 |
11 |
30 |
31 |
32 |
33 | async-preloader
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "async-preloader",
3 | "version": "8.0.4",
4 | "description": "Assets preloader using async/await and fetch for usage both in the browser and Node.js.",
5 | "keywords": [
6 | "preloader",
7 | "assets",
8 | "async",
9 | "await",
10 | "typescript",
11 | "ES2017",
12 | "fetch"
13 | ],
14 | "homepage": "https://github.com/dmnsgn/async-preloader",
15 | "bugs": "https://github.com/dmnsgn/async-preloader/issues",
16 | "repository": {
17 | "type": "git",
18 | "url": "git+https://github.com/dmnsgn/async-preloader.git"
19 | },
20 | "funding": [
21 | {
22 | "type": "individual",
23 | "url": "https://paypal.me/dmnsgn"
24 | },
25 | {
26 | "type": "individual",
27 | "url": "https://commerce.coinbase.com/checkout/56cbdf28-e323-48d8-9c98-7019e72c97f3"
28 | }
29 | ],
30 | "license": "MIT",
31 | "author": "Damien Seguin (https://github.com/dmnsgn)",
32 | "type": "module",
33 | "exports": {
34 | ".": {
35 | "types": "./types/index.d.ts",
36 | "default": "./lib/index.js"
37 | }
38 | },
39 | "main": "lib/index.js",
40 | "types": "types/index.d.ts",
41 | "scripts": {
42 | "coverage": "nyc report --reporter=text --reporter=html",
43 | "dev": "npx snowdev --no-serve",
44 | "prejest": "npx snowdev install",
45 | "jest": "NODE_OPTIONS=--experimental-vm-modules npx jest test/ --watch --no-cache",
46 | "pretest": "npx snowdev install && npx snowdev build --no-docs --no-lint",
47 | "test": "NODE_OPTIONS=--experimental-vm-modules npx jest test/ --runInBand"
48 | },
49 | "jest": {
50 | "globalSetup": "jest-environment-puppeteer/setup",
51 | "globalTeardown": "/test/global-teardown.js",
52 | "moduleNameMapper": {
53 | "^(\\.{1,2}/.*)\\.js$": "$1"
54 | },
55 | "preset": "ts-jest/presets/js-with-ts-esm",
56 | "resolver": "jest-ts-webcompat-resolver",
57 | "setupFilesAfterEnv": [
58 | "expect-puppeteer"
59 | ],
60 | "testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(ts|js)x?$",
61 | "transform": {
62 | "^.+\\.(t|j)s$": [
63 | "ts-jest",
64 | {
65 | "useESM": true
66 | }
67 | ]
68 | }
69 | },
70 | "dependencies": {
71 | "fontfaceobserver-es": "^3.3.3",
72 | "tslib": "^2.6.3"
73 | },
74 | "devDependencies": {
75 | "@types/css-font-loading-module": "^0.0.13",
76 | "@types/expect-puppeteer": "^5.0.6",
77 | "@types/jest": "^29.5.12",
78 | "@types/jest-environment-puppeteer": "^5.0.6",
79 | "@types/node": "^20.14.10",
80 | "@types/puppeteer": "^5.4.7",
81 | "@xmldom/xmldom": "^0.8.10",
82 | "es-module-shims": "^1.10.0",
83 | "jest": "^29.7.0",
84 | "jest-puppeteer": "^10.0.1",
85 | "jest-ts-webcompat-resolver": "^1.0.0",
86 | "mime-types": "^2.1.35",
87 | "nyc": "^17.0.0",
88 | "portfinder": "^1.0.32",
89 | "puppeteer": "^22.12.1",
90 | "puppeteer-to-istanbul": "^1.4.0",
91 | "snowdev": "^2.2.3",
92 | "ts-jest": "^29.1.5",
93 | "typescript": "^5.5.3"
94 | },
95 | "engines": {
96 | "node": ">=22.0.0",
97 | "npm": ">=10.5.1",
98 | "snowdev": ">=2.2.x"
99 | },
100 | "jest-puppeteer": {
101 | "launch": {
102 | "headless": "new"
103 | }
104 | },
105 | "snowdev": {
106 | "dependencies": [
107 | "es-module-shims",
108 | "fontfaceobserver-es"
109 | ]
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import FontFaceObserver from "fontfaceobserver-es";
2 |
3 | import {
4 | BodyMethod,
5 | LoadItem,
6 | LoadedValue,
7 | LoadedXMLValue,
8 | LoaderKey,
9 | LoaderValue,
10 | FontOptions,
11 | } from "./types.js";
12 |
13 | const isSafari =
14 | /^((?!chrome|android).)*safari/i.test(globalThis.navigator?.userAgent) ===
15 | true;
16 |
17 | /**
18 | * AsyncPreloader: assets preloader using ES2017 async/await and fetch.
19 | *
20 | * It exports an instance of itself as default so you can:
21 | *
22 | * ```js
23 | * import Preloader from "async-preloader";
24 | *
25 | * await Preloader.loadItems([]);
26 | * ```
27 | *
28 | * to use directly as a singleton or
29 | *
30 | * ```js
31 | * import { AsyncPreloader as Preloader } from "async-preloader";
32 | *
33 | * const preloader = new Preloader();
34 | * await preloader.loadItems([]);
35 | * ```
36 | * if you need more than one instance.
37 | */
38 | class AsyncPreloader {
39 | // Properties
40 | /**
41 | * Object that contains the loaded items
42 | */
43 | public items: Map = new Map();
44 |
45 | /**
46 | * Default body method to be called on the Response from fetch if no body option is specified on the LoadItem
47 | */
48 | public defaultBodyMethod: BodyMethod = "blob";
49 |
50 | /**
51 | * Default loader to use if no loader key is specified in the {@link LoadItem} or if the extension doesn't match any of the {@link AsyncPreloader.loaders} extensions
52 | */
53 | public defaultLoader: LoaderKey = LoaderKey.Text;
54 |
55 | /**
56 | * Loader types and the extensions they handle
57 | *
58 | * Allows the omission of the loader key in a {@link LoadItem.loader} for some generic extensions
59 | */
60 | private static loaders: Map = new Map()
61 | .set(LoaderKey.Text, { extensions: ["txt"] })
62 | .set(LoaderKey.Json, { extensions: ["json"] })
63 | .set(LoaderKey.Image, { extensions: ["jpeg", "jpg", "gif", "png", "webp"] })
64 | .set(LoaderKey.Video, { extensions: ["webm", "ogg", "mp4"] })
65 | .set(LoaderKey.Audio, { extensions: ["webm", "ogg", "mp3", "wav", "flac"] })
66 | .set(LoaderKey.Xml, {
67 | extensions: ["xml", "svg", "html"],
68 | mimeType: {
69 | xml: "text/xml",
70 | svg: "image/svg+xml",
71 | html: "text/html",
72 | },
73 | defaultMimeType: "text/xml",
74 | })
75 | .set(LoaderKey.Font, {
76 | extensions: ["woff2", "woff", "ttf", "otf", "eot"],
77 | });
78 |
79 | /**
80 | * DOMParser instance for the XML loader
81 | */
82 | private static domParser =
83 | typeof DOMParser !== "undefined" && new DOMParser();
84 |
85 | // API
86 | /**
87 | * Load the specified manifest (array of items)
88 | *
89 | * @param items Items to load
90 | * @returns Resolve when all items are loaded, reject for any error
91 | */
92 | public loadItems = async (
93 | items: LoadItem[] | string[],
94 | ): Promise => {
95 | return await Promise.all(items.map(this.loadItem));
96 | };
97 |
98 | /**
99 | * Load a single item
100 | *
101 | * @param item Item to load
102 | * @returns Resolve when item is loaded, reject for any error
103 | */
104 | public loadItem = async (item: LoadItem | string): Promise => {
105 | if (typeof item === "string") item = { src: item };
106 |
107 | const extension: string = AsyncPreloader.getFileExtension(
108 | (item.src as string) || "",
109 | );
110 | const loaderKey: LoaderKey =
111 | item.loader || AsyncPreloader.getLoaderKey(extension);
112 |
113 | const loadedItem: LoadedValue = await this[`load` + loaderKey](item);
114 |
115 | this.items.set((item.id || item.src) as string, loadedItem);
116 |
117 | return loadedItem;
118 | };
119 |
120 | // Special loaders
121 | /**
122 | * Load a manifest of items
123 | *
124 | * @param src Manifest src url
125 | * @param key Manifest key in the JSON object containing the array of LoadItem.
126 | * @returns
127 | */
128 | public loadManifest = async (
129 | src: string,
130 | key = "items",
131 | ): Promise => {
132 | const loadedManifest: JSON = await this.loadJson({
133 | src,
134 | });
135 | const items: LoadItem[] = AsyncPreloader.getProp(loadedManifest, key);
136 |
137 | return await this.loadItems(items);
138 | };
139 |
140 | // Text loaders
141 | /**
142 | * Load an item and parse the Response as text
143 | *
144 | * @param item Item to load
145 | * @returns Fulfilled value of parsed Response
146 | */
147 | public loadText = async (item: LoadItem): Promise => {
148 | const response: Response = await AsyncPreloader.fetchItem(item);
149 | return await response.text();
150 | };
151 |
152 | /**
153 | * Load an item and parse the Response as json
154 | *
155 | * @param item Item to load
156 | * @returns Fulfilled value of parsed Response
157 | */
158 | public loadJson = async (item: LoadItem): Promise => {
159 | const response: Response = await AsyncPreloader.fetchItem(item);
160 | return await response.json();
161 | };
162 |
163 | /**
164 | * Load an item and parse the Response as arrayBuffer
165 | *
166 | * @param item Item to load
167 | * @returns Fulfilled value of parsed Response
168 | */
169 | public loadArrayBuffer = async (item: LoadItem): Promise => {
170 | const response: Response = await AsyncPreloader.fetchItem(item);
171 | return await response.arrayBuffer();
172 | };
173 |
174 | /**
175 | * Load an item and parse the Response as blob
176 | *
177 | * @param item Item to load
178 | * @returns Fulfilled value of parsed Response
179 | */
180 | public loadBlob = async (item: LoadItem): Promise => {
181 | const response: Response = await AsyncPreloader.fetchItem(item);
182 | return await response.blob();
183 | };
184 |
185 | /**
186 | * Load an item and parse the Response as formData
187 | *
188 | * @param item Item to load
189 | * @returns Fulfilled value of parsed Response
190 | */
191 | public loadFormData = async (item: LoadItem): Promise => {
192 | const response: Response = await AsyncPreloader.fetchItem(item);
193 | return await response.formData();
194 | };
195 |
196 | // Custom loaders
197 | /**
198 | * Load an item in one of the following cases:
199 | * - item's "loader" option set as "Image"
200 | * - item's "src" option extensions matching the loaders Map
201 | * - direct call of the method
202 | *
203 | * @param item Item to load
204 | * @returns Fulfilled value with a decoded HTMLImageElement instance of or a parsed Response according to the "body" option. Defaults to a decoded HTMLImageElement.
205 | */
206 | public loadImage = async (item: LoadItem): Promise => {
207 | const image = new Image();
208 |
209 | if (item.body) {
210 | const response: Response = await AsyncPreloader.fetchItem(item);
211 | const data: LoadedValue = await response[item.body]();
212 |
213 | if (item.body !== "blob") return data;
214 |
215 | return await new Promise((resolve, reject) => {
216 | image.addEventListener("load", function load() {
217 | image.removeEventListener("load", load);
218 | resolve(image);
219 | });
220 | image.addEventListener("error", function error(event) {
221 | image.removeEventListener("error", error);
222 | reject(event);
223 | });
224 | image.src = URL.createObjectURL(data as Blob);
225 | });
226 | }
227 |
228 | image.src = item.src as string;
229 |
230 | if (!item.noDecode) await image.decode();
231 |
232 | return image;
233 | };
234 |
235 | /**
236 | * Load an item in one of the following cases:
237 | * - item's "loader" option set as "Video"
238 | * - item's "src" option extensions matching the loaders Map
239 | * - direct call of the method
240 | *
241 | * @param item Item to load
242 | * @returns Fulfilled value of parsed Response according to the "body" option. Defaults to an HTMLVideoElement with a blob as srcObject or src.
243 | */
244 | public loadVideo = async (item: LoadItem): Promise => {
245 | const response: Response = await AsyncPreloader.fetchItem(item);
246 | const data: LoadedValue =
247 | await response[item.body || this.defaultBodyMethod]();
248 |
249 | if (item.body) return data;
250 |
251 | const video = document.createElement("video");
252 |
253 | return await new Promise((resolve, reject) => {
254 | video.addEventListener("canplaythrough", function canplaythrough() {
255 | video.removeEventListener("canplaythrough", canplaythrough);
256 | resolve(video);
257 | });
258 | video.addEventListener("error", function error(event) {
259 | video.removeEventListener("error", error);
260 | reject(event);
261 | });
262 |
263 | try {
264 | if (isSafari) throw "";
265 | video.srcObject = data as Blob;
266 | } catch (error) {
267 | video.src = URL.createObjectURL(data as Blob);
268 | }
269 |
270 | video.load();
271 | });
272 | };
273 |
274 | /**
275 | * Load an item in one of the following cases:
276 | * - item's "loader" option set as "Audio"
277 | * - item's "src" option extensions matching the loaders Map
278 | * - direct call of the method
279 | *
280 | * @param item Item to load
281 | * @returns Fulfilled value of parsed Response according to the "body" option. Defaults to an HTMLAudioElement with a blob as srcObject or src.
282 | */
283 | public loadAudio = async (item: LoadItem): Promise => {
284 | const response: Response = await AsyncPreloader.fetchItem(item);
285 | const data: LoadedValue =
286 | await response[item.body || this.defaultBodyMethod]();
287 |
288 | if (item.body) return data;
289 |
290 | const audio = document.createElement("audio");
291 | audio.autoplay = false;
292 | audio.preload = "auto";
293 |
294 | return await new Promise((resolve, reject) => {
295 | audio.addEventListener("canplaythrough", function canplaythrough() {
296 | audio.removeEventListener("canplaythrough", canplaythrough);
297 | resolve(audio);
298 | });
299 | audio.addEventListener("error", function error(event) {
300 | audio.removeEventListener("error", error);
301 | reject(event);
302 | });
303 |
304 | try {
305 | if (isSafari) throw "";
306 | audio.srcObject = data as Blob;
307 | } catch (error) {
308 | audio.src = URL.createObjectURL(data as Blob);
309 | }
310 |
311 | audio.load();
312 | });
313 | };
314 |
315 | /**
316 | * Load an item in one of the following cases:
317 | * - item's "loader" option set as "Xml"
318 | * - item's "src" option extensions matching the loaders Map
319 | * - direct call of the method
320 | *
321 | * @param item Item to load (need a mimeType specified or default to "application/xml")
322 | * @returns Result of Response parsed as a document.
323 | */
324 | public loadXml = async (item: LoadItem): Promise => {
325 | if (!item.mimeType) {
326 | const extension: string = AsyncPreloader.getFileExtension(
327 | item.src as string,
328 | );
329 | item = {
330 | ...item,
331 | mimeType: AsyncPreloader.getMimeType(LoaderKey.Xml, extension),
332 | };
333 | }
334 |
335 | if (!AsyncPreloader.domParser) {
336 | throw new Error("DomParser is not supported.");
337 | }
338 |
339 | const response: Response = await AsyncPreloader.fetchItem(item);
340 | const data: string = await response.text();
341 |
342 | return AsyncPreloader.domParser.parseFromString(data, item.mimeType);
343 | };
344 |
345 | /**
346 | * Load a font via FontFace or check a font is loaded via FontFaceObserver instance
347 | *
348 | * @param item Item to load (id correspond to the font family name).
349 | * @returns Fulfilled value with FontFace instance or initial id if no src provided.
350 | */
351 | public loadFont = async (item: LoadItem): Promise => {
352 | const fontName = (item.id ||
353 | AsyncPreloader.getFileName(item.src as string)) as string;
354 | const options = (item.fontOptions || {}) as FontOptions;
355 |
356 | if (!item.src) {
357 | const font = new FontFaceObserver(
358 | fontName,
359 | (options.variant as FontFaceObserver.FontVariant) || {},
360 | );
361 | await font.load(options.testString, options.timeout);
362 |
363 | return fontName;
364 | }
365 |
366 | const source =
367 | item.body === "arrayBuffer"
368 | ? await this.loadArrayBuffer({ src: item.src })
369 | : `url(${item.src})`;
370 |
371 | const font = new FontFace(fontName, source, options.descriptors);
372 |
373 | return await font.load().then((font) => {
374 | document.fonts.add(font);
375 | return font;
376 | });
377 | };
378 |
379 | // Utils
380 | /**
381 | * Fetch wrapper for LoadItem
382 | *
383 | * @param item Item to fetch
384 | * @returns Fetch response
385 | */
386 | private static fetchItem(item: LoadItem): Promise {
387 | return fetch(item.src, item.options || {});
388 | }
389 |
390 | /**
391 | * Get an object property by its path in the form 'a[0].b.c' or ['a', '0', 'b', 'c'].
392 | * Similar to [lodash.get](https://lodash.com/docs/4.17.5#get).
393 | *
394 | * @param object Object with nested properties
395 | * @param path Path to the desired property
396 | * @returns The returned object property
397 | */
398 | private static getProp(object: unknown, path: string | string[]) {
399 | const p = Array.isArray(path)
400 | ? path
401 | : path.split(".").filter((index) => index.length);
402 |
403 | if (!p.length) return object;
404 |
405 | return AsyncPreloader.getProp(object[p.shift()], p);
406 | }
407 |
408 | /**
409 | * Get file extension
410 | *
411 | * @param path
412 | * @returns
413 | */
414 | private static getFileExtension(path: string): string {
415 | return (path?.match(/[^\\/]\.([^.\\/]+)$/) || [null]).pop();
416 | }
417 |
418 | /**
419 | * Get file base name
420 | *
421 | * @param path
422 | * @returns
423 | */
424 | private static getFileBaseName(path: string): string {
425 | return path.split(/[\\/]/).pop();
426 | }
427 |
428 | /**
429 | * Get file name
430 | *
431 | * @param path
432 | * @returns
433 | */
434 | private static getFileName(path: string): string {
435 | return (
436 | AsyncPreloader.getFileBaseName(path).split(".").slice(0, -1).join(".") ||
437 | path
438 | );
439 | }
440 |
441 | /**
442 | * Retrieve loader key from extension (when the loader option isn't specified in the LoadItem)
443 | *
444 | * @param extension
445 | * @returns
446 | */
447 | private static getLoaderKey(extension: string): LoaderKey {
448 | const loader = Array.from(AsyncPreloader.loaders).find((loader) =>
449 | loader[1].extensions.includes(extension),
450 | );
451 | return loader ? loader[0] : LoaderKey.Text;
452 | }
453 |
454 | /**
455 | * Retrieve mime type from extension
456 | *
457 | * @param loaderKey
458 | * @param extension
459 | * @returns
460 | */
461 | private static getMimeType(
462 | loaderKey: LoaderKey,
463 | extension: string,
464 | ): DOMParserSupportedType {
465 | const loader: LoaderValue = AsyncPreloader.loaders.get(loaderKey);
466 | return loader.mimeType[extension] || loader.defaultMimeType;
467 | }
468 | }
469 |
470 | export { AsyncPreloader };
471 |
472 | const AsyncPreloaderInstance = new AsyncPreloader();
473 | export default AsyncPreloaderInstance;
474 |
--------------------------------------------------------------------------------
/src/types.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Methods that can be called on a Request (object returned by fetch and that implements the [Body](https://developer.mozilla.org/en-US/docs/Web/API/Response/body) interface)
3 | */
4 | export type BodyMethod = "arrayBuffer" | "blob" | "formData" | "json" | "text";
5 |
6 | /**
7 | * Types that can be returned by all the {@link BodyMethod}
8 | */
9 | export type BodyResolveValue = ArrayBuffer | Blob | FormData | JSON | string;
10 |
11 | /**
12 | * Types that can be returned by the Xml loader. See the {@link LoadItem.mimeType}.
13 | */
14 | export type LoadedXMLValue = Document | XMLDocument;
15 |
16 | /**
17 | * Types that can be returned by all the loaders.
18 | */
19 | export type LoadedValue =
20 | | BodyResolveValue
21 | | HTMLImageElement
22 | | HTMLVideoElement
23 | | HTMLAudioElement
24 | | LoadedXMLValue;
25 |
26 | /**
27 | * Main interface representing an object to load. src is the only mandatory key.
28 | */
29 | export interface LoadItem {
30 | /**
31 | * Input for the [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API)
32 | */
33 | src: RequestInfo | string;
34 | /**
35 | * Optional key.
36 | *
37 | * Used to retrieve the {@link LoadedValue} using `AsyncPreloader.items.get(id)`
38 | */
39 | id?: unknown;
40 | /**
41 | * Optional {@link LoaderKey}.
42 | *
43 | * If none specified, the loader is inferred from the file extension.
44 | * Default to `Text` if the extension doesn't match any of the extensions specified in {@link AsyncPreloader.loaders}.
45 | *
46 | * Note: It needs to be specified for Font and Audio (webm, ogg).
47 | */
48 | loader?: LoaderKey;
49 | /**
50 | * Optional `RequestInit` object to pass to the [fetch method](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch).
51 | */
52 | options?: RequestInit;
53 | /**
54 | * Font options used by FontFace and FontFaceObserver
55 | */
56 | fontOptions?: FontOptions;
57 | /**
58 | * Optional{@link BodyMethod} used to handle the Response.
59 | *
60 | * Default to `blob` for Image, Video and Audio. See {@link AsyncPreloader.defaultBodyMethod}.
61 | */
62 | body?: BodyMethod;
63 | /**
64 | * Optional mimeType used to handle the Response.
65 | *
66 | * Note: Only used to parse the document in the Xml Loader.
67 | */
68 | mimeType?: DOMParserSupportedType;
69 | /**
70 | * Optional disable [image decoding](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/decode).
71 | *
72 | * Note: Only used for loadImage.
73 | */
74 | noDecode?: boolean;
75 | }
76 |
77 | /**
78 | * Keys used for the {@link AsyncPreloader.loaders}
79 | */
80 | export enum LoaderKey {
81 | Json = "Json",
82 | ArrayBuffer = "ArrayBuffer",
83 | Blob = "Blob",
84 | FormData = "FormData",
85 | Text = "Text",
86 |
87 | Image = "Image",
88 | Video = "Video",
89 | Audio = "Audio",
90 | Xml = "Xml",
91 | Font = "Font",
92 | }
93 |
94 | /**
95 | * Font options used by FontFace and FontFaceObserver
96 | */
97 | export interface FontOptions {
98 | /**
99 | * [FontFace constructor descriptors](https://developer.mozilla.org/en-US/docs/Web/API/FontFace/FontFace)
100 | */
101 | descriptors?: FontFaceDescriptors;
102 | /**
103 | * FontFaceObserver.FontVariant
104 | */
105 | variant?: FontFaceObserver.FontVariant;
106 | /**
107 | * Argument for [FontFace.load](https://developer.mozilla.org/en-US/docs/Web/API/FontFace/load)
108 | */
109 | testString?: string;
110 | /**
111 | * Argument for [FontFace.load](https://developer.mozilla.org/en-US/docs/Web/API/FontFace/load)
112 | */
113 | timeout?: number;
114 | }
115 |
116 | /**
117 | * Values used for the {@link AsyncPreloader.loaders}
118 | */
119 | export interface LoaderValue {
120 | /**
121 | * {@link LoadItem} with no loader key specified will use the following array to find which loader should be used.
122 | */
123 | extensions: string[];
124 | /**
125 | * Optional mimeType used to handle the Response.
126 | *
127 | * Note: Only used to parse the document in the Xml Loader.
128 | */
129 | mimeType?: { [key: string]: DOMParserSupportedType };
130 | /**
131 | * Optional defaultMimeType used to handle the Response.
132 | *
133 | * Note: Only used to parse the document in the Xml Loader.
134 | */
135 | defaultMimeType?: DOMParserSupportedType;
136 | }
137 |
--------------------------------------------------------------------------------
/test/assets/audio.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dmnsgn/async-preloader/c1a85109afec2a2500fc29044601caef31825333/test/assets/audio.mp3
--------------------------------------------------------------------------------
/test/assets/default.unknown:
--------------------------------------------------------------------------------
1 | test string
2 |
--------------------------------------------------------------------------------
/test/assets/font.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dmnsgn/async-preloader/c1a85109afec2a2500fc29044601caef31825333/test/assets/font.ttf
--------------------------------------------------------------------------------
/test/assets/image.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dmnsgn/async-preloader/c1a85109afec2a2500fc29044601caef31825333/test/assets/image.gif
--------------------------------------------------------------------------------
/test/assets/image.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dmnsgn/async-preloader/c1a85109afec2a2500fc29044601caef31825333/test/assets/image.jpg
--------------------------------------------------------------------------------
/test/assets/image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dmnsgn/async-preloader/c1a85109afec2a2500fc29044601caef31825333/test/assets/image.png
--------------------------------------------------------------------------------
/test/assets/json.json:
--------------------------------------------------------------------------------
1 | {
2 | "test": "json"
3 | }
4 |
--------------------------------------------------------------------------------
/test/assets/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "items": [
3 | {
4 | "id": "myDefaultFile",
5 | "src": "/test/assets/default.unknown"
6 | },
7 | {
8 | "id": "myTextFile",
9 | "src": "/test/assets/text.txt"
10 | },
11 | {
12 | "id": "myJsonFile",
13 | "src": "/test/assets/json.json"
14 | },
15 | {
16 | "id": "myXmlFile",
17 | "src": "/test/assets/xml.xml"
18 | },
19 | {
20 | "id": "mySvgFile",
21 | "src": "/test/assets/xml.svg"
22 | },
23 | {
24 | "id": "myHtmlFile",
25 | "src": "/test/assets/xml.html"
26 | },
27 | {
28 | "id": "myXmlDefaulFile",
29 | "src": "/test/assets/xml.unknown",
30 | "loader": "Xml"
31 | }
32 | ],
33 | "custom": {
34 | "path": [
35 | {
36 | "id": "myDefaultFile",
37 | "src": "/test/assets/default.unknown"
38 | },
39 | {
40 | "id": "myTextFile",
41 | "src": "/test/assets/text.txt"
42 | },
43 | {
44 | "id": "myJsonFile",
45 | "src": "/test/assets/json.json"
46 | },
47 | {
48 | "id": "myXmlFile",
49 | "src": "/test/assets/xml.xml"
50 | },
51 | {
52 | "id": "mySvgFile",
53 | "src": "/test/assets/xml.svg"
54 | },
55 | {
56 | "id": "myHtmlFile",
57 | "src": "/test/assets/xml.html"
58 | },
59 | {
60 | "id": "myXmlDefaulFile",
61 | "src": "/test/assets/xml.unknown",
62 | "loader": "Xml"
63 | }
64 | ]
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/test/assets/text.txt:
--------------------------------------------------------------------------------
1 | test string
2 |
--------------------------------------------------------------------------------
/test/assets/video.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dmnsgn/async-preloader/c1a85109afec2a2500fc29044601caef31825333/test/assets/video.mp4
--------------------------------------------------------------------------------
/test/assets/xml.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/test/assets/xml.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/test/assets/xml.unknown:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Arctic
5 | 13,000
6 | 1,200
7 |
8 |
9 | Atlantic
10 | 87,000
11 | 3,900
12 |
13 |
14 | Pacific
15 | 180,000
16 | 4,000
17 |
18 |
19 | Indian
20 | 75,000
21 | 3,900
22 |
23 |
24 | Southern
25 | 20,000
26 | 4,500
27 |
28 |
29 |
--------------------------------------------------------------------------------
/test/assets/xml.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Arctic
5 | 13,000
6 | 1,200
7 |
8 |
9 | Atlantic
10 | 87,000
11 | 3,900
12 |
13 |
14 | Pacific
15 | 180,000
16 | 4,000
17 |
18 |
19 | Indian
20 | 75,000
21 | 3,900
22 |
23 |
24 | Southern
25 | 20,000
26 | 4,500
27 |
28 |
29 |
--------------------------------------------------------------------------------
/test/browser.spec.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @jest-environment puppeteer
3 | */
4 | import { jest } from "@jest/globals";
5 | import "expect-puppeteer";
6 | import pti from "puppeteer-to-istanbul";
7 | import { getPortPromise } from "portfinder";
8 |
9 | import { start } from "./server.js";
10 | import { items, manifestSrc } from "./data.js";
11 |
12 | jest.setTimeout(20000);
13 |
14 | describe("Browser", () => {
15 | let server;
16 | let url;
17 |
18 | beforeAll(async () => {
19 | const port = await getPortPromise();
20 | server = await start({ port });
21 |
22 | url = `http://localhost:${port}`;
23 |
24 | await page.coverage.startJSCoverage();
25 |
26 | await page.goto(url);
27 |
28 | // page
29 | // .on("console", async (message) => {
30 | // if (message.text() !== "JSHandle@error") {
31 | // console.log(
32 | // `${message.type().substring(0, 3).toUpperCase()} ${message.text()}`,
33 | // );
34 | // return;
35 | // }
36 | // const messages = await Promise.all(
37 | // message.args().map((arg) => arg.getProperty("message")),
38 | // );
39 | // console.log(
40 | // `${message.type().substr(0, 3).toUpperCase()} error ${messages.filter(Boolean)}`,
41 | // );
42 | // })
43 | // .on("pageerror", ({ message }) => console.log(message))
44 | // .on("response", (response) =>
45 | // console.log(`${response.status()} ${response.url()}`),
46 | // )
47 | // .on("requestfailed", (request) =>
48 | // console.log(`${request.failure().errorText} ${request.url()}`),
49 | // );
50 | });
51 |
52 | afterAll(async () => {
53 | await new Promise((resolve, reject) => {
54 | server.close((error) => {
55 | if (error) return reject(error);
56 | resolve();
57 | });
58 | });
59 |
60 | const jsCoverage = await page.coverage.stopJSCoverage();
61 | pti.write(
62 | [...jsCoverage.filter((item) => item.url.startsWith(`${url}/lib`))],
63 | {
64 | includeHostname: false,
65 | storagePath: "./.nyc_output",
66 | },
67 | );
68 | });
69 |
70 | it("should load a LoadItem with Blob loader and return instanceof Blob", async () => {
71 | const result = await page.evaluate(async (item) => {
72 | const { default: Preloader } = await importShim("./lib/index.js");
73 | const data = await Preloader.loadBlob(item);
74 | return data.constructor.name;
75 | }, items.get("jpg"));
76 |
77 | expect(result).toEqual("Blob");
78 | });
79 |
80 | it("should load a LoadItem with Image loader and return HTMLImageElement", async () => {
81 | const result = await page.evaluate(async (item) => {
82 | const { default: Preloader } = await importShim("./lib/index.js");
83 | const data = await Preloader.loadItem(item);
84 | return data.constructor.name;
85 | }, items.get("jpg"));
86 |
87 | expect(result).toEqual("HTMLImageElement");
88 | });
89 |
90 | it("should load a LoadItem with Image loader and body Blob and return HTMLImageElement", async () => {
91 | const result = await page.evaluate(async (item) => {
92 | const { default: Preloader } = await importShim("./lib/index.js");
93 | const data = await Preloader.loadImage({ ...item, body: "blob" });
94 | return data.constructor.name;
95 | }, items.get("jpg"));
96 |
97 | expect(result).toEqual("HTMLImageElement");
98 | });
99 |
100 | it("should try to load a LoadItem (jpg) with Image loader and throw decode error", async () => {
101 | const result = await page.evaluate(async (item) => {
102 | const { default: Preloader } = await importShim("./lib/index.js");
103 | try {
104 | await Preloader.loadImage({
105 | ...item,
106 | src: item.src.replace(".jpg", "unknown.jpg"),
107 | });
108 | } catch (error) {
109 | return error.message;
110 | }
111 | return "Test didn't throw";
112 | }, items.get("jpg"));
113 |
114 | expect(result).toEqual("The source image cannot be decoded.");
115 | });
116 |
117 | it("should load a LoadItem with Image loader and body arrayBuffer and return ArrayBuffer", async () => {
118 | const result = await page.evaluate(async (item) => {
119 | const { default: Preloader } = await importShim("./lib/index.js");
120 | const data = await Preloader.loadImage({ ...item, body: "arrayBuffer" });
121 | return data.constructor.name;
122 | }, items.get("jpg"));
123 |
124 | expect(result).toEqual("ArrayBuffer");
125 | });
126 |
127 | it("should load a LoadItem with body = arrayBuffer with Image loader and return an ArrayBuffer", async () => {
128 | const result = await page.evaluate(async (item) => {
129 | const { default: Preloader } = await importShim("./lib/index.js");
130 | const data = await Preloader.loadItem({ ...item, body: "arrayBuffer" });
131 | return data.constructor.name;
132 | }, items.get("jpg"));
133 |
134 | expect(result).toEqual("ArrayBuffer");
135 | });
136 |
137 | it.skip("should load a LoadItem with Video loader and return HTMLVideoElement", async () => {
138 | const result = await page.evaluate(async (item) => {
139 | const { default: Preloader } = await importShim("./lib/index.js");
140 | const data = await Preloader.loadItem(item);
141 | return data.constructor.name;
142 | }, items.get("mp4"));
143 |
144 | expect(result).toEqual("HTMLVideoElement");
145 | });
146 |
147 | it("should load a LoadItem with Audio loader and return HTMLAudioElement", async () => {
148 | const result = await page.evaluate(async (item) => {
149 | const { default: Preloader } = await importShim("./lib/index.js");
150 | const data = await Preloader.loadItem(item);
151 | return data.constructor.name;
152 | }, items.get("mp3"));
153 |
154 | expect(result).toEqual("HTMLAudioElement");
155 | });
156 |
157 | it("should load a LoadItem with body = arrayBuffer with Audio Loader and return an ArrayBuffer", async () => {
158 | const result = await page.evaluate(async (item) => {
159 | const { default: Preloader } = await importShim("./lib/index.js");
160 | const data = await Preloader.loadItem({ ...item, body: "arrayBuffer" });
161 | return data.constructor.name;
162 | }, items.get("mp3"));
163 |
164 | expect(result).toEqual("ArrayBuffer");
165 | });
166 |
167 | it("should load a LoadItem (xml) with Xml Loader and return Document", async () => {
168 | const result = await page.evaluate(async (item) => {
169 | const { default: Preloader } = await importShim("./lib/index.js");
170 | const data = await Preloader.loadItem(item);
171 | return {
172 | constructor: data.constructor.name,
173 | };
174 | }, items.get("xml"));
175 |
176 | expect(result.constructor).toEqual("XMLDocument");
177 | // expect(result.data).toEqual(expected.get("xml"));
178 | });
179 |
180 | it("should load a LoadItem (html) with Xml Loader and return Document", async () => {
181 | const result = await page.evaluate(async (item) => {
182 | const { default: Preloader } = await importShim("./lib/index.js");
183 | const data = await Preloader.loadItem(item);
184 | return {
185 | constructor: data.constructor.name,
186 | };
187 | }, items.get("html"));
188 |
189 | expect(result.constructor).toEqual("HTMLDocument");
190 | // expect(data).toEqual(expected.get("html"));
191 | });
192 |
193 | it("should load a LoadItem (svg) with Xml Loader and return Document", async () => {
194 | const result = await page.evaluate(async (item) => {
195 | const { default: Preloader } = await importShim("./lib/index.js");
196 | const data = await Preloader.loadItem(item);
197 | return {
198 | constructor: data.constructor.name,
199 | };
200 | }, items.get("svg"));
201 |
202 | expect(result.constructor).toEqual("XMLDocument");
203 | // expect(data).toEqual(expected.get("svg"));
204 | });
205 |
206 | it("should load a LoadItem (svg file with a special extension) with Xml Loader and return Document", async () => {
207 | const result = await page.evaluate(async (item) => {
208 | const { default: Preloader } = await importShim("./lib/index.js");
209 | const data = await Preloader.loadItem(item);
210 | return {
211 | constructor: data.constructor.name,
212 | };
213 | }, items.get("defaultXml"));
214 |
215 | expect(result.constructor).toEqual("XMLDocument");
216 | // expect(data).toEqual(expected.get("xml"));
217 | });
218 |
219 | it("should load a LoadItem (ttf) with Font Loader and return FontFace", async () => {
220 | const result = await page.evaluate(async (item) => {
221 | const { default: Preloader } = await importShim("./lib/index.js");
222 | const data = await Preloader.loadItem(item);
223 | return {
224 | constructor: data.constructor.name,
225 | family: data.family,
226 | };
227 | }, items.get("font"));
228 |
229 | expect(result.constructor).toEqual("FontFace");
230 | expect(result.family).toEqual("myFont");
231 | });
232 |
233 | it("should load a LoadItem (ttf) by src only with Font Loader and return FontFace", async () => {
234 | const result = await page.evaluate(
235 | async (item) => {
236 | const { default: Preloader } = await importShim("./lib/index.js");
237 | const data = await Preloader.loadItem(item);
238 | return {
239 | constructor: data.constructor.name,
240 | family: data.family,
241 | };
242 | },
243 | { src: items.get("font").src },
244 | );
245 |
246 | expect(result.constructor).toEqual("FontFace");
247 | expect(result.family).toEqual("font");
248 | });
249 |
250 | it("should load a LoadItem (ttf buffer) with Font Loader and return FontFace", async () => {
251 | const result = await page.evaluate(async (item) => {
252 | const { default: Preloader } = await importShim("./lib/index.js");
253 | const data = await Preloader.loadItem({ ...item, body: "arrayBuffer" });
254 | return {
255 | constructor: data.constructor.name,
256 | family: data.family,
257 | };
258 | }, items.get("font"));
259 |
260 | expect(result.constructor).toEqual("FontFace");
261 | expect(result.family).toEqual("myFont");
262 | });
263 |
264 | it("should load a LoadItem (font observer) with Font Loader and return FontFace", async () => {
265 | const item = items.get("fontface");
266 | await page.addStyleTag({
267 | content: /* css */ `@font-face {
268 | font-family: '${item.id}';
269 | src: url(${items.get("font").src}) format('truetype');
270 | }`,
271 | });
272 |
273 | const result = await page.evaluate(async (item) => {
274 | const { default: Preloader } = await importShim("./lib/index.js");
275 | const data = await Preloader.loadItem(item);
276 | return data;
277 | }, item);
278 |
279 | expect(result).toEqual(item.id);
280 | });
281 |
282 | it("should load a manifest, load its items and return an array of LoadedValue", async () => {
283 | const result = await page.evaluate(async (item) => {
284 | const { default: Preloader } = await importShim("./lib/index.js");
285 | const data = await Preloader.loadManifest(item);
286 | return data;
287 | }, `${url}/${manifestSrc}`);
288 |
289 | expect(result).toHaveLength(7);
290 | // expect(result).toEqual(
291 | // expect.arrayContaining(Array.from(expected.values()))
292 | // );
293 | });
294 |
295 | it("should load a manifest with a specified data path, load its items and return an array of LoadedValue", async () => {
296 | const result = await page.evaluate(async (item) => {
297 | const { default: Preloader } = await importShim("./lib/index.js");
298 | const data = await Preloader.loadManifest(item, "custom.path");
299 | return data;
300 | }, `${url}/${manifestSrc}`);
301 |
302 | expect(result).toHaveLength(7);
303 | // expect(result).toEqual(
304 | // expect.arrayContaining(Array.from(expected.values()))
305 | // );
306 | });
307 |
308 | it("should cancel loading an array of LoadItem and return an CancelLoading", async () => {
309 | let itemsToLoad = Array.from(items.values());
310 |
311 | const result = await page.evaluate(async (items) => {
312 | const { default: Preloader } = await importShim("./lib/index.js");
313 |
314 | const controller = new AbortController();
315 | const { signal } = controller;
316 |
317 | const load = async () => {
318 | try {
319 | await Preloader.loadItems(
320 | items.map((item) => ({
321 | ...item,
322 | options: {
323 | ...(item.options || {}),
324 | signal,
325 | },
326 | })),
327 | );
328 | } catch (error) {
329 | return error;
330 | }
331 | return "NoError";
332 | };
333 |
334 | const results = await Promise.allSettled([
335 | (async () => {
336 | controller.abort("CancelLoading");
337 | })(),
338 | load(),
339 | ]);
340 |
341 | return results[1].value;
342 | }, itemsToLoad);
343 |
344 | expect(result).toEqual("CancelLoading");
345 | });
346 | });
347 |
--------------------------------------------------------------------------------
/test/data.js:
--------------------------------------------------------------------------------
1 | import { AsyncPreloader } from "../src";
2 | import { DOMParser } from "@xmldom/xmldom";
3 |
4 | const pathname = "test/assets/";
5 |
6 | export const items = new Map()
7 | .set("default", { id: "myDefaultFile", src: `${pathname}default.unknown` })
8 | .set("txt", { id: "myTextFile", src: `${pathname}text.txt` })
9 | .set("json", { id: "myJsonFile", src: `${pathname}json.json` })
10 | .set("jpg", { id: "myImageFile", src: `${pathname}image.jpg` })
11 | .set("mp4", { id: "myVideoFile", src: `${pathname}video.mp4` })
12 | .set("mp3", { id: "myAudioFile", src: `${pathname}audio.mp3` })
13 | .set("xml", { id: "myXmlFile", src: `${pathname}xml.xml` })
14 | .set("svg", { id: "mySvgFile", src: `${pathname}xml.svg` })
15 | .set("html", { id: "myHtmlFile", src: `${pathname}xml.html` })
16 | .set("defaultXml", {
17 | id: "myXmlDefaultFile",
18 | src: `${pathname}xml.unknown`,
19 | loader: "Xml",
20 | })
21 | .set("font", { id: "myFont", src: `${pathname}font.ttf` })
22 | .set("fontface", {
23 | id: "Space Regular",
24 | loader: "Font",
25 | fontOptions: { timeout: 10000 },
26 | });
27 |
28 | export const expected = new Map()
29 | .set("string", `test string\n`)
30 | .set(
31 | "html",
32 | new DOMParser().parseFromString(
33 | /* html */ `
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | \n`,
45 | "text/html"
46 | )
47 | )
48 | .set(
49 | "svg",
50 | new DOMParser().parseFromString(
51 | /* html */ `
56 | \n`,
57 | "image/svg+xml"
58 | )
59 | )
60 | .set(
61 | "xml",
62 | new DOMParser().parseFromString(
63 | /* xml */ `
64 |
65 |
66 | Arctic
67 | 13,000
68 | 1,200
69 |
70 |
71 | Atlantic
72 | 87,000
73 | 3,900
74 |
75 |
76 | Pacific
77 | 180,000
78 | 4,000
79 |
80 |
81 | Indian
82 | 75,000
83 | 3,900
84 |
85 |
86 | Southern
87 | 20,000
88 | 4,500
89 |
90 |
91 |
92 | \n`,
93 | "application/xml"
94 | )
95 | )
96 | .set(
97 | "defaultXml",
98 | new DOMParser().parseFromString(
99 | /* xml */ `
100 |
101 |
102 | Arctic
103 | 13,000
104 | 1,200
105 |
106 |
107 | Atlantic
108 | 87,000
109 | 3,900
110 |
111 |
112 | Pacific
113 | 180,000
114 | 4,000
115 |
116 |
117 | Indian
118 | 75,000
119 | 3,900
120 |
121 |
122 | Southern
123 | 20,000
124 | 4,500
125 |
126 |
127 |
128 | \n`,
129 | "application/xml"
130 | )
131 | )
132 | .set("json", { test: "json" });
133 |
134 | export const manifestSrc = `${pathname}manifest.json`;
135 |
136 | // Node
137 | const filterNodeSupportedItems = (key) =>
138 | ["default", "txt", "json", "string"].includes(key);
139 |
140 | export const getNodeSupportedItems = (items) =>
141 | Array.from(
142 | new Map([...items].filter(([k]) => filterNodeSupportedItems(k))).values()
143 | );
144 |
145 | export const expectedNode = new Map(
146 | [...expected].filter(([k]) => filterNodeSupportedItems(k))
147 | );
148 |
--------------------------------------------------------------------------------
/test/global-setup.js:
--------------------------------------------------------------------------------
1 | import { promisify } from "util";
2 | import { exec as execCb } from "child_process";
3 | import setupPuppeteer from "jest-environment-puppeteer/setup";
4 |
5 | const exec = promisify(execCb);
6 |
7 | export default async function globalSetup(globalConfig) {
8 | const { stdout, stderr } = await exec("npm run coverage");
9 | if (stderr) throw new Error(stderr);
10 | console.log(stdout);
11 | await setupPuppeteer(globalConfig);
12 | }
13 |
--------------------------------------------------------------------------------
/test/global-teardown.js:
--------------------------------------------------------------------------------
1 | import { promisify } from "util";
2 | import { exec as execCb } from "child_process";
3 | import teardownPuppeteer from "jest-environment-puppeteer/teardown";
4 |
5 | const exec = promisify(execCb);
6 |
7 | export default async function globalTeardown(globalConfig) {
8 | const { stdout, stderr } = await exec("npm run coverage");
9 | if (stderr) throw new Error(stderr);
10 | console.log(stdout);
11 | await teardownPuppeteer(globalConfig);
12 | }
13 |
--------------------------------------------------------------------------------
/test/index.spec.js:
--------------------------------------------------------------------------------
1 | import { getPortPromise } from "portfinder";
2 |
3 | import Preloader, { AsyncPreloader } from "../src";
4 | import {
5 | items as dataItems,
6 | expectedNode,
7 | getNodeSupportedItems,
8 | } from "./data.js";
9 | import { start } from "./server.js";
10 |
11 | // Suite
12 | describe("AsyncPreloader", () => {
13 | let server;
14 | let url;
15 | let items;
16 |
17 | beforeAll(async () => {
18 | const port = await getPortPromise();
19 | server = await start({ port });
20 |
21 | url = `http://localhost:${port}`;
22 |
23 | items = new Map(
24 | Array.from(dataItems, ([key, value]) => [
25 | key,
26 | { ...value, src: `${url}/${value.src}` },
27 | ])
28 | );
29 | });
30 |
31 | afterAll(async () => {
32 | await new Promise((resolve, reject) => {
33 | server.close((error) => {
34 | if (error) return reject(error);
35 | resolve();
36 | });
37 | });
38 | });
39 |
40 | describe("Loader", () => {
41 | afterEach(() => {
42 | Preloader.items.clear();
43 | });
44 |
45 | describe("for several items", () => {
46 | it("should load an array of LoadItem and return an array of LoadedValue", async () => {
47 | expect.assertions(3);
48 |
49 | const itemsToLoad = getNodeSupportedItems(items);
50 | const data = await Preloader.loadItems(itemsToLoad);
51 |
52 | expect(data).toBeInstanceOf(Array);
53 | expect(data).toHaveLength(itemsToLoad.length);
54 | expect(data).toEqual(
55 | expect.arrayContaining(Array.from(expectedNode.values()))
56 | );
57 | });
58 | it("should load an array of strings and return an array of LoadedValue", async () => {
59 | expect.assertions(3);
60 |
61 | const itemsToLoad = getNodeSupportedItems(items);
62 | const data = await Preloader.loadItems(
63 | itemsToLoad.map(({ src }) => src)
64 | );
65 |
66 | expect(data).toBeInstanceOf(Array);
67 | expect(data).toHaveLength(itemsToLoad.length);
68 | expect(data).toEqual(
69 | expect.arrayContaining(Array.from(expectedNode.values()))
70 | );
71 | });
72 | });
73 |
74 | describe("for default fetch methods", () => {
75 | it("should load a LoadItem with Json loader and return the JSON Object", async () => {
76 | expect.assertions(1);
77 |
78 | const item = items.get("json");
79 |
80 | const data = await Preloader.loadJson(item);
81 | expect(data).toEqual(expectedNode.get("json"));
82 | });
83 |
84 | it("should load a LoadItem with Text loader and return the string", async () => {
85 | expect.assertions(1);
86 |
87 | const item = items.get("txt");
88 |
89 | const data = await Preloader.loadText(item);
90 | expect(data).toBe(expectedNode.get("string"));
91 | });
92 |
93 | it("should load a LoadItem with ArrayBuffer loader and return instanceof ArrayBuffer", async () => {
94 | expect.assertions(1);
95 |
96 | const item = items.get("mp3");
97 |
98 | const data = await Preloader.loadArrayBuffer(item);
99 | expect(data.constructor.name).toEqual(ArrayBuffer.name);
100 | });
101 |
102 | it("should load a LoadItem with Blob loader and return instanceof Blob", async () => {
103 | expect.assertions(1);
104 |
105 | const item = items.get("jpg");
106 |
107 | const data = await Preloader.loadBlob(item);
108 |
109 | expect(data.constructor.name).toEqual(Blob.name);
110 | });
111 |
112 | it.skip("should load a LoadItem with FormData loader and return instanceof FormData", async () => {
113 | expect.assertions(1);
114 |
115 | const formData = new FormData();
116 | formData.set("greeting", "Hello, world!");
117 |
118 | const item = {
119 | src: "https://httpbin.org/post",
120 | options: {
121 | method: "POST",
122 | body: new URLSearchParams(formData),
123 | },
124 | };
125 |
126 | const data = await Preloader.loadFormData(item);
127 |
128 | expect(data.constructor.name).toEqual(FormData.name);
129 | });
130 | });
131 |
132 | describe("for a single item", () => {
133 | it("should load a LoadItem with Default loader and return a string", async () => {
134 | expect.assertions(1);
135 |
136 | const item = items.get("default");
137 |
138 | const data = await Preloader.loadItem(item);
139 | expect(data).toBe(expectedNode.get("string"));
140 | });
141 |
142 | it("should load a LoadItem with Json loader and return the JSON Object", async () => {
143 | expect.assertions(1);
144 |
145 | const item = items.get("json");
146 |
147 | const data = await Preloader.loadItem(item);
148 | expect(data).toEqual(expectedNode.get("json"));
149 | });
150 |
151 | it("should load a LoadItem with body = arrayBuffer with Audio Loader and return an ArrayBuffer", async () => {
152 | expect.assertions(1);
153 |
154 | const item = items.get("mp3");
155 | item.body = "arrayBuffer";
156 |
157 | const data = await Preloader.loadItem(item);
158 | expect(data.constructor.name).toEqual(ArrayBuffer.name);
159 | });
160 | it("should load a LoadItem with Text Loader and return String", async () => {
161 | expect.assertions(1);
162 |
163 | const item = items.get("txt");
164 |
165 | const data = await Preloader.loadItem(item);
166 | expect(data).toBe(expectedNode.get("string"));
167 | });
168 |
169 | it("should try to load a LoadItem (ttf) with FontFace Loader and throw", async () => {
170 | expect.assertions(1);
171 |
172 | await expect(Preloader.loadItem(items.get("font"))).rejects.toThrow(
173 | "FontFace is not defined"
174 | );
175 | });
176 | });
177 |
178 | describe("for edge cases", () => {
179 | it("should load a LoadItem without id with Default loader and return a string", async () => {
180 | expect.assertions(1);
181 |
182 | const item = items.get("default");
183 |
184 | const data = await Preloader.loadItem({ src: item.src });
185 | expect(data).toBe(expectedNode.get("string"));
186 | });
187 |
188 | it("should load an array of LoadItem and update a loadedCount variable", async () => {
189 | expect.assertions(1);
190 |
191 | const itemsToLoad = getNodeSupportedItems(items);
192 |
193 | let loadedCount = 0;
194 | async function preload() {
195 | await Promise.all(
196 | itemsToLoad.map(async (item) => {
197 | const data = await Preloader.loadItem(item);
198 | loadedCount++;
199 | // console.log(`Progress: ${100 * loadedCount / itemsToLoad.length}%`);
200 | })
201 | );
202 | }
203 | await preload();
204 | expect(loadedCount).toEqual(itemsToLoad.length);
205 | });
206 | });
207 | });
208 |
209 | describe("Static methods", () => {
210 | describe("Utils", () => {
211 | it("should return the file extension from file path", () => {
212 | const data = AsyncPreloader.getFileExtension("assets/json.json");
213 | expect(data).toBe("json");
214 | });
215 |
216 | it("should return null from file path with no extension", () => {
217 | const data = AsyncPreloader.getFileExtension("assets/default");
218 | expect(data).toBe(null);
219 | });
220 | });
221 |
222 | describe("Get loader key", () => {
223 | it("should return the loader from file extension", () => {
224 | const data = AsyncPreloader.getLoaderKey("json");
225 | expect(data).toBe("Json");
226 | });
227 |
228 | it("should return the default loader from null", () => {
229 | const data = AsyncPreloader.getLoaderKey(null);
230 | expect(data).toBe("Text");
231 | });
232 | });
233 | });
234 | });
235 |
--------------------------------------------------------------------------------
/test/server.js:
--------------------------------------------------------------------------------
1 | import { readFile } from "node:fs/promises";
2 | import http from "node:http";
3 | import { join, dirname } from "node:path";
4 | import { fileURLToPath } from "node:url";
5 |
6 | import mime from "mime-types";
7 |
8 | const __dirname = dirname(fileURLToPath(import.meta.url));
9 |
10 | const start = async ({ hostname = "localhost", port = 3000 } = {}) => {
11 | const server = http.createServer(async (req, res) => {
12 | if (req.url === "/") req.url = "/index.html";
13 |
14 | try {
15 | const data = await readFile(join(__dirname, "..", req.url));
16 | res.writeHead(200, { "Content-Type": mime.lookup(req.url) });
17 | res.end(data);
18 | return;
19 | } catch (error) {
20 | if (error) {
21 | res.writeHead(404);
22 | res.end(JSON.stringify(error));
23 | return;
24 | }
25 | }
26 | });
27 |
28 | return new Promise((resolve, reject) => {
29 | server.listen(port, hostname, (error) => {
30 | if (error) return reject(error);
31 | console.log(`Server running at http://${hostname}:${port}/`);
32 | resolve(server);
33 | });
34 | });
35 | };
36 |
37 | export { start };
38 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowJs": true,
4 | "allowSyntheticDefaultImports": true,
5 | "declaration": true,
6 | "declarationDir": "types",
7 | "esModuleInterop": true,
8 | "forceConsistentCasingInFileNames": true,
9 | "importHelpers": true,
10 | "lib": ["DOM", "ES2015", "ES2016", "ES2017", "ES2018", "ES2019", "ES2020", "ES2021"],
11 | "module": "ES2020",
12 | "moduleResolution": "node",
13 | "outDir": "lib",
14 | "sourceMap": true,
15 | "strictFunctionTypes": true,
16 | "target": "ES2021"
17 | },
18 | "include": [
19 | "src/**/*"
20 | ],
21 | "exclude": [
22 | "node_modules",
23 | "web_modules",
24 | "**/*.spec.ts"
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------
Methods that can be called on a Request (object returned by fetch and that implements the Body interface)
160 |