import-in-the-middle 1.7.3 → 1.8.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/README.md +1 -1
- package/hook.js +97 -151
- package/index.js +1 -0
- package/lib/get-esm-exports.js +5 -26
- package/lib/get-exports.js +9 -16
- package/package.json +6 -2
- package/test/README.md +1 -1
- package/test/fixtures/cyclical-self.mjs +8 -0
- package/test/fixtures/duplicate-a.mjs +4 -0
- package/test/fixtures/duplicate-b.mjs +1 -0
- package/test/fixtures/duplicate.mjs +5 -0
- package/test/fixtures/export-types/default-call-expression-renamed.mjs +2 -0
- package/test/fixtures/export-types/default-call-expression.mjs +1 -0
- package/test/fixtures/got-alike.mjs +0 -6
- package/test/fixtures/re-export-star-external.mjs +1 -0
- package/test/fixtures/sub-directory/re-export-star-external.mjs +1 -0
- package/test/get-esm-exports/v20-get-esm-exports.js +1 -1
- package/test/hook/cyclical-self.mjs +4 -0
- package/test/hook/default-export.mjs +8 -0
- package/test/hook/duplicate-exports.mjs +19 -0
- package/test/hook/dynamic-import.js +1 -1
- package/test/hook/module-toStringTag.mjs +25 -0
- package/test/hook/re-export-star-module.mjs +17 -0
- package/test/hook/static-import-default.mjs +1 -1
- package/test/hook/v14-date-fns.mjs +10 -0
- package/test/hook/v14-react-email-components.mjs +16 -0
- package/test/hook/v16-got.mjs +16 -0
- package/test/hook/v18.19-openai.mjs +10 -0
- package/test/hook/v18.19-static-import-gotalike.mjs +4 -13
- package/test/multiple-loaders/multiple-loaders.test.mjs +6 -0
- package/test/multiple-loaders/typescript-loader.mjs +26 -0
- package/test/version-check.js +7 -1
- package/test/fixtures/default-class.mjs +0 -3
- package/test/fixtures/snake_case.mjs +0 -2
- /package/test/hook/{v18-static-import-assert.mjs → v18-v21-static-import-assert.mjs} +0 -0
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@ imported in ESM files, regardless of whether they're imported statically or
|
|
|
18
18
|
dynamically.
|
|
19
19
|
|
|
20
20
|
```js
|
|
21
|
-
import Hook from 'import-in-the-middle'
|
|
21
|
+
import { Hook } from 'import-in-the-middle'
|
|
22
22
|
import { foo } from 'package-i-want-to-modify'
|
|
23
23
|
|
|
24
24
|
console.log(foo) // whatever that module exported
|
package/hook.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
//
|
|
3
3
|
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc.
|
|
4
4
|
|
|
5
|
-
const {
|
|
5
|
+
const { URL } = require('url')
|
|
6
6
|
const specifiers = new Map()
|
|
7
7
|
const isWin = process.platform === 'win32'
|
|
8
8
|
|
|
@@ -19,7 +19,7 @@ let getExports
|
|
|
19
19
|
if (NODE_MAJOR >= 20 || (NODE_MAJOR === 18 && NODE_MINOR >= 19)) {
|
|
20
20
|
getExports = require('./lib/get-exports.js')
|
|
21
21
|
} else {
|
|
22
|
-
getExports = (
|
|
22
|
+
getExports = (url) => import(url).then(Object.keys)
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
function hasIitm (url) {
|
|
@@ -91,135 +91,94 @@ function isStarExportLine (line) {
|
|
|
91
91
|
return /^\* from /.test(line)
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
94
|
+
function isBareSpecifier (specifier) {
|
|
95
|
+
// Relative and absolute paths are not bare specifiers.
|
|
96
|
+
if (
|
|
97
|
+
specifier.startsWith('.') ||
|
|
98
|
+
specifier.startsWith('/')) {
|
|
99
|
+
return false
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Valid URLs are not bare specifiers. (file:, http:, node:, etc.)
|
|
103
|
+
|
|
104
|
+
// eslint-disable-next-line no-prototype-builtins
|
|
105
|
+
if (URL.hasOwnProperty('canParse')) {
|
|
106
|
+
return !URL.canParse(specifier)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
// eslint-disable-next-line no-new
|
|
111
|
+
new URL(specifier)
|
|
112
|
+
return false
|
|
113
|
+
} catch (err) {
|
|
114
|
+
return true
|
|
115
|
+
}
|
|
116
|
+
}
|
|
106
117
|
|
|
107
118
|
/**
|
|
108
|
-
* Processes a module's exports and builds a set of
|
|
109
|
-
* namespace names, and setter blocks. If an export all export if encountered,
|
|
110
|
-
* the target exports will be hoisted to the current module via a generated
|
|
111
|
-
* namespace.
|
|
119
|
+
* Processes a module's exports and builds a set of setter blocks.
|
|
112
120
|
*
|
|
113
121
|
* @param {object} params
|
|
114
122
|
* @param {string} params.srcUrl The full URL to the module to process.
|
|
115
123
|
* @param {object} params.context Provided by the loaders API.
|
|
116
|
-
* @param {Function} params.parentGetSource Provides the source code for the
|
|
117
|
-
*
|
|
118
|
-
* @param {string} [params.ns='namespace'] A string identifier that will be
|
|
119
|
-
* used as the namespace for the identifiers exported by the module.
|
|
120
|
-
* @param {string} [params.defaultAs='default'] The name to give the default
|
|
121
|
-
* identifier exported by the module (if one exists). This is really only
|
|
122
|
-
* useful in a recursive situation where a transitive module's default export
|
|
123
|
-
* needs to be renamed to the name of the module.
|
|
124
|
+
* @param {Function} params.parentGetSource Provides the source code for the parent module.
|
|
125
|
+
* @param {bool} params.excludeDefault Exclude the default export.
|
|
124
126
|
*
|
|
125
|
-
* @returns {Promise<
|
|
127
|
+
* @returns {Promise<Map<string, string>>} The shimmed setters for all the exports
|
|
128
|
+
* from the module and any transitive export all modules.
|
|
126
129
|
*/
|
|
127
|
-
async function processModule ({
|
|
128
|
-
srcUrl,
|
|
129
|
-
|
|
130
|
-
parentGetSource,
|
|
131
|
-
ns = 'namespace',
|
|
132
|
-
defaultAs = 'default'
|
|
133
|
-
}) {
|
|
134
|
-
const exportNames = await getExports({
|
|
135
|
-
url: srcUrl,
|
|
136
|
-
context,
|
|
137
|
-
parentLoad: parentGetSource,
|
|
138
|
-
defaultAs
|
|
139
|
-
})
|
|
140
|
-
const imports = [`import * as ${ns} from ${JSON.stringify(srcUrl)}`]
|
|
141
|
-
const namespaces = [ns]
|
|
142
|
-
|
|
143
|
-
// As we iterate found module exports we will add setter code blocks
|
|
144
|
-
// to this map that will eventually be inserted into the shim module's
|
|
145
|
-
// source code. We utilize a map in order to prevent duplicate exports.
|
|
146
|
-
// As a consequence of default renaming, it is possible that a file named
|
|
147
|
-
// `foo.mjs` which has `export function foo() {}` and `export default foo`
|
|
148
|
-
// exports will result in the "foo" export being defined twice in our shim.
|
|
149
|
-
// The map allows us to avoid this situation at the cost of losing the
|
|
150
|
-
// named export in favor of the default export.
|
|
130
|
+
async function processModule ({ srcUrl, context, parentGetSource, parentResolve, excludeDefault }) {
|
|
131
|
+
const exportNames = await getExports(srcUrl, context, parentGetSource)
|
|
132
|
+
const duplicates = new Set()
|
|
151
133
|
const setters = new Map()
|
|
152
134
|
|
|
135
|
+
const addSetter = (name, setter) => {
|
|
136
|
+
// When doing an `import *` duplicates become undefined, so do the same
|
|
137
|
+
if (setters.has(name)) {
|
|
138
|
+
duplicates.add(name)
|
|
139
|
+
setters.delete(name)
|
|
140
|
+
} else if (!duplicates.has(name)) {
|
|
141
|
+
setters.set(name, setter)
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
153
145
|
for (const n of exportNames) {
|
|
146
|
+
if (n === 'default' && excludeDefault) continue
|
|
147
|
+
|
|
154
148
|
if (isStarExportLine(n) === true) {
|
|
155
149
|
const [, modFile] = n.split('* from ')
|
|
156
|
-
const normalizedModName = normalizeModName(modFile)
|
|
157
|
-
const modUrl = new URL(modFile, srcUrl).toString()
|
|
158
|
-
const modName = Buffer.from(modFile, 'hex') + Date.now() + randomBytes(4).toString('hex')
|
|
159
150
|
|
|
160
|
-
|
|
151
|
+
let modUrl
|
|
152
|
+
if (isBareSpecifier(modFile)) {
|
|
153
|
+
// Bare specifiers need to be resolved relative to the parent module.
|
|
154
|
+
const result = await parentResolve(modFile, { parentURL: srcUrl })
|
|
155
|
+
modUrl = result.url
|
|
156
|
+
} else {
|
|
157
|
+
modUrl = new URL(modFile, srcUrl).href
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const setters = await processModule({
|
|
161
161
|
srcUrl: modUrl,
|
|
162
162
|
context,
|
|
163
163
|
parentGetSource,
|
|
164
|
-
|
|
165
|
-
defaultAs: normalizedModName
|
|
164
|
+
excludeDefault: true
|
|
166
165
|
})
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
for (const [k, v] of data.setters.entries()) {
|
|
170
|
-
setters.set(k, v)
|
|
166
|
+
for (const [name, setter] of setters.entries()) {
|
|
167
|
+
addSetter(name, setter)
|
|
171
168
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
// Transitive modules that export a default identifier need to have
|
|
179
|
-
// that identifier renamed to the name of module. And our shim setter
|
|
180
|
-
// needs to utilize that new name while being initialized from the
|
|
181
|
-
// corresponding origin namespace.
|
|
182
|
-
const renamedExport = matches[2]
|
|
183
|
-
setters.set(`$${renamedExport}${ns}`, `
|
|
184
|
-
let $${renamedExport} = ${ns}.default
|
|
185
|
-
export { $${renamedExport} as ${renamedExport} }
|
|
186
|
-
set.${renamedExport} = (v) => {
|
|
187
|
-
$${renamedExport} = v
|
|
169
|
+
} else {
|
|
170
|
+
addSetter(n, `
|
|
171
|
+
let $${n} = _.${n}
|
|
172
|
+
export { $${n} as ${n} }
|
|
173
|
+
set.${n} = (v) => {
|
|
174
|
+
$${n} = v
|
|
188
175
|
return true
|
|
189
176
|
}
|
|
190
177
|
`)
|
|
191
|
-
continue
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
setters.set(`$${n}` + ns, `
|
|
195
|
-
let $${n} = ${ns}.${n}
|
|
196
|
-
export { $${n} as ${n} }
|
|
197
|
-
set.${n} = (v) => {
|
|
198
|
-
$${n} = v
|
|
199
|
-
return true
|
|
200
178
|
}
|
|
201
|
-
`)
|
|
202
179
|
}
|
|
203
180
|
|
|
204
|
-
return
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* Given a module name, e.g. 'foo-bar' or './foo-bar.js', normalize it to a
|
|
209
|
-
* string that is a valid JavaScript identifier, e.g. `fooBar`. Normalization
|
|
210
|
-
* means converting kebab-case to camelCase while removing any path tokens and
|
|
211
|
-
* file extensions.
|
|
212
|
-
*
|
|
213
|
-
* @param {string} name The module name to normalize.
|
|
214
|
-
*
|
|
215
|
-
* @returns {string} The normalized identifier.
|
|
216
|
-
*/
|
|
217
|
-
function normalizeModName (name) {
|
|
218
|
-
return name
|
|
219
|
-
.split('/')
|
|
220
|
-
.pop()
|
|
221
|
-
.replace(/(.+)\.(?:js|mjs)$/, '$1')
|
|
222
|
-
.replaceAll(/(-.)/g, x => x[1].toUpperCase())
|
|
181
|
+
return setters
|
|
223
182
|
}
|
|
224
183
|
|
|
225
184
|
function addIitm (url) {
|
|
@@ -229,7 +188,20 @@ function addIitm (url) {
|
|
|
229
188
|
}
|
|
230
189
|
|
|
231
190
|
function createHook (meta) {
|
|
191
|
+
let cachedResolve
|
|
192
|
+
const iitmURL = new URL('lib/register.js', meta.url).toString()
|
|
193
|
+
|
|
232
194
|
async function resolve (specifier, context, parentResolve) {
|
|
195
|
+
cachedResolve = parentResolve
|
|
196
|
+
|
|
197
|
+
// See github.com/DataDog/import-in-the-middle/pull/76.
|
|
198
|
+
if (specifier === iitmURL) {
|
|
199
|
+
return {
|
|
200
|
+
url: specifier,
|
|
201
|
+
shortCircuit: true
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
233
205
|
const { parentURL = '' } = context
|
|
234
206
|
const newSpecifier = deleteIitm(specifier)
|
|
235
207
|
if (isWin && parentURL.indexOf('file:node') === 0) {
|
|
@@ -253,6 +225,15 @@ function createHook (meta) {
|
|
|
253
225
|
return url
|
|
254
226
|
}
|
|
255
227
|
|
|
228
|
+
// If the file is referencing itself, we need to skip adding the iitm search params
|
|
229
|
+
if (url.url === parentURL) {
|
|
230
|
+
return {
|
|
231
|
+
url: url.url,
|
|
232
|
+
shortCircuit: true,
|
|
233
|
+
format: url.format
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
256
237
|
specifiers.set(url.url, specifier)
|
|
257
238
|
|
|
258
239
|
return {
|
|
@@ -262,63 +243,28 @@ function createHook (meta) {
|
|
|
262
243
|
}
|
|
263
244
|
}
|
|
264
245
|
|
|
265
|
-
const iitmURL = new URL('lib/register.js', meta.url).toString()
|
|
266
246
|
async function getSource (url, context, parentGetSource) {
|
|
267
247
|
if (hasIitm(url)) {
|
|
268
248
|
const realUrl = deleteIitm(url)
|
|
269
|
-
const
|
|
249
|
+
const setters = await processModule({
|
|
270
250
|
srcUrl: realUrl,
|
|
271
251
|
context,
|
|
272
|
-
parentGetSource
|
|
252
|
+
parentGetSource,
|
|
253
|
+
parentResolve: cachedResolve
|
|
273
254
|
})
|
|
274
|
-
const setters = Array.from(mapSetters.values())
|
|
275
|
-
|
|
276
|
-
// When we encounter modules that re-export all identifiers from other
|
|
277
|
-
// modules, it is possible that the transitive modules export a default
|
|
278
|
-
// identifier. Due to us having to merge all transitive modules into a
|
|
279
|
-
// single common namespace, we need to recognize these default exports
|
|
280
|
-
// and remap them to a name based on the module name. This prevents us
|
|
281
|
-
// from overriding the top-level module's (the one actually being imported
|
|
282
|
-
// by some source code) default export when we merge the namespaces.
|
|
283
|
-
const renamedDefaults = setters
|
|
284
|
-
.map(s => {
|
|
285
|
-
const matches = /let \$(.+) = (\$.+)\.default/.exec(s)
|
|
286
|
-
if (matches === null) return undefined
|
|
287
|
-
return `_['${matches[1]}'] = ${matches[2]}.default`
|
|
288
|
-
})
|
|
289
|
-
.filter(s => s)
|
|
290
|
-
|
|
291
|
-
// The for loops are how we merge namespaces into a common namespace that
|
|
292
|
-
// can be proxied. We can't use a simple `Object.assign` style merging
|
|
293
|
-
// because transitive modules can export a default identifier that would
|
|
294
|
-
// override the desired default identifier. So we need to do manual
|
|
295
|
-
// merging with some logic around default identifiers.
|
|
296
|
-
//
|
|
297
|
-
// Additionally, we need to make sure any renamed default exports in
|
|
298
|
-
// transitive dependencies are added to the common namespace. This is
|
|
299
|
-
// accomplished through the `renamedDefaults` array.
|
|
300
255
|
return {
|
|
301
256
|
source: `
|
|
302
257
|
import { register } from '${iitmURL}'
|
|
303
|
-
${
|
|
258
|
+
import * as namespace from ${JSON.stringify(realUrl)}
|
|
304
259
|
|
|
305
|
-
|
|
306
|
-
const _ =
|
|
260
|
+
// Mimic a Module object (https://tc39.es/ecma262/#sec-module-namespace-objects).
|
|
261
|
+
const _ = Object.assign(
|
|
262
|
+
Object.create(null, { [Symbol.toStringTag]: { value: 'Module' } }),
|
|
263
|
+
namespace
|
|
264
|
+
)
|
|
307
265
|
const set = {}
|
|
308
266
|
|
|
309
|
-
|
|
310
|
-
for (const [k, v] of Object.entries(primary)) {
|
|
311
|
-
_[k] = v
|
|
312
|
-
}
|
|
313
|
-
for (const ns of namespaces) {
|
|
314
|
-
for (const [k, v] of Object.entries(ns)) {
|
|
315
|
-
if (k === 'default') continue
|
|
316
|
-
_[k] = v
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
${setters.join('\n')}
|
|
321
|
-
${renamedDefaults.join('\n')}
|
|
267
|
+
${Array.from(setters.values()).join('\n')}
|
|
322
268
|
|
|
323
269
|
register(${JSON.stringify(realUrl)}, _, set, ${JSON.stringify(specifiers.get(realUrl))})
|
|
324
270
|
`
|
package/index.js
CHANGED
package/lib/get-esm-exports.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { Parser } = require('acorn')
|
|
4
|
-
const { importAssertions } = require('acorn-import-
|
|
4
|
+
const { importAssertions } = require('acorn-import-attributes')
|
|
5
5
|
|
|
6
6
|
const acornOpts = {
|
|
7
7
|
ecmaVersion: 'latest',
|
|
@@ -18,30 +18,21 @@ function warn (txt) {
|
|
|
18
18
|
* Utilizes an AST parser to interpret ESM source code and build a list of
|
|
19
19
|
* exported identifiers. In the baseline case, the list of identifiers will be
|
|
20
20
|
* the simple identifier names as written in the source code of the module.
|
|
21
|
-
* However, there
|
|
21
|
+
* However, there is a special case:
|
|
22
22
|
*
|
|
23
|
-
*
|
|
23
|
+
* When an `export * from './foo.js'` line is encountered it is rewritten
|
|
24
24
|
* as `* from ./foo.js`. This allows the interpreting code to recognize a
|
|
25
25
|
* transitive export and recursively parse the indicated module. The returned
|
|
26
26
|
* identifier list will have "* from ./foo.js" as an item.
|
|
27
27
|
*
|
|
28
|
-
* 2. When `defaultAs` has a value other than 'default', the export line will
|
|
29
|
-
* be rewritten as `rename <identifier> as <defaultAsValue>`. This rename string
|
|
30
|
-
* will be an item in the returned identifier list.
|
|
31
|
-
*
|
|
32
28
|
* @param {object} params
|
|
33
29
|
* @param {string} params.moduleSource The source code of the module to parse
|
|
34
30
|
* and interpret.
|
|
35
|
-
* @param {string} [defaultAs='default'] When anything other than 'default' any
|
|
36
|
-
* `export default` lines will be rewritten utilizing the value provided. For
|
|
37
|
-
* example, if a module 'foo-bar.js' has the line `export default foo` and the
|
|
38
|
-
* value of this parameter is 'baz', then the export will be rewritten to
|
|
39
|
-
* `rename foo as baz`.
|
|
40
31
|
*
|
|
41
32
|
* @returns {string[]} The identifiers exported by the module along with any
|
|
42
33
|
* custom directives.
|
|
43
34
|
*/
|
|
44
|
-
function getEsmExports (
|
|
35
|
+
function getEsmExports (moduleSource) {
|
|
45
36
|
const exportedNames = new Set()
|
|
46
37
|
const tree = parser.parse(moduleSource, acornOpts)
|
|
47
38
|
for (const node of tree.body) {
|
|
@@ -56,19 +47,7 @@ function getEsmExports ({ moduleSource, defaultAs = 'default' }) {
|
|
|
56
47
|
break
|
|
57
48
|
|
|
58
49
|
case 'ExportDefaultDeclaration': {
|
|
59
|
-
|
|
60
|
-
exportedNames.add('default')
|
|
61
|
-
break
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
if (node.declaration.type.toLowerCase() === 'identifier') {
|
|
65
|
-
// e.g. `export default foo`
|
|
66
|
-
exportedNames.add(`rename ${node.declaration.name} as ${defaultAs}`)
|
|
67
|
-
} else {
|
|
68
|
-
// e.g. `export function foo () {}
|
|
69
|
-
exportedNames.add(`rename ${node.declaration.id.name} as ${defaultAs}`)
|
|
70
|
-
}
|
|
71
|
-
|
|
50
|
+
exportedNames.add('default')
|
|
72
51
|
break
|
|
73
52
|
}
|
|
74
53
|
|
package/lib/get-exports.js
CHANGED
|
@@ -20,13 +20,13 @@ async function getFullCjsExports (url, context, parentLoad, source) {
|
|
|
20
20
|
const ex = getCjsExports(source)
|
|
21
21
|
const full = Array.from(new Set([
|
|
22
22
|
...addDefault(ex.exports),
|
|
23
|
-
...(await Promise.all(ex.reexports.map(re => getExports(
|
|
24
|
-
|
|
23
|
+
...(await Promise.all(ex.reexports.map(re => getExports(
|
|
24
|
+
(/^(..?($|\/|\\))/).test(re)
|
|
25
25
|
? pathToFileURL(require.resolve(fileURLToPath(new URL(re, url)))).toString()
|
|
26
26
|
: pathToFileURL(require.resolve(re)).toString(),
|
|
27
27
|
context,
|
|
28
28
|
parentLoad
|
|
29
|
-
|
|
29
|
+
)))).flat()
|
|
30
30
|
]))
|
|
31
31
|
|
|
32
32
|
urlsBeingProcessed.delete(url)
|
|
@@ -38,25 +38,18 @@ async function getFullCjsExports (url, context, parentLoad, source) {
|
|
|
38
38
|
* source code for said module from the loader API, and parses the result
|
|
39
39
|
* for the entities exported from that module.
|
|
40
40
|
*
|
|
41
|
-
* @param {
|
|
42
|
-
* @param {string} params.url A file URL string pointing to the module that
|
|
41
|
+
* @param {string} url A file URL string pointing to the module that
|
|
43
42
|
* we should get the exports of.
|
|
44
|
-
* @param {object}
|
|
43
|
+
* @param {object} context Context object as provided by the `load`
|
|
45
44
|
* hook from the loaders API.
|
|
46
|
-
* @param {Function}
|
|
45
|
+
* @param {Function} parentLoad Next hook function in the loaders API
|
|
47
46
|
* hook chain.
|
|
48
|
-
* @param {string} [defaultAs='default'] When anything other than 'default',
|
|
49
|
-
* will trigger remapping of default exports in ESM source files to the
|
|
50
|
-
* provided name. For example, if a submodule has `export default foo` and
|
|
51
|
-
* 'myFoo' is provided for this parameter, the export line will be rewritten
|
|
52
|
-
* to `rename foo as myFoo`. This is key to being able to support
|
|
53
|
-
* `export * from 'something'` exports.
|
|
54
47
|
*
|
|
55
48
|
* @returns {Promise<string[]>} An array of identifiers exported by the module.
|
|
56
49
|
* Please see {@link getEsmExports} for caveats on special identifiers that may
|
|
57
50
|
* be included in the result set.
|
|
58
51
|
*/
|
|
59
|
-
async function getExports (
|
|
52
|
+
async function getExports (url, context, parentLoad) {
|
|
60
53
|
// `parentLoad` gives us the possibility of getting the source
|
|
61
54
|
// from an upstream loader. This doesn't always work though,
|
|
62
55
|
// so later on we fall back to reading it from disk.
|
|
@@ -77,7 +70,7 @@ async function getExports ({ url, context, parentLoad, defaultAs = 'default' })
|
|
|
77
70
|
}
|
|
78
71
|
|
|
79
72
|
if (format === 'module') {
|
|
80
|
-
return getEsmExports(
|
|
73
|
+
return getEsmExports(source)
|
|
81
74
|
}
|
|
82
75
|
if (format === 'commonjs') {
|
|
83
76
|
return getFullCjsExports(url, context, parentLoad, source)
|
|
@@ -85,7 +78,7 @@ async function getExports ({ url, context, parentLoad, defaultAs = 'default' })
|
|
|
85
78
|
|
|
86
79
|
// At this point our `format` is either undefined or not known by us. Fall
|
|
87
80
|
// back to parsing as ESM/CJS.
|
|
88
|
-
const esmExports = getEsmExports(
|
|
81
|
+
const esmExports = getEsmExports(source)
|
|
89
82
|
if (!esmExports.length) {
|
|
90
83
|
// TODO(bengl) it's might be possible to get here if somehow the format
|
|
91
84
|
// isn't set at first and yet we have an ESM module with no exports.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "import-in-the-middle",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.0",
|
|
4
4
|
"description": "Intercept imports in Node.js",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -36,21 +36,25 @@
|
|
|
36
36
|
"@babel/core": "^7.23.7",
|
|
37
37
|
"@babel/eslint-parser": "^7.23.3",
|
|
38
38
|
"@babel/plugin-syntax-import-assertions": "^7.23.3",
|
|
39
|
+
"@react-email/components": "^0.0.19",
|
|
39
40
|
"@types/node": "^18.0.6",
|
|
40
41
|
"c8": "^7.8.0",
|
|
42
|
+
"date-fns": "^3.6.0",
|
|
41
43
|
"eslint": "^8.55.0",
|
|
42
44
|
"eslint-config-standard": "^17.1.0",
|
|
43
45
|
"eslint-plugin-import": "^2.29.0",
|
|
44
46
|
"eslint-plugin-n": "^16.4.0",
|
|
45
47
|
"eslint-plugin-node": "^11.1.0",
|
|
46
48
|
"eslint-plugin-promise": "^6.1.1",
|
|
49
|
+
"got": "^14.3.0",
|
|
47
50
|
"imhotap": "^2.1.0",
|
|
51
|
+
"openai": "^4.47.2",
|
|
48
52
|
"ts-node": "^10.9.1",
|
|
49
53
|
"typescript": "^4.7.4"
|
|
50
54
|
},
|
|
51
55
|
"dependencies": {
|
|
52
56
|
"acorn": "^8.8.2",
|
|
53
|
-
"acorn-import-
|
|
57
|
+
"acorn-import-attributes": "^1.9.5",
|
|
54
58
|
"cjs-module-lexer": "^1.2.2",
|
|
55
59
|
"module-details-from-path": "^1.0.3"
|
|
56
60
|
}
|
package/test/README.md
CHANGED
|
@@ -11,7 +11,7 @@ options (assuming they're run from the project root):
|
|
|
11
11
|
|
|
12
12
|
```
|
|
13
13
|
--require ./test/version-check.js
|
|
14
|
-
--experimental-loader ./test/generic-loader.
|
|
14
|
+
--experimental-loader ./test/generic-loader.mjs
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
The entire test suite can be run with `npm test`.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// File generated from our OpenAPI spec by Stainless.
|
|
2
|
+
import * as BatchesAPI from './cyclical-self.mjs'
|
|
3
|
+
export class Batches {}
|
|
4
|
+
export class BatchesPage {}
|
|
5
|
+
(function (Batches) {
|
|
6
|
+
Batches.BatchesPage = BatchesAPI.BatchesPage
|
|
7
|
+
// eslint-disable-next-line no-class-assign
|
|
8
|
+
})(Batches || (Batches = {}))
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const foo = 'a'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default parseInt('1')
|
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
// The purpose of this fixture is to replicate a situation where we may
|
|
2
|
-
// end up exporting `default` twice: first from this script itself and second
|
|
3
|
-
// via the `export * from` line.
|
|
4
|
-
//
|
|
5
1
|
// This replicates the way the in-the-wild `got` module does things:
|
|
6
2
|
// https://github.com/sindresorhus/got/blob/3822412/source/index.ts
|
|
7
3
|
|
|
@@ -12,6 +8,4 @@ class got {
|
|
|
12
8
|
export default got
|
|
13
9
|
export { got }
|
|
14
10
|
export * from './something.mjs'
|
|
15
|
-
export * from './default-class.mjs'
|
|
16
|
-
export * from './snake_case.mjs'
|
|
17
11
|
export { default as renamedDefaultExport } from './lib/baz.mjs'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from 'some-external-module'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from 'some-external-module/sub'
|
|
@@ -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(
|
|
18
|
+
const names = getEsmExports(mod)
|
|
19
19
|
assert.deepEqual(expectedNames, names)
|
|
20
20
|
console.log(`${mod}\n ✅ contains exports: ${testStr}`)
|
|
21
21
|
})
|
|
@@ -12,6 +12,8 @@ import gfn from '../fixtures/export-types/default-generator.mjs'
|
|
|
12
12
|
import afn from '../fixtures/export-types/default-function-anon.mjs'
|
|
13
13
|
import acn from '../fixtures/export-types/default-class-anon.mjs'
|
|
14
14
|
import agfn from '../fixtures/export-types/default-generator-anon.mjs'
|
|
15
|
+
import callEx from '../fixtures/export-types/default-call-expression.mjs'
|
|
16
|
+
import { somethingElse } from '../fixtures/export-types/default-call-expression-renamed.mjs'
|
|
15
17
|
import defaultImportExport from '../fixtures/export-types/import-default-export.mjs'
|
|
16
18
|
import varDefaultExport from '../fixtures/export-types/variable-default-export.mjs'
|
|
17
19
|
import { strictEqual } from 'assert'
|
|
@@ -61,6 +63,10 @@ Hook((exports, name) => {
|
|
|
61
63
|
exports.default = function () {
|
|
62
64
|
return orig4() + 1
|
|
63
65
|
}
|
|
66
|
+
} else if (name.match(/default-call-expression\.m?js/)) {
|
|
67
|
+
exports.default += 1
|
|
68
|
+
} else if (name.match(/default-call-expression-renamed\.m?js/)) {
|
|
69
|
+
exports.somethingElse += 1
|
|
64
70
|
}
|
|
65
71
|
})
|
|
66
72
|
|
|
@@ -76,3 +82,5 @@ strictEqual(new acn().getFoo(), 2)
|
|
|
76
82
|
strictEqual(agfn().next().value, 2)
|
|
77
83
|
strictEqual(n, 2)
|
|
78
84
|
strictEqual(s, 'dogdawg')
|
|
85
|
+
strictEqual(callEx, 2)
|
|
86
|
+
strictEqual(somethingElse, 2)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import * as lib from '../fixtures/duplicate.mjs'
|
|
2
|
+
import { notEqual, strictEqual } from 'assert'
|
|
3
|
+
import Hook from '../../index.js'
|
|
4
|
+
|
|
5
|
+
Hook((exports, name) => {
|
|
6
|
+
if (name.match(/duplicate\.mjs/)) {
|
|
7
|
+
// foo should not be exported because there are duplicate exports
|
|
8
|
+
strictEqual(exports.foo, undefined)
|
|
9
|
+
// default should be exported
|
|
10
|
+
strictEqual(exports.default, 'foo')
|
|
11
|
+
}
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
notEqual(lib, undefined)
|
|
15
|
+
|
|
16
|
+
// foo should not be exported because there are duplicate exports
|
|
17
|
+
strictEqual(lib.foo, undefined)
|
|
18
|
+
// default should be exported
|
|
19
|
+
strictEqual(lib.default, 'foo')
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
//
|
|
3
3
|
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc.
|
|
4
4
|
|
|
5
|
-
const Hook = require('../../index.js')
|
|
5
|
+
const { Hook } = require('../../index.js')
|
|
6
6
|
const { strictEqual } = require('assert')
|
|
7
7
|
|
|
8
8
|
Hook((exports, name) => {
|
|
@@ -0,0 +1,25 @@
|
|
|
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 Hook from '../../index.js'
|
|
6
|
+
import { foo as fooMjs } from '../fixtures/something.mjs'
|
|
7
|
+
import { foo as fooJs } from '../fixtures/something.js'
|
|
8
|
+
import { strictEqual, deepStrictEqual } from 'assert'
|
|
9
|
+
|
|
10
|
+
let hookedExports
|
|
11
|
+
|
|
12
|
+
Hook((exports, name) => {
|
|
13
|
+
hookedExports = exports
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
strictEqual(fooMjs, 42)
|
|
17
|
+
strictEqual(fooJs, 42)
|
|
18
|
+
|
|
19
|
+
strictEqual(hookedExports[Symbol.toStringTag], 'Module')
|
|
20
|
+
deepStrictEqual(Object.getOwnPropertyDescriptor(hookedExports, Symbol.toStringTag), {
|
|
21
|
+
value: 'Module',
|
|
22
|
+
enumerable: false,
|
|
23
|
+
writable: false,
|
|
24
|
+
configurable: false
|
|
25
|
+
})
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import Hook from '../../index.js'
|
|
2
|
+
import { foo } from '../fixtures/re-export-star-external.mjs'
|
|
3
|
+
import { bar } from '../fixtures/sub-directory/re-export-star-external.mjs'
|
|
4
|
+
import { strictEqual } from 'assert'
|
|
5
|
+
|
|
6
|
+
Hook((exports, name) => {
|
|
7
|
+
if (name.endsWith('fixtures/re-export-star-external.mjs')) {
|
|
8
|
+
exports.foo = '1'
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
if (name.endsWith('fixtures/sub-directory/re-export-star-external.mjs')) {
|
|
12
|
+
exports.bar = '2'
|
|
13
|
+
}
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
strictEqual(foo, '1')
|
|
17
|
+
strictEqual(bar, '2')
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
//
|
|
3
3
|
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc.
|
|
4
4
|
|
|
5
|
-
import Hook from '../../index.js'
|
|
5
|
+
import { Hook } from '../../index.js'
|
|
6
6
|
import barMjs from '../fixtures/something.mjs'
|
|
7
7
|
import barJs from '../fixtures/something.js'
|
|
8
8
|
import { strictEqual } from 'assert'
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import * as lib from '@react-email/components'
|
|
2
|
+
import { Heading } from '@react-email/components'
|
|
3
|
+
import Hook from '../../index.js'
|
|
4
|
+
import { strictEqual } from 'assert'
|
|
5
|
+
|
|
6
|
+
Hook((exports, name) => {
|
|
7
|
+
if (name.match(/@react-email\/components/)) {
|
|
8
|
+
exports.Heading = function wrappedHeading () {
|
|
9
|
+
return 'heading-wrapped'
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
strictEqual(typeof lib.Button, 'function')
|
|
15
|
+
strictEqual(lib.Heading(), 'heading-wrapped')
|
|
16
|
+
strictEqual(Heading(), 'heading-wrapped')
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import got, { Options } from 'got'
|
|
2
|
+
import { strictEqual } from 'assert'
|
|
3
|
+
import Hook from '../../index.js'
|
|
4
|
+
|
|
5
|
+
Hook((exports, name) => {
|
|
6
|
+
if (name === 'got' && 'Options' in exports) {
|
|
7
|
+
exports.Options = 'nothing'
|
|
8
|
+
}
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
strictEqual(typeof got, 'function')
|
|
12
|
+
strictEqual(typeof got.post, 'function')
|
|
13
|
+
strictEqual(typeof got.stream, 'function')
|
|
14
|
+
strictEqual(typeof got.extend, 'function')
|
|
15
|
+
|
|
16
|
+
strictEqual(Options, 'nothing')
|
|
@@ -3,10 +3,8 @@ import Hook from '../../index.js'
|
|
|
3
3
|
Hook((exports, name) => {
|
|
4
4
|
if (/got-alike\.mjs/.test(name) === false) return
|
|
5
5
|
|
|
6
|
-
const
|
|
7
|
-
exports.
|
|
8
|
-
return bar() + '-wrapped'
|
|
9
|
-
}
|
|
6
|
+
const foo = exports.foo
|
|
7
|
+
exports.foo = foo + '-wrapped'
|
|
10
8
|
|
|
11
9
|
const renamedDefaultExport = exports.renamedDefaultExport
|
|
12
10
|
exports.renamedDefaultExport = function bazWrapped () {
|
|
@@ -15,21 +13,14 @@ Hook((exports, name) => {
|
|
|
15
13
|
})
|
|
16
14
|
|
|
17
15
|
/* eslint-disable import/no-named-default */
|
|
18
|
-
/* eslint-disable camelcase */
|
|
19
16
|
import {
|
|
20
17
|
default as Got,
|
|
21
|
-
|
|
22
|
-
defaultClass as DefaultClass,
|
|
23
|
-
snake_case,
|
|
18
|
+
foo,
|
|
24
19
|
renamedDefaultExport
|
|
25
20
|
} from '../fixtures/got-alike.mjs'
|
|
26
21
|
|
|
27
|
-
strictEqual(
|
|
22
|
+
strictEqual(foo, '42-wrapped')
|
|
28
23
|
const got = new Got()
|
|
29
24
|
strictEqual(got.foo, 'foo')
|
|
30
25
|
|
|
31
|
-
const dc = new DefaultClass()
|
|
32
|
-
strictEqual(dc.value, 'DefaultClass')
|
|
33
|
-
|
|
34
|
-
strictEqual(snake_case, 'snake_case')
|
|
35
26
|
strictEqual(renamedDefaultExport(), 'baz-wrapped')
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This simulates what something like `tsx` (https://github.com/privatenumber/tsx)
|
|
3
|
+
* will do: it will try to resolve a URL with a `.js` extension to a `.ts` extension.
|
|
4
|
+
*
|
|
5
|
+
* Combined with the test case in the adjacent `multiple-loaders.test.mjs` file,
|
|
6
|
+
* this forces `import-in-the-middle` into what used to be a failure state: where
|
|
7
|
+
* `context.parentURL` is a `node:*` specifier and the `specifier` refers to a file
|
|
8
|
+
* that does not exist.
|
|
9
|
+
*
|
|
10
|
+
* See https://github.com/nodejs/node/issues/52987 for more details.
|
|
11
|
+
*/
|
|
12
|
+
export async function resolve (specifier, context, defaultResolve) {
|
|
13
|
+
if (!specifier.endsWith('.js') && !specifier.endsWith('.mjs')) {
|
|
14
|
+
return await defaultResolve(specifier, context)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
return await defaultResolve(specifier.replace(/\.m?js/, '.ts'), context)
|
|
19
|
+
} catch (err) {
|
|
20
|
+
if (err.code !== 'ERR_MODULE_NOT_FOUND') {
|
|
21
|
+
throw err
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return await defaultResolve(specifier, context)
|
|
25
|
+
}
|
|
26
|
+
}
|
package/test/version-check.js
CHANGED
|
@@ -12,10 +12,11 @@ process.env.IITM_TEST_FILE = filename
|
|
|
12
12
|
|
|
13
13
|
const [processMajor, processMinor] = process.versions.node.split('.').map(Number)
|
|
14
14
|
|
|
15
|
-
const match = filename.match(/v([0-9]+)(?:\.([0-9]+))?/)
|
|
15
|
+
const match = filename.match(/v([0-9]+)(?:\.([0-9]+))?(?:-v([0-9]+))?/)
|
|
16
16
|
|
|
17
17
|
const majorRequirement = match ? match[1] : 0
|
|
18
18
|
const minorRequirement = match && match[2]
|
|
19
|
+
const majorMax = match ? match[3] : Infinity
|
|
19
20
|
|
|
20
21
|
if (processMajor < majorRequirement) {
|
|
21
22
|
console.log(`skipping ${filename} as this is Node.js v${processMajor} and test wants v${majorRequirement}`)
|
|
@@ -25,3 +26,8 @@ if (processMajor <= majorRequirement && processMinor < minorRequirement) {
|
|
|
25
26
|
console.log(`skipping ${filename} as this is Node.js v${processMajor}.${processMinor} and test wants >=v${majorRequirement}.${minorRequirement}`)
|
|
26
27
|
process.exit(0)
|
|
27
28
|
}
|
|
29
|
+
|
|
30
|
+
if (processMajor > majorMax) {
|
|
31
|
+
console.log(`skipping ${filename} as this is Node.js v${processMajor} and test wants <=v${majorMax}`)
|
|
32
|
+
process.exit(0)
|
|
33
|
+
}
|
|
File without changes
|