From 8d0544c8ba0b84bb98ee80889916b59d9eb5c5a8 Mon Sep 17 00:00:00 2001 From: Chunpeng Huo Date: Tue, 24 Dec 2024 09:58:14 +1000 Subject: [PATCH] feat: support node: import like require('node:fs/promises') related to #17 --- lib/index.js | 7 +++- lib/modules-todo.js | 10 ++--- lib/transformers/replace.js | 5 +++ test/bundler.spec.js | 69 +++++++++++++++++++++++++++++++ test/modules-todo.spec.js | 24 +++++------ test/transformers/replace.spec.js | 4 ++ 6 files changed, 101 insertions(+), 18 deletions(-) diff --git a/lib/index.js b/lib/index.js index 1015769..081b3d2 100644 --- a/lib/index.js +++ b/lib/index.js @@ -326,8 +326,9 @@ module.exports = class Bundler { const bareId = parsedId.bareId; const packageName = parsedId.parts[0]; const resource = bareId.slice(packageName.length + 1); + const isNodeCore = packageName.startsWith('node:'); - const stub = stubModule(bareId, this._resolve); + const stub = stubModule(isNodeCore ? packageName.slice(5) : packageName, this._resolve); if (typeof stub === 'string') { return this.capture({ @@ -339,6 +340,10 @@ module.exports = class Bundler { }); } + if (isNodeCore && !stub) { + throw new Error(`Core Node.js module "${packageName}" is not stubbed`); + } + return this.packageReaderFor(stub || {name: packageName}) .then(reader => resource ? reader.readResource(resource, isRelative) : reader.readMain()) .then(unit => this.capture(unit)) diff --git a/lib/modules-todo.js b/lib/modules-todo.js index 6a52cd2..c3bce54 100644 --- a/lib/modules-todo.js +++ b/lib/modules-todo.js @@ -60,12 +60,12 @@ module.exports = class ModulesTodo { let pluginSpace = space; if (space !== PACKAGE) pluginSpace = USER_OR_PACKAGE; const isRelativePlugin = pluginName.startsWith('.') ? 1 : 0; - const key = pluginSpace + ':' + pluginName + ':' + isRelativePlugin; + const key = pluginSpace + ' ' + pluginName + ' ' + isRelativePlugin; if (!this.todos[key]) this.todos[key] = []; this.todos[key].push(moduleId); } - const key = space + ':' + parsedId.cleanId + ':' + isRelative; + const key = space + ' ' + parsedId.cleanId + ' ' + isRelative; if (!this.todos[key]) this.todos[key] = []; this.todos[key].push(moduleId); }); @@ -87,7 +87,7 @@ module.exports = class ModulesTodo { groups[SERIAL_GROUP] = []; keys.forEach(key => { - const [space, id] = key.split(':'); + const [space, id] = key.split(' '); // Don't need the parallel optimization when running // in nodejs. @@ -95,7 +95,7 @@ module.exports = class ModulesTodo { groups[SERIAL_GROUP].push(key); } else { const packageName = parse(id).parts[0]; - const group = space + ':' + packageName; + const group = space + ' ' + packageName; if (!groups[group]) groups[group] = []; groups[group].push(key); } @@ -108,7 +108,7 @@ module.exports = class ModulesTodo { let p = Promise.resolve(); keys.forEach(key => { - const [space, id, isRelative] = key.split(':'); + const [space, id, isRelative] = key.split(' '); const requiredBy = todos[key]; p = p.then(() => cb(id, { diff --git a/lib/transformers/replace.js b/lib/transformers/replace.js index e28a7f1..7b81cbe 100644 --- a/lib/transformers/replace.js +++ b/lib/transformers/replace.js @@ -40,6 +40,11 @@ module.exports = function replace(unit) { dep = dep.slice(0, -1); } + if (dep.startsWith('node:')) { + // remove node: prefix for node built-in modules + dep = dep.slice(5); + } + // browser replacement; if (replacement && replacement[dep]) { dep = replacement[dep]; diff --git a/test/bundler.spec.js b/test/bundler.spec.js index e432ac5..282aea7 100644 --- a/test/bundler.spec.js +++ b/test/bundler.spec.js @@ -1958,3 +1958,72 @@ test("Bundler ignores runtime modules", async (t) => { (err) => t.fail(err.stack) ); }); + +test("Bundler traces node:fs/promises", async (t) => { + const fakeFs = { + "node_modules/dumber-module-loader/dist/index.debug.js": + "dumber-module-loader", + + "node_modules/fs-browser-stub/package.json": JSON.stringify({ + name: "fs-browser-stub", + main: "index.js", + exports: { + ".": "./index.js", + "./promises": "./promises.js" + } + }), + "node_modules/fs-browser-stub/index.js": "module.exports = { promises: {}}", + "node_modules/fs-browser-stub/promises.js": "module.exports = {}", + }; + const bundler = mockBundler(fakeFs); + + return Promise.resolve() + .then(() => + bundler.capture({ + path: "src/app.js", + contents: "require('node:fs/promises');", + moduleId: "app.js", + }) + ) + .then(() => bundler.resolve()) + .then(() => bundler.bundle()) + .then( + (bundleMap) => { + t.deepEqual(bundleMap, { + "entry-bundle": { + files: [ + { + path: "node_modules/dumber-module-loader/dist/index.debug.js", + contents: "dumber-module-loader;", + }, + { + contents: "define.switchToUserSpace();", + }, + { + path: "src/app.js", + contents: + "define('app.js',['require','exports','module','fs/promises'],function (require, exports, module) {\nrequire('fs/promises');\n});\n", + }, + { + contents: "define.switchToPackageSpace();", + }, + { + path: "node_modules/fs-browser-stub/promises.js", + contents: + "define('fs/promises.js',['require','exports','module'],function (require, exports, module) {\nmodule.exports = {}\n});\n", + }, + { + contents: "define.switchToUserSpace();", + }, + ], + config: { + baseUrl: "/dist", + paths: {}, + bundles: {}, + }, + }, + }); + }, + (err) => t.fail(err.stack) + ); +}); \ No newline at end of file diff --git a/test/modules-todo.spec.js b/test/modules-todo.spec.js index d114e70..b8c94c9 100644 --- a/test/modules-todo.spec.js +++ b/test/modules-todo.spec.js @@ -14,11 +14,11 @@ test('ModulesTodo process traced unit', t => { }); t.deepEqual(Object.assign({}, md.todos), { - '0:text!foo.html:1': ['foo'], - '2:some-plugin:0': ['foo'], - '0:some-plugin!readme.md:1': ['foo'], - '2:bar:0': ['foo'], - '1:bar/lo:1': ['bar/index'] + '0 text!foo.html 1': ['foo'], + '2 some-plugin 0': ['foo'], + '0 some-plugin!readme.md 1': ['foo'], + '2 bar 0': ['foo'], + '1 bar/lo 1': ['bar/index'] }); t.notOk(md.needCssInjection); t.ok(md.hasTodo()); @@ -109,7 +109,7 @@ test('ModulesTodo handles additional todos, set needCssInjection', async t => { ]); t.notOk(md.needCssInjection); t.deepEqual(Object.assign({}, md.todos), { - '1:bar/lor:1': ['bar/lo'], + '1 bar/lor 1': ['bar/lo'], }); t.ok(md.hasTodo()); @@ -123,8 +123,8 @@ test('ModulesTodo handles additional todos, set needCssInjection', async t => { ]); t.ok(md.needCssInjection); t.deepEqual(Object.assign({}, md.todos), { - '1:bar/lor/tool.css:1': ['bar/lor/index'], - '1:bar/lor/tool2:1': ['bar/lor/index'] + '1 bar/lor/tool.css 1': ['bar/lor/index'], + '1 bar/lor/tool2 1': ['bar/lor/index'] }); t.ok(md.hasTodo()); @@ -154,7 +154,7 @@ test('ModulesTodo sets needCssInjection for less module', t => { }); t.deepEqual(Object.assign({}, md.todos), { - '0:foo.less:1': ['foo'] + '0 foo.less 1': ['foo'] }); t.ok(md.needCssInjection); t.ok(md.hasTodo()); @@ -168,7 +168,7 @@ test('ModulesTodo sets needCssInjection for scss module', t => { }); t.deepEqual(Object.assign({}, md.todos), { - '0:foo.scss:1': ['foo'] + '0 foo.scss 1': ['foo'] }); t.ok(md.needCssInjection); t.ok(md.hasTodo()); @@ -182,7 +182,7 @@ test('ModulesTodo sets needCssInjection for sass module', t => { }); t.deepEqual(Object.assign({}, md.todos), { - '0:foo.sass:1': ['foo'] + '0 foo.sass 1': ['foo'] }); t.ok(md.needCssInjection); t.ok(md.hasTodo()); @@ -196,7 +196,7 @@ test('ModulesTodo sets needCssInjection for styl module', t => { }); t.deepEqual(Object.assign({}, md.todos), { - '0:foo.styl:1': ['foo'] + '0 foo.styl 1': ['foo'] }); t.ok(md.needCssInjection); t.ok(md.hasTodo()); diff --git a/test/transformers/replace.spec.js b/test/transformers/replace.spec.js index c8d67ad..2b4e7b4 100644 --- a/test/transformers/replace.spec.js +++ b/test/transformers/replace.spec.js @@ -18,12 +18,16 @@ test('replace transform ignores empty replacement', t => { test('replace transform cleanup dep, even with empty replacement', t => { const source = `define('foo', ['require', 'module-a.js', './bar/', 'o/a.js'], function (require) { require('module-a.js'); + const {rm} = require('node:fs'); + const {rmdir} = require('node:fs/promises'); require('./bar/'); require('o/a.js'); })`; const result = `define('foo', ['require', 'module-a.js', './bar', 'o/a.js'], function (require) { require('module-a.js'); + const {rm} = require('fs'); + const {rmdir} = require('fs/promises'); require('./bar'); require('o/a.js'); })`;