Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 67 additions & 3 deletions lib/get-exports.js
Original file line number Diff line number Diff line change
Expand Up @@ -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])
Expand All @@ -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 (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) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
} catch (error) {
} catch {

Alternatively, we could also just add the cause

throw new Error(`Failed to find export: ${specifier}`)
}

return null
}

async function getCjsExports (url, context, parentLoad, source) {
if (urlsBeingProcessed.has(url)) {
return []
Expand All @@ -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')) {
Expand Down
1 change: 1 addition & 0 deletions test/fixtures/nested-folder/specifier.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = { ...require('#main-entry-point') }
17 changes: 17 additions & 0 deletions test/fixtures/package.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
1 change: 1 addition & 0 deletions test/fixtures/specifier-external.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = { ...require('#main-entry-point-external') }
1 change: 1 addition & 0 deletions test/fixtures/specifier-string.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = { ...require('#main-entry-point-string') }
1 change: 1 addition & 0 deletions test/fixtures/specifier.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from '#main-entry-point'
11 changes: 11 additions & 0 deletions test/hook/specifier-external-imports.mjs
Original file line number Diff line number Diff line change
@@ -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')
10 changes: 10 additions & 0 deletions test/hook/specifier-imports-mjs.mjs
Original file line number Diff line number Diff line change
@@ -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)
11 changes: 11 additions & 0 deletions test/hook/specifier-imports.mjs
Original file line number Diff line number Diff line change
@@ -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)
11 changes: 11 additions & 0 deletions test/hook/specifier-string-imports.mjs
Original file line number Diff line number Diff line change
@@ -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)