import-in-the-middle 1.8.0 → 1.8.1
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 +54 -15
- package/index.d.ts +1 -1
- package/lib/get-esm-exports.js +2 -2
- package/lib/get-exports.js +54 -30
- package/package.json +1 -1
- package/test/fixtures/duplicate-b.mjs +1 -1
- package/test/fixtures/duplicate-c.mjs +1 -0
- package/test/fixtures/duplicate-explicit.mjs +5 -0
- package/test/fixtures/json-attributes.mjs +5 -0
- package/test/fixtures/re-export-cjs-built-in.js +1 -0
- package/test/fixtures/re-export-cjs.js +1 -0
- package/test/get-esm-exports/v20-get-esm-exports.js +1 -1
- package/test/hook/duplicate-exports-explicit.mjs +13 -0
- package/test/hook/duplicate-exports.mjs +3 -3
- package/test/hook/re-export-cjs.mjs +19 -0
- package/test/hook/v18.19-openai.mjs +6 -0
- package/test/hook/v20.10-static-import-attributes.mjs +17 -0
- package/test/other/v20.10-import-executable.mjs +8 -0
- package/test/typescript/ts-node.test.mts +10 -2
- package/test/other/import-executable.mjs +0 -19
- /package/test/{other → fixtures}/executable +0 -0
package/hook.js
CHANGED
|
@@ -129,15 +129,32 @@ function isBareSpecifier (specifier) {
|
|
|
129
129
|
*/
|
|
130
130
|
async function processModule ({ srcUrl, context, parentGetSource, parentResolve, excludeDefault }) {
|
|
131
131
|
const exportNames = await getExports(srcUrl, context, parentGetSource)
|
|
132
|
-
const
|
|
132
|
+
const starExports = new Set()
|
|
133
133
|
const setters = new Map()
|
|
134
134
|
|
|
135
|
-
const addSetter = (name, setter) => {
|
|
136
|
-
// When doing an `import *` duplicates become undefined, so do the same
|
|
135
|
+
const addSetter = (name, setter, isStarExport = false) => {
|
|
137
136
|
if (setters.has(name)) {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
137
|
+
if (isStarExport) {
|
|
138
|
+
// If there's already a matching star export, delete it
|
|
139
|
+
if (starExports.has(name)) {
|
|
140
|
+
setters.delete(name)
|
|
141
|
+
}
|
|
142
|
+
// and return so this is excluded
|
|
143
|
+
return
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// if we already have this export but it is from a * export, overwrite it
|
|
147
|
+
if (starExports.has(name)) {
|
|
148
|
+
starExports.delete(name)
|
|
149
|
+
setters.set(name, setter)
|
|
150
|
+
}
|
|
151
|
+
} else {
|
|
152
|
+
// Store export * exports so we know they can be overridden by explicit
|
|
153
|
+
// named exports
|
|
154
|
+
if (isStarExport) {
|
|
155
|
+
starExports.add(name)
|
|
156
|
+
}
|
|
157
|
+
|
|
141
158
|
setters.set(name, setter)
|
|
142
159
|
}
|
|
143
160
|
}
|
|
@@ -161,10 +178,11 @@ async function processModule ({ srcUrl, context, parentGetSource, parentResolve,
|
|
|
161
178
|
srcUrl: modUrl,
|
|
162
179
|
context,
|
|
163
180
|
parentGetSource,
|
|
181
|
+
parentResolve,
|
|
164
182
|
excludeDefault: true
|
|
165
183
|
})
|
|
166
184
|
for (const [name, setter] of setters.entries()) {
|
|
167
|
-
addSetter(name, setter)
|
|
185
|
+
addSetter(name, setter, true)
|
|
168
186
|
}
|
|
169
187
|
} else {
|
|
170
188
|
addSetter(n, `
|
|
@@ -246,14 +264,16 @@ function createHook (meta) {
|
|
|
246
264
|
async function getSource (url, context, parentGetSource) {
|
|
247
265
|
if (hasIitm(url)) {
|
|
248
266
|
const realUrl = deleteIitm(url)
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
267
|
+
|
|
268
|
+
try {
|
|
269
|
+
const setters = await processModule({
|
|
270
|
+
srcUrl: realUrl,
|
|
271
|
+
context,
|
|
272
|
+
parentGetSource,
|
|
273
|
+
parentResolve: cachedResolve
|
|
274
|
+
})
|
|
275
|
+
return {
|
|
276
|
+
source: `
|
|
257
277
|
import { register } from '${iitmURL}'
|
|
258
278
|
import * as namespace from ${JSON.stringify(realUrl)}
|
|
259
279
|
|
|
@@ -268,6 +288,25 @@ ${Array.from(setters.values()).join('\n')}
|
|
|
268
288
|
|
|
269
289
|
register(${JSON.stringify(realUrl)}, _, set, ${JSON.stringify(specifiers.get(realUrl))})
|
|
270
290
|
`
|
|
291
|
+
}
|
|
292
|
+
} catch (cause) {
|
|
293
|
+
// If there are other ESM loader hooks registered as well as iitm,
|
|
294
|
+
// depending on the order they are registered, source might not be
|
|
295
|
+
// JavaScript.
|
|
296
|
+
//
|
|
297
|
+
// If we fail to parse a module for exports, we should fall back to the
|
|
298
|
+
// parent loader. These modules will not be wrapped with proxies and
|
|
299
|
+
// cannot be Hook'ed but at least this does not take down the entire app
|
|
300
|
+
// and block iitm from being used.
|
|
301
|
+
//
|
|
302
|
+
// We log the error because there might be bugs in iitm and without this
|
|
303
|
+
// it would be very tricky to debug
|
|
304
|
+
const err = new Error(`'import-in-the-middle' failed to wrap '${realUrl}'`)
|
|
305
|
+
err.cause = cause
|
|
306
|
+
console.warn(err)
|
|
307
|
+
|
|
308
|
+
// Revert back to the non-iitm URL
|
|
309
|
+
url = realUrl
|
|
271
310
|
}
|
|
272
311
|
}
|
|
273
312
|
|
package/index.d.ts
CHANGED
|
@@ -27,7 +27,7 @@ export type Options = {
|
|
|
27
27
|
internals?: boolean
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
declare class Hook {
|
|
30
|
+
export declare class Hook {
|
|
31
31
|
/**
|
|
32
32
|
* Creates a hook to be run on any already loaded modules and any that will
|
|
33
33
|
* be loaded in the future. It will be run once per loaded module. If
|
package/lib/get-esm-exports.js
CHANGED
|
@@ -29,7 +29,7 @@ function warn (txt) {
|
|
|
29
29
|
* @param {string} params.moduleSource The source code of the module to parse
|
|
30
30
|
* and interpret.
|
|
31
31
|
*
|
|
32
|
-
* @returns {string
|
|
32
|
+
* @returns {Set<string>} The identifiers exported by the module along with any
|
|
33
33
|
* custom directives.
|
|
34
34
|
*/
|
|
35
35
|
function getEsmExports (moduleSource) {
|
|
@@ -62,7 +62,7 @@ function getEsmExports (moduleSource) {
|
|
|
62
62
|
warn('unrecognized export type: ' + node.type)
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
|
-
return
|
|
65
|
+
return exportedNames
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
function parseDeclaration (node, exportedNames) {
|
package/lib/get-exports.js
CHANGED
|
@@ -1,36 +1,60 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const getEsmExports = require('./get-esm-exports.js')
|
|
4
|
-
const { parse:
|
|
5
|
-
const
|
|
4
|
+
const { parse: parseCjs } = require('cjs-module-lexer')
|
|
5
|
+
const { readFileSync } = require('fs')
|
|
6
|
+
const { builtinModules } = require('module')
|
|
6
7
|
const { fileURLToPath, pathToFileURL } = require('url')
|
|
8
|
+
const { dirname } = require('path')
|
|
7
9
|
|
|
8
10
|
function addDefault (arr) {
|
|
9
|
-
return
|
|
11
|
+
return new Set(['default', ...arr])
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Cached exports for Node built-in modules
|
|
15
|
+
const BUILT_INS = new Map()
|
|
16
|
+
|
|
17
|
+
function getExportsForNodeBuiltIn (name) {
|
|
18
|
+
let exports = BUILT_INS.get()
|
|
19
|
+
|
|
20
|
+
if (!exports) {
|
|
21
|
+
exports = new Set(addDefault(Object.keys(require(name))))
|
|
22
|
+
BUILT_INS.set(name, exports)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return exports
|
|
10
26
|
}
|
|
11
27
|
|
|
12
28
|
const urlsBeingProcessed = new Set() // Guard against circular imports.
|
|
13
29
|
|
|
14
|
-
async function
|
|
30
|
+
async function getCjsExports (url, context, parentLoad, source) {
|
|
15
31
|
if (urlsBeingProcessed.has(url)) {
|
|
16
32
|
return []
|
|
17
33
|
}
|
|
18
34
|
urlsBeingProcessed.add(url)
|
|
19
35
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
36
|
+
try {
|
|
37
|
+
const result = parseCjs(source)
|
|
38
|
+
const full = addDefault(result.exports)
|
|
39
|
+
|
|
40
|
+
await Promise.all(result.reexports.map(async re => {
|
|
41
|
+
if (re.startsWith('node:') || builtinModules.includes(re)) {
|
|
42
|
+
for (const each of getExportsForNodeBuiltIn(re)) {
|
|
43
|
+
full.add(each)
|
|
44
|
+
}
|
|
45
|
+
} else {
|
|
46
|
+
// Resolve the re-exported module relative to the current module.
|
|
47
|
+
const newUrl = pathToFileURL(require.resolve(re, { paths: [dirname(fileURLToPath(url))] })).href
|
|
48
|
+
for (const each of await getExports(newUrl, context, parentLoad)) {
|
|
49
|
+
full.add(each)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}))
|
|
53
|
+
|
|
54
|
+
return full
|
|
55
|
+
} finally {
|
|
56
|
+
urlsBeingProcessed.delete(url)
|
|
57
|
+
}
|
|
34
58
|
}
|
|
35
59
|
|
|
36
60
|
/**
|
|
@@ -45,7 +69,7 @@ async function getFullCjsExports (url, context, parentLoad, source) {
|
|
|
45
69
|
* @param {Function} parentLoad Next hook function in the loaders API
|
|
46
70
|
* hook chain.
|
|
47
71
|
*
|
|
48
|
-
* @returns {Promise<string
|
|
72
|
+
* @returns {Promise<Set<string>>} An array of identifiers exported by the module.
|
|
49
73
|
* Please see {@link getEsmExports} for caveats on special identifiers that may
|
|
50
74
|
* be included in the result set.
|
|
51
75
|
*/
|
|
@@ -57,23 +81,23 @@ async function getExports (url, context, parentLoad) {
|
|
|
57
81
|
let source = parentCtx.source
|
|
58
82
|
const format = parentCtx.format
|
|
59
83
|
|
|
60
|
-
// TODO support non-node/file urls somehow?
|
|
61
|
-
if (format === 'builtin') {
|
|
62
|
-
// Builtins don't give us the source property, so we're stuck
|
|
63
|
-
// just requiring it to get the exports.
|
|
64
|
-
return addDefault(Object.keys(require(url)))
|
|
65
|
-
}
|
|
66
|
-
|
|
67
84
|
if (!source) {
|
|
68
|
-
|
|
69
|
-
|
|
85
|
+
if (format === 'builtin') {
|
|
86
|
+
// Builtins don't give us the source property, so we're stuck
|
|
87
|
+
// just requiring it to get the exports.
|
|
88
|
+
return getExportsForNodeBuiltIn(url)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Sometimes source is retrieved by parentLoad, CommonJs isn't.
|
|
92
|
+
source = readFileSync(fileURLToPath(url), 'utf8')
|
|
70
93
|
}
|
|
71
94
|
|
|
72
95
|
if (format === 'module') {
|
|
73
96
|
return getEsmExports(source)
|
|
74
97
|
}
|
|
98
|
+
|
|
75
99
|
if (format === 'commonjs') {
|
|
76
|
-
return
|
|
100
|
+
return getCjsExports(url, context, parentLoad, source)
|
|
77
101
|
}
|
|
78
102
|
|
|
79
103
|
// At this point our `format` is either undefined or not known by us. Fall
|
|
@@ -84,7 +108,7 @@ async function getExports (url, context, parentLoad) {
|
|
|
84
108
|
// isn't set at first and yet we have an ESM module with no exports.
|
|
85
109
|
// I couldn't construct an example that would do this, so maybe it's
|
|
86
110
|
// impossible?
|
|
87
|
-
return
|
|
111
|
+
return getCjsExports(url, context, parentLoad, source)
|
|
88
112
|
}
|
|
89
113
|
}
|
|
90
114
|
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const foo = '
|
|
1
|
+
export const foo = 'b'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const foo = 'c'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
module.exports = require('util').deprecate
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
module.exports = require('some-external-cjs-module').foo
|
|
@@ -15,7 +15,7 @@ fixture.split('\n').forEach(line => {
|
|
|
15
15
|
if (expectedNames[0] === '') {
|
|
16
16
|
expectedNames.length = 0
|
|
17
17
|
}
|
|
18
|
-
const names = getEsmExports(mod)
|
|
18
|
+
const names = Array.from(getEsmExports(mod))
|
|
19
19
|
assert.deepEqual(expectedNames, names)
|
|
20
20
|
console.log(`${mod}\n ✅ contains exports: ${testStr}`)
|
|
21
21
|
})
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import * as lib from '../fixtures/duplicate-explicit.mjs'
|
|
2
|
+
import { strictEqual } from 'assert'
|
|
3
|
+
import Hook from '../../index.js'
|
|
4
|
+
|
|
5
|
+
Hook((exports, name) => {
|
|
6
|
+
if (name.endsWith('duplicate-explicit.mjs')) {
|
|
7
|
+
strictEqual(exports.foo, 'c')
|
|
8
|
+
exports.foo += '-wrapped'
|
|
9
|
+
}
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
// foo should not be exported because there are duplicate exports
|
|
13
|
+
strictEqual(lib.foo, 'c-wrapped')
|
|
@@ -4,8 +4,8 @@ import Hook from '../../index.js'
|
|
|
4
4
|
|
|
5
5
|
Hook((exports, name) => {
|
|
6
6
|
if (name.match(/duplicate\.mjs/)) {
|
|
7
|
-
// foo should not be exported because there are duplicate exports
|
|
8
|
-
strictEqual(exports
|
|
7
|
+
// foo should not be exported because there are duplicate * exports
|
|
8
|
+
strictEqual('foo' in exports, false)
|
|
9
9
|
// default should be exported
|
|
10
10
|
strictEqual(exports.default, 'foo')
|
|
11
11
|
}
|
|
@@ -14,6 +14,6 @@ Hook((exports, name) => {
|
|
|
14
14
|
notEqual(lib, undefined)
|
|
15
15
|
|
|
16
16
|
// foo should not be exported because there are duplicate exports
|
|
17
|
-
strictEqual(lib
|
|
17
|
+
strictEqual('foo' in lib, false)
|
|
18
18
|
// default should be exported
|
|
19
19
|
strictEqual(lib.default, 'foo')
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import Hook from '../../index.js'
|
|
2
|
+
import foo from '../fixtures/re-export-cjs-built-in.js'
|
|
3
|
+
import foo2 from '../fixtures/re-export-cjs.js'
|
|
4
|
+
import { strictEqual } from 'assert'
|
|
5
|
+
|
|
6
|
+
Hook((exports, name) => {
|
|
7
|
+
if (name.endsWith('fixtures/re-export-cjs-built-in.js')) {
|
|
8
|
+
strictEqual(typeof exports.default, 'function')
|
|
9
|
+
exports.default = '1'
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
if (name.endsWith('fixtures/re-export-cjs.js')) {
|
|
13
|
+
strictEqual(exports.default, 'bar')
|
|
14
|
+
exports.default = '2'
|
|
15
|
+
}
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
strictEqual(foo, '1')
|
|
19
|
+
strictEqual(foo2, '2')
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2.0 License.
|
|
2
|
+
//
|
|
3
|
+
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc.
|
|
4
|
+
|
|
5
|
+
import jsonMjs from '../fixtures/json-attributes.mjs'
|
|
6
|
+
import { strictEqual } from 'assert'
|
|
7
|
+
|
|
8
|
+
// Acorn does not support import attributes so an error is logged but the import
|
|
9
|
+
// still works!
|
|
10
|
+
//
|
|
11
|
+
// Hook((exports, name) => {
|
|
12
|
+
// if (name.match(/json\.mjs/)) {
|
|
13
|
+
// exports.default.data += '-dawg'
|
|
14
|
+
// }
|
|
15
|
+
// })
|
|
16
|
+
|
|
17
|
+
strictEqual(jsonMjs.data, 'dog')
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2.0 License.
|
|
2
|
+
//
|
|
3
|
+
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc.
|
|
4
|
+
|
|
5
|
+
(async () => {
|
|
6
|
+
const lib = await import('../fixtures/executable')
|
|
7
|
+
console.assert(lib)
|
|
8
|
+
})()
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import assert from 'node:assert/strict'
|
|
2
|
-
import { addHook } from '../../index.js'
|
|
2
|
+
import defaultHook, { Hook, addHook } from '../../index.js'
|
|
3
3
|
import { sayHi } from '../fixtures/say-hi.mjs'
|
|
4
4
|
|
|
5
5
|
addHook((url, exported) => {
|
|
@@ -8,4 +8,12 @@ addHook((url, exported) => {
|
|
|
8
8
|
}
|
|
9
9
|
})
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
new defaultHook((exported: any, name: string, baseDir: string|void) => {
|
|
12
|
+
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
new Hook((exported: any, name: string, baseDir: string|void) => {
|
|
16
|
+
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
assert.equal(sayHi('test'), 'Hooked')
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2.0 License.
|
|
2
|
-
//
|
|
3
|
-
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc.
|
|
4
|
-
|
|
5
|
-
import { rejects } from 'assert'
|
|
6
|
-
(async () => {
|
|
7
|
-
const [processMajor, processMinor] = process.versions.node.split('.').map(Number)
|
|
8
|
-
const extensionlessSupported = processMajor >= 21 ||
|
|
9
|
-
(processMajor === 20 && processMinor >= 10) ||
|
|
10
|
-
(processMajor === 18 && processMinor >= 19)
|
|
11
|
-
if (extensionlessSupported) {
|
|
12
|
-
// Files without extension are supported in Node.js ^21, ^20.10.0, and ^18.19.0
|
|
13
|
-
return
|
|
14
|
-
}
|
|
15
|
-
await rejects(() => import('./executable'), {
|
|
16
|
-
name: 'TypeError',
|
|
17
|
-
code: 'ERR_UNKNOWN_FILE_EXTENSION'
|
|
18
|
-
})
|
|
19
|
-
})()
|
|
File without changes
|