@atlaspack/runtime-js 2.15.0 → 2.15.1

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 CHANGED
@@ -1,5 +1,16 @@
1
1
  # @atlaspack/runtime-js
2
2
 
3
+ ## 2.15.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [#692](https://github.com/atlassian-labs/atlaspack/pull/692) [`13aef17`](https://github.com/atlassian-labs/atlaspack/commit/13aef177eea289a6e40d2113b5ec1ac9be18a33d) Thanks [@JakeLane](https://github.com/JakeLane)! - Add fallback behaviour when conditional bundle is missing
8
+
9
+ - Updated dependencies [[`13aef17`](https://github.com/atlassian-labs/atlaspack/commit/13aef177eea289a6e40d2113b5ec1ac9be18a33d)]:
10
+ - @atlaspack/feature-flags@2.19.1
11
+ - @atlaspack/utils@2.17.1
12
+ - @atlaspack/plugin@2.14.19
13
+
3
14
  ## 2.15.0
4
15
 
5
16
  ### Minor Changes
package/lib/JSRuntime.js CHANGED
@@ -208,9 +208,19 @@ var _default = exports.default = new (_plugin().Runtime)({
208
208
  const conditions = bundleGraph.getConditionsForDependencies(conditionalDependencies, bundle);
209
209
  for (const cond of conditions) {
210
210
  const requireName = (0, _featureFlags().getFeatureFlag)('hmrImprovements') || bundle.env.shouldScopeHoist ? 'parcelRequire' : '__parcel__require__';
211
- const assetCode = `module.exports = require('../helpers/conditional-loader${options.mode === 'development' ? '-dev' : ''}')('${cond.key}', function (){return ${requireName}('${cond.ifTrueAssetId}')}, function (){return ${requireName}('${cond.ifFalseAssetId}')})`;
211
+ const fallbackUrls = cond => {
212
+ return `[${[...cond.ifTrueBundles, ...cond.ifFalseBundles].map(target => {
213
+ let relativePathExpr = getRelativePathExpr(bundle, target, options);
214
+ return getAbsoluteUrlExpr(relativePathExpr, bundle, config.domainSharding);
215
+ }).join(',')}]`;
216
+ };
217
+ const shouldUseFallback = options.mode === 'development' ? (0, _featureFlags().getFeatureFlag)('condbDevFallbackDev') : (0, _featureFlags().getFeatureFlag)('condbDevFallbackProd');
218
+ const loaderPath = `./helpers/conditional-loader${options.mode === 'development' ? '-dev' : ''}`;
219
+ const ifTrue = `function (){return ${requireName}('${cond.ifTrueAssetId}')}`;
220
+ const ifFalse = `function (){return ${requireName}('${cond.ifFalseAssetId}')}`;
221
+ const assetCode = `module.exports = require('${loaderPath}')('${cond.key}', ${ifTrue}, ${ifFalse}${shouldUseFallback ? `, {loader: require('./helpers/browser/sync-js-loader'), urls: ${fallbackUrls(cond)}}` : ''})`;
212
222
  assets.push({
213
- filePath: _path().default.join(__dirname, `/conditions/${cond.publicId}.js`),
223
+ filePath: _path().default.join(__dirname, `/conditions-${cond.publicId}.js`),
214
224
  code: assetCode,
215
225
  // This dependency is important, as it's the last symbol handled in scope hoisting.
216
226
  // That means that scope hoisting will use the module id for this asset to replace the symbol
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+
3
+ const cacheLoader = require('../cacheLoader');
4
+ module.exports = function loadJSBundle(bundle) {
5
+ // Don't insert the same script twice (e.g. if it was already in the HTML)
6
+ let existingScripts = document.getElementsByTagName('script');
7
+ let isCurrentBundle = function (script) {
8
+ return script.src === bundle;
9
+ };
10
+ if ([].concat(existingScripts).some(isCurrentBundle)) {
11
+ return;
12
+ }
13
+
14
+ // Request using XHR because it's synchronous and we can't use promises here
15
+ // This has extremely poor performance because we're idle during this fetch, so we only use this so that the app won't crash
16
+ const xhr = new XMLHttpRequest();
17
+ xhr.open('GET', bundle, false);
18
+ try {
19
+ xhr.send();
20
+ if (xhr.status === 200) {
21
+ const script = document.createElement('script');
22
+ script.type = 'text/javascript';
23
+ script.text = xhr.responseText;
24
+
25
+ // Execute the script synchronously
26
+ document.head.appendChild(script);
27
+ } else {
28
+ throw new TypeError(`Failed to fetch dynamically imported module: ${bundle}. Status: ${xhr.status}`);
29
+ }
30
+ } catch (e) {
31
+ throw new TypeError(`Failed to fetch dynamically imported module: ${bundle}. Error: ${e.message}`);
32
+ }
33
+ };
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
 
3
- module.exports = function loadCond(cond, ifTrue, ifFalse) {
3
+ module.exports = function loadCond(cond, ifTrue, ifFalse, fallback) {
4
4
  if (typeof globalThis.__MCOND !== 'function') {
5
5
  throw new TypeError('"globalThis.__MCOND" was not set to an object. Ensure the function is set to return the key condition for conditional bundles to load with.');
6
6
  }
@@ -10,7 +10,14 @@ module.exports = function loadCond(cond, ifTrue, ifFalse) {
10
10
  try {
11
11
  return globalThis.__MCOND(cond) ? ifTrue() : ifFalse();
12
12
  } catch (err) {
13
- console.error('Conditional dependency was missing. Ensure the server sends the correct scripts to the client ("conditional-manifest.json").');
14
- throw err;
13
+ console.error('Conditional dependency was not registered when executing. Ensure the server sends the correct scripts to the client. Falling back to synchronous bundle loading.');
14
+ if (fallback) {
15
+ for (const url of fallback.urls) {
16
+ fallback.loader(url);
17
+ }
18
+ return globalThis.__MCOND(cond) ? ifTrue() : ifFalse();
19
+ } else {
20
+ throw new Error('No fallback urls specified, cannot fallback safely');
21
+ }
15
22
  }
16
23
  };
@@ -1,5 +1,18 @@
1
1
  "use strict";
2
2
 
3
- module.exports = function loadCond(cond, ifTrue, ifFalse) {
4
- return globalThis.__MCOND && globalThis.__MCOND(cond) ? ifTrue() : ifFalse();
3
+ module.exports = function loadCond(cond, ifTrue, ifFalse, fallback) {
4
+ let result = globalThis.__MCOND(cond);
5
+ try {
6
+ return result ? ifTrue() : ifFalse();
7
+ } catch (err) {
8
+ if (fallback) {
9
+ console.error('Conditional dependency was not registered when executing. Falling back to synchronous bundle loading.');
10
+ for (const url of fallback.urls) {
11
+ fallback.loader(url);
12
+ }
13
+ return result ? ifTrue() : ifFalse();
14
+ } else {
15
+ throw err;
16
+ }
17
+ }
5
18
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaspack/runtime-js",
3
- "version": "2.15.0",
3
+ "version": "2.15.1",
4
4
  "license": "(MIT OR Apache-2.0)",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -17,9 +17,9 @@
17
17
  "dependencies": {
18
18
  "@atlaspack/diagnostic": "2.14.1",
19
19
  "@atlaspack/domain-sharding": "2.14.1",
20
- "@atlaspack/feature-flags": "2.19.0",
21
- "@atlaspack/plugin": "2.14.18",
22
- "@atlaspack/utils": "2.17.0",
20
+ "@atlaspack/feature-flags": "2.19.1",
21
+ "@atlaspack/plugin": "2.14.19",
22
+ "@atlaspack/utils": "2.17.1",
23
23
  "nullthrows": "^1.1.1"
24
24
  },
25
25
  "type": "commonjs"
package/src/JSRuntime.js CHANGED
@@ -221,14 +221,47 @@ export default (new Runtime({
221
221
  ? 'parcelRequire'
222
222
  : '__parcel__require__';
223
223
 
224
- const assetCode = `module.exports = require('../helpers/conditional-loader${
224
+ const fallbackUrls = (cond) => {
225
+ return `[${[...cond.ifTrueBundles, ...cond.ifFalseBundles]
226
+ .map((target) => {
227
+ let relativePathExpr = getRelativePathExpr(
228
+ bundle,
229
+ target,
230
+ options,
231
+ );
232
+ return getAbsoluteUrlExpr(
233
+ relativePathExpr,
234
+ bundle,
235
+ config.domainSharding,
236
+ );
237
+ })
238
+ .join(',')}]`;
239
+ };
240
+
241
+ const shouldUseFallback =
242
+ options.mode === 'development'
243
+ ? getFeatureFlag('condbDevFallbackDev')
244
+ : getFeatureFlag('condbDevFallbackProd');
245
+
246
+ const loaderPath = `./helpers/conditional-loader${
225
247
  options.mode === 'development' ? '-dev' : ''
226
- }')('${cond.key}', function (){return ${requireName}('${
227
- cond.ifTrueAssetId
228
- }')}, function (){return ${requireName}('${cond.ifFalseAssetId}')})`;
248
+ }`;
249
+
250
+ const ifTrue = `function (){return ${requireName}('${cond.ifTrueAssetId}')}`;
251
+ const ifFalse = `function (){return ${requireName}('${cond.ifFalseAssetId}')}`;
252
+
253
+ const assetCode = `module.exports = require('${loaderPath}')('${
254
+ cond.key
255
+ }', ${ifTrue}, ${ifFalse}${
256
+ shouldUseFallback
257
+ ? `, {loader: require('./helpers/browser/sync-js-loader'), urls: ${fallbackUrls(
258
+ cond,
259
+ )}}`
260
+ : ''
261
+ })`;
229
262
 
230
263
  assets.push({
231
- filePath: path.join(__dirname, `/conditions/${cond.publicId}.js`),
264
+ filePath: path.join(__dirname, `/conditions-${cond.publicId}.js`),
232
265
  code: assetCode,
233
266
  // This dependency is important, as it's the last symbol handled in scope hoisting.
234
267
  // That means that scope hoisting will use the module id for this asset to replace the symbol
@@ -0,0 +1,39 @@
1
+ const cacheLoader = require('../cacheLoader');
2
+
3
+ module.exports = function loadJSBundle(bundle) {
4
+ // Don't insert the same script twice (e.g. if it was already in the HTML)
5
+ let existingScripts = document.getElementsByTagName('script');
6
+ let isCurrentBundle = function (script) {
7
+ return script.src === bundle;
8
+ };
9
+
10
+ if ([].concat(existingScripts).some(isCurrentBundle)) {
11
+ return;
12
+ }
13
+
14
+ // Request using XHR because it's synchronous and we can't use promises here
15
+ // This has extremely poor performance because we're idle during this fetch, so we only use this so that the app won't crash
16
+ const xhr = new XMLHttpRequest();
17
+ xhr.open('GET', bundle, false);
18
+
19
+ try {
20
+ xhr.send();
21
+
22
+ if (xhr.status === 200) {
23
+ const script = document.createElement('script');
24
+ script.type = 'text/javascript';
25
+ script.text = xhr.responseText;
26
+
27
+ // Execute the script synchronously
28
+ document.head.appendChild(script);
29
+ } else {
30
+ throw new TypeError(
31
+ `Failed to fetch dynamically imported module: ${bundle}. Status: ${xhr.status}`,
32
+ );
33
+ }
34
+ } catch (e) {
35
+ throw new TypeError(
36
+ `Failed to fetch dynamically imported module: ${bundle}. Error: ${e.message}`,
37
+ );
38
+ }
39
+ };
@@ -1,4 +1,4 @@
1
- module.exports = function loadCond(cond, ifTrue, ifFalse) {
1
+ module.exports = function loadCond(cond, ifTrue, ifFalse, fallback) {
2
2
  if (typeof globalThis.__MCOND !== 'function') {
3
3
  throw new TypeError(
4
4
  '"globalThis.__MCOND" was not set to an object. Ensure the function is set to return the key condition for conditional bundles to load with.',
@@ -15,9 +15,17 @@ module.exports = function loadCond(cond, ifTrue, ifFalse) {
15
15
  return globalThis.__MCOND(cond) ? ifTrue() : ifFalse();
16
16
  } catch (err) {
17
17
  console.error(
18
- 'Conditional dependency was missing. Ensure the server sends the correct scripts to the client ("conditional-manifest.json").',
18
+ 'Conditional dependency was not registered when executing. Ensure the server sends the correct scripts to the client. Falling back to synchronous bundle loading.',
19
19
  );
20
20
 
21
- throw err;
21
+ if (fallback) {
22
+ for (const url of fallback.urls) {
23
+ fallback.loader(url);
24
+ }
25
+
26
+ return globalThis.__MCOND(cond) ? ifTrue() : ifFalse();
27
+ } else {
28
+ throw new Error('No fallback urls specified, cannot fallback safely');
29
+ }
22
30
  }
23
31
  };
@@ -1,3 +1,19 @@
1
- module.exports = function loadCond(cond, ifTrue, ifFalse) {
2
- return globalThis.__MCOND && globalThis.__MCOND(cond) ? ifTrue() : ifFalse();
1
+ module.exports = function loadCond(cond, ifTrue, ifFalse, fallback) {
2
+ let result = globalThis.__MCOND(cond);
3
+ try {
4
+ return result ? ifTrue() : ifFalse();
5
+ } catch (err) {
6
+ if (fallback) {
7
+ console.error(
8
+ 'Conditional dependency was not registered when executing. Falling back to synchronous bundle loading.',
9
+ );
10
+ for (const url of fallback.urls) {
11
+ fallback.loader(url);
12
+ }
13
+
14
+ return result ? ifTrue() : ifFalse();
15
+ } else {
16
+ throw err;
17
+ }
18
+ }
3
19
  };