Skip to content

Commit 4f3bcaa

Browse files
authored
Support bundling node native modules (#10066)
1 parent 1dd802c commit 4f3bcaa

File tree

11 files changed

+222
-29
lines changed

11 files changed

+222
-29
lines changed

packages/configs/default/index.json

+1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
],
4545
"*.svg": ["@parcel/transformer-svg"],
4646
"*.{xml,rss,atom}": ["@parcel/transformer-xml"],
47+
"*.node": ["@parcel/transformer-node"],
4748
"url:*": ["...", "@parcel/transformer-raw"]
4849
},
4950
"namers": ["@parcel/namer-default"],

packages/configs/default/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
"@parcel/transformer-image": "2.13.3",
4545
"@parcel/transformer-js": "2.13.3",
4646
"@parcel/transformer-json": "2.13.3",
47+
"@parcel/transformer-node": "2.13.3",
4748
"@parcel/transformer-postcss": "2.13.3",
4849
"@parcel/transformer-posthtml": "2.13.3",
4950
"@parcel/transformer-raw": "2.13.3",

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

+48
Original file line numberDiff line numberDiff line change
@@ -6231,4 +6231,52 @@ describe('javascript', function () {
62316231
assert.equal(res.result, 2);
62326232
});
62336233
}
6234+
6235+
for (let defaultTargetOptions of [
6236+
{shouldScopeHoist: false},
6237+
{shouldScopeHoist: true, outputFormat: 'commonjs'},
6238+
{shouldScopeHoist: true, outputFormat: 'esmodule'},
6239+
]) {
6240+
it(
6241+
'supports native .node modules with options: ' +
6242+
JSON.stringify(defaultTargetOptions),
6243+
async function () {
6244+
await fsFixture(overlayFS, __dirname)`
6245+
native-node
6246+
index.js:
6247+
output = require('@parcel/rust');
6248+
6249+
package.json:
6250+
{
6251+
"targets": {
6252+
"default": {
6253+
"context": "node",
6254+
"includeNodeModules": true
6255+
}
6256+
}
6257+
}
6258+
6259+
yarn.lock:`;
6260+
6261+
let b = await bundle(path.join(__dirname, 'native-node/index.js'), {
6262+
defaultTargetOptions,
6263+
inputFS: overlayFS,
6264+
outputFS: inputFS,
6265+
});
6266+
6267+
let res = await run(
6268+
b,
6269+
{output: null},
6270+
{require: false},
6271+
{
6272+
fs: () => require('fs'),
6273+
path: () => require('path'),
6274+
module: () => require('module'),
6275+
url: () => require('url'),
6276+
},
6277+
);
6278+
assert.equal(typeof res.output.hashString, 'function');
6279+
},
6280+
);
6281+
}
62346282
});

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,7 @@ describe('svg', function () {
314314

315315
assertBundles(b, [
316316
{
317-
assets: ['index.js', 'bundle-url.js'],
317+
assets: ['index.js'],
318318
},
319319
{
320320
assets: ['circle.svg'],

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

+17-1
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,9 @@ export async function runBundles(
365365
overlayFS,
366366
externalModules,
367367
true,
368+
target === 'node' ||
369+
target === 'electron-main' ||
370+
target === 'react-server',
368371
);
369372

370373
esmOutput = bundles.length === 1 ? res[0] : res;
@@ -927,6 +930,9 @@ function prepareNodeContext(
927930
readFileSync: (file, encoding) => {
928931
return overlayFS.readFileSync(file, encoding);
929932
},
933+
existsSync: file => {
934+
return overlayFS.existsSync(file);
935+
},
930936
};
931937
}
932938

@@ -939,6 +945,11 @@ function prepareNodeContext(
939945
return {};
940946
}
941947

948+
if (path.extname(res) === '.node') {
949+
// $FlowFixMe[unsupported-syntax]
950+
return require(res);
951+
}
952+
942953
let cached = nodeCache.get(res);
943954
if (cached) {
944955
return cached.module.exports;
@@ -1002,6 +1013,7 @@ export async function runESM(
10021013
fs: FileSystem,
10031014
externalModules: ExternalModules = {},
10041015
requireExtensions: boolean = false,
1016+
isNode: boolean = false,
10051017
): Promise<Array<{|[string]: mixed|}>> {
10061018
let id = instanceId++;
10071019
let cache = new Map();
@@ -1048,7 +1060,11 @@ export async function runESM(
10481060
entry(specifier, referrer),
10491061
context,
10501062
initializeImportMeta(meta) {
1051-
meta.url = `http://localhost/${path.basename(filename)}`;
1063+
if (isNode) {
1064+
meta.url = url.pathToFileURL(filename).toString();
1065+
} else {
1066+
meta.url = `http://localhost/${path.basename(filename)}`;
1067+
}
10521068
},
10531069
});
10541070
cache.set(filename, m);

packages/runtimes/js/src/JSRuntime.js

+16-2
Original file line numberDiff line numberDiff line change
@@ -193,10 +193,23 @@ export default (new Runtime({
193193
.find(e => e.id === bundleGroup.entryAssetId);
194194
if (
195195
dependency.specifierType === 'url' ||
196-
mainBundle.type !== 'js' ||
197196
mainAsset?.meta.jsRuntime === 'url'
198197
) {
199198
assets.push(getURLRuntime(dependency, bundle, mainBundle, options));
199+
continue;
200+
}
201+
202+
if (mainBundle.type === 'node' && mainBundle.env.isNode()) {
203+
let relativePathExpr = getAbsoluteUrlExpr(
204+
getRelativePathExpr(bundle, mainBundle, options),
205+
mainBundle,
206+
);
207+
assets.push({
208+
filePath: __filename,
209+
code: `module.exports = require('./helpers/node/node-loader.js')(${relativePathExpr});`,
210+
dependency,
211+
env: {sourceType: 'module'},
212+
});
200213
}
201214
}
202215

@@ -655,7 +668,8 @@ function getAbsoluteUrlExpr(relativePathExpr: string, bundle: NamedBundle) {
655668
if (
656669
(bundle.env.outputFormat === 'esmodule' &&
657670
bundle.env.supports('import-meta-url')) ||
658-
bundle.env.outputFormat === 'commonjs'
671+
bundle.env.outputFormat === 'commonjs' ||
672+
bundle.env.isNode()
659673
) {
660674
// This will be compiled to new URL(url, import.meta.url) or new URL(url, 'file:' + __filename).
661675
return `new __parcel__URL__(${relativePathExpr}).toString()`;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
const url = require('url');
2+
const {createRequire} = require('module');
3+
4+
module.exports = function loadNodeModule(bundle) {
5+
let path = url.fileURLToPath(bundle);
6+
let require = createRequire(path);
7+
return require(path);
8+
};

packages/transformers/js/core/src/lib.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -485,10 +485,11 @@ pub fn transform(
485485
source_map: source_map.clone(),
486486
items: &mut global_deps,
487487
global_mark,
488-
globals: HashMap::new(),
488+
globals: IndexMap::new(),
489489
filename: Path::new(&config.filename),
490490
unresolved_mark,
491491
has_node_replacements: &mut result.has_node_replacements,
492+
is_esm: config.is_esm_output,
492493
},
493494
config.node_replacer(),
494495
),

0 commit comments

Comments
 (0)