@atlaspack/runtime-js 2.12.1-canary.3581 → 2.12.1-canary.3583
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/lib/JSRuntime.js +32 -14
- package/package.json +7 -7
- package/src/JSRuntime.js +68 -11
- package/src/helpers/bundle-url-common.ts +10 -0
- package/src/helpers/bundle-url-shards.ts +95 -0
- package/src/helpers/bundle-url.ts +36 -0
- package/test/bundle-url-common.test.js +27 -0
- package/test/bundle-url-shards.test.js +139 -0
- package/lib/helpers/bundle-url.js +0 -39
- package/src/helpers/bundle-url.js +0 -51
package/lib/JSRuntime.js
CHANGED
|
@@ -93,6 +93,19 @@ const CONFIG_SCHEMA = {
|
|
|
93
93
|
properties: {
|
|
94
94
|
splitManifestThreshold: {
|
|
95
95
|
type: 'number'
|
|
96
|
+
},
|
|
97
|
+
domainSharding: {
|
|
98
|
+
type: 'object',
|
|
99
|
+
properties: {
|
|
100
|
+
maxShards: {
|
|
101
|
+
type: 'number'
|
|
102
|
+
},
|
|
103
|
+
cookieName: {
|
|
104
|
+
type: 'string'
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
additionalProperties: false,
|
|
108
|
+
required: ['maxShards', 'cookieName']
|
|
96
109
|
}
|
|
97
110
|
},
|
|
98
111
|
additionalProperties: false
|
|
@@ -181,7 +194,8 @@ var _default = exports.default = new (_plugin().Runtime)({
|
|
|
181
194
|
dependency,
|
|
182
195
|
bundleGraph,
|
|
183
196
|
bundleGroup: resolved.value,
|
|
184
|
-
options
|
|
197
|
+
options,
|
|
198
|
+
shardingConfig: config.domainSharding
|
|
185
199
|
});
|
|
186
200
|
if (loaderRuntime != null) {
|
|
187
201
|
assets.push(loaderRuntime);
|
|
@@ -257,7 +271,7 @@ var _default = exports.default = new (_plugin().Runtime)({
|
|
|
257
271
|
}
|
|
258
272
|
|
|
259
273
|
// URL dependency or not, fall back to including a runtime that exports the url
|
|
260
|
-
assets.push(getURLRuntime(dependency, bundle, mainBundle, options));
|
|
274
|
+
assets.push(getURLRuntime(dependency, bundle, mainBundle, options, config.domainSharding));
|
|
261
275
|
}
|
|
262
276
|
|
|
263
277
|
// In development, bundles can be created lazily. This means that the parent bundle may not
|
|
@@ -275,7 +289,7 @@ var _default = exports.default = new (_plugin().Runtime)({
|
|
|
275
289
|
continue;
|
|
276
290
|
}
|
|
277
291
|
let relativePathExpr = getRelativePathExpr(bundle, referencedBundle, options);
|
|
278
|
-
let loaderCode = `require(${JSON.stringify(loader)})(
|
|
292
|
+
let loaderCode = `require(${JSON.stringify(loader)})(${getAbsoluteUrlExpr(relativePathExpr, bundle, referencedBundle, config.domainSharding)})`;
|
|
279
293
|
assets.push({
|
|
280
294
|
filePath: __filename,
|
|
281
295
|
code: loaderCode,
|
|
@@ -338,7 +352,8 @@ function getLoaderRuntime({
|
|
|
338
352
|
dependency,
|
|
339
353
|
bundleGroup,
|
|
340
354
|
bundleGraph,
|
|
341
|
-
options
|
|
355
|
+
options,
|
|
356
|
+
shardingConfig
|
|
342
357
|
}) {
|
|
343
358
|
let loaders = getLoaders(bundle.env);
|
|
344
359
|
if (loaders == null) {
|
|
@@ -372,7 +387,7 @@ function getLoaderRuntime({
|
|
|
372
387
|
let needsDynamicImportPolyfill = !bundle.env.isLibrary && !bundle.env.supports('dynamic-import', true);
|
|
373
388
|
let needsEsmLoadPrelude = false;
|
|
374
389
|
let loaderModules = [];
|
|
375
|
-
function getLoaderForBundle(bundle, to) {
|
|
390
|
+
function getLoaderForBundle(bundle, to, shardingConfig) {
|
|
376
391
|
let loader = loaders[to.type];
|
|
377
392
|
if (!loader) {
|
|
378
393
|
return;
|
|
@@ -392,7 +407,7 @@ function getLoaderRuntime({
|
|
|
392
407
|
} else if (to.type === 'js' && to.env.outputFormat === 'commonjs') {
|
|
393
408
|
return `Promise.resolve(__parcel__require__("./" + ${relativePathExpr}))`;
|
|
394
409
|
}
|
|
395
|
-
let absoluteUrlExpr = shouldUseRuntimeManifest(bundle, options) ? `require('./helpers/bundle-manifest').resolve(${JSON.stringify(to.publicId)})` : getAbsoluteUrlExpr(relativePathExpr, bundle);
|
|
410
|
+
let absoluteUrlExpr = shouldUseRuntimeManifest(bundle, options) ? `require('./helpers/bundle-manifest').resolve(${JSON.stringify(to.publicId)})` : getAbsoluteUrlExpr(relativePathExpr, bundle, to, shardingConfig);
|
|
396
411
|
let code = `require(${JSON.stringify(loader)})(${absoluteUrlExpr})`;
|
|
397
412
|
|
|
398
413
|
// In development, clear the require cache when an error occurs so the
|
|
@@ -415,7 +430,7 @@ function getLoaderRuntime({
|
|
|
415
430
|
}
|
|
416
431
|
}
|
|
417
432
|
for (let to of externalBundles) {
|
|
418
|
-
let loaderModule = getLoaderForBundle(bundle, to);
|
|
433
|
+
let loaderModule = getLoaderForBundle(bundle, to, shardingConfig);
|
|
419
434
|
if (loaderModule !== undefined) loaderModules.push(loaderModule);
|
|
420
435
|
}
|
|
421
436
|
|
|
@@ -513,7 +528,7 @@ function getHintLoaders(bundleGraph, from, bundleGroups, loader, options) {
|
|
|
513
528
|
for (let bundleToPreload of bundlesToPreload) {
|
|
514
529
|
let relativePathExpr = getRelativePathExpr(from, bundleToPreload, options);
|
|
515
530
|
let priority = TYPE_TO_RESOURCE_PRIORITY[bundleToPreload.type];
|
|
516
|
-
hintLoaders.push(`require(${JSON.stringify(loader)})(${getAbsoluteUrlExpr(relativePathExpr, from)}, ${priority ? JSON.stringify(priority) : 'null'}, ${JSON.stringify(bundleToPreload.target.env.outputFormat === 'esmodule')})`);
|
|
531
|
+
hintLoaders.push(`require(${JSON.stringify(loader)})(${getAbsoluteUrlExpr(relativePathExpr, from, bundleToPreload)}, ${priority ? JSON.stringify(priority) : 'null'}, ${JSON.stringify(bundleToPreload.target.env.outputFormat === 'esmodule')})`);
|
|
517
532
|
}
|
|
518
533
|
}
|
|
519
534
|
return hintLoaders;
|
|
@@ -523,7 +538,7 @@ function isNewContext(bundle, bundleGraph) {
|
|
|
523
538
|
let isInEntryBundleGroup = bundleGraph.getBundleGroupsContainingBundle(bundle).some(g => bundleGraph.isEntryBundleGroup(g));
|
|
524
539
|
return isInEntryBundleGroup || parents.length === 0 || parents.some(parent => parent.env.context !== bundle.env.context || parent.type !== 'js');
|
|
525
540
|
}
|
|
526
|
-
function getURLRuntime(dependency, from, to, options) {
|
|
541
|
+
function getURLRuntime(dependency, from, to, options, shardingConfig) {
|
|
527
542
|
let relativePathExpr = getRelativePathExpr(from, to, options);
|
|
528
543
|
let code;
|
|
529
544
|
if (dependency.meta.webworker === true && !from.env.isLibrary) {
|
|
@@ -537,7 +552,7 @@ function getURLRuntime(dependency, from, to, options) {
|
|
|
537
552
|
code += `module.exports = workerURL(url, bundleURL.getOrigin(url), ${String(from.env.outputFormat === 'esmodule')});`;
|
|
538
553
|
}
|
|
539
554
|
} else {
|
|
540
|
-
code = `module.exports = ${getAbsoluteUrlExpr(relativePathExpr, from)};`;
|
|
555
|
+
code = `module.exports = ${getAbsoluteUrlExpr(relativePathExpr, from, to, shardingConfig)};`;
|
|
541
556
|
}
|
|
542
557
|
return {
|
|
543
558
|
filePath: __filename,
|
|
@@ -585,13 +600,16 @@ function getRelativePathExpr(from, to, options) {
|
|
|
585
600
|
}
|
|
586
601
|
return res;
|
|
587
602
|
}
|
|
588
|
-
function getAbsoluteUrlExpr(relativePathExpr,
|
|
589
|
-
if (
|
|
603
|
+
function getAbsoluteUrlExpr(relativePathExpr, fromBundle, toBundle, shardingConfig) {
|
|
604
|
+
if (fromBundle.env.outputFormat === 'esmodule' && fromBundle.env.supports('import-meta-url') || fromBundle.env.outputFormat === 'commonjs') {
|
|
590
605
|
// This will be compiled to new URL(url, import.meta.url) or new URL(url, 'file:' + __filename).
|
|
591
606
|
return `new __parcel__URL__(${relativePathExpr}).toString()`;
|
|
592
|
-
} else {
|
|
593
|
-
return `require('./helpers/bundle-url').getBundleURL('${bundle.publicId}') + ${relativePathExpr}`;
|
|
594
607
|
}
|
|
608
|
+
if (shardingConfig) {
|
|
609
|
+
const bundleUrlArgs = [`'${toBundle.name}'`, `'${shardingConfig.cookieName}'`, 'document.cookie', shardingConfig.maxShards].join(', ');
|
|
610
|
+
return `require('./helpers/bundle-url-shards').getShardedBundleURL(${bundleUrlArgs}) + ${relativePathExpr}`;
|
|
611
|
+
}
|
|
612
|
+
return `require('./helpers/bundle-url').getBundleURL('${fromBundle.publicId}') + ${relativePathExpr}`;
|
|
595
613
|
}
|
|
596
614
|
function shouldUseRuntimeManifest(bundle, options) {
|
|
597
615
|
let env = bundle.env;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atlaspack/runtime-js",
|
|
3
|
-
"version": "2.12.1-canary.
|
|
3
|
+
"version": "2.12.1-canary.3583+3054dda39",
|
|
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.1-canary.
|
|
15
|
+
"atlaspack": "2.12.1-canary.3583+3054dda39",
|
|
16
16
|
"node": ">= 16.0.0"
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@atlaspack/diagnostic": "2.12.1-canary.
|
|
20
|
-
"@atlaspack/feature-flags": "2.12.1-canary.
|
|
21
|
-
"@atlaspack/plugin": "2.12.1-canary.
|
|
22
|
-
"@atlaspack/utils": "2.12.1-canary.
|
|
19
|
+
"@atlaspack/diagnostic": "2.12.1-canary.3583+3054dda39",
|
|
20
|
+
"@atlaspack/feature-flags": "2.12.1-canary.3583+3054dda39",
|
|
21
|
+
"@atlaspack/plugin": "2.12.1-canary.3583+3054dda39",
|
|
22
|
+
"@atlaspack/utils": "2.12.1-canary.3583+3054dda39",
|
|
23
23
|
"nullthrows": "^1.1.1"
|
|
24
24
|
},
|
|
25
|
-
"gitHead": "
|
|
25
|
+
"gitHead": "3054dda39b54666caba268f09cadf801b43ce3a5"
|
|
26
26
|
}
|
package/src/JSRuntime.js
CHANGED
|
@@ -74,6 +74,10 @@ let bundleDependencies = new WeakMap<
|
|
|
74
74
|
|
|
75
75
|
type JSRuntimeConfig = {|
|
|
76
76
|
splitManifestThreshold: number,
|
|
77
|
+
domainSharding?: {|
|
|
78
|
+
maxShards: number,
|
|
79
|
+
cookieName: string,
|
|
80
|
+
|},
|
|
77
81
|
|};
|
|
78
82
|
|
|
79
83
|
let defaultConfig: JSRuntimeConfig = {
|
|
@@ -86,6 +90,19 @@ const CONFIG_SCHEMA: SchemaEntity = {
|
|
|
86
90
|
splitManifestThreshold: {
|
|
87
91
|
type: 'number',
|
|
88
92
|
},
|
|
93
|
+
domainSharding: {
|
|
94
|
+
type: 'object',
|
|
95
|
+
properties: {
|
|
96
|
+
maxShards: {
|
|
97
|
+
type: 'number',
|
|
98
|
+
},
|
|
99
|
+
cookieName: {
|
|
100
|
+
type: 'string',
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
additionalProperties: false,
|
|
104
|
+
required: ['maxShards', 'cookieName'],
|
|
105
|
+
},
|
|
89
106
|
},
|
|
90
107
|
additionalProperties: false,
|
|
91
108
|
};
|
|
@@ -181,6 +198,7 @@ export default (new Runtime({
|
|
|
181
198
|
bundleGraph,
|
|
182
199
|
bundleGroup: resolved.value,
|
|
183
200
|
options,
|
|
201
|
+
shardingConfig: config.domainSharding,
|
|
184
202
|
});
|
|
185
203
|
|
|
186
204
|
if (loaderRuntime != null) {
|
|
@@ -271,7 +289,15 @@ export default (new Runtime({
|
|
|
271
289
|
}
|
|
272
290
|
|
|
273
291
|
// URL dependency or not, fall back to including a runtime that exports the url
|
|
274
|
-
assets.push(
|
|
292
|
+
assets.push(
|
|
293
|
+
getURLRuntime(
|
|
294
|
+
dependency,
|
|
295
|
+
bundle,
|
|
296
|
+
mainBundle,
|
|
297
|
+
options,
|
|
298
|
+
config.domainSharding,
|
|
299
|
+
),
|
|
300
|
+
);
|
|
275
301
|
}
|
|
276
302
|
|
|
277
303
|
// In development, bundles can be created lazily. This means that the parent bundle may not
|
|
@@ -297,7 +323,12 @@ export default (new Runtime({
|
|
|
297
323
|
);
|
|
298
324
|
let loaderCode = `require(${JSON.stringify(
|
|
299
325
|
loader,
|
|
300
|
-
)})(
|
|
326
|
+
)})(${getAbsoluteUrlExpr(
|
|
327
|
+
relativePathExpr,
|
|
328
|
+
bundle,
|
|
329
|
+
referencedBundle,
|
|
330
|
+
config.domainSharding,
|
|
331
|
+
)})`;
|
|
301
332
|
assets.push({
|
|
302
333
|
filePath: __filename,
|
|
303
334
|
code: loaderCode,
|
|
@@ -376,12 +407,14 @@ function getLoaderRuntime({
|
|
|
376
407
|
bundleGroup,
|
|
377
408
|
bundleGraph,
|
|
378
409
|
options,
|
|
410
|
+
shardingConfig,
|
|
379
411
|
}: {|
|
|
380
412
|
bundle: NamedBundle,
|
|
381
413
|
dependency: Dependency,
|
|
382
414
|
bundleGroup: BundleGroup,
|
|
383
415
|
bundleGraph: BundleGraph<NamedBundle>,
|
|
384
416
|
options: PluginOptions,
|
|
417
|
+
shardingConfig: JSRuntimeConfig['domainSharding'],
|
|
385
418
|
|}): ?RuntimeAsset {
|
|
386
419
|
let loaders = getLoaders(bundle.env);
|
|
387
420
|
if (loaders == null) {
|
|
@@ -423,6 +456,7 @@ function getLoaderRuntime({
|
|
|
423
456
|
function getLoaderForBundle(
|
|
424
457
|
bundle: NamedBundle,
|
|
425
458
|
to: NamedBundle,
|
|
459
|
+
shardingConfig: JSRuntimeConfig['domainSharding'],
|
|
426
460
|
): string | void {
|
|
427
461
|
let loader = loaders[to.type];
|
|
428
462
|
if (!loader) {
|
|
@@ -459,7 +493,7 @@ function getLoaderRuntime({
|
|
|
459
493
|
? `require('./helpers/bundle-manifest').resolve(${JSON.stringify(
|
|
460
494
|
to.publicId,
|
|
461
495
|
)})`
|
|
462
|
-
: getAbsoluteUrlExpr(relativePathExpr, bundle);
|
|
496
|
+
: getAbsoluteUrlExpr(relativePathExpr, bundle, to, shardingConfig);
|
|
463
497
|
let code = `require(${JSON.stringify(loader)})(${absoluteUrlExpr})`;
|
|
464
498
|
|
|
465
499
|
// In development, clear the require cache when an error occurs so the
|
|
@@ -508,7 +542,7 @@ function getLoaderRuntime({
|
|
|
508
542
|
}
|
|
509
543
|
|
|
510
544
|
for (let to of externalBundles) {
|
|
511
|
-
let loaderModule = getLoaderForBundle(bundle, to);
|
|
545
|
+
let loaderModule = getLoaderForBundle(bundle, to, shardingConfig);
|
|
512
546
|
if (loaderModule !== undefined) loaderModules.push(loaderModule);
|
|
513
547
|
}
|
|
514
548
|
|
|
@@ -650,6 +684,7 @@ function getHintLoaders(
|
|
|
650
684
|
`require(${JSON.stringify(loader)})(${getAbsoluteUrlExpr(
|
|
651
685
|
relativePathExpr,
|
|
652
686
|
from,
|
|
687
|
+
bundleToPreload,
|
|
653
688
|
)}, ${priority ? JSON.stringify(priority) : 'null'}, ${JSON.stringify(
|
|
654
689
|
bundleToPreload.target.env.outputFormat === 'esmodule',
|
|
655
690
|
)})`,
|
|
@@ -683,6 +718,7 @@ function getURLRuntime(
|
|
|
683
718
|
from: NamedBundle,
|
|
684
719
|
to: NamedBundle,
|
|
685
720
|
options: PluginOptions,
|
|
721
|
+
shardingConfig: JSRuntimeConfig['domainSharding'],
|
|
686
722
|
): RuntimeAsset {
|
|
687
723
|
let relativePathExpr = getRelativePathExpr(from, to, options);
|
|
688
724
|
let code;
|
|
@@ -705,7 +741,12 @@ function getURLRuntime(
|
|
|
705
741
|
)});`;
|
|
706
742
|
}
|
|
707
743
|
} else {
|
|
708
|
-
code = `module.exports = ${getAbsoluteUrlExpr(
|
|
744
|
+
code = `module.exports = ${getAbsoluteUrlExpr(
|
|
745
|
+
relativePathExpr,
|
|
746
|
+
from,
|
|
747
|
+
to,
|
|
748
|
+
shardingConfig,
|
|
749
|
+
)};`;
|
|
709
750
|
}
|
|
710
751
|
|
|
711
752
|
return {
|
|
@@ -775,17 +816,33 @@ function getRelativePathExpr(
|
|
|
775
816
|
return res;
|
|
776
817
|
}
|
|
777
818
|
|
|
778
|
-
function getAbsoluteUrlExpr(
|
|
819
|
+
function getAbsoluteUrlExpr(
|
|
820
|
+
relativePathExpr: string,
|
|
821
|
+
fromBundle: NamedBundle,
|
|
822
|
+
toBundle: NamedBundle,
|
|
823
|
+
shardingConfig: JSRuntimeConfig['domainSharding'],
|
|
824
|
+
) {
|
|
779
825
|
if (
|
|
780
|
-
(
|
|
781
|
-
|
|
782
|
-
|
|
826
|
+
(fromBundle.env.outputFormat === 'esmodule' &&
|
|
827
|
+
fromBundle.env.supports('import-meta-url')) ||
|
|
828
|
+
fromBundle.env.outputFormat === 'commonjs'
|
|
783
829
|
) {
|
|
784
830
|
// This will be compiled to new URL(url, import.meta.url) or new URL(url, 'file:' + __filename).
|
|
785
831
|
return `new __parcel__URL__(${relativePathExpr}).toString()`;
|
|
786
|
-
} else {
|
|
787
|
-
return `require('./helpers/bundle-url').getBundleURL('${bundle.publicId}') + ${relativePathExpr}`;
|
|
788
832
|
}
|
|
833
|
+
|
|
834
|
+
if (shardingConfig) {
|
|
835
|
+
const bundleUrlArgs = [
|
|
836
|
+
`'${toBundle.name}'`,
|
|
837
|
+
`'${shardingConfig.cookieName}'`,
|
|
838
|
+
'document.cookie',
|
|
839
|
+
shardingConfig.maxShards,
|
|
840
|
+
].join(', ');
|
|
841
|
+
|
|
842
|
+
return `require('./helpers/bundle-url-shards').getShardedBundleURL(${bundleUrlArgs}) + ${relativePathExpr}`;
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
return `require('./helpers/bundle-url').getBundleURL('${fromBundle.publicId}') + ${relativePathExpr}`;
|
|
789
846
|
}
|
|
790
847
|
|
|
791
848
|
function shouldUseRuntimeManifest(
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Get the URL without the filename (last / segment)
|
|
2
|
+
function getBaseURL(url: string) {
|
|
3
|
+
return url.slice(0, url.lastIndexOf('/')) + '/';
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
const stackTraceUrlRegexp =
|
|
7
|
+
/(https?|file|ftp|(chrome|moz|safari-web)-extension):\/\/[^)\n]+/g;
|
|
8
|
+
|
|
9
|
+
exports.getBaseURL = getBaseURL;
|
|
10
|
+
exports.stackTraceUrlRegexp = stackTraceUrlRegexp;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
const {getBaseURL, stackTraceUrlRegexp} = require('./bundle-url-common');
|
|
2
|
+
|
|
3
|
+
const bundleURL: Record<string, string> = {};
|
|
4
|
+
|
|
5
|
+
function getShardedBundleURL(
|
|
6
|
+
bundleName: string,
|
|
7
|
+
cookieName: string,
|
|
8
|
+
cookieString: string,
|
|
9
|
+
maxShards: number,
|
|
10
|
+
inputError?: string,
|
|
11
|
+
): string {
|
|
12
|
+
let value = bundleURL[bundleName];
|
|
13
|
+
|
|
14
|
+
if (value) {
|
|
15
|
+
return value;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
throw inputError ?? new Error();
|
|
20
|
+
} catch (err) {
|
|
21
|
+
var matches = ('' + err.stack).match(stackTraceUrlRegexp);
|
|
22
|
+
|
|
23
|
+
if (!matches) {
|
|
24
|
+
return '/';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// The first stack frame will be this function.
|
|
28
|
+
// Use the 2nd one, which will be a runtime in the original bundle.
|
|
29
|
+
const stackUrl = matches[1];
|
|
30
|
+
const baseUrl = getBaseURL(stackUrl);
|
|
31
|
+
|
|
32
|
+
// If the cookie doesn't exist then we don't need to shard
|
|
33
|
+
if (cookieString.indexOf(cookieName) === -1) {
|
|
34
|
+
return baseUrl;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const shardNumber = getDomainShardIndex(bundleName, maxShards);
|
|
38
|
+
const url = new URL(baseUrl);
|
|
39
|
+
|
|
40
|
+
const shardedDomain = getShardedDomain(url.hostname, shardNumber);
|
|
41
|
+
url.hostname = shardedDomain;
|
|
42
|
+
|
|
43
|
+
value = url.toString();
|
|
44
|
+
|
|
45
|
+
bundleURL[bundleName] = value;
|
|
46
|
+
return value;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function getDomainShardIndex(str: string, maxShards: number) {
|
|
51
|
+
let shard = str.split('').reduce((a, b) => {
|
|
52
|
+
const n = (a << maxShards) - a + b.charCodeAt(0);
|
|
53
|
+
|
|
54
|
+
// The value returned by << is 64 bit, the & operator coerces to 32,
|
|
55
|
+
// prevents overflow as we iterate.
|
|
56
|
+
return n & n;
|
|
57
|
+
}, 0);
|
|
58
|
+
|
|
59
|
+
shard = shard % maxShards;
|
|
60
|
+
|
|
61
|
+
// Make number positive
|
|
62
|
+
if (shard < 0) {
|
|
63
|
+
shard += maxShards;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return shard;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function getShardedDomain(domain: string, shard: number) {
|
|
70
|
+
let i = domain.indexOf('.');
|
|
71
|
+
|
|
72
|
+
// Domains like localhost have no . separators
|
|
73
|
+
if (i === -1) {
|
|
74
|
+
return `${removeTrailingShard(domain)}-${shard}`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// If this domain already has a shard number in it, strip it out before adding
|
|
78
|
+
// the new one
|
|
79
|
+
const firstSubdomain = removeTrailingShard(domain.slice(0, i));
|
|
80
|
+
|
|
81
|
+
return `${firstSubdomain}-${shard}${domain.slice(i)}`;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const trailingShardRegex = /-\d+$/;
|
|
85
|
+
|
|
86
|
+
function removeTrailingShard(subdomain: string) {
|
|
87
|
+
if (!trailingShardRegex.test(subdomain)) {
|
|
88
|
+
return subdomain;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const shardIdx = subdomain.lastIndexOf('-');
|
|
92
|
+
return subdomain.slice(0, shardIdx);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
exports.getShardedBundleURL = getShardedBundleURL;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
const {getBaseURL, stackTraceUrlRegexp} = require('./bundle-url-common');
|
|
2
|
+
|
|
3
|
+
const bundleURL = {};
|
|
4
|
+
|
|
5
|
+
function getBundleURLCached(id: string) {
|
|
6
|
+
let value = bundleURL[id];
|
|
7
|
+
|
|
8
|
+
if (!value) {
|
|
9
|
+
value = getBundleURL();
|
|
10
|
+
bundleURL[id] = value;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return value;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function getBundleURL() {
|
|
17
|
+
try {
|
|
18
|
+
throw new Error();
|
|
19
|
+
} catch (err) {
|
|
20
|
+
var matches = ('' + err.stack).match(stackTraceUrlRegexp);
|
|
21
|
+
if (matches) {
|
|
22
|
+
// The first two stack frames will be this function and getBundleURLCached.
|
|
23
|
+
// Use the 3rd one, which will be a runtime in the original bundle.
|
|
24
|
+
return getBaseURL(matches[2]);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return '/';
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function getOrigin(url: string) {
|
|
32
|
+
return new URL(url).origin;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
exports.getOrigin = getOrigin;
|
|
36
|
+
exports.getBundleURL = getBundleURLCached;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import assert from 'assert';
|
|
2
|
+
|
|
3
|
+
import {getBaseURL} from '../src/helpers/bundle-url-common';
|
|
4
|
+
|
|
5
|
+
describe('getBaseUrl', () => {
|
|
6
|
+
it('should return the URL with the filename removed', () => {
|
|
7
|
+
const testUrl =
|
|
8
|
+
'https://bundle-shard-3.assets.example.com/assets/testBundle.123abc.js';
|
|
9
|
+
|
|
10
|
+
assert.equal(
|
|
11
|
+
getBaseURL(testUrl),
|
|
12
|
+
'https://bundle-shard-3.assets.example.com/assets/',
|
|
13
|
+
);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('should handle domains with no .', () => {
|
|
17
|
+
const testUrl = 'http://localhost/assets/testBundle.123abc.js';
|
|
18
|
+
|
|
19
|
+
assert.equal(getBaseURL(testUrl), 'http://localhost/assets/');
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('should handle domains with ports', () => {
|
|
23
|
+
const testUrl = 'http://localhost:8081/assets/testBundle.123abc.js';
|
|
24
|
+
|
|
25
|
+
assert.equal(getBaseURL(testUrl), 'http://localhost:8081/assets/');
|
|
26
|
+
});
|
|
27
|
+
});
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import assert from 'assert';
|
|
2
|
+
|
|
3
|
+
import {fsFixture, overlayFS, bundle} from '@atlaspack/test-utils';
|
|
4
|
+
|
|
5
|
+
import {getShardedBundleURL} from '../src/helpers/bundle-url-shards';
|
|
6
|
+
|
|
7
|
+
const testingCookieName = 'DOMAIN_SHARDING_TEST';
|
|
8
|
+
|
|
9
|
+
const createErrorStack = (url) => {
|
|
10
|
+
// This error stack is copied from a local dev, with a bunch
|
|
11
|
+
// of lines trimmed off the end so it's not unnecessarily long
|
|
12
|
+
return `
|
|
13
|
+
Error
|
|
14
|
+
at Object.getShardedBundleURL (http://localhost:8081/main-bundle.1a2fa8b7.js:15688:29)
|
|
15
|
+
at a7u9v.6d3ceb6ac67fea50 (${url}.1a2fa8b7.js:361466:46)
|
|
16
|
+
at newRequire (http://localhost:8081/main-bundle.1a2fa8b7.js:71:24)
|
|
17
|
+
at localRequire (http://localhost:8081/main-bundle.1a2fa8b7.js:84:35)
|
|
18
|
+
at 7H8wc.react-intl-next (http://localhost:8081/main-bundle.1a2fa8b7.js:279746:28)
|
|
19
|
+
at newRequire (http://localhost:8081/main-bundle.1a2fa8b7.js:71:24)
|
|
20
|
+
at localRequire (http://localhost:8081/main-bundle.1a2fa8b7.js:84:35)
|
|
21
|
+
at 1nL5S../manifest (http://localhost:8081/main-bundle.1a2fa8b7.js:279714:17)
|
|
22
|
+
at newRequire (http://localhost:8081/main-bundle.1a2fa8b7.js:71:24)
|
|
23
|
+
at localRequire (http://localhost:8081/main-bundle.1a2fa8b7.js:84:35)
|
|
24
|
+
`.trim();
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
describe('bundle-url-shards helper', () => {
|
|
28
|
+
describe('getShardedBundleURL', () => {
|
|
29
|
+
it('should shard a URL if the cookie is present', () => {
|
|
30
|
+
const testBundle = 'test-bundle.123abc.js';
|
|
31
|
+
|
|
32
|
+
const err = new Error();
|
|
33
|
+
err.stack = createErrorStack(
|
|
34
|
+
'https://bundle-shard.assets.example.com/assets/ParentBundle.cba321.js',
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
const result = getShardedBundleURL(
|
|
38
|
+
testBundle,
|
|
39
|
+
testingCookieName,
|
|
40
|
+
`${testingCookieName}=1`,
|
|
41
|
+
5,
|
|
42
|
+
err,
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
assert.equal(result, 'https://bundle-shard-0.assets.example.com/assets/');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should re-shard a domain that has already been sharded', () => {
|
|
49
|
+
const testBundle = 'TestBundle.1a2b3c.js';
|
|
50
|
+
|
|
51
|
+
const err = new Error();
|
|
52
|
+
err.stack = createErrorStack(
|
|
53
|
+
'https://bundle-shard-1.assets.example.com/assets/ParentBundle.cba321.js',
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
const result = getShardedBundleURL(
|
|
57
|
+
testBundle,
|
|
58
|
+
testingCookieName,
|
|
59
|
+
`${testingCookieName}=1`,
|
|
60
|
+
5,
|
|
61
|
+
err,
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
assert.equal(result, 'https://bundle-shard-4.assets.example.com/assets/');
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('should not add a shard if the cookie is not present', () => {
|
|
68
|
+
const testBundle = 'UnshardedBundle.9z8x7y.js';
|
|
69
|
+
|
|
70
|
+
const err = new Error();
|
|
71
|
+
err.stack = createErrorStack(
|
|
72
|
+
'https://bundle-unsharded.assets.example.com/assets/ParentBundle.cba321.js',
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
const result = getShardedBundleURL(
|
|
76
|
+
testBundle,
|
|
77
|
+
testingCookieName,
|
|
78
|
+
`some.other.cookie=1`,
|
|
79
|
+
5,
|
|
80
|
+
err,
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
assert.equal(
|
|
84
|
+
result,
|
|
85
|
+
'https://bundle-unsharded.assets.example.com/assets/',
|
|
86
|
+
);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe('compiled into JS Runtime', () => {
|
|
91
|
+
it('should insert all arguments into compiled output', async () => {
|
|
92
|
+
const maxShards = 8;
|
|
93
|
+
await fsFixture(overlayFS)`
|
|
94
|
+
package.json:
|
|
95
|
+
{
|
|
96
|
+
"name": "bundle-sharding-test",
|
|
97
|
+
"@atlaspack/runtime-js": {
|
|
98
|
+
"domainSharding": {
|
|
99
|
+
"maxShards": ${maxShards},
|
|
100
|
+
"cookieName": "${testingCookieName}"
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
src/index.js:
|
|
106
|
+
async function fn() {
|
|
107
|
+
const a = await import('./a.js');
|
|
108
|
+
const b = await import('./b.js');
|
|
109
|
+
console.log('a', a, b);
|
|
110
|
+
}
|
|
111
|
+
fn();
|
|
112
|
+
|
|
113
|
+
src/a.js:
|
|
114
|
+
export const a = async () => {
|
|
115
|
+
const b = await import('./b');
|
|
116
|
+
return b + 'A';
|
|
117
|
+
}
|
|
118
|
+
src/b.js:
|
|
119
|
+
export const b = 'B';
|
|
120
|
+
|
|
121
|
+
yarn.lock:
|
|
122
|
+
`;
|
|
123
|
+
|
|
124
|
+
const bundleGraph = await bundle('src/index.js', {inputFS: overlayFS});
|
|
125
|
+
|
|
126
|
+
const mainBundle = bundleGraph
|
|
127
|
+
.getBundles()
|
|
128
|
+
.find((b) => b.name === 'index.js');
|
|
129
|
+
|
|
130
|
+
const code = await overlayFS.readFile(mainBundle.filePath, 'utf-8');
|
|
131
|
+
assert.ok(
|
|
132
|
+
code.includes(
|
|
133
|
+
`require("449af90f11ccd363").getShardedBundleURL('b.8575baaf.js', '${testingCookieName}', document.cookie, ${maxShards}) + "b.8575baaf.js"`,
|
|
134
|
+
),
|
|
135
|
+
'Expected generated code for getShardedBundleURL was not found',
|
|
136
|
+
);
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
});
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
var bundleURL = {};
|
|
4
|
-
function getBundleURLCached(id) {
|
|
5
|
-
var value = bundleURL[id];
|
|
6
|
-
if (!value) {
|
|
7
|
-
value = getBundleURL();
|
|
8
|
-
bundleURL[id] = value;
|
|
9
|
-
}
|
|
10
|
-
return value;
|
|
11
|
-
}
|
|
12
|
-
function getBundleURL() {
|
|
13
|
-
try {
|
|
14
|
-
throw new Error();
|
|
15
|
-
} catch (err) {
|
|
16
|
-
var matches = ('' + err.stack).match(/(https?|file|ftp|(chrome|moz|safari-web)-extension):\/\/[^)\n]+/g);
|
|
17
|
-
if (matches) {
|
|
18
|
-
// The first two stack frames will be this function and getBundleURLCached.
|
|
19
|
-
// Use the 3rd one, which will be a runtime in the original bundle.
|
|
20
|
-
return getBaseURL(matches[2]);
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
return '/';
|
|
24
|
-
}
|
|
25
|
-
function getBaseURL(url) {
|
|
26
|
-
return ('' + url).replace(/^((?:https?|file|ftp|(chrome|moz|safari-web)-extension):\/\/.+)\/[^/]+$/, '$1') + '/';
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// TODO: Replace uses with `new URL(url).origin` when ie11 is no longer supported.
|
|
30
|
-
function getOrigin(url) {
|
|
31
|
-
var matches = ('' + url).match(/(https?|file|ftp|(chrome|moz|safari-web)-extension):\/\/[^/]+/);
|
|
32
|
-
if (!matches) {
|
|
33
|
-
throw new Error('Origin not found');
|
|
34
|
-
}
|
|
35
|
-
return matches[0];
|
|
36
|
-
}
|
|
37
|
-
exports.getBundleURL = getBundleURLCached;
|
|
38
|
-
exports.getBaseURL = getBaseURL;
|
|
39
|
-
exports.getOrigin = getOrigin;
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
var bundleURL = {};
|
|
2
|
-
function getBundleURLCached(id) {
|
|
3
|
-
var value = bundleURL[id];
|
|
4
|
-
if (!value) {
|
|
5
|
-
value = getBundleURL();
|
|
6
|
-
bundleURL[id] = value;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
return value;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
function getBundleURL() {
|
|
13
|
-
try {
|
|
14
|
-
throw new Error();
|
|
15
|
-
} catch (err) {
|
|
16
|
-
var matches = ('' + err.stack).match(
|
|
17
|
-
/(https?|file|ftp|(chrome|moz|safari-web)-extension):\/\/[^)\n]+/g,
|
|
18
|
-
);
|
|
19
|
-
if (matches) {
|
|
20
|
-
// The first two stack frames will be this function and getBundleURLCached.
|
|
21
|
-
// Use the 3rd one, which will be a runtime in the original bundle.
|
|
22
|
-
return getBaseURL(matches[2]);
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
return '/';
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function getBaseURL(url) {
|
|
30
|
-
return (
|
|
31
|
-
('' + url).replace(
|
|
32
|
-
/^((?:https?|file|ftp|(chrome|moz|safari-web)-extension):\/\/.+)\/[^/]+$/,
|
|
33
|
-
'$1',
|
|
34
|
-
) + '/'
|
|
35
|
-
);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// TODO: Replace uses with `new URL(url).origin` when ie11 is no longer supported.
|
|
39
|
-
function getOrigin(url) {
|
|
40
|
-
let matches = ('' + url).match(
|
|
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];
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
exports.getBundleURL = getBundleURLCached;
|
|
50
|
-
exports.getBaseURL = getBaseURL;
|
|
51
|
-
exports.getOrigin = getOrigin;
|