From 37b3c16292b41a4905f8bc99ffb98efd5cf31ee4 Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Tue, 3 Sep 2024 10:30:47 -0400 Subject: [PATCH] fix: Support resolving `module.register` dependencies (#429) Closes #428 [`module.register(specifier[, parentURL][, options])`](https://nodejs.org/api/module.html#moduleregisterspecifier-parenturl-options) --------- Co-authored-by: Steven Co-authored-by: Sean Massa --- src/analyze.ts | 69 ++++++++++++++++++- test/unit/module-register/hook.mjs | 1 + test/unit/module-register/hook2.mjs | 1 + test/unit/module-register/hook3.mjs | 1 + test/unit/module-register/input-esm.mjs | 8 +++ test/unit/module-register/input.js | 10 +++ .../node_modules/test-pkg/index.mjs | 1 + .../node_modules/test-pkg/package.json | 7 ++ test/unit/module-register/output.js | 10 +++ 9 files changed, 105 insertions(+), 3 deletions(-) create mode 100644 test/unit/module-register/hook.mjs create mode 100644 test/unit/module-register/hook2.mjs create mode 100644 test/unit/module-register/hook3.mjs create mode 100644 test/unit/module-register/input-esm.mjs create mode 100644 test/unit/module-register/input.js create mode 100644 test/unit/module-register/node_modules/test-pkg/index.mjs create mode 100644 test/unit/module-register/node_modules/test-pkg/package.json create mode 100644 test/unit/module-register/output.js diff --git a/src/analyze.ts b/src/analyze.ts index d6a8a821..9f781c3c 100644 --- a/src/analyze.ts +++ b/src/analyze.ts @@ -37,6 +37,7 @@ const acorn = Parser.extend( ); import os from 'os'; +import url from 'url'; import { handleWrappers } from './utils/wrappers'; import resolveFrom from 'resolve-from'; import { @@ -94,6 +95,10 @@ const fsExtraSymbols = { readJsonSync: FS_FN, readJSONSync: FS_FN, }; +const MODULE_FN = Symbol(); +const moduleSymbols = { + register: MODULE_FN, +}; const staticModules = Object.assign(Object.create(null), { bindings: { default: BINDINGS, @@ -111,6 +116,10 @@ const staticModules = Object.assign(Object.create(null), { default: fsSymbols, ...fsSymbols, }, + module: { + default: moduleSymbols, + ...moduleSymbols, + }, 'fs-extra': { default: fsExtraSymbols, ...fsExtraSymbols, @@ -131,6 +140,10 @@ const staticModules = Object.assign(Object.create(null), { default: os, ...os, }, + url: { + default: url, + ...url, + }, '@mapbox/node-pre-gyp': { default: mapboxPregyp, ...mapboxPregyp, @@ -530,15 +543,19 @@ export default async function analyze( let computed = await computePureStaticValue(expression, true); if (!computed) return; + function add(value: string) { + (isImport ? imports : deps).add(value); + } + if ('value' in computed && typeof computed.value === 'string') { - if (!computed.wildcards) (isImport ? imports : deps).add(computed.value); + if (!computed.wildcards) add(computed.value); else if (computed.wildcards.length >= 1) emitWildcardRequire(computed.value); } else { if ('then' in computed && typeof computed.then === 'string') - (isImport ? imports : deps).add(computed.then); + add(computed.then); if ('else' in computed && typeof computed.else === 'string') - (isImport ? imports : deps).add(computed.else); + add(computed.else); } } @@ -876,6 +893,52 @@ export default async function analyze( pjsonPath = path.resolve(pjsonPath, '../../package.json'); if (pjsonPath !== rootPjson) assets.add(pjsonPath); break; + case MODULE_FN: + if ( + node.arguments.length && + // TODO: We only cater for the case where the first argument is a string + node.arguments[0].type === 'Literal' + ) { + const pathOrSpecifier = node.arguments[0].value; + // It's a relative URL + if (pathOrSpecifier.startsWith('.')) { + // Compute the parentURL if it's statically analyzable + const computedParentURL = + node.arguments.length > 1 + ? await computePureStaticValue(node.arguments[1]) + : undefined; + + if (computedParentURL && 'value' in computedParentURL) { + const parentURL = + computedParentURL.value instanceof URL + ? computedParentURL.value.href + : typeof computedParentURL.value === 'string' + ? computedParentURL.value + : computedParentURL.value.parentURL; + + // Resolve the srcURL from the parentURL + const srcURL = new URL(pathOrSpecifier, parentURL).href; + + const currentDirURL = importMetaUrl.slice( + 0, + importMetaUrl.lastIndexOf('/'), + ); + + // Resolve the srcPath relative to the current file + const srcPath = path.relative(currentDirURL, srcURL); + // Make it a proper relative path + const relativeSrcPath = srcPath.startsWith('.') + ? srcPath + : './' + srcPath; + + imports.add(relativeSrcPath); + } + } else { + // It's a bare specifier, so just add into the imports + imports.add(pathOrSpecifier); + } + } + break; } } } else if ( diff --git a/test/unit/module-register/hook.mjs b/test/unit/module-register/hook.mjs new file mode 100644 index 00000000..acb24698 --- /dev/null +++ b/test/unit/module-register/hook.mjs @@ -0,0 +1 @@ +console.log('hook.mjs'); \ No newline at end of file diff --git a/test/unit/module-register/hook2.mjs b/test/unit/module-register/hook2.mjs new file mode 100644 index 00000000..8f135b6b --- /dev/null +++ b/test/unit/module-register/hook2.mjs @@ -0,0 +1 @@ +console.log('hook2.mjs'); \ No newline at end of file diff --git a/test/unit/module-register/hook3.mjs b/test/unit/module-register/hook3.mjs new file mode 100644 index 00000000..028552aa --- /dev/null +++ b/test/unit/module-register/hook3.mjs @@ -0,0 +1 @@ +console.log('hook3.mjs'); \ No newline at end of file diff --git a/test/unit/module-register/input-esm.mjs b/test/unit/module-register/input-esm.mjs new file mode 100644 index 00000000..65be6b1d --- /dev/null +++ b/test/unit/module-register/input-esm.mjs @@ -0,0 +1,8 @@ +import { register } from 'module'; + +// Load relative to the current file +register('./hook.mjs', import.meta.url); +// Load from a bare specifier +register('test-pkg'); +// Load with parentURL in options object +register('./hook.mjs', { parentURL: import.meta.url }); \ No newline at end of file diff --git a/test/unit/module-register/input.js b/test/unit/module-register/input.js new file mode 100644 index 00000000..aab2a6fc --- /dev/null +++ b/test/unit/module-register/input.js @@ -0,0 +1,10 @@ +const { register } = require('module'); +const { pathToFileURL } = require('url'); + +import('./input-esm.mjs') + +// Load relative to the current file +register('./hook2.mjs', pathToFileURL(__filename)); +// Load relative to the current working directory +register('./test/unit/module-register/hook3.mjs', pathToFileURL('./')); + diff --git a/test/unit/module-register/node_modules/test-pkg/index.mjs b/test/unit/module-register/node_modules/test-pkg/index.mjs new file mode 100644 index 00000000..549aa588 --- /dev/null +++ b/test/unit/module-register/node_modules/test-pkg/index.mjs @@ -0,0 +1 @@ +console.log('test-pkg/index.mjs'); \ No newline at end of file diff --git a/test/unit/module-register/node_modules/test-pkg/package.json b/test/unit/module-register/node_modules/test-pkg/package.json new file mode 100644 index 00000000..61acc81f --- /dev/null +++ b/test/unit/module-register/node_modules/test-pkg/package.json @@ -0,0 +1,7 @@ +{ + "name": "test-pkg", + "main": "./index.mjs", + "exports":{ + ".": "./index.mjs" + } +} \ No newline at end of file diff --git a/test/unit/module-register/output.js b/test/unit/module-register/output.js new file mode 100644 index 00000000..fbdc1fcc --- /dev/null +++ b/test/unit/module-register/output.js @@ -0,0 +1,10 @@ +[ + "package.json", + "test/unit/module-register/hook.mjs", + "test/unit/module-register/hook2.mjs", + "test/unit/module-register/hook3.mjs", + "test/unit/module-register/input-esm.mjs", + "test/unit/module-register/input.js", + "test/unit/module-register/node_modules/test-pkg/index.mjs", + "test/unit/module-register/node_modules/test-pkg/package.json" +]