@atlaspack/runtime-js 2.12.1-dev.3567 → 2.13.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/CHANGELOG.md +16 -0
- package/lib/JSRuntime.js +48 -17
- package/lib/helpers/browser/analytics/analytics.js +11 -0
- package/lib/helpers/browser/css-loader.js +3 -3
- package/lib/helpers/browser/esm-js-loader-retry.js +66 -45
- package/lib/helpers/browser/esm-js-loader-shards.js +7 -0
- package/lib/helpers/browser/html-loader.js +1 -1
- package/lib/helpers/browser/import-polyfill.js +7 -7
- package/lib/helpers/browser/js-loader.js +4 -4
- package/lib/helpers/browser/prefetch-loader.js +1 -1
- package/lib/helpers/browser/preload-loader.js +1 -1
- package/lib/helpers/browser/wasm-loader.js +1 -1
- package/lib/helpers/bundle-manifest.js +2 -1
- package/lib/helpers/bundle-url.js +36 -18
- package/lib/helpers/cacheLoader.js +4 -4
- package/lib/helpers/conditional-loader-dev.js +1 -1
- package/lib/helpers/get-worker-url.js +1 -1
- package/lib/helpers/node/html-loader.js +2 -2
- package/lib/helpers/node/js-loader.js +2 -2
- package/lib/helpers/node/wasm-loader.js +2 -2
- package/lib/helpers/worker/js-loader.js +1 -1
- package/lib/helpers/worker/wasm-loader.js +1 -1
- package/package.json +8 -8
- package/src/JSRuntime.js +98 -26
- package/src/helpers/browser/analytics/analytics.d.ts +6 -0
- package/src/helpers/browser/analytics/analytics.js +6 -0
- package/src/helpers/browser/analytics/analytics.js.flow +8 -0
- package/src/helpers/browser/esm-js-loader-retry.js +61 -14
- package/src/helpers/browser/esm-js-loader-shards.js +11 -0
- package/src/helpers/bundle-manifest.js +4 -1
- package/src/helpers/bundle-url.js +37 -27
- package/test/analytics.test.ts +47 -0
- package/test/bundle-url.test.js +62 -0
- package/test/esm-js-loader-retry.test.ts +65 -0
- package/LICENSE +0 -201
- package/src/helpers/.babelrc +0 -12
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atlaspack/runtime-js",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.13.0",
|
|
4
4
|
"license": "(MIT OR Apache-2.0)",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -12,15 +12,15 @@
|
|
|
12
12
|
"main": "lib/JSRuntime.js",
|
|
13
13
|
"source": "src/JSRuntime.js",
|
|
14
14
|
"engines": {
|
|
15
|
-
"atlaspack": "^2.12.
|
|
15
|
+
"atlaspack": "^2.12.0",
|
|
16
16
|
"node": ">= 16.0.0"
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@atlaspack/diagnostic": "2.
|
|
20
|
-
"@atlaspack/
|
|
21
|
-
"@atlaspack/
|
|
22
|
-
"@atlaspack/
|
|
19
|
+
"@atlaspack/diagnostic": "2.13.0",
|
|
20
|
+
"@atlaspack/domain-sharding": "2.13.0",
|
|
21
|
+
"@atlaspack/feature-flags": "2.13.0",
|
|
22
|
+
"@atlaspack/plugin": "2.13.0",
|
|
23
|
+
"@atlaspack/utils": "2.13.0",
|
|
23
24
|
"nullthrows": "^1.1.1"
|
|
24
|
-
}
|
|
25
|
-
"gitHead": "c06f4487ebddc632957147c8b585a97e149062a1"
|
|
25
|
+
}
|
|
26
26
|
}
|
package/src/JSRuntime.js
CHANGED
|
@@ -74,6 +74,9 @@ let bundleDependencies = new WeakMap<
|
|
|
74
74
|
|
|
75
75
|
type JSRuntimeConfig = {|
|
|
76
76
|
splitManifestThreshold: number,
|
|
77
|
+
domainSharding?: {|
|
|
78
|
+
maxShards: number,
|
|
79
|
+
|},
|
|
77
80
|
|};
|
|
78
81
|
|
|
79
82
|
let defaultConfig: JSRuntimeConfig = {
|
|
@@ -86,6 +89,16 @@ const CONFIG_SCHEMA: SchemaEntity = {
|
|
|
86
89
|
splitManifestThreshold: {
|
|
87
90
|
type: 'number',
|
|
88
91
|
},
|
|
92
|
+
domainSharding: {
|
|
93
|
+
type: 'object',
|
|
94
|
+
properties: {
|
|
95
|
+
maxShards: {
|
|
96
|
+
type: 'number',
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
additionalProperties: false,
|
|
100
|
+
required: ['maxShards'],
|
|
101
|
+
},
|
|
89
102
|
},
|
|
90
103
|
additionalProperties: false,
|
|
91
104
|
};
|
|
@@ -181,6 +194,7 @@ export default (new Runtime({
|
|
|
181
194
|
bundleGraph,
|
|
182
195
|
bundleGroup: resolved.value,
|
|
183
196
|
options,
|
|
197
|
+
shardingConfig: config.domainSharding,
|
|
184
198
|
});
|
|
185
199
|
|
|
186
200
|
if (loaderRuntime != null) {
|
|
@@ -271,7 +285,15 @@ export default (new Runtime({
|
|
|
271
285
|
}
|
|
272
286
|
|
|
273
287
|
// URL dependency or not, fall back to including a runtime that exports the url
|
|
274
|
-
assets.push(
|
|
288
|
+
assets.push(
|
|
289
|
+
getURLRuntime(
|
|
290
|
+
dependency,
|
|
291
|
+
bundle,
|
|
292
|
+
mainBundle,
|
|
293
|
+
options,
|
|
294
|
+
config.domainSharding,
|
|
295
|
+
),
|
|
296
|
+
);
|
|
275
297
|
}
|
|
276
298
|
|
|
277
299
|
// In development, bundles can be created lazily. This means that the parent bundle may not
|
|
@@ -297,7 +319,11 @@ export default (new Runtime({
|
|
|
297
319
|
);
|
|
298
320
|
let loaderCode = `require(${JSON.stringify(
|
|
299
321
|
loader,
|
|
300
|
-
)})(
|
|
322
|
+
)})(${getAbsoluteUrlExpr(
|
|
323
|
+
relativePathExpr,
|
|
324
|
+
bundle,
|
|
325
|
+
config.domainSharding,
|
|
326
|
+
)})`;
|
|
301
327
|
assets.push({
|
|
302
328
|
filePath: __filename,
|
|
303
329
|
code: loaderCode,
|
|
@@ -376,12 +402,14 @@ function getLoaderRuntime({
|
|
|
376
402
|
bundleGroup,
|
|
377
403
|
bundleGraph,
|
|
378
404
|
options,
|
|
405
|
+
shardingConfig,
|
|
379
406
|
}: {|
|
|
380
407
|
bundle: NamedBundle,
|
|
381
408
|
dependency: Dependency,
|
|
382
409
|
bundleGroup: BundleGroup,
|
|
383
410
|
bundleGraph: BundleGraph<NamedBundle>,
|
|
384
411
|
options: PluginOptions,
|
|
412
|
+
shardingConfig: JSRuntimeConfig['domainSharding'],
|
|
385
413
|
|}): ?RuntimeAsset {
|
|
386
414
|
let loaders = getLoaders(bundle.env);
|
|
387
415
|
if (loaders == null) {
|
|
@@ -423,6 +451,7 @@ function getLoaderRuntime({
|
|
|
423
451
|
function getLoaderForBundle(
|
|
424
452
|
bundle: NamedBundle,
|
|
425
453
|
to: NamedBundle,
|
|
454
|
+
shardingConfig: JSRuntimeConfig['domainSharding'],
|
|
426
455
|
): string | void {
|
|
427
456
|
let loader = loaders[to.type];
|
|
428
457
|
if (!loader) {
|
|
@@ -455,11 +484,22 @@ function getLoaderRuntime({
|
|
|
455
484
|
return `Promise.resolve(__parcel__require__("./" + ${relativePathExpr}))`;
|
|
456
485
|
}
|
|
457
486
|
|
|
458
|
-
let absoluteUrlExpr
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
487
|
+
let absoluteUrlExpr;
|
|
488
|
+
if (shouldUseRuntimeManifest(bundle, options)) {
|
|
489
|
+
let publicId = JSON.stringify(to.publicId);
|
|
490
|
+
absoluteUrlExpr = `require('./helpers/bundle-manifest').resolve(${publicId})`;
|
|
491
|
+
|
|
492
|
+
if (shardingConfig) {
|
|
493
|
+
absoluteUrlExpr = `require('@atlaspack/domain-sharding').shardUrl(${absoluteUrlExpr}, ${shardingConfig.maxShards})`;
|
|
494
|
+
}
|
|
495
|
+
} else {
|
|
496
|
+
absoluteUrlExpr = getAbsoluteUrlExpr(
|
|
497
|
+
relativePathExpr,
|
|
498
|
+
bundle,
|
|
499
|
+
shardingConfig,
|
|
500
|
+
);
|
|
501
|
+
}
|
|
502
|
+
|
|
463
503
|
let code = `require(${JSON.stringify(loader)})(${absoluteUrlExpr})`;
|
|
464
504
|
|
|
465
505
|
// In development, clear the require cache when an error occurs so the
|
|
@@ -483,22 +523,32 @@ function getLoaderRuntime({
|
|
|
483
523
|
bundle,
|
|
484
524
|
)) {
|
|
485
525
|
// This bundle has a conditional dependency, we need to load the bundle group
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
loaderModules.push(
|
|
489
|
-
`require('./helpers/conditional-loader${
|
|
490
|
-
options.mode === 'development' ? '-dev' : ''
|
|
491
|
-
}')('${cond.key}', function (){return Promise.all([${cond.ifTrueBundles
|
|
492
|
-
.map((targetBundle) => getLoaderForBundle(bundle, targetBundle))
|
|
493
|
-
.join(',')}]);}, function (){return Promise.all([${cond.ifFalseBundles
|
|
494
|
-
.map((targetBundle) => getLoaderForBundle(bundle, targetBundle))
|
|
495
|
-
.join(',')}]);})`,
|
|
526
|
+
const ifTrueLoaders = cond.ifTrueBundles.map((targetBundle) =>
|
|
527
|
+
getLoaderForBundle(bundle, targetBundle),
|
|
496
528
|
);
|
|
529
|
+
const ifFalseLoaders = cond.ifFalseBundles.map((targetBundle) =>
|
|
530
|
+
getLoaderForBundle(bundle, targetBundle),
|
|
531
|
+
);
|
|
532
|
+
|
|
533
|
+
if (ifTrueLoaders.length > 0 || ifFalseLoaders.length > 0) {
|
|
534
|
+
// Load conditional bundles with helper (and a dev mode with additional hints)
|
|
535
|
+
loaderModules.push(
|
|
536
|
+
`require('./helpers/conditional-loader${
|
|
537
|
+
options.mode === 'development' ? '-dev' : ''
|
|
538
|
+
}')('${
|
|
539
|
+
cond.key
|
|
540
|
+
}', function (){return Promise.all([${ifTrueLoaders.join(
|
|
541
|
+
',',
|
|
542
|
+
)}]);}, function (){return Promise.all([${ifFalseLoaders.join(
|
|
543
|
+
',',
|
|
544
|
+
)}]);})`,
|
|
545
|
+
);
|
|
546
|
+
}
|
|
497
547
|
}
|
|
498
548
|
}
|
|
499
549
|
|
|
500
550
|
for (let to of externalBundles) {
|
|
501
|
-
let loaderModule = getLoaderForBundle(bundle, to);
|
|
551
|
+
let loaderModule = getLoaderForBundle(bundle, to, shardingConfig);
|
|
502
552
|
if (loaderModule !== undefined) loaderModules.push(loaderModule);
|
|
503
553
|
}
|
|
504
554
|
|
|
@@ -573,7 +623,11 @@ function getLoaderRuntime({
|
|
|
573
623
|
let code = [];
|
|
574
624
|
|
|
575
625
|
if (needsEsmLoadPrelude) {
|
|
576
|
-
|
|
626
|
+
let preludeLoad = shardingConfig
|
|
627
|
+
? `let load = require('./helpers/browser/esm-js-loader-shards')(${shardingConfig.maxShards});`
|
|
628
|
+
: `let load = require('./helpers/browser/esm-js-loader');`;
|
|
629
|
+
|
|
630
|
+
code.push(preludeLoad);
|
|
577
631
|
}
|
|
578
632
|
|
|
579
633
|
code.push(`module.exports = ${loaderCode};`);
|
|
@@ -673,6 +727,7 @@ function getURLRuntime(
|
|
|
673
727
|
from: NamedBundle,
|
|
674
728
|
to: NamedBundle,
|
|
675
729
|
options: PluginOptions,
|
|
730
|
+
shardingConfig: JSRuntimeConfig['domainSharding'],
|
|
676
731
|
): RuntimeAsset {
|
|
677
732
|
let relativePathExpr = getRelativePathExpr(from, to, options);
|
|
678
733
|
let code;
|
|
@@ -690,12 +745,19 @@ function getURLRuntime(
|
|
|
690
745
|
} else {
|
|
691
746
|
code += `let bundleURL = require('./helpers/bundle-url');\n`;
|
|
692
747
|
code += `let url = bundleURL.getBundleURL('${from.publicId}') + ${relativePathExpr};`;
|
|
748
|
+
if (shardingConfig) {
|
|
749
|
+
code += `url = require('@atlaspack/domain-sharding').shardUrl(url, ${shardingConfig.maxShards});`;
|
|
750
|
+
}
|
|
693
751
|
code += `module.exports = workerURL(url, bundleURL.getOrigin(url), ${String(
|
|
694
752
|
from.env.outputFormat === 'esmodule',
|
|
695
753
|
)});`;
|
|
696
754
|
}
|
|
697
755
|
} else {
|
|
698
|
-
code = `module.exports = ${getAbsoluteUrlExpr(
|
|
756
|
+
code = `module.exports = ${getAbsoluteUrlExpr(
|
|
757
|
+
relativePathExpr,
|
|
758
|
+
from,
|
|
759
|
+
shardingConfig,
|
|
760
|
+
)};`;
|
|
699
761
|
}
|
|
700
762
|
|
|
701
763
|
return {
|
|
@@ -765,17 +827,27 @@ function getRelativePathExpr(
|
|
|
765
827
|
return res;
|
|
766
828
|
}
|
|
767
829
|
|
|
768
|
-
function getAbsoluteUrlExpr(
|
|
830
|
+
function getAbsoluteUrlExpr(
|
|
831
|
+
relativePathExpr: string,
|
|
832
|
+
fromBundle: NamedBundle,
|
|
833
|
+
shardingConfig: JSRuntimeConfig['domainSharding'],
|
|
834
|
+
) {
|
|
769
835
|
if (
|
|
770
|
-
(
|
|
771
|
-
|
|
772
|
-
|
|
836
|
+
(fromBundle.env.outputFormat === 'esmodule' &&
|
|
837
|
+
fromBundle.env.supports('import-meta-url')) ||
|
|
838
|
+
fromBundle.env.outputFormat === 'commonjs'
|
|
773
839
|
) {
|
|
774
840
|
// This will be compiled to new URL(url, import.meta.url) or new URL(url, 'file:' + __filename).
|
|
775
841
|
return `new __parcel__URL__(${relativePathExpr}).toString()`;
|
|
776
|
-
} else {
|
|
777
|
-
return `require('./helpers/bundle-url').getBundleURL('${bundle.publicId}') + ${relativePathExpr}`;
|
|
778
842
|
}
|
|
843
|
+
|
|
844
|
+
const regularBundleUrl = `require('./helpers/bundle-url').getBundleURL('${fromBundle.publicId}') + ${relativePathExpr}`;
|
|
845
|
+
|
|
846
|
+
if (!shardingConfig) {
|
|
847
|
+
return regularBundleUrl;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
return `require('@atlaspack/domain-sharding').shardUrl(${regularBundleUrl}, ${shardingConfig.maxShards})`;
|
|
779
851
|
}
|
|
780
852
|
|
|
781
853
|
function shouldUseRuntimeManifest(
|
|
@@ -1,26 +1,73 @@
|
|
|
1
1
|
async function load(id) {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
}
|
|
2
|
+
// Global state maps the initial url to the completed task.
|
|
3
|
+
// This ensures the same URL is used in subsequent imports
|
|
4
|
+
if (!parcelRequire._retryState) parcelRequire._retryState = {};
|
|
5
|
+
/** @type {Record<string, Promise<void>>} */
|
|
6
|
+
const retryState = parcelRequire._retryState;
|
|
7
|
+
|
|
8
|
+
// The number of retries before rethrowing the error
|
|
9
|
+
const maxRetries = 6;
|
|
5
10
|
|
|
11
|
+
// Resolve the request URL from the bundle ID
|
|
12
|
+
const url = require('../bundle-manifest').resolve(id);
|
|
13
|
+
|
|
14
|
+
// Wait for the user to go online before making a request
|
|
6
15
|
if (!globalThis.navigator.onLine) {
|
|
7
|
-
await new Promise((
|
|
8
|
-
globalThis.addEventListener('online',
|
|
16
|
+
await new Promise((resolve) =>
|
|
17
|
+
globalThis.addEventListener('online', resolve, {once: true}),
|
|
9
18
|
);
|
|
10
19
|
}
|
|
11
20
|
|
|
12
|
-
|
|
21
|
+
// If the import has not run or is not currently running
|
|
22
|
+
// then start the import retry task. Otherwise reuse the
|
|
23
|
+
// existing result or wait for the current task to complete
|
|
24
|
+
if (!retryState[url]) {
|
|
25
|
+
retryState[url] = (async () => {
|
|
26
|
+
// Try first request with normal import circuit
|
|
27
|
+
try {
|
|
28
|
+
// eslint-disable-next-line no-undef
|
|
29
|
+
return await __parcel__import__(url);
|
|
30
|
+
} catch {
|
|
31
|
+
/**/
|
|
32
|
+
}
|
|
13
33
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
34
|
+
// Attempt to retry request
|
|
35
|
+
for (let i = 1; i <= maxRetries; i++) {
|
|
36
|
+
try {
|
|
37
|
+
// Wait for an increasing delay time
|
|
38
|
+
const jitter = Math.round(Math.random() * 100);
|
|
39
|
+
const delay = Math.min(Math.pow(2, i), 8) * 1000;
|
|
40
|
+
await new Promise((resolve) => setTimeout(resolve, delay + jitter));
|
|
17
41
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
42
|
+
// Append the current time to the request URL
|
|
43
|
+
// to ensure it has not been cached by the browser
|
|
44
|
+
// eslint-disable-next-line no-undef
|
|
45
|
+
const result = await __parcel__import__(`${url}?t=${Date.now()}`);
|
|
46
|
+
sendAnalyticsEvent('recovered', url, i);
|
|
47
|
+
return result;
|
|
48
|
+
} catch (error) {
|
|
49
|
+
if (i === maxRetries) {
|
|
50
|
+
sendAnalyticsEvent('failure', url, i);
|
|
51
|
+
throw error;
|
|
52
|
+
}
|
|
53
|
+
sendAnalyticsEvent('progress', url, i);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
})();
|
|
23
57
|
}
|
|
58
|
+
|
|
59
|
+
return retryState[url];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function sendAnalyticsEvent(status, targetUrl, attempt) {
|
|
63
|
+
require('./analytics/analytics.js').sendAnalyticsEvent({
|
|
64
|
+
action: 'importRetry',
|
|
65
|
+
attributes: {
|
|
66
|
+
status,
|
|
67
|
+
targetUrl,
|
|
68
|
+
attempt,
|
|
69
|
+
},
|
|
70
|
+
});
|
|
24
71
|
}
|
|
25
72
|
|
|
26
73
|
module.exports = load;
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
var mapping = new Map();
|
|
2
2
|
|
|
3
|
-
function register(
|
|
3
|
+
function register(
|
|
4
|
+
/** @type {string} */ baseUrl,
|
|
5
|
+
/** @type {Array<string>} */ manifest, // ['id', 'path', 'id2', 'path2']
|
|
6
|
+
) {
|
|
4
7
|
for (var i = 0; i < manifest.length - 1; i += 2) {
|
|
5
8
|
mapping.set(manifest[i], {
|
|
6
9
|
baseUrl: baseUrl,
|
|
@@ -1,21 +1,42 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
const stackTraceUrlRegexp =
|
|
2
|
+
/(https?|file|ftp|(chrome|moz|safari-web)-extension):\/\/[^)\n]+/g;
|
|
3
|
+
|
|
4
|
+
const bundleURL = {};
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Retrieves the cached bundle URL for a given identifier.
|
|
8
|
+
* If the URL is not cached, it computes and stores it in the cache.
|
|
9
|
+
*
|
|
10
|
+
* @param {string} id - The identifier for the bundle.
|
|
11
|
+
* @param {Error?} inputError - An error object to extract the stack trace from
|
|
12
|
+
* (for testing purposes).
|
|
13
|
+
* @returns {string} The URL of the bundle, without file name.
|
|
14
|
+
*/
|
|
15
|
+
function getBundleURLCached(id, inputError) {
|
|
16
|
+
let value = bundleURL[id];
|
|
17
|
+
|
|
4
18
|
if (!value) {
|
|
5
|
-
value = getBundleURL();
|
|
19
|
+
value = getBundleURL(inputError);
|
|
6
20
|
bundleURL[id] = value;
|
|
7
21
|
}
|
|
8
22
|
|
|
9
23
|
return value;
|
|
10
24
|
}
|
|
11
25
|
|
|
12
|
-
|
|
26
|
+
/** Get the URL without the filename (last / segment)
|
|
27
|
+
*
|
|
28
|
+
* @param {string} url
|
|
29
|
+
* @returns {string} The URL with the file name removed
|
|
30
|
+
*/
|
|
31
|
+
function getBaseURL(url) {
|
|
32
|
+
return url.slice(0, url.lastIndexOf('/')) + '/';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function getBundleURL(inputError) {
|
|
13
36
|
try {
|
|
14
|
-
throw new Error();
|
|
37
|
+
throw inputError ?? new Error();
|
|
15
38
|
} catch (err) {
|
|
16
|
-
var matches = ('' + err.stack).match(
|
|
17
|
-
/(https?|file|ftp|(chrome|moz|safari-web)-extension):\/\/[^)\n]+/g,
|
|
18
|
-
);
|
|
39
|
+
var matches = ('' + err.stack).match(stackTraceUrlRegexp);
|
|
19
40
|
if (matches) {
|
|
20
41
|
// The first two stack frames will be this function and getBundleURLCached.
|
|
21
42
|
// Use the 3rd one, which will be a runtime in the original bundle.
|
|
@@ -26,26 +47,15 @@ function getBundleURL() {
|
|
|
26
47
|
return '/';
|
|
27
48
|
}
|
|
28
49
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
'$1',
|
|
34
|
-
) + '/'
|
|
35
|
-
);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// TODO: Replace uses with `new URL(url).origin` when ie11 is no longer supported.
|
|
50
|
+
/**
|
|
51
|
+
* @param {string} url
|
|
52
|
+
* @returns {string}
|
|
53
|
+
*/
|
|
39
54
|
function getOrigin(url) {
|
|
40
|
-
|
|
41
|
-
/(https?|file|ftp|(chrome|moz|safari-web)-extension):\/\/[^/]+/,
|
|
42
|
-
);
|
|
43
|
-
if (!matches) {
|
|
44
|
-
throw new Error('Origin not found');
|
|
45
|
-
}
|
|
46
|
-
return matches[0];
|
|
55
|
+
return new URL(url).origin;
|
|
47
56
|
}
|
|
48
57
|
|
|
58
|
+
// TODO: convert this file to ESM once HMR issues are resolved
|
|
59
|
+
exports.getOrigin = getOrigin;
|
|
49
60
|
exports.getBundleURL = getBundleURLCached;
|
|
50
61
|
exports.getBaseURL = getBaseURL;
|
|
51
|
-
exports.getOrigin = getOrigin;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import {sendAnalyticsEvent} from '../src/helpers/browser/analytics/analytics.js';
|
|
2
|
+
import {mock} from 'node:test';
|
|
3
|
+
import type {Mock} from 'node:test';
|
|
4
|
+
import assert from 'node:assert';
|
|
5
|
+
|
|
6
|
+
describe('@atlaspack/analytics', () => {
|
|
7
|
+
let dispatchEventMock: Mock<Window['dispatchEvent']>;
|
|
8
|
+
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
// @ts-expect-error
|
|
11
|
+
globalThis.CustomEvent = MockCustomEvent;
|
|
12
|
+
dispatchEventMock = mock.fn();
|
|
13
|
+
globalThis.dispatchEvent = dispatchEventMock;
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('should not throw', () => {
|
|
17
|
+
assert.doesNotThrow(() =>
|
|
18
|
+
sendAnalyticsEvent({
|
|
19
|
+
action: 'test',
|
|
20
|
+
}),
|
|
21
|
+
);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('should raise event on window', () => {
|
|
25
|
+
sendAnalyticsEvent({
|
|
26
|
+
action: 'test',
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
assert.equal(dispatchEventMock.mock.callCount(), 1);
|
|
30
|
+
assert.deepEqual(dispatchEventMock.mock.calls[0].arguments[0], {
|
|
31
|
+
eventName: 'atlaspack:analytics',
|
|
32
|
+
detail: {
|
|
33
|
+
action: 'test',
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
class MockCustomEvent {
|
|
40
|
+
eventName: string;
|
|
41
|
+
detail: any;
|
|
42
|
+
|
|
43
|
+
constructor(eventName: string, options: any = {}) {
|
|
44
|
+
this.eventName = eventName;
|
|
45
|
+
this.detail = options.detail;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
import assert from 'assert';
|
|
3
|
+
|
|
4
|
+
import {getBaseURL, getBundleURL} from '../src/helpers/bundle-url';
|
|
5
|
+
|
|
6
|
+
const createErrorStack = (url) => {
|
|
7
|
+
// This error stack is copied from a local dev, with a bunch
|
|
8
|
+
// of lines trimmed off the end so it's not unnecessarily long
|
|
9
|
+
return `
|
|
10
|
+
Error
|
|
11
|
+
at Object.getBundleURL (http://localhost:8081/main-bundle.1a2fa8b7.js:15688:29)
|
|
12
|
+
at Object.getBundleURLCached (http://localhost:8081/main-bundle.1a2fa8b7.js:15688:29)
|
|
13
|
+
at a7u9v.6d3ceb6ac67fea50 (${url}:361466:46)
|
|
14
|
+
at newRequire (http://localhost:8081/main-bundle.1a2fa8b7.js:71:24)
|
|
15
|
+
at localRequire (http://localhost:8081/main-bundle.1a2fa8b7.js:84:35)
|
|
16
|
+
at 7H8wc.react-intl-next (http://localhost:8081/main-bundle.1a2fa8b7.js:279746:28)
|
|
17
|
+
at newRequire (http://localhost:8081/main-bundle.1a2fa8b7.js:71:24)
|
|
18
|
+
at localRequire (http://localhost:8081/main-bundle.1a2fa8b7.js:84:35)
|
|
19
|
+
at 1nL5S../manifest (http://localhost:8081/main-bundle.1a2fa8b7.js:279714:17)
|
|
20
|
+
at newRequire (http://localhost:8081/main-bundle.1a2fa8b7.js:71:24)
|
|
21
|
+
at localRequire (http://localhost:8081/main-bundle.1a2fa8b7.js:84:35)
|
|
22
|
+
`.trim();
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
describe('getBaseUrl', () => {
|
|
26
|
+
it('should return the URL with the filename removed', () => {
|
|
27
|
+
const testUrl =
|
|
28
|
+
'https://bundle-shard-3.assets.example.com/assets/testBundle.123abc.js';
|
|
29
|
+
|
|
30
|
+
assert.equal(
|
|
31
|
+
getBaseURL(testUrl),
|
|
32
|
+
'https://bundle-shard-3.assets.example.com/assets/',
|
|
33
|
+
);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should handle domains with no .', () => {
|
|
37
|
+
const testUrl = 'http://localhost/assets/testBundle.123abc.js';
|
|
38
|
+
|
|
39
|
+
assert.equal(getBaseURL(testUrl), 'http://localhost/assets/');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('should handle domains with ports', () => {
|
|
43
|
+
const testUrl = 'http://localhost:8081/assets/testBundle.123abc.js';
|
|
44
|
+
|
|
45
|
+
assert.equal(getBaseURL(testUrl), 'http://localhost:8081/assets/');
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe('getBundleURL', () => {
|
|
50
|
+
it('should return the URL of the bundle without the file name', () => {
|
|
51
|
+
const testUrl =
|
|
52
|
+
'https://bundle-domain.assets.example.com/assets/testBundle.123abc.js';
|
|
53
|
+
|
|
54
|
+
const errorStack = createErrorStack(testUrl);
|
|
55
|
+
const error = new Error();
|
|
56
|
+
error.stack = errorStack;
|
|
57
|
+
|
|
58
|
+
const result = getBundleURL('test-asset', error);
|
|
59
|
+
|
|
60
|
+
assert.equal(result, 'https://bundle-domain.assets.example.com/assets/');
|
|
61
|
+
});
|
|
62
|
+
});
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import load from '../src/helpers/browser/esm-js-loader-retry.js';
|
|
2
|
+
import bundleManifest from '../src/helpers/bundle-manifest.js';
|
|
3
|
+
import {mock} from 'node:test';
|
|
4
|
+
import type {Mock} from 'node:test';
|
|
5
|
+
import assert from 'node:assert';
|
|
6
|
+
|
|
7
|
+
declare var globalThis: Window & {[key: string]: any};
|
|
8
|
+
|
|
9
|
+
describe('esm-js-loader-retry', () => {
|
|
10
|
+
let mockSetTimeout: Mock<Window['setTimeout']>;
|
|
11
|
+
let mockParcelImport: Mock<() => Promise<void>>;
|
|
12
|
+
|
|
13
|
+
// eslint-disable-next-line require-await
|
|
14
|
+
const importError = async () => {
|
|
15
|
+
throw new Error('TypeError: Failed to fetch dynamically imported module');
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
before(() => {
|
|
19
|
+
bundleManifest.register('http://localhost', ['1', 'foo.js']);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
beforeEach(() => {
|
|
23
|
+
mockSetTimeout = mock.fn((callback: any, duration: any, ...args: any[]) =>
|
|
24
|
+
callback(),
|
|
25
|
+
);
|
|
26
|
+
globalThis.setTimeout = mockSetTimeout;
|
|
27
|
+
|
|
28
|
+
mockParcelImport = mock.fn(() => Promise.resolve());
|
|
29
|
+
globalThis.__parcel__import__ = mockParcelImport;
|
|
30
|
+
|
|
31
|
+
globalThis.parcelRequire = mock.fn();
|
|
32
|
+
// @ts-expect-error
|
|
33
|
+
globalThis.navigator = {onLine: true};
|
|
34
|
+
globalThis.CustomEvent = globalThis.CustomEvent || class {};
|
|
35
|
+
globalThis.dispatchEvent = mock.fn();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should not throw', async () => {
|
|
39
|
+
await assert.doesNotReject(() => load('1'));
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('should throw if all requests fail', async () => {
|
|
43
|
+
mockParcelImport.mock.mockImplementationOnce(importError, 0);
|
|
44
|
+
mockParcelImport.mock.mockImplementationOnce(importError, 1);
|
|
45
|
+
mockParcelImport.mock.mockImplementationOnce(importError, 2);
|
|
46
|
+
mockParcelImport.mock.mockImplementationOnce(importError, 3);
|
|
47
|
+
mockParcelImport.mock.mockImplementationOnce(importError, 4);
|
|
48
|
+
mockParcelImport.mock.mockImplementationOnce(importError, 5);
|
|
49
|
+
mockParcelImport.mock.mockImplementationOnce(importError, 6);
|
|
50
|
+
await assert.rejects(() => load('1'));
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('should resolve if the first request fails', async () => {
|
|
54
|
+
mockParcelImport.mock.mockImplementationOnce(importError, 0);
|
|
55
|
+
await assert.doesNotReject(() => load('1'));
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('should resolve if the first few requests fails', async () => {
|
|
59
|
+
mockParcelImport.mock.mockImplementationOnce(importError, 0);
|
|
60
|
+
mockParcelImport.mock.mockImplementationOnce(importError, 1);
|
|
61
|
+
mockParcelImport.mock.mockImplementationOnce(importError, 2);
|
|
62
|
+
mockParcelImport.mock.mockImplementationOnce(importError, 3);
|
|
63
|
+
await assert.doesNotReject(() => load('1'));
|
|
64
|
+
});
|
|
65
|
+
});
|