diff --git a/packages/core/integration-tests/test/html.js b/packages/core/integration-tests/test/html.js index fcc7a239e97..07011f0efc3 100644 --- a/packages/core/integration-tests/test/html.js +++ b/packages/core/integration-tests/test/html.js @@ -1802,7 +1802,13 @@ describe('html', function () { }, { type: 'js', - assets: ['bundle-manifest.js', 'index.js', 'index.js', 'index.js'], + assets: [ + 'bundle-manifest.js', + 'esm-js-loader.js', + 'index.js', + 'index.js', + 'index.js', + ], }, { name: 'index.html', @@ -1883,6 +1889,7 @@ describe('html', function () { type: 'js', assets: [ 'bundle-manifest.js', + 'esm-js-loader.js', 'get-worker-url.js', 'index.js', 'lodash.js', @@ -1944,6 +1951,7 @@ describe('html', function () { type: 'js', assets: [ 'bundle-manifest.js', + 'esm-js-loader.js', 'get-worker-url.js', 'index.js', 'lodash.js', @@ -2183,7 +2191,7 @@ describe('html', function () { assets: ['index.html'], }, { - assets: ['a.js', 'bundle-manifest.js'], + assets: ['a.js', 'bundle-manifest.js', 'esm-js-loader.js'], }, { assets: [ @@ -2311,6 +2319,7 @@ describe('html', function () { 'index.js', 'client.js', 'bundle-manifest.js', + 'esm-js-loader.js', ], }, { diff --git a/packages/core/integration-tests/test/javascript.js b/packages/core/integration-tests/test/javascript.js index 8a3db3ec077..c1572ea9db9 100644 --- a/packages/core/integration-tests/test/javascript.js +++ b/packages/core/integration-tests/test/javascript.js @@ -2191,6 +2191,7 @@ describe('javascript', function () { { assets: [ 'bundle-manifest.js', + 'esm-js-loader.js', 'get-worker-url.js', 'index.js', 'large.js', diff --git a/packages/core/integration-tests/test/output-formats.js b/packages/core/integration-tests/test/output-formats.js index 07a126c6d4b..ab9601b61c7 100644 --- a/packages/core/integration-tests/test/output-formats.js +++ b/packages/core/integration-tests/test/output-formats.js @@ -1149,11 +1149,24 @@ describe('output formats', function () { ); let async2Bundle = bundles.find(b => b.name.startsWith('async2')); + let esmLoaderPublicId; + b.traverse((node, _, actions) => { + if ( + node.type === 'asset' && + node.value.filePath.endsWith('esm-js-loader.js') + ) { + esmLoaderPublicId = b.getAssetPublicId(node.value); + actions.stop(); + } + }); + + assert(esmLoaderPublicId != null, 'Could not find esm loader public id'); + for (let bundle of [async1Bundle, async2Bundle]) { // async import both bundles in parallel for performance assert( new RegExp( - `import\\("\\./" \\+ .+\\.resolve\\("${sharedBundle.publicId}"\\)\\),\\n\\s*import\\("./" \\+ .+\\.resolve\\("${bundle.publicId}"\\)\\)`, + `\\$${esmLoaderPublicId}\\("${sharedBundle.publicId}"\\),\\n\\s*\\$${esmLoaderPublicId}\\("${bundle.publicId}"\\)`, ).test(entry), ); } diff --git a/packages/runtimes/js/src/JSRuntime.js b/packages/runtimes/js/src/JSRuntime.js index b2ec4e57817..130c737754b 100644 --- a/packages/runtimes/js/src/JSRuntime.js +++ b/packages/runtimes/js/src/JSRuntime.js @@ -376,46 +376,72 @@ function getLoaderRuntime({ let needsDynamicImportPolyfill = !bundle.env.isLibrary && !bundle.env.supports('dynamic-import', true); - let loaderModules = externalBundles - .map(to => { - let loader = loaders[to.type]; - if (!loader) { - return; - } + let needsEsmLoadPrelude = false; + let loaderModules = []; - let relativePathExpr = getRelativePathExpr(bundle, to, options); + for (let to of externalBundles) { + let loader = loaders[to.type]; + if (!loader) { + continue; + } - // Use esmodule loader if possible - if (to.type === 'js' && to.env.outputFormat === 'esmodule') { - if (!needsDynamicImportPolyfill) { - return `__parcel__import__("./" + ${relativePathExpr})`; - } + if ( + to.type === 'js' && + to.env.outputFormat === 'esmodule' && + !needsDynamicImportPolyfill && + shouldUseRuntimeManifest(bundle, options) + ) { + let params = [JSON.stringify(to.publicId)]; - loader = nullthrows( - loaders.IMPORT_POLYFILL, - `No import() polyfill available for context '${bundle.env.context}'`, - ); - } else if (to.type === 'js' && to.env.outputFormat === 'commonjs') { - return `Promise.resolve(__parcel__require__("./" + ${relativePathExpr}))`; + let relativeBase = getRelativeBasePath( + relativeBundlePath(bundle, to, {leadingDotSlash: false}), + ); + + if (relativeBase) { + params.push(relativeBase); } - let code = `require(${JSON.stringify(loader)})(${getAbsoluteUrlExpr( - relativePathExpr, - bundle, - )})`; + loaderModules.push(`load(${params.join(',')})`); + needsEsmLoadPrelude = true; + continue; + } - // In development, clear the require cache when an error occurs so the - // user can try again (e.g. after fixing a build error). - if ( - options.mode === 'development' && - bundle.env.outputFormat === 'global' - ) { - code += - '.catch(err => {delete module.bundle.cache[module.id]; throw err;})'; + let relativePathExpr = getRelativePathExpr(bundle, to, options); + + // Use esmodule loader if possible + if (to.type === 'js' && to.env.outputFormat === 'esmodule') { + if (!needsDynamicImportPolyfill) { + loaderModules.push(`__parcel__import__("./" + ${relativePathExpr})`); + continue; } - return code; - }) - .filter(Boolean); + + loader = nullthrows( + loaders.IMPORT_POLYFILL, + `No import() polyfill available for context '${bundle.env.context}'`, + ); + } else if (to.type === 'js' && to.env.outputFormat === 'commonjs') { + loaderModules.push( + `Promise.resolve(__parcel__require__("./" + ${relativePathExpr}))`, + ); + continue; + } + + let code = `require(${JSON.stringify(loader)})(${getAbsoluteUrlExpr( + relativePathExpr, + bundle, + )})`; + + // In development, clear the require cache when an error occurs so the + // user can try again (e.g. after fixing a build error). + if ( + options.mode === 'development' && + bundle.env.outputFormat === 'global' + ) { + code += + '.catch(err => {delete module.bundle.cache[module.id]; throw err;})'; + } + loaderModules.push(code); + } if (bundle.env.context === 'browser' && !options.shouldBuildLazily) { loaderModules.push( @@ -465,9 +491,17 @@ function getLoaderRuntime({ )}'))`; } + let code = []; + + if (needsEsmLoadPrelude) { + code.push(`let load = require('./helpers/browser/esm-js-loader');`); + } + + code.push(`module.exports = ${loaderCode};`); + return { filePath: __filename, - code: `module.exports = ${loaderCode};`, + code: code.join('\n'), dependency, env: {sourceType: 'module'}, }; @@ -623,6 +657,15 @@ function getRegisterCode( ); } +function getRelativeBasePath(relativePath: string) { + // Get the relative part of the path. This part is not in the manifest, only the basename is. + let relativeBase = path.posix.dirname(relativePath); + if (relativeBase === '.') { + return ''; + } + return JSON.stringify(relativeBase + '/'); +} + function getRelativePathExpr( from: NamedBundle, to: NamedBundle, @@ -630,19 +673,15 @@ function getRelativePathExpr( ): string { let relativePath = relativeBundlePath(from, to, {leadingDotSlash: false}); if (shouldUseRuntimeManifest(from, options)) { - // Get the relative part of the path. This part is not in the manifest, only the basename is. - let relativeBase = path.posix.dirname(relativePath); - if (relativeBase === '.') { - relativeBase = ''; - } else { - relativeBase = `${JSON.stringify(relativeBase + '/')} + `; + let basePath = getRelativeBasePath(relativePath); + let resolvedPath = `require('./helpers/bundle-manifest').resolve(${JSON.stringify( + to.publicId, + )})`; + + if (basePath) { + return `${basePath} + ${resolvedPath}`; } - return ( - relativeBase + - `require('./helpers/bundle-manifest').resolve(${JSON.stringify( - to.publicId, - )})` - ); + return resolvedPath; } let res = JSON.stringify(relativePath); diff --git a/packages/runtimes/js/src/helpers/browser/esm-js-loader.js b/packages/runtimes/js/src/helpers/browser/esm-js-loader.js new file mode 100644 index 00000000000..729d419247c --- /dev/null +++ b/packages/runtimes/js/src/helpers/browser/esm-js-loader.js @@ -0,0 +1,9 @@ +function load(id, base) { + let bundleId = require('../bundle-manifest').resolve(id); + let request = base ? './' + base + bundleId : './' + bundleId; + + // eslint-disable-next-line no-undef + return __parcel__import__(request); +} + +module.exports = load;