import-in-the-middle 1.7.1 → 1.7.3

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.
Files changed (58) hide show
  1. package/.eslintrc.yaml +23 -0
  2. package/hook.js +200 -17
  3. package/index.js +4 -4
  4. package/lib/get-esm-exports.js +48 -6
  5. package/lib/get-exports.js +54 -7
  6. package/lib/register.js +3 -4
  7. package/package.json +19 -4
  8. package/test/README.md +9 -5
  9. package/test/fixtures/a.mjs +7 -0
  10. package/test/fixtures/b.mjs +5 -0
  11. package/test/fixtures/bundle.mjs +4 -0
  12. package/test/fixtures/circular-a.js +18 -0
  13. package/test/fixtures/circular-b.js +15 -0
  14. package/test/fixtures/cyclical-a.mjs +7 -0
  15. package/test/fixtures/cyclical-b.mjs +6 -0
  16. package/test/fixtures/default-class.mjs +3 -0
  17. package/test/fixtures/env.mjs +8 -0
  18. package/test/fixtures/esm-exports.txt +1 -1
  19. package/test/fixtures/export-types/declarations.mjs +19 -0
  20. package/test/fixtures/export-types/default-class-anon.mjs +1 -0
  21. package/test/fixtures/export-types/default-class.mjs +1 -0
  22. package/test/fixtures/export-types/default-expression-array.mjs +1 -0
  23. package/test/fixtures/export-types/default-expression-num.mjs +1 -0
  24. package/test/fixtures/export-types/default-expression-string.mjs +1 -0
  25. package/test/fixtures/export-types/default-function-anon.mjs +1 -0
  26. package/test/fixtures/export-types/default-function.mjs +1 -0
  27. package/test/fixtures/export-types/default-generator-anon.mjs +1 -0
  28. package/test/fixtures/export-types/default-generator.mjs +1 -0
  29. package/test/fixtures/export-types/fn-default-export.mjs +3 -0
  30. package/test/fixtures/export-types/import-default-export.mjs +2 -0
  31. package/test/fixtures/export-types/list.mjs +12 -0
  32. package/test/fixtures/export-types/variable-default-export.mjs +5 -0
  33. package/test/fixtures/foo.mjs +5 -0
  34. package/test/fixtures/got-alike.mjs +17 -0
  35. package/test/fixtures/json.mjs +2 -2
  36. package/test/fixtures/lib/baz.mjs +4 -0
  37. package/test/fixtures/reexport.js +15 -0
  38. package/test/fixtures/snake_case.mjs +2 -0
  39. package/test/fixtures/something.js +1 -1
  40. package/test/generic-loader.mjs +14 -0
  41. package/test/get-esm-exports/v20-get-esm-exports.js +1 -1
  42. package/test/hook/circular-imports.mjs +11 -0
  43. package/test/hook/default-export.mjs +78 -0
  44. package/test/hook/dynamic-import-default.js +2 -2
  45. package/test/hook/dynamic-import-default.mjs +1 -1
  46. package/test/hook/import-reexport-cjs.mjs +11 -0
  47. package/test/hook/static-import-default.mjs +2 -2
  48. package/test/hook/static-import-package-internals-enabled.mjs +1 -1
  49. package/test/hook/static-import-star.mjs +34 -0
  50. package/test/hook/v14-declaration-exports.mjs +84 -0
  51. package/test/hook/v18.19-static-import-gotalike.mjs +35 -0
  52. package/test/low-level/dynamic-import-default.js +1 -1
  53. package/test/low-level/dynamic-import-default.mjs +1 -1
  54. package/test/low-level/static-import-default.mjs +1 -1
  55. package/test/other/v14-assert-cyclical-dependency-failure.mjs +36 -0
  56. package/test/typescript/iitm-ts-node-loader.mjs +4 -4
  57. package/test/version-check.js +27 -0
  58. package/test/runtest +0 -46
package/.eslintrc.yaml ADDED
@@ -0,0 +1,23 @@
1
+ overrides:
2
+ - files:
3
+ - '**/*.{js,cjs,mjs}'
4
+
5
+ parser: '@babel/eslint-parser'
6
+ parserOptions:
7
+ ecmaVersion: latest
8
+ requireConfigFile: false
9
+ sourceType: 'script'
10
+ babelOptions:
11
+ plugins:
12
+ - '@babel/plugin-syntax-import-assertions'
13
+
14
+ rules:
15
+ "import/first": off
16
+
17
+ extends:
18
+ - "standard"
19
+
20
+ ignorePatterns:
21
+ - test/fixtures/circular-a.js
22
+ - test/fixtures/circular-b.js
23
+ - test/fixtures/reexport.js
package/hook.js CHANGED
@@ -2,8 +2,9 @@
2
2
  //
3
3
  // This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc.
4
4
 
5
+ const { randomBytes } = require('crypto')
5
6
  const specifiers = new Map()
6
- const isWin = process.platform === "win32"
7
+ const isWin = process.platform === 'win32'
7
8
 
8
9
  // FIXME: Typescript extensions are added temporarily until we find a better
9
10
  // way of supporting arbitrary extensions
@@ -15,10 +16,10 @@ const NODE_MINOR = Number(NODE_VERSION[1])
15
16
  let entrypoint
16
17
 
17
18
  let getExports
18
- if (NODE_MAJOR >= 20 || (NODE_MAJOR == 18 && NODE_MINOR >= 19)) {
19
+ if (NODE_MAJOR >= 20 || (NODE_MAJOR === 18 && NODE_MINOR >= 19)) {
19
20
  getExports = require('./lib/get-exports.js')
20
21
  } else {
21
- getExports = (url) => import(url).then(Object.keys)
22
+ getExports = ({ url }) => import(url).then(Object.keys)
22
23
  }
23
24
 
24
25
  function hasIitm (url) {
@@ -55,7 +56,7 @@ function deleteIitm (url) {
55
56
  return resultUrl
56
57
  }
57
58
 
58
- function isNode16AndBiggerOrEqualsThan16_17_0() {
59
+ function isNodeMajor16AndMinor17OrGreater () {
59
60
  return NODE_MAJOR === 16 && NODE_MINOR >= 17
60
61
  }
61
62
 
@@ -67,16 +68,159 @@ function isNodeProtocol (urlObj) {
67
68
  return urlObj.protocol === 'node:'
68
69
  }
69
70
 
70
- function needsToAddFileProtocol(urlObj) {
71
+ function needsToAddFileProtocol (urlObj) {
71
72
  if (NODE_MAJOR === 17) {
72
73
  return !isFileProtocol(urlObj)
73
74
  }
74
- if (isNode16AndBiggerOrEqualsThan16_17_0()) {
75
+ if (isNodeMajor16AndMinor17OrGreater()) {
75
76
  return !isFileProtocol(urlObj) && !isNodeProtocol(urlObj)
76
77
  }
77
78
  return !isFileProtocol(urlObj) && NODE_MAJOR < 18
78
79
  }
79
80
 
81
+ /**
82
+ * Determines if a specifier represents an export all ESM line.
83
+ * Note that the expected `line` isn't 100% valid ESM. It is derived
84
+ * from the `getExports` function wherein we have recognized the true
85
+ * line and re-mapped it to one we expect.
86
+ *
87
+ * @param {string} line
88
+ * @returns {boolean}
89
+ */
90
+ function isStarExportLine (line) {
91
+ return /^\* from /.test(line)
92
+ }
93
+
94
+ /**
95
+ * @typedef {object} ProcessedModule
96
+ * @property {string[]} imports A set of ESM import lines to be added to the
97
+ * shimmed module source.
98
+ * @property {string[]} namespaces A set of identifiers representing the
99
+ * modules in `imports`, e.g. for `import * as foo from 'bar'`, "foo" will be
100
+ * present in this array.
101
+ * @property {Map<string, string>} setters The shimmed setters for all the
102
+ * exports from the module and any transitive export all modules. The key is
103
+ * used to deduplicate conflicting exports, assigning a priority to `default`
104
+ * exports.
105
+ */
106
+
107
+ /**
108
+ * Processes a module's exports and builds a set of new import statements,
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.
112
+ *
113
+ * @param {object} params
114
+ * @param {string} params.srcUrl The full URL to the module to process.
115
+ * @param {object} params.context Provided by the loaders API.
116
+ * @param {Function} params.parentGetSource Provides the source code for the
117
+ * parent module.
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
+ *
125
+ * @returns {Promise<ProcessedModule>}
126
+ */
127
+ async function processModule ({
128
+ srcUrl,
129
+ context,
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.
151
+ const setters = new Map()
152
+
153
+ for (const n of exportNames) {
154
+ if (isStarExportLine(n) === true) {
155
+ 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
+
160
+ const data = await processModule({
161
+ srcUrl: modUrl,
162
+ context,
163
+ parentGetSource,
164
+ ns: `$${modName}`,
165
+ defaultAs: normalizedModName
166
+ })
167
+ Array.prototype.push.apply(imports, data.imports)
168
+ Array.prototype.push.apply(namespaces, data.namespaces)
169
+ for (const [k, v] of data.setters.entries()) {
170
+ setters.set(k, v)
171
+ }
172
+
173
+ continue
174
+ }
175
+
176
+ const matches = /^rename (.+) as (.+)$/.exec(n)
177
+ if (matches !== null) {
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
188
+ return true
189
+ }
190
+ `)
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
+ }
201
+ `)
202
+ }
203
+
204
+ return { imports, namespaces, setters }
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())
223
+ }
80
224
 
81
225
  function addIitm (url) {
82
226
  const urlObj = new URL(url)
@@ -109,7 +253,6 @@ function createHook (meta) {
109
253
  return url
110
254
  }
111
255
 
112
-
113
256
  specifiers.set(url.url, specifier)
114
257
 
115
258
  return {
@@ -123,21 +266,61 @@ function createHook (meta) {
123
266
  async function getSource (url, context, parentGetSource) {
124
267
  if (hasIitm(url)) {
125
268
  const realUrl = deleteIitm(url)
126
- const exportNames = await getExports(realUrl, context, parentGetSource)
269
+ const { imports, namespaces, setters: mapSetters } = await processModule({
270
+ srcUrl: realUrl,
271
+ context,
272
+ parentGetSource
273
+ })
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.
127
300
  return {
128
301
  source: `
129
302
  import { register } from '${iitmURL}'
130
- import * as namespace from ${JSON.stringify(url)}
303
+ ${imports.join('\n')}
304
+
305
+ const namespaces = [${namespaces.join(', ')}]
306
+ const _ = {}
131
307
  const set = {}
132
- ${exportNames.map((n) => `
133
- let $${n} = namespace.${n}
134
- export { $${n} as ${n} }
135
- set.${n} = (v) => {
136
- $${n} = v
137
- return true
308
+
309
+ const primary = namespaces.shift()
310
+ for (const [k, v] of Object.entries(primary)) {
311
+ _[k] = v
138
312
  }
139
- `).join('\n')}
140
- register(${JSON.stringify(realUrl)}, namespace, set, ${JSON.stringify(specifiers.get(realUrl))})
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')}
322
+
323
+ register(${JSON.stringify(realUrl)}, _, set, ${JSON.stringify(specifiers.get(realUrl))})
141
324
  `
142
325
  }
143
326
  }
package/index.js CHANGED
@@ -12,26 +12,26 @@ const {
12
12
  toHook
13
13
  } = require('./lib/register')
14
14
 
15
- function addHook(hook) {
15
+ function addHook (hook) {
16
16
  importHooks.push(hook)
17
17
  toHook.forEach(([name, namespace]) => hook(name, namespace))
18
18
  }
19
19
 
20
- function removeHook(hook) {
20
+ function removeHook (hook) {
21
21
  const index = importHooks.indexOf(hook)
22
22
  if (index > -1) {
23
23
  importHooks.splice(index, 1)
24
24
  }
25
25
  }
26
26
 
27
- function callHookFn(hookFn, namespace, name, baseDir) {
27
+ function callHookFn (hookFn, namespace, name, baseDir) {
28
28
  const newDefault = hookFn(namespace, name, baseDir)
29
29
  if (newDefault && newDefault !== namespace) {
30
30
  namespace.default = newDefault
31
31
  }
32
32
  }
33
33
 
34
- function Hook(modules, options, hookFn) {
34
+ function Hook (modules, options, hookFn) {
35
35
  if ((this instanceof Hook) === false) return new Hook(modules, options, hookFn)
36
36
  if (typeof modules === 'function') {
37
37
  hookFn = modules
@@ -1,7 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const { Parser } = require('acorn')
4
- const { importAssertions } = require('acorn-import-assertions');
4
+ const { importAssertions } = require('acorn-import-assertions')
5
5
 
6
6
  const acornOpts = {
7
7
  ecmaVersion: 'latest',
@@ -14,9 +14,36 @@ function warn (txt) {
14
14
  process.emitWarning(txt, 'get-esm-exports')
15
15
  }
16
16
 
17
- function getEsmExports (moduleStr) {
17
+ /**
18
+ * Utilizes an AST parser to interpret ESM source code and build a list of
19
+ * exported identifiers. In the baseline case, the list of identifiers will be
20
+ * the simple identifier names as written in the source code of the module.
21
+ * However, there are some special cases:
22
+ *
23
+ * 1. When an `export * from './foo.js'` line is encountered it is rewritten
24
+ * as `* from ./foo.js`. This allows the interpreting code to recognize a
25
+ * transitive export and recursively parse the indicated module. The returned
26
+ * identifier list will have "* from ./foo.js" as an item.
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
+ * @param {object} params
33
+ * @param {string} params.moduleSource The source code of the module to parse
34
+ * 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
+ *
41
+ * @returns {string[]} The identifiers exported by the module along with any
42
+ * custom directives.
43
+ */
44
+ function getEsmExports ({ moduleSource, defaultAs = 'default' }) {
18
45
  const exportedNames = new Set()
19
- const tree = parser.parse(moduleStr, acornOpts)
46
+ const tree = parser.parse(moduleSource, acornOpts)
20
47
  for (const node of tree.body) {
21
48
  if (!node.type.startsWith('Export')) continue
22
49
  switch (node.type) {
@@ -27,14 +54,29 @@ function getEsmExports (moduleStr) {
27
54
  parseSpecifiers(node, exportedNames)
28
55
  }
29
56
  break
30
- case 'ExportDefaultDeclaration':
31
- exportedNames.add('default')
57
+
58
+ case 'ExportDefaultDeclaration': {
59
+ if (defaultAs === 'default') {
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
+
32
72
  break
73
+ }
74
+
33
75
  case 'ExportAllDeclaration':
34
76
  if (node.exported) {
35
77
  exportedNames.add(node.exported.name)
36
78
  } else {
37
- exportedNames.add('*')
79
+ exportedNames.add(`* from ${node.source.value}`)
38
80
  }
39
81
  break
40
82
  default:
@@ -3,13 +3,60 @@
3
3
  const getEsmExports = require('./get-esm-exports.js')
4
4
  const { parse: getCjsExports } = require('cjs-module-lexer')
5
5
  const fs = require('fs')
6
- const { fileURLToPath } = require('url')
6
+ const { fileURLToPath, pathToFileURL } = require('url')
7
7
 
8
- function addDefault(arr) {
8
+ function addDefault (arr) {
9
9
  return Array.from(new Set(['default', ...arr]))
10
10
  }
11
11
 
12
- async function getExports (url, context, parentLoad) {
12
+ const urlsBeingProcessed = new Set() // Guard against circular imports.
13
+
14
+ async function getFullCjsExports (url, context, parentLoad, source) {
15
+ if (urlsBeingProcessed.has(url)) {
16
+ return []
17
+ }
18
+ urlsBeingProcessed.add(url)
19
+
20
+ const ex = getCjsExports(source)
21
+ const full = Array.from(new Set([
22
+ ...addDefault(ex.exports),
23
+ ...(await Promise.all(ex.reexports.map(re => getExports(({
24
+ url: (/^(..?($|\/|\\))/).test(re)
25
+ ? pathToFileURL(require.resolve(fileURLToPath(new URL(re, url)))).toString()
26
+ : pathToFileURL(require.resolve(re)).toString(),
27
+ context,
28
+ parentLoad
29
+ }))))).flat()
30
+ ]))
31
+
32
+ urlsBeingProcessed.delete(url)
33
+ return full
34
+ }
35
+
36
+ /**
37
+ * Inspects a module for its type (commonjs or module), attempts to get the
38
+ * source code for said module from the loader API, and parses the result
39
+ * for the entities exported from that module.
40
+ *
41
+ * @param {object} params
42
+ * @param {string} params.url A file URL string pointing to the module that
43
+ * we should get the exports of.
44
+ * @param {object} params.context Context object as provided by the `load`
45
+ * hook from the loaders API.
46
+ * @param {Function} params.parentLoad Next hook function in the loaders API
47
+ * 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
+ *
55
+ * @returns {Promise<string[]>} An array of identifiers exported by the module.
56
+ * Please see {@link getEsmExports} for caveats on special identifiers that may
57
+ * be included in the result set.
58
+ */
59
+ async function getExports ({ url, context, parentLoad, defaultAs = 'default' }) {
13
60
  // `parentLoad` gives us the possibility of getting the source
14
61
  // from an upstream loader. This doesn't always work though,
15
62
  // so later on we fall back to reading it from disk.
@@ -30,21 +77,21 @@ async function getExports (url, context, parentLoad) {
30
77
  }
31
78
 
32
79
  if (format === 'module') {
33
- return getEsmExports(source)
80
+ return getEsmExports({ moduleSource: source, defaultAs })
34
81
  }
35
82
  if (format === 'commonjs') {
36
- return addDefault(getCjsExports(source).exports)
83
+ return getFullCjsExports(url, context, parentLoad, source)
37
84
  }
38
85
 
39
86
  // At this point our `format` is either undefined or not known by us. Fall
40
87
  // back to parsing as ESM/CJS.
41
- const esmExports = getEsmExports(source)
88
+ const esmExports = getEsmExports({ moduleSource: source, defaultAs })
42
89
  if (!esmExports.length) {
43
90
  // TODO(bengl) it's might be possible to get here if somehow the format
44
91
  // isn't set at first and yet we have an ESM module with no exports.
45
92
  // I couldn't construct an example that would do this, so maybe it's
46
93
  // impossible?
47
- return addDefault(getCjsExports(source).exports)
94
+ return getFullCjsExports(url, context, parentLoad, source)
48
95
  }
49
96
  }
50
97
 
package/lib/register.js CHANGED
@@ -2,18 +2,17 @@
2
2
  //
3
3
  // This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc.
4
4
 
5
-
6
5
  const importHooks = [] // TODO should this be a Set?
7
6
  const setters = new WeakMap()
8
7
  const specifiers = new Map()
9
8
  const toHook = []
10
9
 
11
10
  const proxyHandler = {
12
- set(target, name, value) {
11
+ set (target, name, value) {
13
12
  return setters.get(target)[name](value)
14
13
  },
15
14
 
16
- defineProperty(target, property, descriptor) {
15
+ defineProperty (target, property, descriptor) {
17
16
  if ((!('value' in descriptor))) {
18
17
  throw new Error('Getters/setters are not supported for exports property descriptors.')
19
18
  }
@@ -22,7 +21,7 @@ const proxyHandler = {
22
21
  }
23
22
  }
24
23
 
25
- function register(name, namespace, set, specifier) {
24
+ function register (name, namespace, set, specifier) {
26
25
  specifiers.set(name, specifier)
27
26
  setters.set(namespace, set)
28
27
  const proxy = new Proxy(namespace, proxyHandler)
package/package.json CHANGED
@@ -1,12 +1,14 @@
1
1
  {
2
2
  "name": "import-in-the-middle",
3
- "version": "1.7.1",
3
+ "version": "1.7.3",
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 'node test/runtest' --files test/{hook,low-level,other,get-esm-exports}/*",
8
- "test:ts": "c8 imhotap --runner 'node test/runtest' --files test/typescript/*.test.mts",
9
- "coverage": "c8 --reporter html imhotap --runner 'node test/runtest' --files test/{hook,low-level,other,get-esm-exports}/* && echo '\nNow open coverage/index.html\n'"
7
+ "test": "c8 --reporter lcov --check-coverage --lines 50 imhotap --files test/{hook,low-level,other,get-esm-exports}/*",
8
+ "test:ts": "c8 --reporter lcov imhotap --files test/typescript/*.test.mts",
9
+ "coverage": "c8 --reporter html imhotap --files test/{hook,low-level,other,get-esm-exports}/* && echo '\nNow open coverage/index.html\n'",
10
+ "lint": "eslint .",
11
+ "lint:fix": "eslint . --fix"
10
12
  },
11
13
  "repository": {
12
14
  "type": "git",
@@ -26,9 +28,22 @@
26
28
  "url": "https://github.com/DataDog/import-in-the-middle/issues"
27
29
  },
28
30
  "homepage": "https://github.com/DataDog/import-in-the-middle#readme",
31
+ "imhotap": {
32
+ "runner": "node",
33
+ "test-env": "NODE_OPTIONS=--no-warnings --require ./test/version-check.js --experimental-loader ./test/generic-loader.mjs"
34
+ },
29
35
  "devDependencies": {
36
+ "@babel/core": "^7.23.7",
37
+ "@babel/eslint-parser": "^7.23.3",
38
+ "@babel/plugin-syntax-import-assertions": "^7.23.3",
30
39
  "@types/node": "^18.0.6",
31
40
  "c8": "^7.8.0",
41
+ "eslint": "^8.55.0",
42
+ "eslint-config-standard": "^17.1.0",
43
+ "eslint-plugin-import": "^2.29.0",
44
+ "eslint-plugin-n": "^16.4.0",
45
+ "eslint-plugin-node": "^11.1.0",
46
+ "eslint-plugin-promise": "^6.1.1",
32
47
  "imhotap": "^2.1.0",
33
48
  "ts-node": "^10.9.1",
34
49
  "typescript": "^4.7.4"
package/test/README.md CHANGED
@@ -3,11 +3,15 @@ These tests are organized as follows:
3
3
  * Located in the `hook` directory if they use the `Hook` class.
4
4
  * Located in the `low-level` directory if they use the "low-level" API,
5
5
  `addHook` and `removeHook`.
6
+ * Other tests are in other adjacent directories.
6
7
 
7
- The tests should be run with the `runtest` command found in this directory. If
8
- the command exits with a non-zero code, then it's a test failure.
8
+ The tests can be run individually as Node.js programs with non-zero exit codes
9
+ upon failures. They should be run with the following Node.js command-line
10
+ options (assuming they're run from the project root):
9
11
 
10
- Running of all the tests can be done with `npm test`.
12
+ ```
13
+ --require ./test/version-check.js
14
+ --experimental-loader ./test/generic-loader.mmjs
15
+ ```
11
16
 
12
- Coverage must be 100% according to `c8`. If you don't have 100% coverage, you
13
- can run `npm run coverage` to get coverage data in HTML form.
17
+ The entire test suite can be run with `npm test`.
@@ -0,0 +1,7 @@
1
+ export const a = 'a'
2
+
3
+ export function aFunc () {
4
+ return a
5
+ }
6
+
7
+ export * from './foo.mjs'
@@ -0,0 +1,5 @@
1
+ export const b = 'b'
2
+
3
+ export function bFunc () {
4
+ return b
5
+ }
@@ -0,0 +1,4 @@
1
+ import bar from './something.mjs'
2
+ export default bar
3
+ export * from './a.mjs'
4
+ export * from './b.mjs'
@@ -0,0 +1,18 @@
1
+ // The following is generated by tslib. __exportStar is a format which cjs-module-lexer exposes as a reexport
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
5
+ }) : (function(o, m, k, k2) {
6
+ if (k2 === undefined) k2 = k;
7
+ o[k2] = m[k];
8
+ }));
9
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
10
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
11
+ };
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+
14
+
15
+ __exportStar(require("./circular-b"), exports);
16
+
17
+ exports.foo = 42;
18
+
@@ -0,0 +1,15 @@
1
+ // The following is generated by tslib. __exportStar is a format which cjs-module-lexer exposes as a reexport
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
5
+ }) : (function(o, m, k, k2) {
6
+ if (k2 === undefined) k2 = k;
7
+ o[k2] = m[k];
8
+ }));
9
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
10
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
11
+ };
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+
14
+ __exportStar(require("./circular-a"), exports);
15
+
@@ -0,0 +1,7 @@
1
+ import { testB } from './cyclical-b.mjs'
2
+
3
+ export function testA () {
4
+ console.log('testA')
5
+ }
6
+
7
+ testB()
@@ -0,0 +1,6 @@
1
+ import { testA } from './cyclical-a.mjs'
2
+
3
+ export function testB () {
4
+ console.log('testB')
5
+ testA()
6
+ }
@@ -0,0 +1,3 @@
1
+ export default class DefaultClass {
2
+ value = 'DefaultClass'
3
+ }