Skip to content

Commit 4246048

Browse files
committed
Support ESM externals and exports in dev packager
1 parent 6e9a485 commit 4246048

File tree

5 files changed

+140
-4
lines changed

5 files changed

+140
-4
lines changed

packages/core/integration-tests/test/javascript.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6211,4 +6211,42 @@ describe('javascript', function () {
62116211
},
62126212
);
62136213
}
6214+
6215+
it('should support ESM externals and exports in development mode', async () => {
6216+
await fsFixture(overlayFS, __dirname)`
6217+
esm-externals
6218+
index.js:
6219+
import {createHash} from 'crypto';
6220+
let hash = createHash('md5');
6221+
hash.update('testing');
6222+
export const hashed = hash.digest('hex');
6223+
export default "Test";
6224+
6225+
package.json:
6226+
{
6227+
"targets": {
6228+
"default": {
6229+
"context": "node",
6230+
"outputFormat": "esmodule"
6231+
}
6232+
}
6233+
}
6234+
6235+
yarn.lock:`;
6236+
6237+
let b = await bundle(path.join(__dirname, 'esm-externals/index.js'), {
6238+
inputFS: overlayFS,
6239+
});
6240+
6241+
let res = await run(
6242+
b,
6243+
{},
6244+
{},
6245+
{
6246+
crypto: () => require('crypto'),
6247+
},
6248+
);
6249+
assert.equal(res.hashed, 'ae2b1fca515949e5d54fb22b8ed95575');
6250+
assert.equal(res.default, 'Test');
6251+
});
62146252
});

packages/packagers/js/src/DevPackager.js

Lines changed: 90 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,12 @@ import SourceMap from '@parcel/source-map';
1212
import invariant from 'assert';
1313
import path from 'path';
1414
import fs from 'fs';
15-
import {replaceScriptDependencies, getSpecifier} from './utils';
15+
import {
16+
replaceScriptDependencies,
17+
getSpecifier,
18+
makeValidIdentifier,
19+
isValidIdentifier,
20+
} from './utils';
1621
import {helpers} from './helpers';
1722

1823
const PRELUDE = fs
@@ -61,6 +66,7 @@ export class DevPackager {
6166
let prefix = this.getPrefix();
6267
let lineOffset = countLines(prefix);
6368
let script: ?{|code: string, mapBuffer: ?Buffer|} = null;
69+
let externals = new Set();
6470

6571
let usedHelpers = 0;
6672
this.bundle.traverse(node => {
@@ -116,6 +122,9 @@ export class DevPackager {
116122
} else {
117123
// An external module - map placeholder to original specifier.
118124
deps[specifier] = dep.specifier;
125+
if (this.bundle.env.outputFormat === 'esmodule') {
126+
externals.add(dep.specifier);
127+
}
119128
}
120129
}
121130

@@ -209,7 +218,20 @@ export class DevPackager {
209218
prefix = prefix.replace('// INSERT_LOAD_HERE', load.replace(/\n/g, ''));
210219
}
211220

221+
let externalImports = '';
222+
let externalMap = '{';
223+
let e = 0;
224+
for (let external of externals) {
225+
let name = `__parcelExternal${e++}`;
226+
externalImports += `import * as ${name} from ${JSON.stringify(
227+
external,
228+
)};\n`;
229+
externalMap += `${JSON.stringify(external)}: ${name},`;
230+
}
231+
externalMap += '}';
232+
212233
let contents =
234+
externalImports +
213235
prefix +
214236
'({' +
215237
assets +
@@ -222,7 +244,9 @@ export class DevPackager {
222244
mainEntry ? this.bundleGraph.getAssetPublicId(mainEntry) : null,
223245
) +
224246
', ' +
225-
JSON.stringify(this.parcelRequireName);
247+
JSON.stringify(this.parcelRequireName) +
248+
', ' +
249+
externalMap;
226250

227251
if (usedHelpers & 1) {
228252
// Generate a relative path from this bundle to the root of the dist dir.
@@ -258,6 +282,70 @@ export class DevPackager {
258282

259283
contents += ')\n';
260284

285+
// Add ES module exports from the entry asset.
286+
if (
287+
this.bundle.env.outputFormat === 'esmodule' &&
288+
mainEntry &&
289+
(this.bundle.env.isLibrary || !this.bundle.env.isBrowser())
290+
) {
291+
let hasNamespace = mainEntry.symbols.hasExportSymbol('*');
292+
let importedSymbols = new Map();
293+
let exportedSymbols = new Map();
294+
for (let {
295+
exportAs,
296+
symbol,
297+
exportSymbol,
298+
} of this.bundleGraph.getExportedSymbols(mainEntry)) {
299+
if (typeof symbol === 'string') {
300+
if (hasNamespace && exportAs !== '*') {
301+
continue;
302+
}
303+
304+
if (exportAs === '*') {
305+
exportAs = 'default';
306+
}
307+
308+
let id = makeValidIdentifier(exportSymbol);
309+
if (id === 'default') {
310+
id = '_default';
311+
}
312+
importedSymbols.set(exportSymbol, id);
313+
exportedSymbols.set(exportAs, id);
314+
}
315+
}
316+
317+
contents += 'let {';
318+
for (let [key, value] of importedSymbols) {
319+
contents += isValidIdentifier(key) ? key : JSON.stringify(key);
320+
if (value !== key) {
321+
contents += ': ';
322+
contents += value;
323+
}
324+
contents += ', ';
325+
}
326+
327+
contents +=
328+
'} = ' +
329+
this.parcelRequireName +
330+
'(' +
331+
JSON.stringify(this.bundleGraph.getAssetPublicId(mainEntry)) +
332+
');\n';
333+
contents += 'export {';
334+
335+
for (let [exportAs, ident] of exportedSymbols) {
336+
contents += ident;
337+
if (exportAs !== ident) {
338+
contents += ' as ';
339+
contents += isValidIdentifier(exportAs)
340+
? exportAs
341+
: JSON.stringify(exportAs);
342+
}
343+
contents += ', ';
344+
}
345+
346+
contents += '};\n';
347+
}
348+
261349
// The entry asset of a script bundle gets hoisted outside the bundle wrapper function
262350
// so that its variables become globals. We need to replace any require calls for
263351
// runtimes with a parcelRequire call.

packages/packagers/js/src/ScopeHoistingPackager.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -363,7 +363,7 @@ export class ScopeHoistingPackager {
363363

364364
buildExportedSymbols() {
365365
if (
366-
!this.bundle.env.isLibrary ||
366+
(!this.bundle.env.isLibrary && this.bundle.env.isBrowser()) ||
367367
this.bundle.env.outputFormat !== 'esmodule'
368368
) {
369369
return;

packages/packagers/js/src/dev-prelude.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
entry,
1212
mainEntry,
1313
parcelRequireName,
14+
externals,
1415
distDir,
1516
publicUrl,
1617
devServer
@@ -44,6 +45,9 @@
4445
function newRequire(name, jumped) {
4546
if (!cache[name]) {
4647
if (!modules[name]) {
48+
if (externals[name]) {
49+
return externals[name];
50+
}
4751
// if we cannot find the module within our internal map or
4852
// cache jump to the current global require ie. the last bundle
4953
// that was added to the page.

packages/runtimes/hmr/src/loaders/hmr-runtime.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,13 @@ if (!parent || !parent.isParcelRequire) {
164164
protocol + '://' + hostname + (port ? ':' + port : '') + '/',
165165
);
166166
} catch (err) {
167-
if (err.message) {
167+
// Ignore cloudflare workers error.
168+
if (
169+
err.message &&
170+
!err.message.includes(
171+
'Disallowed operation called within global scope',
172+
)
173+
) {
168174
console.error(err.message);
169175
}
170176
}

0 commit comments

Comments
 (0)