diff --git a/lib/get-source-loader.mjs b/lib/get-source-loader.mjs deleted file mode 100644 index d3061e7..0000000 --- a/lib/get-source-loader.mjs +++ /dev/null @@ -1,6 +0,0 @@ -import { send } from './ipc.mjs'; - -export async function getSource(url, context, defaultGetSource) { - send({ required: new URL(url).pathname }); - return defaultGetSource(url, context, defaultGetSource); -} diff --git a/lib/hook.js b/lib/hook.js index 5915f18..a7059dc 100644 --- a/lib/hook.js +++ b/lib/hook.js @@ -1,6 +1,6 @@ const vm = require('vm'); -module.exports = (patchVM, wrapper, callback) => { +module.exports = (patchVM, callback) => { // Hook into Node's `require(...)` updateHooks(); @@ -48,12 +48,6 @@ module.exports = (patchVM, wrapper, callback) => { */ function createHook(handler) { return function nodeDevHook(module, filename) { - if (module.parent === wrapper) { - // If the main module is required conceal the wrapper - module.id = '.'; - module.parent = null; - process.mainModule = module; - } if (!module.loaded) callback(module.filename); // Invoke the original handler diff --git a/lib/index.js b/lib/index.js index afad06a..10794ed 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,8 +1,7 @@ const { fork } = require('child_process'); const filewatcher = require('filewatcher'); -const { extname } = require('path'); const semver = require('semver'); -const getPackageType = require('get-package-type'); +const { pathToFileURL } = require('url'); const { clearFactory } = require('./clear'); const { configureDeps, configureIgnore } = require('./ignore'); @@ -55,8 +54,6 @@ module.exports = function ( const isIgnored = configureIgnore(ignore); const isTooDeep = configureDeps(deps); - const wrapper = resolveMain(localPath('wrap.js')); - // Run ./dedupe.js as preload script if (dedupe) process.env.NODE_DEV_PRELOAD = localPath('dedupe'); @@ -96,27 +93,24 @@ module.exports = function ( const args = nodeArgs.slice(); - if (extname(script) === '.mjs' || getPackageType.sync(script) === 'module') { - if (semver.satisfies(process.version, '>=10 <12.11.1')) { - const resolveLoader = resolveMain(localPath('resolve-loader.mjs')); - args.push('--experimental-modules', `--loader=${resolveLoader}`); - } else if (semver.satisfies(process.version, '>=12.11.1')) { - if (semver.satisfies(process.version, '<12.17.0')) { - args.push('--experimental-modules'); - } - const loaderPath = semver.satisfies(process.version, '>=16.12.0') - ? 'load-loader.mjs' - : 'get-source-loader.mjs'; - const experimentalLoader = resolveMain(localPath(loaderPath)); - args.push(`--experimental-loader=${experimentalLoader}`); - } + args.push(`--require=${resolveMain(localPath('wrap'))}`); + const loaderPath = semver.satisfies(process.version, '<16.12.0') + ? 'loader-legacy.mjs' + : 'loader.mjs'; + const loader = pathToFileURL(resolveMain(localPath(loaderPath))); + if (semver.satisfies(process.version, '<12.17.0')) { + args.push('--experimental-modules'); + } + if (semver.satisfies(process.version, '<12.11.1')) { + args.push(`--loader=${loader.href}`); + } else { + args.push(`--experimental-loader=${loader.href}`); } - const cmd = args.concat(wrapper, script, scriptArgs); - - child = fork(cmd[0], cmd.slice(1), { + child = fork(script, scriptArgs, { cwd: process.cwd(), - env: process.env + env: process.env, + execArgv: args }); if (respawn) { diff --git a/lib/load-loader.mjs b/lib/load-loader.mjs deleted file mode 100644 index 3e8498f..0000000 --- a/lib/load-loader.mjs +++ /dev/null @@ -1,6 +0,0 @@ -import { send } from './ipc.js'; - -export async function load(url, context, defaultLoad) { - send({ required: new URL(url).pathname }); - return defaultLoad(url, context, defaultLoad); -} diff --git a/lib/loader-legacy.mjs b/lib/loader-legacy.mjs new file mode 100644 index 0000000..0026437 --- /dev/null +++ b/lib/loader-legacy.mjs @@ -0,0 +1,20 @@ +import { createRequire } from 'module'; +import { fileURLToPath } from 'url'; +import { send } from './ipc.mjs'; + +const require = createRequire(import.meta.url); + +export async function getFormat(url, context, defaultGetFormat) { + const getPackageType = require('get-package-type'); + const filePath = fileURLToPath(url); + + send({ required: filePath }); + try { + return await defaultGetFormat(url, context, defaultGetFormat); + } catch (err) { + if (err.code === 'ERR_UNKNOWN_FILE_EXTENSION') { + return { format: await getPackageType(filePath) }; + } + throw err; + } +} diff --git a/lib/loader.mjs b/lib/loader.mjs new file mode 100644 index 0000000..302001e --- /dev/null +++ b/lib/loader.mjs @@ -0,0 +1,20 @@ +import { createRequire } from 'module'; +import { fileURLToPath } from 'url'; +import { send } from './ipc.mjs'; + +const require = createRequire(import.meta.url); + +export async function load(url, context, defaultLoad) { + const getPackageType = require('get-package-type'); + const filePath = fileURLToPath(url); + + send({ required: filePath }); + try { + return await defaultLoad(url, context, defaultLoad); + } catch (err) { + if (err.code === 'ERR_UNKNOWN_FILE_EXTENSION') { + return { format: await getPackageType(filePath), source: null }; + } + throw err; + } +} diff --git a/lib/resolve-loader.mjs b/lib/resolve-loader.mjs deleted file mode 100644 index 4832e04..0000000 --- a/lib/resolve-loader.mjs +++ /dev/null @@ -1,13 +0,0 @@ -import ipc from './ipc.js'; - -const { send } = ipc; - -export function resolve(specifier, parentModule, defaultResolve) { - const resolved = defaultResolve(specifier, parentModule); - - if (parentModule) { - send({ required: new URL(resolved.url).pathname }); - } - - return resolved; -} diff --git a/lib/suppress-experimental.js b/lib/suppress-experimental.js new file mode 100644 index 0000000..b75e84d --- /dev/null +++ b/lib/suppress-experimental.js @@ -0,0 +1,13 @@ +const { emitWarning } = process; + +process.emitWarning = (warning, ...args) => { + if (args[0] === 'ExperimentalWarning') { + return; + } + + if (args[0] && typeof args[0] === 'object' && args[0].type === 'ExperimentalWarning') { + return; + } + + return emitWarning(warning, ...args); +}; diff --git a/lib/wrap.js b/lib/wrap.js index a05bd11..d99186f 100755 --- a/lib/wrap.js +++ b/lib/wrap.js @@ -1,22 +1,19 @@ const { dirname, extname } = require('path'); const childProcess = require('child_process'); const { sync: resolve } = require('resolve'); -const getPackageType = require('get-package-type'); const { getConfig } = require('./cfg'); const hook = require('./hook'); const { relay, send } = require('./ipc'); const resolveMain = require('./resolve-main'); -// Remove wrap.js from the argv array -process.argv.splice(1, 1); - const script = process.argv[1]; const { extensions, fork, vm } = getConfig(script); if (process.env.NODE_DEV_PRELOAD) { require(process.env.NODE_DEV_PRELOAD); } +require('./suppress-experimental'); // We want to exit on SIGTERM, but defer to existing SIGTERM handlers. process.once('SIGTERM', () => process.listenerCount('SIGTERM') || process.exit(0)); @@ -26,7 +23,7 @@ if (fork) { // too. We also need to relay messages about required files to the parent. const originalFork = childProcess.fork; childProcess.fork = (modulePath, args, options) => { - const child = originalFork(__filename, [modulePath].concat(args), options); + const child = originalFork(modulePath, args, options); relay(child); return child; }; @@ -48,7 +45,7 @@ process.on('uncaughtException', err => { }); // Hook into require() and notify the parent process about required files -hook(vm, module, required => send({ required })); +hook(vm, required => send({ required })); // Check if a module is registered for this extension const main = resolveMain(script); @@ -66,6 +63,3 @@ if (typeof mod === 'object' && mod.name) { } else if (typeof mod === 'string') { require(resolve(mod, { basedir })); } - -// Execute the wrapped script -ext === 'mjs' || getPackageType.sync(main) === 'module' ? import(main) : require(main); diff --git a/test/spawn/argv.js b/test/spawn/argv.js index 9338fc5..a1c2587 100644 --- a/test/spawn/argv.js +++ b/test/spawn/argv.js @@ -6,7 +6,7 @@ tap.test('should not show up in argv', t => { spawn('argv.js foo', out => { const argv = JSON.parse(out.replace(/'/g, '"')); t.match(argv[0], /.*?node(js|\.exe)?$/); - t.equal(argv[1], 'argv.js'); + t.match(argv[1], /.*[\\/]argv\.js$/); t.equal(argv[2], 'foo'); return { exit: t.end.bind(t) }; }); diff --git a/test/spawn/esmodule.js b/test/spawn/esmodule.js index d02734f..1b78287 100644 --- a/test/spawn/esmodule.js +++ b/test/spawn/esmodule.js @@ -6,7 +6,6 @@ const { spawn, touchFile } = require('../utils'); tap.test('Supports ECMAScript modules with experimental-specifier-resolution', t => { if (semver.satisfies(process.version, '<12.17')) return t.skip('experimental-specifier-resolution requires node >= 12.17'); - if (process.platform === 'win32') return t.skip('ESM support on windows is broken'); spawn('--experimental-specifier-resolution=node resolution.mjs', out => { if (out.match(/touch message.js/)) { @@ -22,8 +21,6 @@ tap.test('Supports ECMAScript modules with experimental-specifier-resolution', t }); tap.test('Supports ECMAScript modules', t => { - if (process.platform === 'win32') return t.skip('ESM support on windows is broken'); - spawn('ecma-script-modules.mjs', out => { if (out.match(/touch message.mjs/)) { touchFile('message.mjs'); @@ -38,8 +35,6 @@ tap.test('Supports ECMAScript modules', t => { }); tap.test('Supports ECMAScript module packages', t => { - if (process.platform === 'win32') return t.skip('ESM support on windows is broken'); - spawn('ecma-script-module-package/index.js', out => { if (out.match(/touch ecma-script-module-package\/message.js/)) { touchFile('ecma-script-module-package/message.js'); @@ -54,8 +49,6 @@ tap.test('Supports ECMAScript module packages', t => { }); tap.test('We can hide the experimental warning by passing --no-warnings', t => { - if (process.platform === 'win32') return t.skip('ESM support on windows is broken'); - spawn('--no-warnings ecma-script-modules.mjs', out => { if (out.match(/ExperimentalWarning/)) return t.fail('Should not log an ExperimentalWarning');