diff --git a/hook.js b/hook.js index 3639fbf..85e61a9 100644 --- a/hook.js +++ b/hook.js @@ -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 { @@ -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))}) ` } } diff --git a/lib/get-esm-exports.js b/lib/get-esm-exports.js index 3b4fa30..c04799e 100644 --- a/lib/get-esm-exports.js +++ b/lib/get-esm-exports.js @@ -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: diff --git a/test/fixtures/bundle.mjs b/test/fixtures/bundle.mjs new file mode 100644 index 0000000..45812bf --- /dev/null +++ b/test/fixtures/bundle.mjs @@ -0,0 +1,3 @@ +import bar from './something.mjs' +export default bar +export * from './fantasia.mjs' diff --git a/test/fixtures/esm-exports.txt b/test/fixtures/esm-exports.txt index 341fa53..9d5337b 100644 --- a/test/fixtures/esm-exports.txt +++ b/test/fixtures/esm-exports.txt @@ -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 diff --git a/test/fixtures/fantasia.mjs b/test/fixtures/fantasia.mjs new file mode 100644 index 0000000..3413721 --- /dev/null +++ b/test/fixtures/fantasia.mjs @@ -0,0 +1,5 @@ +export function sayName() { + return 'Moon Child' +} + +export const Morla = 'Ancient one' diff --git a/test/hook/static-import-star.mjs b/test/hook/static-import-star.mjs new file mode 100644 index 0000000..fe04ea9 --- /dev/null +++ b/test/hook/static-import-star.mjs @@ -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"')