Skip to content

Commit

Permalink
Support export * from 'module' ESM syntax
Browse files Browse the repository at this point in the history
This change adds support for modules that export entities through the
`export * from 'module'` ESM syntax. This resolves issue nodejs#31.
  • Loading branch information
jsumners-nr committed Dec 7, 2023
1 parent 5845e21 commit 20e8b5c
Show file tree
Hide file tree
Showing 6 changed files with 80 additions and 11 deletions.
59 changes: 50 additions & 9 deletions hook.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const NODE_MINOR = Number(NODE_VERSION[1])

let entrypoint

let getExports
if (NODE_MAJOR >= 20) {
getExports = require('./lib/get-exports.js')
} else {
Expand Down Expand Up @@ -116,23 +117,63 @@ function createHook (meta) {

const iitmURL = new URL('lib/register.js', meta.url).toString()
async function getSource (url, context, parentGetSource) {
const imports = []
const namespaceIds = []

if (hasIitm(url)) {
const realUrl = deleteIitm(url)
const exportNames = await getExports(realUrl, context, parentGetSource)
const isExportAllLine = /^\* from /
const setters = []
for (const n of exportNames) {
if (isExportAllLine.test(n) === true) {
// Encountered a `export * from 'module'` line. Thus, we need to
// get all exports from the specified module and shim them into the
// current module.
const [_, modFile] = n.split('* from ')
const modName = Buffer.from(modFile, 'hex') + Date.now()
const modUrl = new URL(modFile, url).toString()
const innerExports = await getExports(modUrl, context, parentGetSource)
const innerSetters = []

for (const _n of innerExports) {
innerSetters.push(`
let $${_n} = _.${_n}
export { $${_n} as ${_n} }
set.${_n} = (v) => {
$${_n} = v
return true
}
`)
}

imports.push(`import * as $${modName} from ${JSON.stringify(modUrl)}`)
namespaceIds.push(`$${modName}`)
setters.push(innerSetters.join('\n'))
continue
}

setters.push(`
let $${n} = _.${n}
export { $${n} as ${n} }
set.${n} = (v) => {
$${n} = v
return true
}
`)
}

return {
source: `
import { register } from '${iitmURL}'
import * as namespace from ${JSON.stringify(url)}
${imports.join('\n')}
const _ = Object.assign({}, ...[namespace, ${namespaceIds.join(', ')}])
const set = {}
${exportNames.map((n) => `
let $${n} = namespace.${n}
export { $${n} as ${n} }
set.${n} = (v) => {
$${n} = v
return true
}
`).join('\n')}
register(${JSON.stringify(realUrl)}, namespace, set, ${JSON.stringify(specifiers.get(realUrl))})
${setters.join('\n')}
register(${JSON.stringify(realUrl)}, _, set, ${JSON.stringify(specifiers.get(realUrl))})
`
}
}
Expand Down
2 changes: 1 addition & 1 deletion lib/get-esm-exports.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ function getEsmExports (moduleStr) {
if (node.exported) {
exportedNames.add(node.exported.name)
} else {
exportedNames.add('*')
exportedNames.add(`* from ${node.source.value}`)
}
break
default:
Expand Down
3 changes: 3 additions & 0 deletions test/fixtures/bundle.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import bar from './something.mjs'
export default bar
export * from './fantasia.mjs'
2 changes: 1 addition & 1 deletion test/fixtures/esm-exports.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export default class { /* … */ } //| default
export default function* () { /* … */ } //| default

// Aggregating modules
export * from "module-name"; //| *
export * from "module-name"; //| * from module-name
export * as name1 from "module-name"; //| name1
export { name1, /* …, */ nameN } from "module-name"; //| name1,nameN
export { import1 as name1, import2 as name2, /* …, */ nameN } from "module-name"; //| name1,name2,nameN
Expand Down
5 changes: 5 additions & 0 deletions test/fixtures/fantasia.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export function sayName() {
return 'Moon Child'
}

export const Morla = 'Ancient one'
20 changes: 20 additions & 0 deletions test/hook/static-import-star.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { strictEqual } from 'assert'
import Hook from '../../index.js'
Hook((exports, name) => {
if (/bundle\.mjs/.test(name) === false) return

const bar = exports.default
exports.default = function wrappedBar() {
return bar() + '-wrapped'
}

const sayName = exports.sayName
exports.sayName = function wrappedSayName() {
return `Bastion: "${sayName()}"`
}
})

import { default as bar, sayName } from '../fixtures/bundle.mjs'

strictEqual(bar(), '42-wrapped')
strictEqual(sayName(), 'Bastion: "Moon Child"')

0 comments on commit 20e8b5c

Please sign in to comment.