diff --git a/lib/get-exports.js b/lib/get-exports.js index ddb81e1..5204522 100644 --- a/lib/get-exports.js +++ b/lib/get-exports.js @@ -2,10 +2,10 @@ const getEsmExports = require('./get-esm-exports.js') const { parse: parseCjs } = require('cjs-module-lexer') -const { readFileSync } = require('fs') +const { readFileSync, existsSync } = require('fs') const { builtinModules } = require('module') const { fileURLToPath, pathToFileURL } = require('url') -const { dirname } = require('path') +const { dirname, join } = require('path') function addDefault (arr) { return new Set(['default', ...arr]) @@ -27,6 +27,63 @@ function getExportsForNodeBuiltIn (name) { const urlsBeingProcessed = new Set() // Guard against circular imports. +/** + * This function looks for the package.json which contains the specifier trying to resolve. + * Once the package.json file has been found, we extract the file path from the specifier + * @param {string} specifier The specifier that is being search for inside the imports object + * @param {URL|string} fromUrl The url from which the search starts from + * @returns array with url and resolvedExport + */ +function resolvePackageImports (specifier, fromUrl) { + try { + const fromPath = fileURLToPath(fromUrl) + let currentDir = dirname(fromPath) + + // search for package.json file which has the real url to export + while (currentDir !== dirname(currentDir)) { + const packageJsonPath = join(currentDir, 'package.json') + + if (existsSync(packageJsonPath)) { + const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8')) + if (packageJson.imports && packageJson.imports[specifier]) { + const imports = packageJson.imports[specifier] + + // Look for path inside packageJson + let resolvedExport + if (imports && typeof imports === 'object') { + const requireExport = imports.require + const importExport = imports.import + // look for the possibility of require and import which is standard for CJS/ESM + if (requireExport || importExport) { + // trying to resolve based on order of importance + resolvedExport = requireExport.node || requireExport.default || importExport.node || importExport.default + } else if (imports.node || imports.default) { + resolvedExport = imports.node || imports.default + } + } else if (typeof imports === 'string') { + resolvedExport = imports + } + + if (resolvedExport) { + const url = resolvedExport.startsWith('.') + ? pathToFileURL(join(currentDir, resolvedExport)) + : fromUrl + return [url, resolvedExport] + } + } + // return if we find a package.json but did not find an import + return null + } + + currentDir = dirname(currentDir) + } + } catch (error) { + throw new Error(`Failed to find export: ${specifier}`) + } + + return null +} + async function getCjsExports (url, context, parentLoad, source) { if (urlsBeingProcessed.has(url)) { return [] @@ -46,7 +103,14 @@ async function getCjsExports (url, context, parentLoad, source) { if (re === '.') { re = './' } - // Resolve the re-exported module relative to the current module. + + // Entries in the import field should always start with # + if (re.startsWith('#')) { + const resolved = resolvePackageImports(re, url) + if (!resolved) return + [url, re] = resolved + } + const newUrl = pathToFileURL(require.resolve(re, { paths: [dirname(fileURLToPath(url))] })).href if (newUrl.endsWith('.node') || newUrl.endsWith('.json')) { diff --git a/test/fixtures/nested-folder/specifier.js b/test/fixtures/nested-folder/specifier.js new file mode 100644 index 0000000..a7c05de --- /dev/null +++ b/test/fixtures/nested-folder/specifier.js @@ -0,0 +1 @@ +module.exports = { ...require('#main-entry-point') } diff --git a/test/fixtures/package.json b/test/fixtures/package.json new file mode 100644 index 0000000..6b3ac90 --- /dev/null +++ b/test/fixtures/package.json @@ -0,0 +1,17 @@ +{ + "name": "test-fixtures", + "imports": { + "#main-entry-point": { + "require": { + "node": "./something.js", + "default": "./something.js" + }, + "import": { + "node":"./something.mjs", + "default": "./something.mjs" + } + }, + "#main-entry-point-string" : "./something.js", + "#main-entry-point-external" : "some-external-cjs-module" + } +} diff --git a/test/fixtures/specifier-external.js b/test/fixtures/specifier-external.js new file mode 100644 index 0000000..a8ae668 --- /dev/null +++ b/test/fixtures/specifier-external.js @@ -0,0 +1 @@ +module.exports = { ...require('#main-entry-point-external') } diff --git a/test/fixtures/specifier-string.js b/test/fixtures/specifier-string.js new file mode 100644 index 0000000..305ea5c --- /dev/null +++ b/test/fixtures/specifier-string.js @@ -0,0 +1 @@ +module.exports = { ...require('#main-entry-point-string') } diff --git a/test/fixtures/specifier.mjs b/test/fixtures/specifier.mjs new file mode 100644 index 0000000..764e309 --- /dev/null +++ b/test/fixtures/specifier.mjs @@ -0,0 +1 @@ +export * from '#main-entry-point' diff --git a/test/hook/specifier-external-imports.mjs b/test/hook/specifier-external-imports.mjs new file mode 100644 index 0000000..b3e1df0 --- /dev/null +++ b/test/hook/specifier-external-imports.mjs @@ -0,0 +1,11 @@ +import { foo } from '../fixtures/specifier-external.js' +import Hook from '../../index.js' +import { strictEqual } from 'assert' + +Hook((exports, name) => { + if (name.endsWith('fixtures/specifier-external.js')) { + exports.foo = 'bar2' + } +}) + +strictEqual(foo, 'bar2') diff --git a/test/hook/specifier-imports-mjs.mjs b/test/hook/specifier-imports-mjs.mjs new file mode 100644 index 0000000..6f71a7a --- /dev/null +++ b/test/hook/specifier-imports-mjs.mjs @@ -0,0 +1,10 @@ +import { foo } from '../fixtures/specifier.mjs' +import Hook from '../../index.js' +import { strictEqual } from 'assert' +Hook((exports, name) => { + if (name.endsWith('fixtures/specifier.mjs')) { + exports.foo = 1 + } +}) + +strictEqual(foo, 1) diff --git a/test/hook/specifier-imports.mjs b/test/hook/specifier-imports.mjs new file mode 100644 index 0000000..f2058fb --- /dev/null +++ b/test/hook/specifier-imports.mjs @@ -0,0 +1,11 @@ +import { foo } from '../fixtures/nested-folder/specifier.js' +import Hook from '../../index.js' +import { strictEqual } from 'assert' + +Hook((exports, name) => { + if (name.endsWith('fixtures/nested-folder/specifier.js')) { + exports.foo = 1 + } +}) + +strictEqual(foo, 1) diff --git a/test/hook/specifier-string-imports.mjs b/test/hook/specifier-string-imports.mjs new file mode 100644 index 0000000..4311355 --- /dev/null +++ b/test/hook/specifier-string-imports.mjs @@ -0,0 +1,11 @@ +import { foo } from '../fixtures/specifier-string.js' +import Hook from '../../index.js' +import { strictEqual } from 'assert' + +Hook((exports, name) => { + if (name.endsWith('fixtures/specifier-string.js')) { + exports.foo = 1 + } +}) + +strictEqual(foo, 1)