Skip to content

Commit

Permalink
fix: Resolve re-exports of external modules (#78)
Browse files Browse the repository at this point in the history
Fixes (potentially) #59, #62, #63

`parentResolve` is cached and later used when attempting to load bare
specifiers.
  • Loading branch information
timfish authored May 28, 2024
1 parent 0d9f351 commit bf3a4fb
Show file tree
Hide file tree
Showing 7 changed files with 69 additions and 2 deletions.
42 changes: 40 additions & 2 deletions hook.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc.

const { randomBytes } = require('crypto')
const { URL } = require('url')
const specifiers = new Map()
const isWin = process.platform === 'win32'

Expand Down Expand Up @@ -91,6 +92,30 @@ function isStarExportLine (line) {
return /^\* from /.test(line)
}

function isBareSpecifier (specifier) {
// Relative and absolute paths are not bare specifiers.
if (
specifier.startsWith('.') ||
specifier.startsWith('/')) {
return false
}

// Valid URLs are not bare specifiers. (file:, http:, node:, etc.)

// eslint-disable-next-line no-prototype-builtins
if (URL.hasOwnProperty('canParse')) {
return !URL.canParse(specifier)
}

try {
// eslint-disable-next-line no-new
new URL(specifier)
return false
} catch (err) {
return true
}
}

/**
* @typedef {object} ProcessedModule
* @property {string[]} imports A set of ESM import lines to be added to the
Expand Down Expand Up @@ -128,6 +153,7 @@ async function processModule ({
srcUrl,
context,
parentGetSource,
parentResolve,
ns = 'namespace',
defaultAs = 'default'
}) {
Expand All @@ -154,13 +180,22 @@ async function processModule ({
if (isStarExportLine(n) === true) {
const [, modFile] = n.split('* from ')
const normalizedModName = normalizeModName(modFile)
const modUrl = new URL(modFile, srcUrl).toString()
const modName = Buffer.from(modFile, 'hex') + Date.now() + randomBytes(4).toString('hex')

let modUrl
if (isBareSpecifier(modFile)) {
// Bare specifiers need to be resolved relative to the parent module.
const result = await parentResolve(modFile, { parentURL: srcUrl })
modUrl = result.url
} else {
modUrl = new URL(modFile, srcUrl).href
}

const data = await processModule({
srcUrl: modUrl,
context,
parentGetSource,
parentResolve,
ns: `$${modName}`,
defaultAs: normalizedModName
})
Expand Down Expand Up @@ -229,7 +264,9 @@ function addIitm (url) {
}

function createHook (meta) {
let cachedResolve
async function resolve (specifier, context, parentResolve) {
cachedResolve = parentResolve
const { parentURL = '' } = context
const newSpecifier = deleteIitm(specifier)
if (isWin && parentURL.indexOf('file:node') === 0) {
Expand Down Expand Up @@ -278,7 +315,8 @@ function createHook (meta) {
const { imports, namespaces, setters: mapSetters } = await processModule({
srcUrl: realUrl,
context,
parentGetSource
parentGetSource,
parentResolve: cachedResolve
})
const setters = Array.from(mapSetters.values())

Expand Down
1 change: 1 addition & 0 deletions test/fixtures/node_modules/some-external-module/index.mjs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions test/fixtures/node_modules/some-external-module/package.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions test/fixtures/node_modules/some-external-module/sub.mjs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions test/fixtures/re-export-star-external.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from 'some-external-module'
1 change: 1 addition & 0 deletions test/fixtures/sub-directory/re-export-star-external.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from 'some-external-module/sub'
17 changes: 17 additions & 0 deletions test/hook/re-export-star-module.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import Hook from '../../index.js'
import { foo } from '../fixtures/re-export-star-external.mjs'
import { bar } from '../fixtures/sub-directory/re-export-star-external.mjs'
import { strictEqual } from 'assert'

Hook((exports, name) => {
if (name.endsWith('fixtures/re-export-star-external.mjs')) {
exports.foo = '1'
}

if (name.endsWith('fixtures/sub-directory/re-export-star-external.mjs')) {
exports.bar = '2'
}
})

strictEqual(foo, '1')
strictEqual(bar, '2')

0 comments on commit bf3a4fb

Please sign in to comment.