import-in-the-middle 1.3.5 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/hook.js +11 -8
- package/lib/get-esm-exports.js +97 -0
- package/lib/get-exports.js +51 -0
- package/package.json +7 -5
- package/test/fixtures/esm-exports.txt +32 -0
- package/test/get-esm-exports/v20-get-esm-exports.js +31 -0
package/hook.js
CHANGED
|
@@ -14,6 +14,12 @@ const NODE_MINOR = Number(NODE_VERSION[1])
|
|
|
14
14
|
|
|
15
15
|
let entrypoint
|
|
16
16
|
|
|
17
|
+
if (NODE_MAJOR >= 20) {
|
|
18
|
+
getExports = require('./lib/get-exports.js')
|
|
19
|
+
} else {
|
|
20
|
+
getExports = (url) => import(url).then(Object.keys)
|
|
21
|
+
}
|
|
22
|
+
|
|
17
23
|
function hasIitm (url) {
|
|
18
24
|
try {
|
|
19
25
|
return new URL(url).searchParams.has('iitm')
|
|
@@ -103,7 +109,8 @@ function createHook (meta) {
|
|
|
103
109
|
|
|
104
110
|
return {
|
|
105
111
|
url: addIitm(url.url),
|
|
106
|
-
shortCircuit: true
|
|
112
|
+
shortCircuit: true,
|
|
113
|
+
format: url.format
|
|
107
114
|
}
|
|
108
115
|
}
|
|
109
116
|
|
|
@@ -111,8 +118,7 @@ function createHook (meta) {
|
|
|
111
118
|
async function getSource (url, context, parentGetSource) {
|
|
112
119
|
if (hasIitm(url)) {
|
|
113
120
|
const realUrl = deleteIitm(url)
|
|
114
|
-
const
|
|
115
|
-
const exportNames = Object.keys(realModule)
|
|
121
|
+
const exportNames = await getExports(realUrl, context, parentGetSource)
|
|
116
122
|
return {
|
|
117
123
|
source: `
|
|
118
124
|
import { register } from '${iitmURL}'
|
|
@@ -137,7 +143,7 @@ register('${realUrl}', namespace, set, '${specifiers.get(realUrl)}')
|
|
|
137
143
|
// For Node.js 16.12.0 and higher.
|
|
138
144
|
async function load (url, context, parentLoad) {
|
|
139
145
|
if (hasIitm(url)) {
|
|
140
|
-
const { source } = await getSource(url, context)
|
|
146
|
+
const { source } = await getSource(url, context, parentLoad)
|
|
141
147
|
return {
|
|
142
148
|
source,
|
|
143
149
|
shortCircuit: true,
|
|
@@ -148,10 +154,7 @@ register('${realUrl}', namespace, set, '${specifiers.get(realUrl)}')
|
|
|
148
154
|
return parentLoad(url, context, parentLoad)
|
|
149
155
|
}
|
|
150
156
|
|
|
151
|
-
if (NODE_MAJOR >=
|
|
152
|
-
process.emitWarning('import-in-the-middle is currently unsupported on Node.js v20 and has been disabled.')
|
|
153
|
-
return {} // TODO: Add support for Node >=20
|
|
154
|
-
} else if (NODE_MAJOR >= 17 || (NODE_MAJOR === 16 && NODE_MINOR >= 12)) {
|
|
157
|
+
if (NODE_MAJOR >= 17 || (NODE_MAJOR === 16 && NODE_MINOR >= 12)) {
|
|
155
158
|
return { load, resolve }
|
|
156
159
|
} else {
|
|
157
160
|
return {
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { Parser } = require('acorn')
|
|
4
|
+
const { importAssertions } = require('acorn-import-assertions');
|
|
5
|
+
|
|
6
|
+
const acornOpts = {
|
|
7
|
+
ecmaVersion: 'latest',
|
|
8
|
+
sourceType: 'module'
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const parser = Parser.extend(importAssertions)
|
|
12
|
+
|
|
13
|
+
function warn (txt) {
|
|
14
|
+
process.emitWarning(txt, 'get-esm-exports')
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function getEsmExports (moduleStr) {
|
|
18
|
+
const exportedNames = new Set()
|
|
19
|
+
const tree = parser.parse(moduleStr, acornOpts)
|
|
20
|
+
for (const node of tree.body) {
|
|
21
|
+
if (!node.type.startsWith('Export')) continue
|
|
22
|
+
switch (node.type) {
|
|
23
|
+
case 'ExportNamedDeclaration':
|
|
24
|
+
if (node.declaration) {
|
|
25
|
+
parseDeclaration(node, exportedNames)
|
|
26
|
+
} else {
|
|
27
|
+
parseSpecifiers(node, exportedNames)
|
|
28
|
+
}
|
|
29
|
+
break
|
|
30
|
+
case 'ExportDefaultDeclaration':
|
|
31
|
+
exportedNames.add('default')
|
|
32
|
+
break
|
|
33
|
+
case 'ExportAllDeclaration':
|
|
34
|
+
if (node.exported) {
|
|
35
|
+
exportedNames.add(node.exported.name)
|
|
36
|
+
} else {
|
|
37
|
+
exportedNames.add('*')
|
|
38
|
+
}
|
|
39
|
+
break
|
|
40
|
+
default:
|
|
41
|
+
warn('unrecognized export type: ' + node.type)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return Array.from(exportedNames)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function parseDeclaration (node, exportedNames) {
|
|
48
|
+
switch (node.declaration.type) {
|
|
49
|
+
case 'FunctionDeclaration':
|
|
50
|
+
exportedNames.add(node.declaration.id.name)
|
|
51
|
+
break
|
|
52
|
+
case 'VariableDeclaration':
|
|
53
|
+
for (const varDecl of node.declaration.declarations) {
|
|
54
|
+
parseVariableDeclaration(varDecl, exportedNames)
|
|
55
|
+
}
|
|
56
|
+
break
|
|
57
|
+
case 'ClassDeclaration':
|
|
58
|
+
exportedNames.add(node.declaration.id.name)
|
|
59
|
+
break
|
|
60
|
+
default:
|
|
61
|
+
warn('unknown declaration type: ' + node.delcaration.type)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function parseVariableDeclaration (node, exportedNames) {
|
|
66
|
+
switch (node.id.type) {
|
|
67
|
+
case 'Identifier':
|
|
68
|
+
exportedNames.add(node.id.name)
|
|
69
|
+
break
|
|
70
|
+
case 'ObjectPattern':
|
|
71
|
+
for (const prop of node.id.properties) {
|
|
72
|
+
exportedNames.add(prop.value.name)
|
|
73
|
+
}
|
|
74
|
+
break
|
|
75
|
+
case 'ArrayPattern':
|
|
76
|
+
for (const elem of node.id.elements) {
|
|
77
|
+
exportedNames.add(elem.name)
|
|
78
|
+
}
|
|
79
|
+
break
|
|
80
|
+
default:
|
|
81
|
+
warn('unknown variable declaration type: ' + node.id.type)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function parseSpecifiers (node, exportedNames) {
|
|
86
|
+
for (const specifier of node.specifiers) {
|
|
87
|
+
if (specifier.exported.type === 'Identifier') {
|
|
88
|
+
exportedNames.add(specifier.exported.name)
|
|
89
|
+
} else if (specifier.exported.type === 'Literal') {
|
|
90
|
+
exportedNames.add(specifier.exported.value)
|
|
91
|
+
} else {
|
|
92
|
+
warn('unrecognized specifier type: ' + specifier.exported.type)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
module.exports = getEsmExports
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const getEsmExports = require('./get-esm-exports.js')
|
|
4
|
+
const { parse: getCjsExports } = require('cjs-module-lexer')
|
|
5
|
+
const fs = require('fs')
|
|
6
|
+
const { fileURLToPath } = require('url')
|
|
7
|
+
|
|
8
|
+
function addDefault(arr) {
|
|
9
|
+
return Array.from(new Set(['default', ...arr]))
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async function getExports (url, context, parentLoad) {
|
|
13
|
+
// `parentLoad` gives us the possibility of getting the source
|
|
14
|
+
// from an upstream loader. This doesn't always work though,
|
|
15
|
+
// so later on we fall back to reading it from disk.
|
|
16
|
+
const parentCtx = await parentLoad(url, context)
|
|
17
|
+
let source = parentCtx.source
|
|
18
|
+
const format = parentCtx.format
|
|
19
|
+
|
|
20
|
+
// TODO support non-node/file urls somehow?
|
|
21
|
+
if (format === 'builtin') {
|
|
22
|
+
// Builtins don't give us the source property, so we're stuck
|
|
23
|
+
// just requiring it to get the exports.
|
|
24
|
+
return addDefault(Object.keys(require(url)))
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (!source) {
|
|
28
|
+
// Sometimes source is retrieved by parentLoad, sometimes it isn't.
|
|
29
|
+
source = fs.readFileSync(fileURLToPath(url), 'utf8')
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (format === 'module') {
|
|
33
|
+
return getEsmExports(source)
|
|
34
|
+
}
|
|
35
|
+
if (format === 'commonjs') {
|
|
36
|
+
return addDefault(getCjsExports(source).exports)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// At this point our `format` is either undefined or not known by us. Fall
|
|
40
|
+
// back to parsing as ESM/CJS.
|
|
41
|
+
const esmExports = getEsmExports(source)
|
|
42
|
+
if (!esmExports.length) {
|
|
43
|
+
// TODO(bengl) it's might be possible to get here if somehow the format
|
|
44
|
+
// isn't set at first and yet we have an ESM module with no exports.
|
|
45
|
+
// I couldn't construct an example that would do this, so maybe it's
|
|
46
|
+
// impossible?
|
|
47
|
+
return addDefault(getCjsExports(source).exports)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
module.exports = getExports
|
package/package.json
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "import-in-the-middle",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "Intercept imports in Node.js",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
7
|
-
"test": "c8 --check-coverage --lines 85 imhotap --runner test/runtest --files test/{hook,low-level,other}/*",
|
|
8
|
-
"test
|
|
9
|
-
"test-win": "c8 --check-coverage --lines 85 imhotap --runner test\\runtest.bat --files test/{hook,low-level,other}/*",
|
|
7
|
+
"test": "c8 --check-coverage --lines 85 imhotap --runner test/runtest --files test/{hook,low-level,other,get-esm-exports}/*",
|
|
8
|
+
"test-win": "c8 --check-coverage --lines 85 imhotap --runner test\\runtest.bat --files test/{hook,low-level,other,get-esm-exports}/*",
|
|
10
9
|
"test:ts": "c8 imhotap --runner test/runtest --files test/typescript/*.test.mts",
|
|
11
10
|
"test-win:ts": "c8 imhotap --runner test\\runtest.bat --files test/typescript/*.test.mts",
|
|
12
|
-
"coverage": "c8 --reporter html imhotap --runner test/runtest --files test/{hook,low-level,other}/* && echo '\nNow open coverage/index.html\n'"
|
|
11
|
+
"coverage": "c8 --reporter html imhotap --runner test/runtest --files test/{hook,low-level,other,get-esm-exports}/* && echo '\nNow open coverage/index.html\n'"
|
|
13
12
|
},
|
|
14
13
|
"repository": {
|
|
15
14
|
"type": "git",
|
|
@@ -37,6 +36,9 @@
|
|
|
37
36
|
"typescript": "^4.7.4"
|
|
38
37
|
},
|
|
39
38
|
"dependencies": {
|
|
39
|
+
"acorn": "^8.8.2",
|
|
40
|
+
"acorn-import-assertions": "^1.9.0",
|
|
41
|
+
"cjs-module-lexer": "^1.2.2",
|
|
40
42
|
"module-details-from-path": "^1.0.3"
|
|
41
43
|
}
|
|
42
44
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Exporting declarations
|
|
2
|
+
export let name1, name2/*, … */; // also var //| name1,name2
|
|
3
|
+
export const name1 = 1, name2 = 2/*, … */; // also var, let //| name1,name2
|
|
4
|
+
export function functionName() { /* … */ } //| functionName
|
|
5
|
+
export class ClassName { /* … */ } //| ClassName
|
|
6
|
+
export function* generatorFunctionName() { /* … */ } //| generatorFunctionName
|
|
7
|
+
export const { name1, name2: bar } = o; //| name1,bar
|
|
8
|
+
export const [ name1, name2 ] = array; //| name1,name2
|
|
9
|
+
|
|
10
|
+
// Export list
|
|
11
|
+
let name1, nameN; export { name1, /* …, */ nameN }; //| name1,nameN
|
|
12
|
+
let variable1, variable2, nameN; export { variable1 as name1, variable2 as name2, /* …, */ nameN }; //| name1,name2,nameN
|
|
13
|
+
let variable1; export { variable1 as "string name" }; //| string name
|
|
14
|
+
let name1; export { name1 as default /*, … */ }; //| default
|
|
15
|
+
|
|
16
|
+
// Default exports
|
|
17
|
+
export default expression; //| default
|
|
18
|
+
export default function functionName() { /* … */ } //| default
|
|
19
|
+
export default class ClassName { /* … */ } //| default
|
|
20
|
+
export default function* generatorFunctionName() { /* … */ } //| default
|
|
21
|
+
export default function () { /* … */ } //| default
|
|
22
|
+
export default class { /* … */ } //| default
|
|
23
|
+
export default function* () { /* … */ } //| default
|
|
24
|
+
|
|
25
|
+
// Aggregating modules
|
|
26
|
+
export * from "module-name"; //| *
|
|
27
|
+
export * as name1 from "module-name"; //| name1
|
|
28
|
+
export { name1, /* …, */ nameN } from "module-name"; //| name1,nameN
|
|
29
|
+
export { import1 as name1, import2 as name2, /* …, */ nameN } from "module-name"; //| name1,name2,nameN
|
|
30
|
+
export { default, /* …, */ } from "module-name"; //| default
|
|
31
|
+
export { default as name1 } from "module-name"; //| name1
|
|
32
|
+
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const getEsmExports = require('../../lib/get-esm-exports.js')
|
|
4
|
+
const fs = require('fs')
|
|
5
|
+
const assert = require('assert')
|
|
6
|
+
const path = require('path')
|
|
7
|
+
|
|
8
|
+
const fixturePath = path.join(__dirname, '../fixtures/esm-exports.txt')
|
|
9
|
+
const fixture = fs.readFileSync(fixturePath, 'utf8')
|
|
10
|
+
|
|
11
|
+
fixture.split('\n').forEach(line => {
|
|
12
|
+
if (!line.includes(' //| ')) return
|
|
13
|
+
const [mod, testStr] = line.split(' //| ')
|
|
14
|
+
const expectedNames = testStr.split(',').map(x => x.trim())
|
|
15
|
+
if (expectedNames[0] === '') {
|
|
16
|
+
expectedNames.length = 0
|
|
17
|
+
}
|
|
18
|
+
const names = getEsmExports(mod)
|
|
19
|
+
assert.deepEqual(expectedNames, names)
|
|
20
|
+
console.log(`${mod}\n ✅ contains exports: ${testStr}`)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
// // Generate fixture data
|
|
24
|
+
// fixture.split('\n').forEach(line => {
|
|
25
|
+
// if (!line.includes('export ')) {
|
|
26
|
+
// console.log(line)
|
|
27
|
+
// return
|
|
28
|
+
// }
|
|
29
|
+
// const names = getEsmExports(line)
|
|
30
|
+
// console.log(line, '//|', names.join(','))
|
|
31
|
+
// })
|