Skip to content

Commit 8fc0c40

Browse files
committed
Update test utils to handle loading multiple CJS bundles
1 parent 03b265f commit 8fc0c40

File tree

2 files changed

+131
-88
lines changed

2 files changed

+131
-88
lines changed

packages/core/integration-tests/test/output-formats.js

+11-5
Original file line numberDiff line numberDiff line change
@@ -552,12 +552,18 @@ describe('output formats', function () {
552552
};
553553

554554
let out = [];
555-
await run(b, {
556-
require: () => external,
557-
output(o) {
558-
out.push(o);
555+
await run(
556+
b,
557+
{
558+
output(o) {
559+
out.push(o);
560+
},
559561
},
560-
});
562+
{},
563+
{
564+
external: () => external,
565+
},
566+
);
561567

562568
assert.deepEqual(out, [1, 2]);
563569
});

packages/core/test-utils/src/utils.js

+120-83
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,11 @@ import EventEmitter from 'events';
3131
import http from 'http';
3232
import https from 'https';
3333

34-
import {makeDeferredWithPromise, normalizeSeparators} from '@parcel/utils';
34+
import {
35+
makeDeferredWithPromise,
36+
normalizeSeparators,
37+
loadConfig,
38+
} from '@parcel/utils';
3539
import _chalk from 'chalk';
3640
import resolve from 'resolve';
3741

@@ -303,6 +307,8 @@ export async function runBundles(
303307
let target = env.context;
304308
let outputFormat = env.outputFormat;
305309

310+
nodeCache.clear();
311+
306312
let ctx, promises;
307313
switch (target) {
308314
case 'browser':
@@ -315,23 +321,11 @@ export async function runBundles(
315321
case 'node':
316322
case 'electron-main':
317323
case 'react-server':
318-
nodeCache.clear();
319-
ctx = prepareNodeContext(
320-
outputFormat === 'commonjs' && parent.filePath,
321-
globals,
322-
undefined,
323-
externalModules,
324-
);
324+
ctx = prepareNodeContext(globals);
325325
break;
326326
case 'electron-renderer': {
327-
nodeCache.clear();
328327
let prepared = prepareBrowserContext(parent, globals);
329-
prepareNodeContext(
330-
outputFormat === 'commonjs' && parent.filePath,
331-
globals,
332-
prepared.ctx,
333-
externalModules,
334-
);
328+
prepareNodeContext(globals, prepared.ctx);
335329
ctx = prepared.ctx;
336330
promises = prepared.promises;
337331
break;
@@ -357,6 +351,7 @@ export async function runBundles(
357351

358352
vm.createContext(ctx);
359353
let esmOutput;
354+
let cjsOutput;
360355
if (outputFormat === 'esmodule') {
361356
let res = await runESM(
362357
bundles[0][1].target.distDir,
@@ -371,9 +366,21 @@ export async function runBundles(
371366
);
372367

373368
esmOutput = bundles.length === 1 ? res[0] : res;
369+
} else if (outputFormat === 'commonjs' || env.isNode()) {
370+
for (let [code, b] of bundles) {
371+
let res = runModule(
372+
b.target.distDir,
373+
ctx,
374+
b.filePath,
375+
code,
376+
externalModules,
377+
opts.strict ?? false,
378+
);
379+
cjsOutput ??= res;
380+
}
374381
} else {
375382
for (let [code, b] of bundles) {
376-
// require, parcelRequire was set up in prepare*Context
383+
// parcelRequire was set up in prepare*Context
377384
new vm.Script((opts.strict ? '"use strict";\n' : '') + code, {
378385
filename:
379386
b.bundleBehavior === 'inline'
@@ -395,6 +402,7 @@ export async function runBundles(
395402
}).runInContext(ctx);
396403
}
397404
}
405+
398406
if (promises) {
399407
// await any ongoing dynamic imports during the run
400408
await Promise.all(promises);
@@ -413,8 +421,7 @@ export async function runBundles(
413421
case 'global':
414422
return typeof ctx.output !== 'undefined' ? ctx.output : undefined;
415423
case 'commonjs':
416-
invariant(typeof ctx.module === 'object' && ctx.module != null);
417-
return ctx.module.exports;
424+
return nullthrows(cjsOutput);
418425
case 'esmodule':
419426
return esmOutput;
420427
default:
@@ -525,14 +532,16 @@ export function assertBundles(
525532
return;
526533
}
527534

528-
if (
529-
opts?.skipNodeModules &&
530-
/node_modules|esmodule-helpers.js/.test(asset.filePath)
531-
) {
535+
if (opts?.skipNodeModules && /node_modules/.test(asset.filePath)) {
532536
return;
533537
}
534538

535-
if (opts?.skipHelpers && /esmodule-helpers.js/.test(asset.filePath)) {
539+
if (
540+
(opts?.skipHelpers || opts?.skipNodeModules) &&
541+
/esmodule-helpers.js|jsx-dev-runtime.js|jsx-runtime.js|rsc-helpers.js/.test(
542+
asset.filePath,
543+
)
544+
) {
536545
return;
537546
}
538547

@@ -683,8 +692,6 @@ function prepareBrowserContext(
683692
currentScript: null,
684693
};
685694

686-
var exports = {};
687-
688695
function PatchedError(message) {
689696
const patchedError = new Error(message);
690697
const stackStart = patchedError.stack.match(/at (new )?Error/)?.index;
@@ -721,8 +728,6 @@ function prepareBrowserContext(
721728
var ctx = Object.assign(
722729
{
723730
Error: PatchedError,
724-
exports,
725-
module: {exports},
726731
document: fakeDocument,
727732
WebSocket,
728733
TextEncoder,
@@ -819,11 +824,8 @@ function prepareWorkerContext(
819824
|} {
820825
let promises = [];
821826

822-
var exports = {};
823827
var ctx = Object.assign(
824828
{
825-
exports,
826-
module: {exports},
827829
WebSocket,
828830
console,
829831
TextEncoder,
@@ -878,15 +880,83 @@ function prepareWorkerContext(
878880
}
879881

880882
const nodeCache = new Map();
881-
// no filepath = ESM
882883
function prepareNodeContext(
883-
filePath,
884884
globals,
885885
// $FlowFixMe
886886
ctx: any = {},
887+
) {
888+
ctx.console = console;
889+
ctx.process = process;
890+
ctx.setTimeout = setTimeout;
891+
ctx.setImmediate = setImmediate;
892+
ctx.global = ctx;
893+
ctx.URL = URL;
894+
ctx.TextEncoder = TextEncoder;
895+
ctx.TextDecoder = TextDecoder;
896+
Object.assign(ctx, globals);
897+
return ctx;
898+
}
899+
900+
function runModule(
901+
distDir: string,
902+
// $FlowFixMe
903+
ctx: any,
904+
filePath: string,
905+
code: string,
887906
externalModules?: ExternalModules,
907+
strict: boolean,
888908
) {
889-
let exports = {};
909+
let cached = nodeCache.get(filePath);
910+
if (cached) {
911+
return cached.exports;
912+
}
913+
914+
let f = vm.compileFunction(
915+
(strict ? '"use strict";\n' : '') + code,
916+
['module', 'exports', '__filename', '__dirname', 'require'],
917+
{
918+
filename: path.basename(filePath),
919+
parsingContext: ctx,
920+
async importModuleDynamically(specifier) {
921+
let resolved = path.resolve(path.dirname(filePath), specifier);
922+
let code = await overlayFS.readFile(resolved, 'utf8');
923+
if (path.extname(resolved) === '.mjs' || (await isESM(resolved))) {
924+
let modules = await runESM(
925+
distDir,
926+
[[code, filePath]],
927+
ctx,
928+
overlayFS,
929+
externalModules,
930+
true,
931+
);
932+
return modules[0];
933+
} else {
934+
let mod = runModule(
935+
distDir,
936+
ctx,
937+
resolved,
938+
code,
939+
externalModules,
940+
strict,
941+
);
942+
// $FlowFixMe Experimental
943+
let m = new vm.SyntheticModule(
944+
Object.keys(mod),
945+
function () {
946+
for (let [k, v] of Object.entries(mod)) {
947+
this.setExport(k, v);
948+
}
949+
},
950+
{identifier: resolved, context: ctx},
951+
);
952+
await m.link(() => {});
953+
await m.evaluate();
954+
return m;
955+
}
956+
},
957+
},
958+
);
959+
890960
let req =
891961
filePath &&
892962
(specifier => {
@@ -950,59 +1020,26 @@ function prepareNodeContext(
9501020
return require(res);
9511021
}
9521022

953-
let cached = nodeCache.get(res);
954-
if (cached) {
955-
return cached.module.exports;
956-
}
957-
958-
let g = {
959-
...globals,
960-
};
961-
962-
for (let key in ctx) {
963-
if (
964-
key !== 'module' &&
965-
key !== 'exports' &&
966-
key !== '__filename' &&
967-
key !== '__dirname' &&
968-
key !== 'require'
969-
) {
970-
g[key] = ctx[key];
971-
}
972-
}
973-
974-
let childCtx = prepareNodeContext(res, g);
975-
nodeCache.set(res, childCtx);
976-
977-
vm.createContext(childCtx);
978-
new vm.Script(
979-
//'"use strict";\n' +
1023+
return runModule(
1024+
distDir,
1025+
ctx,
1026+
res,
9801027
overlayFS.readFileSync(res, 'utf8'),
981-
{
982-
filename: path.basename(res),
983-
},
984-
).runInContext(childCtx);
985-
return childCtx.module.exports;
1028+
externalModules,
1029+
strict,
1030+
);
9861031
});
9871032

988-
if (filePath) {
989-
ctx.module = {exports, require: req};
990-
ctx.exports = exports;
991-
ctx.__filename = filePath;
992-
ctx.__dirname = path.dirname(filePath);
993-
ctx.require = req;
994-
}
1033+
let exports = {};
1034+
let module = {exports, require: req};
1035+
nodeCache.set(filePath, module);
1036+
f(module, exports, filePath, path.dirname(filePath), req);
1037+
return module.exports;
1038+
}
9951039

996-
ctx.console = console;
997-
ctx.process = process;
998-
ctx.setTimeout = setTimeout;
999-
ctx.setImmediate = setImmediate;
1000-
ctx.global = ctx;
1001-
ctx.URL = URL;
1002-
ctx.TextEncoder = TextEncoder;
1003-
ctx.TextDecoder = TextDecoder;
1004-
Object.assign(ctx, globals);
1005-
return ctx;
1040+
async function isESM(filePath: string) {
1041+
let pkg = await loadConfig(overlayFS, filePath, ['package.json'], '/');
1042+
return pkg?.config?.type === 'module';
10061043
}
10071044

10081045
let instanceId = 0;
@@ -1152,7 +1189,7 @@ export async function assertESMExports(
11521189
let [nodeResult] = await runESM(
11531190
b.getBundles()[0].target.distDir,
11541191
[[await inputFS.readFile(entry.filePath, 'utf8'), entry.filePath]],
1155-
vm.createContext(prepareNodeContext(false, {})),
1192+
vm.createContext(prepareNodeContext({})),
11561193
inputFS,
11571194
externalModules,
11581195
);

0 commit comments

Comments
 (0)