diff --git a/packages/core/integration-tests/test/integration/formats/esm-external/child.js b/packages/core/integration-tests/test/integration/formats/esm-external/child.js new file mode 100644 index 00000000000..054f0642caf --- /dev/null +++ b/packages/core/integration-tests/test/integration/formats/esm-external/child.js @@ -0,0 +1 @@ +export {add} from 'lodash'; diff --git a/packages/core/integration-tests/test/integration/formats/esm-external/re-export-child.js b/packages/core/integration-tests/test/integration/formats/esm-external/re-export-child.js new file mode 100644 index 00000000000..3eaaf8b0c25 --- /dev/null +++ b/packages/core/integration-tests/test/integration/formats/esm-external/re-export-child.js @@ -0,0 +1 @@ +export {add} from './child'; diff --git a/packages/core/integration-tests/test/output-formats.js b/packages/core/integration-tests/test/output-formats.js index 01c20793e13..fed35fa4a01 100644 --- a/packages/core/integration-tests/test/output-formats.js +++ b/packages/core/integration-tests/test/output-formats.js @@ -725,6 +725,24 @@ describe('output formats', function () { ); }); + it('should support esmodule output with external modules (re-export child)', async function () { + let b = await bundle( + path.join( + __dirname, + '/integration/formats/esm-external/re-export-child.js', + ), + ); + + await assertESMExports( + b, + 3, + { + lodash: () => lodash, + }, + ns => ns.add(1, 2), + ); + }); + it('should support importing sibling bundles in library mode', async function () { let b = await bundle( path.join(__dirname, '/integration/formats/esm-siblings/a.js'), diff --git a/packages/packagers/js/src/ScopeHoistingPackager.js b/packages/packagers/js/src/ScopeHoistingPackager.js index 2e230767a4d..cd88c660a82 100644 --- a/packages/packagers/js/src/ScopeHoistingPackager.js +++ b/packages/packagers/js/src/ScopeHoistingPackager.js @@ -400,7 +400,15 @@ export class ScopeHoistingPackager { for (let dep of deps) { let resolved = this.bundleGraph.getResolvedAsset(dep, this.bundle); let skipped = this.bundleGraph.isDependencySkipped(dep); - if (!resolved || skipped) { + if (skipped) { + continue; + } + + if (!resolved) { + if (!dep.isOptional) { + this.addExternal(dep); + } + continue; } @@ -605,62 +613,7 @@ ${code} !dep.isOptional && !this.bundleGraph.isDependencySkipped(dep) ) { - let external = this.addExternal(dep); - for (let [imported, {local}] of dep.symbols) { - // If already imported, just add the already renamed variable to the mapping. - let renamed = external.get(imported); - if (renamed && local !== '*') { - replacements.set(local, renamed); - continue; - } - - // For CJS output, always use a property lookup so that exports remain live. - // For ESM output, use named imports which are always live. - if (this.bundle.env.outputFormat === 'commonjs') { - renamed = external.get('*'); - if (!renamed) { - renamed = this.getTopLevelName( - `$${this.bundle.publicId}$${dep.specifier}`, - ); - - external.set('*', renamed); - } - - if (local !== '*') { - let replacement; - if (imported === '*') { - replacement = renamed; - } else if (imported === 'default') { - replacement = `($parcel$interopDefault(${renamed}))`; - this.usedHelpers.add('$parcel$interopDefault'); - } else { - replacement = this.getPropertyAccess(renamed, imported); - } - - replacements.set(local, replacement); - } - } else { - // Rename the specifier so that multiple local imports of the same imported specifier - // are deduplicated. We have to prefix the imported name with the bundle id so that - // local variables do not shadow it. - if (this.exportedSymbols.has(local)) { - renamed = local; - } else if (imported === 'default' || imported === '*') { - renamed = this.getTopLevelName( - `$${this.bundle.publicId}$${dep.specifier}`, - ); - } else { - renamed = this.getTopLevelName( - `$${this.bundle.publicId}$${imported}`, - ); - } - - external.set(imported, renamed); - if (local !== '*') { - replacements.set(local, renamed); - } - } - } + this.addExternal(dep, replacements); } if (!resolved) { @@ -712,7 +665,7 @@ ${code} return [depMap, replacements]; } - addExternal(dep: Dependency): Map { + addExternal(dep: Dependency, replacements?: Map) { if (this.bundle.env.outputFormat === 'global') { throw new ThrowableDiagnostic({ diagnostic: { @@ -742,7 +695,61 @@ ${code} this.externals.set(dep.specifier, external); } - return external; + for (let [imported, {local}] of dep.symbols) { + // If already imported, just add the already renamed variable to the mapping. + let renamed = external.get(imported); + if (renamed && local !== '*' && replacements) { + replacements.set(local, renamed); + continue; + } + + // For CJS output, always use a property lookup so that exports remain live. + // For ESM output, use named imports which are always live. + if (this.bundle.env.outputFormat === 'commonjs') { + renamed = external.get('*'); + if (!renamed) { + renamed = this.getTopLevelName( + `$${this.bundle.publicId}$${dep.specifier}`, + ); + + external.set('*', renamed); + } + + if (local !== '*' && replacements) { + let replacement; + if (imported === '*') { + replacement = renamed; + } else if (imported === 'default') { + replacement = `($parcel$interopDefault(${renamed}))`; + this.usedHelpers.add('$parcel$interopDefault'); + } else { + replacement = this.getPropertyAccess(renamed, imported); + } + + replacements.set(local, replacement); + } + } else { + // Rename the specifier so that multiple local imports of the same imported specifier + // are deduplicated. We have to prefix the imported name with the bundle id so that + // local variables do not shadow it. + if (this.exportedSymbols.has(local)) { + renamed = local; + } else if (imported === 'default' || imported === '*') { + renamed = this.getTopLevelName( + `$${this.bundle.publicId}$${dep.specifier}`, + ); + } else { + renamed = this.getTopLevelName( + `$${this.bundle.publicId}$${imported}`, + ); + } + + external.set(imported, renamed); + if (local !== '*' && replacements) { + replacements.set(local, renamed); + } + } + } } getSymbolResolution(