import-in-the-middle 1.7.1 → 1.7.2

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 CHANGED
@@ -2,6 +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 { randomBytes } = require('crypto')
5
6
  const specifiers = new Map()
6
7
  const isWin = process.platform === "win32"
7
8
 
@@ -77,6 +78,76 @@ function needsToAddFileProtocol(urlObj) {
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 {string[]} setters The shimmed setters for all the exports
102
+ * from the module and any transitive export all modules.
103
+ */
104
+
105
+ /**
106
+ * Processes a module's exports and builds a set of new import statements,
107
+ * namespace names, and setter blocks. If an export all export if encountered,
108
+ * the target exports will be hoisted to the current module via a generated
109
+ * namespace.
110
+ *
111
+ * @param {object} params
112
+ * @param {string} params.srcUrl The full URL to the module to process.
113
+ * @param {object} params.context Provided by the loaders API.
114
+ * @param {function} parentGetSource Provides the source code for the parent
115
+ * module.
116
+ * @returns {Promise<ProcessedModule>}
117
+ */
118
+ async function processModule({ srcUrl, context, parentGetSource }) {
119
+ const exportNames = await getExports(srcUrl, context, parentGetSource)
120
+ const imports = [`import * as namespace from ${JSON.stringify(srcUrl)}`]
121
+ const namespaces = ['namespace']
122
+ const setters = []
123
+
124
+ for (const n of exportNames) {
125
+ if (isStarExportLine(n) === true) {
126
+ const [_, modFile] = n.split('* from ')
127
+ const modUrl = new URL(modFile, srcUrl).toString()
128
+ const modName = Buffer.from(modFile, 'hex') + Date.now() + randomBytes(4).toString('hex')
129
+
130
+ imports.push(`import * as $${modName} from ${JSON.stringify(modUrl)}`)
131
+ namespaces.push(`$${modName}`)
132
+
133
+ const data = await processModule({ srcUrl: modUrl, context, parentGetSource })
134
+ Array.prototype.push.apply(setters, data.setters)
135
+
136
+ continue
137
+ }
138
+
139
+ setters.push(`
140
+ let $${n} = _.${n}
141
+ export { $${n} as ${n} }
142
+ set.${n} = (v) => {
143
+ $${n} = v
144
+ return true
145
+ }
146
+ `)
147
+ }
148
+
149
+ return { imports, namespaces, setters }
150
+ }
80
151
 
81
152
  function addIitm (url) {
82
153
  const urlObj = new URL(url)
@@ -123,21 +194,22 @@ function createHook (meta) {
123
194
  async function getSource (url, context, parentGetSource) {
124
195
  if (hasIitm(url)) {
125
196
  const realUrl = deleteIitm(url)
126
- const exportNames = await getExports(realUrl, context, parentGetSource)
197
+ const { imports, namespaces, setters } = await processModule({
198
+ srcUrl: realUrl,
199
+ context,
200
+ parentGetSource
201
+ })
202
+
127
203
  return {
128
204
  source: `
129
205
  import { register } from '${iitmURL}'
130
- import * as namespace from ${JSON.stringify(url)}
206
+ ${imports.join('\n')}
207
+
208
+ const _ = Object.assign({}, ...[${namespaces.join(', ')}])
131
209
  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
138
- }
139
- `).join('\n')}
140
- register(${JSON.stringify(realUrl)}, namespace, set, ${JSON.stringify(specifiers.get(realUrl))})
210
+
211
+ ${setters.join('\n')}
212
+ register(${JSON.stringify(realUrl)}, _, set, ${JSON.stringify(specifiers.get(realUrl))})
141
213
  `
142
214
  }
143
215
  }
@@ -34,7 +34,7 @@ function getEsmExports (moduleStr) {
34
34
  if (node.exported) {
35
35
  exportedNames.add(node.exported.name)
36
36
  } else {
37
- exportedNames.add('*')
37
+ exportedNames.add(`* from ${node.source.value}`)
38
38
  }
39
39
  break
40
40
  default:
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "import-in-the-middle",
3
- "version": "1.7.1",
3
+ "version": "1.7.2",
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",
7
+ "test": "c8 --reporter lcov --check-coverage --lines 70 imhotap --runner 'node test/runtest' --files test/{hook,low-level,other,get-esm-exports}/*",
8
+ "test:ts": "c8 --reporter lcov imhotap --runner 'node test/runtest' --files test/typescript/*.test.mts",
9
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'"
10
10
  },
11
11
  "repository": {
@@ -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'
@@ -23,7 +23,7 @@ export default class { /* … */ } //| default
23
23
  export default function* () { /* … */ } //| default
24
24
 
25
25
  // Aggregating modules
26
- export * from "module-name"; //| *
26
+ export * from "module-name"; //| * from module-name
27
27
  export * as name1 from "module-name"; //| name1
28
28
  export { name1, /* …, */ nameN } from "module-name"; //| name1,nameN
29
29
  export { import1 as name1, import2 as name2, /* …, */ nameN } from "module-name"; //| name1,name2,nameN
@@ -0,0 +1,5 @@
1
+ export function foo() {
2
+ return 'foo'
3
+ }
4
+
5
+ export * from './lib/baz.mjs'
@@ -0,0 +1,3 @@
1
+ export function baz() {
2
+ return 'baz'
3
+ }
@@ -0,0 +1,32 @@
1
+ import { strictEqual } from 'assert'
2
+ import Hook from '../../index.js'
3
+ Hook((exports, name) => {
4
+ if (/bundle\.mjs/.test(name) === false) return
5
+
6
+ const bar = exports.default
7
+ exports.default = function wrappedBar() {
8
+ return bar() + '-wrapped'
9
+ }
10
+
11
+ const foo = exports.foo
12
+ exports.foo = function wrappedFoo() {
13
+ return foo() + '-wrapped'
14
+ }
15
+
16
+ const aFunc = exports.aFunc
17
+ exports.aFunc = function wrappedAFunc() {
18
+ return aFunc() + '-wrapped'
19
+ }
20
+ })
21
+
22
+ import {
23
+ default as bar,
24
+ foo,
25
+ aFunc,
26
+ baz
27
+ } from '../fixtures/bundle.mjs'
28
+
29
+ strictEqual(bar(), '42-wrapped')
30
+ strictEqual(foo(), 'foo-wrapped')
31
+ strictEqual(aFunc(), 'a-wrapped')
32
+ strictEqual(baz(), 'baz')