import-in-the-middle 3.3.0 → 3.3.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/CHANGELOG.md +17 -0
- package/README.md +9 -0
- package/create-hook.mjs +33 -91
- package/lib/get-esm-exports.mjs +6 -1
- package/lib/get-exports.mjs +25 -0
- package/lib/register.js +136 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [3.3.2](https://github.com/nodejs/import-in-the-middle/compare/import-in-the-middle-v3.3.1...import-in-the-middle-v3.3.2) (2026-07-20)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* delegate tagged modules to their owning loader ([#276](https://github.com/nodejs/import-in-the-middle/issues/276)) ([e0c57bd](https://github.com/nodejs/import-in-the-middle/commit/e0c57bd810d89d74e6a22a14c243d650cbda8a3c))
|
|
9
|
+
* escape loader URL in generated wrappers ([#275](https://github.com/nodejs/import-in-the-middle/issues/275)) ([86ca0e5](https://github.com/nodejs/import-in-the-middle/commit/86ca0e579cb8b592b0ae49cef9bc834b3971592d))
|
|
10
|
+
* skip code generation on broken maglev ([#271](https://github.com/nodejs/import-in-the-middle/issues/271)) ([ae914ac](https://github.com/nodejs/import-in-the-middle/commit/ae914ac41a4cc9215cbaf4fa5ec48fbe3fa8e7b3))
|
|
11
|
+
|
|
12
|
+
## [3.3.1](https://github.com/nodejs/import-in-the-middle/compare/import-in-the-middle-v3.3.0...import-in-the-middle-v3.3.1) (2026-07-07)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Performance Improvements
|
|
16
|
+
|
|
17
|
+
* hoist generated wrapper binding runtime ([#267](https://github.com/nodejs/import-in-the-middle/issues/267)) ([4f41140](https://github.com/nodejs/import-in-the-middle/commit/4f41140d44f91448899580cbb97f558539005657))
|
|
18
|
+
* read each wrapped module from disk once ([#265](https://github.com/nodejs/import-in-the-middle/issues/265)) ([22b40aa](https://github.com/nodejs/import-in-the-middle/commit/22b40aa86003934c944b0f586be3889e8c230616))
|
|
19
|
+
|
|
3
20
|
## [3.3.0](https://github.com/nodejs/import-in-the-middle/compare/import-in-the-middle-v3.2.0...import-in-the-middle-v3.3.0) (2026-07-02)
|
|
4
21
|
|
|
5
22
|
|
package/README.md
CHANGED
|
@@ -228,3 +228,12 @@ On Node.js versions where type stripping is not enabled by default, run with
|
|
|
228
228
|
* While bindings to module exports end up being "re-bound" when modified in a
|
|
229
229
|
hook, dynamically imported modules cannot be altered after they're loaded.
|
|
230
230
|
* Modules loaded via `require` are not affected at all.
|
|
231
|
+
* A module's set of export *names* is assumed to be stable for the lifetime of
|
|
232
|
+
the process. `import-in-the-middle` reads a module's source once to lex its
|
|
233
|
+
exports and reuses that export set on later loads of the same URL. An upstream
|
|
234
|
+
loader that returns a *different set of exports* for the same URL across calls
|
|
235
|
+
— a stateful codegen loader, or one that varies its output by `context` — is
|
|
236
|
+
not supported and will be instrumented with the export set from its first
|
|
237
|
+
load. Idempotent transforms (type stripping, AST instrumentation, minifiers)
|
|
238
|
+
are unaffected, and the actual module still executes the source its real load
|
|
239
|
+
returns; only the interceptable export *names* are memoized.
|
package/create-hook.mjs
CHANGED
|
@@ -17,7 +17,6 @@ import { supportsSyncHooks } from './supports-sync-hooks.mjs'
|
|
|
17
17
|
// this file's acorn / cjs-module-lexer dependency graph.
|
|
18
18
|
export { supportsSyncHooks }
|
|
19
19
|
|
|
20
|
-
const specifiers = new Map()
|
|
21
20
|
const isWin = process.platform === 'win32'
|
|
22
21
|
|
|
23
22
|
// Depth at which `processModule` starts tracking visited URLs to break an
|
|
@@ -37,6 +36,9 @@ const HANDLED_FORMATS = new Set([
|
|
|
37
36
|
])
|
|
38
37
|
const TRACE_WARNINGS = process.execArgv.includes('--trace-warnings')
|
|
39
38
|
|
|
39
|
+
/** @typedef {import('node:module').LoadHookContext} LoadContext */
|
|
40
|
+
/** @typedef {import('node:module').LoadFnOutput} LoadResult */
|
|
41
|
+
|
|
40
42
|
function hasIitm (url) {
|
|
41
43
|
// Fast path: avoid URL parsing on the hot path when there's clearly no iitm.
|
|
42
44
|
if (typeof url !== 'string' || url.indexOf('iitm') === -1) {
|
|
@@ -214,9 +216,9 @@ function buildSetter (n, srcUrl, namespaceVar) {
|
|
|
214
216
|
const objectKey = JSON.stringify(n)
|
|
215
217
|
const reExportedName = n === 'default' ? n : objectKey
|
|
216
218
|
|
|
217
|
-
//
|
|
218
|
-
//
|
|
219
|
-
const
|
|
219
|
+
// Fall back to namespace['default'] for the module.exports synthetic export,
|
|
220
|
+
// which builtins don't expose on the native ESM namespace.
|
|
221
|
+
const useFallback = n === 'module.exports'
|
|
220
222
|
|
|
221
223
|
// Builtins don't expose the module.exports synthetic name, so skip its re-export.
|
|
222
224
|
const reExportLine = (n === 'module.exports' && (srcUrl.startsWith('node:') || builtinModules.includes(srcUrl)))
|
|
@@ -224,7 +226,7 @@ function buildSetter (n, srcUrl, namespaceVar) {
|
|
|
224
226
|
: `export { ${variableName} as ${reExportedName} }`
|
|
225
227
|
|
|
226
228
|
return `let ${variableName}
|
|
227
|
-
|
|
229
|
+
__binder.bind(${objectKey}, ${namespaceVar}, v => { ${variableName} = v }, () => ${variableName}, ${useFallback})
|
|
228
230
|
${reExportLine}`
|
|
229
231
|
}
|
|
230
232
|
|
|
@@ -404,7 +406,11 @@ function addIitm (url) {
|
|
|
404
406
|
return urlObj.href
|
|
405
407
|
}
|
|
406
408
|
|
|
409
|
+
/**
|
|
410
|
+
* @param {{ url: string }} meta
|
|
411
|
+
*/
|
|
407
412
|
export function createHook (meta) {
|
|
413
|
+
const specifiers = new Map()
|
|
408
414
|
let cachedResolve
|
|
409
415
|
const iitmURL = new URL('lib/register.js', meta.url).toString()
|
|
410
416
|
let includeModules, excludeModules
|
|
@@ -651,98 +657,16 @@ export function createHook (meta) {
|
|
|
651
657
|
}
|
|
652
658
|
|
|
653
659
|
return `
|
|
654
|
-
import { register } from
|
|
660
|
+
import { register, ModuleBinder } from ${JSON.stringify(iitmURL)}
|
|
655
661
|
import * as namespace from ${JSON.stringify(realUrl)}
|
|
656
662
|
${originImports}
|
|
657
|
-
|
|
658
|
-
const _ = Object.create(null, { [Symbol.toStringTag]: { value: 'Module' } })
|
|
659
|
-
const set = {}
|
|
660
|
-
const get = {}
|
|
661
|
-
const __overridden = Object.create(null)
|
|
662
|
-
let __pending = []
|
|
663
|
-
|
|
664
|
-
function __makeUpdater (key, read, assign) {
|
|
665
|
-
return () => {
|
|
666
|
-
if (__overridden[key] === true) return true
|
|
667
|
-
try {
|
|
668
|
-
const v = read()
|
|
669
|
-
if (v !== undefined) {
|
|
670
|
-
assign(v)
|
|
671
|
-
return true
|
|
672
|
-
}
|
|
673
|
-
return false
|
|
674
|
-
} catch (err) {
|
|
675
|
-
if (err instanceof ReferenceError) return false
|
|
676
|
-
throw err
|
|
677
|
-
}
|
|
678
|
-
}
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
function __flushPendingOnce () {
|
|
682
|
-
if (__pending.length === 0) return
|
|
683
|
-
const next = []
|
|
684
|
-
for (const fn of __pending) {
|
|
685
|
-
// If it still throws ReferenceError, keep it for the (single) next attempt.
|
|
686
|
-
if (fn() !== true) next.push(fn)
|
|
687
|
-
}
|
|
688
|
-
__pending = next
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
function __bind (key, source, write, read, useFallback) {
|
|
692
|
-
const readSource = useFallback
|
|
693
|
-
? () => source[key] ?? source['default']
|
|
694
|
-
: () => source[key]
|
|
695
|
-
__overridden[key] = false
|
|
696
|
-
let deferred = false
|
|
697
|
-
try {
|
|
698
|
-
const v = readSource()
|
|
699
|
-
write(v)
|
|
700
|
-
_[key] = v
|
|
701
|
-
} catch (err) {
|
|
702
|
-
if (!(err instanceof ReferenceError)) throw err
|
|
703
|
-
deferred = true
|
|
704
|
-
}
|
|
705
|
-
if (deferred || read() === undefined) {
|
|
706
|
-
__pending.push(__makeUpdater(key, readSource, (v) => { write(v); _[key] = v }))
|
|
707
|
-
}
|
|
708
|
-
set[key] = (v) => {
|
|
709
|
-
__overridden[key] = true
|
|
710
|
-
write(v)
|
|
711
|
-
return true
|
|
712
|
-
}
|
|
713
|
-
get[key] = read
|
|
714
|
-
}
|
|
663
|
+
const __binder = new ModuleBinder()
|
|
715
664
|
|
|
716
665
|
${Array.from(setters.values()).join('\n')}
|
|
717
666
|
|
|
718
|
-
|
|
719
|
-
queueMicrotask(() => {
|
|
720
|
-
__flushPendingOnce()
|
|
721
|
-
|
|
722
|
-
if (__pending.length > 0) {
|
|
723
|
-
const __retryDelays = [0, 10, 50]
|
|
724
|
-
const __schedulePending = (i) => {
|
|
725
|
-
if (__pending.length === 0) return
|
|
726
|
-
if (i >= __retryDelays.length) {
|
|
727
|
-
// Give up: leave exports as-is to avoid unbounded retries.
|
|
728
|
-
__pending = []
|
|
729
|
-
return
|
|
730
|
-
}
|
|
667
|
+
__binder.flush()
|
|
731
668
|
|
|
732
|
-
|
|
733
|
-
__flushPendingOnce()
|
|
734
|
-
__schedulePending(i + 1)
|
|
735
|
-
}, __retryDelays[i])
|
|
736
|
-
// Don't keep the process alive just for best-effort retries.
|
|
737
|
-
if (t && typeof t.unref === 'function') t.unref()
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
__schedulePending(0)
|
|
741
|
-
}
|
|
742
|
-
})
|
|
743
|
-
}
|
|
744
|
-
|
|
745
|
-
register(${JSON.stringify(realUrl)}, _, set, get, ${JSON.stringify(originalSpecifier)})
|
|
669
|
+
register(${JSON.stringify(realUrl)}, __binder.namespace, __binder.set, __binder.get, ${JSON.stringify(originalSpecifier)})
|
|
746
670
|
`
|
|
747
671
|
}
|
|
748
672
|
|
|
@@ -771,10 +695,19 @@ register(${JSON.stringify(realUrl)}, _, set, get, ${JSON.stringify(originalSpeci
|
|
|
771
695
|
emitWarning(err)
|
|
772
696
|
}
|
|
773
697
|
|
|
698
|
+
/**
|
|
699
|
+
* @param {string} url
|
|
700
|
+
* @param {LoadContext} context
|
|
701
|
+
* @param {(url: string, context?: Partial<LoadContext>) => LoadResult | Promise<LoadResult>} parentGetSource
|
|
702
|
+
*/
|
|
774
703
|
async function getSource (url, context, parentGetSource) {
|
|
775
704
|
if (hasIitm(url)) {
|
|
776
705
|
const realUrl = deleteIitm(url)
|
|
777
706
|
const originalSpecifier = specifiers.get(realUrl)
|
|
707
|
+
if (originalSpecifier === undefined) {
|
|
708
|
+
specifiers.delete(url)
|
|
709
|
+
return parentGetSource(url, context)
|
|
710
|
+
}
|
|
778
711
|
|
|
779
712
|
try {
|
|
780
713
|
const { setters, originNamespaces } = await driveAsync(
|
|
@@ -795,10 +728,19 @@ register(${JSON.stringify(realUrl)}, _, set, get, ${JSON.stringify(originalSpeci
|
|
|
795
728
|
// Synchronous counterpart to `getSource`, for `module.registerHooks`. Drives
|
|
796
729
|
// `processModule` straight through; all bookkeeping and source generation is
|
|
797
730
|
// shared with `getSource`.
|
|
731
|
+
/**
|
|
732
|
+
* @param {string} url
|
|
733
|
+
* @param {LoadContext} context
|
|
734
|
+
* @param {(url: string, context?: Partial<LoadContext>) => LoadResult} nextLoad
|
|
735
|
+
*/
|
|
798
736
|
function getSourceSync (url, context, nextLoad) {
|
|
799
737
|
if (hasIitm(url)) {
|
|
800
738
|
const realUrl = deleteIitm(url)
|
|
801
739
|
const originalSpecifier = specifiers.get(realUrl)
|
|
740
|
+
if (originalSpecifier === undefined) {
|
|
741
|
+
specifiers.delete(url)
|
|
742
|
+
return nextLoad(url, context)
|
|
743
|
+
}
|
|
802
744
|
|
|
803
745
|
try {
|
|
804
746
|
const { setters, originNamespaces } = driveSync(
|
package/lib/get-esm-exports.mjs
CHANGED
|
@@ -15,8 +15,13 @@ const require = createRequire(import.meta.url)
|
|
|
15
15
|
// generation from strings is disallowed. Resolve the parser once at load time
|
|
16
16
|
// so the per-module path stays a bare `parse(source)` call.
|
|
17
17
|
const flag = '--disallow-code-generation-from-strings'
|
|
18
|
+
// Node.js versions with broken maglev compiler
|
|
19
|
+
const hasBrokenMaglev = process.version.startsWith('v20.') &&
|
|
20
|
+
process.version[5] === '.' &&
|
|
21
|
+
Number(process.version[4]) < 9
|
|
18
22
|
const disallowCodegen = process.execArgv.includes(flag) ||
|
|
19
|
-
(process.env.NODE_OPTIONS?.includes(flag) ?? false)
|
|
23
|
+
(process.env.NODE_OPTIONS?.includes(flag) ?? false) ||
|
|
24
|
+
hasBrokenMaglev
|
|
20
25
|
|
|
21
26
|
// initSync compiles the Wasm module up front so `parse` can run inside
|
|
22
27
|
// synchronous loader hooks (`module.registerHooks`) as well as the off-thread
|
package/lib/get-exports.mjs
CHANGED
|
@@ -73,6 +73,24 @@ function getExportsForNodeBuiltIn (name) {
|
|
|
73
73
|
|
|
74
74
|
const urlsBeingProcessed = new Set() // Guard against circular imports.
|
|
75
75
|
|
|
76
|
+
// Memoizes an ESM module's export names by URL. A leaf reached from several
|
|
77
|
+
// barrels (`export *`) would otherwise be read and lexed once per barrel, and
|
|
78
|
+
// the same URL reached from separate top-level wraps would be lexed once per
|
|
79
|
+
// wrap; the first lex serves every later one.
|
|
80
|
+
//
|
|
81
|
+
// This assumes a module's export names are stable for the process: the memo
|
|
82
|
+
// serves a later, independent scan without re-running the loader chain's
|
|
83
|
+
// `load()`, so a loader that returns a *different export set* for the same URL
|
|
84
|
+
// across calls (a stateful codegen loader, or one keyed off `context`) is a
|
|
85
|
+
// deliberately unsupported case — it would be served the first scan's names.
|
|
86
|
+
// Idempotent transforms (type stripping, AST instrumentation such as
|
|
87
|
+
// OrchestrionJS, minifiers) are stable and unaffected; the wrapper's real
|
|
88
|
+
// namespace `load()` still runs every time, so per-load source differences in
|
|
89
|
+
// the *executed* module are preserved (see the wrapper's `import * as
|
|
90
|
+
// namespace`). Only the pure ESM result is cached: the CommonJS path mutates
|
|
91
|
+
// `context.format` and resolves re-exports, and builtins memoize via BUILT_INS.
|
|
92
|
+
const esmExportsCache = new Map()
|
|
93
|
+
|
|
76
94
|
/**
|
|
77
95
|
* This function looks for the package.json which contains the specifier trying to resolve.
|
|
78
96
|
* Once the package.json file has been found, we extract the file path from the specifier
|
|
@@ -212,6 +230,11 @@ function * getCjsExports (url, context, source) {
|
|
|
212
230
|
* be included in the result set.
|
|
213
231
|
*/
|
|
214
232
|
export function * getExports (url, context) {
|
|
233
|
+
const cached = esmExportsCache.get(url)
|
|
234
|
+
if (cached !== undefined) {
|
|
235
|
+
return cached.exportNames
|
|
236
|
+
}
|
|
237
|
+
|
|
215
238
|
// `[LOAD, ...]` gives us the possibility of getting the source from an
|
|
216
239
|
// upstream loader. This doesn't always work though, so later on we fall back
|
|
217
240
|
// to reading it from disk.
|
|
@@ -265,6 +288,7 @@ export function * getExports (url, context) {
|
|
|
265
288
|
const { exportNames, hasModuleSyntax } = lexEsm(source)
|
|
266
289
|
|
|
267
290
|
if (moduleFormat === 'module') {
|
|
291
|
+
esmExportsCache.set(url, { exportNames })
|
|
268
292
|
return exportNames
|
|
269
293
|
}
|
|
270
294
|
|
|
@@ -275,6 +299,7 @@ export function * getExports (url, context) {
|
|
|
275
299
|
if (!exportNames.size && !hasModuleSyntax) {
|
|
276
300
|
return yield * getCjsExports(url, context, source)
|
|
277
301
|
}
|
|
302
|
+
esmExportsCache.set(url, { exportNames })
|
|
278
303
|
return exportNames
|
|
279
304
|
} catch (cause) {
|
|
280
305
|
const err = new Error(`Failed to parse '${url}'`)
|
package/lib/register.js
CHANGED
|
@@ -55,7 +55,143 @@ function register (name, namespace, set, get, specifier) {
|
|
|
55
55
|
toHook.push([name, proxy, specifier])
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
// Delays (ms) for re-reading exports that were still in their temporal dead zone
|
|
59
|
+
// when the wrapper first ran (circular imports). Retried on a microtask first,
|
|
60
|
+
// then at these intervals; unref'd so best-effort retries never hold the process
|
|
61
|
+
// open. Frozen once at module load rather than rebuilt per wrapper.
|
|
62
|
+
const RETRY_DELAYS = [0, 10, 50]
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Per-wrapped-module state a generated wrapper builds once to expose its exports
|
|
66
|
+
* through iitm's proxy. Each wrapper holds a local binding per export plus
|
|
67
|
+
* `write`/`read` closures over it; `bind` seeds that binding from the real
|
|
68
|
+
* module and installs the proxy's `set`/`get` for the name, and `flush` resolves
|
|
69
|
+
* any export that was undefined (circular import) once it becomes available.
|
|
70
|
+
*
|
|
71
|
+
* This is the boilerplate the wrapper used to inline in full per module. Hoisting
|
|
72
|
+
* it here compiles it once instead of once per wrapped module and keeps the
|
|
73
|
+
* per-export bind call site monomorphic.
|
|
74
|
+
*/
|
|
75
|
+
class ModuleBinder {
|
|
76
|
+
// Mimics a Module namespace object (https://tc39.es/ecma262/#sec-module-namespace-objects).
|
|
77
|
+
namespace = Object.create(null, { [Symbol.toStringTag]: { value: 'Module' } })
|
|
78
|
+
set = {}
|
|
79
|
+
get = {}
|
|
80
|
+
#overridden = Object.create(null)
|
|
81
|
+
#pending = []
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Seeds `key` from `source` and installs its proxy accessors. A value that is
|
|
85
|
+
* undefined or throws `ReferenceError` (temporal dead zone during a circular
|
|
86
|
+
* import) is deferred to `flush`; any other throw propagates.
|
|
87
|
+
*
|
|
88
|
+
* @param {string} key The export name.
|
|
89
|
+
* @param {object} source The real module namespace to read the value from.
|
|
90
|
+
* @param {(value: unknown) => void} write Assigns the wrapper's local binding.
|
|
91
|
+
* @param {() => unknown} read Reads the wrapper's local binding.
|
|
92
|
+
* @param {boolean} useFallback Fall back to `source.default` (the synthetic
|
|
93
|
+
* `module.exports` name a builtin does not expose on its ESM namespace).
|
|
94
|
+
* @returns {void}
|
|
95
|
+
*/
|
|
96
|
+
bind (key, source, write, read, useFallback) {
|
|
97
|
+
const readSource = useFallback
|
|
98
|
+
? () => source[key] ?? source.default
|
|
99
|
+
: () => source[key]
|
|
100
|
+
this.#overridden[key] = false
|
|
101
|
+
let deferred = false
|
|
102
|
+
try {
|
|
103
|
+
const value = readSource()
|
|
104
|
+
write(value)
|
|
105
|
+
this.namespace[key] = value
|
|
106
|
+
} catch (error) {
|
|
107
|
+
if (!(error instanceof ReferenceError)) throw error
|
|
108
|
+
deferred = true
|
|
109
|
+
}
|
|
110
|
+
if (deferred || read() === undefined) {
|
|
111
|
+
this.#pending.push(this.#makeUpdater(key, readSource, write))
|
|
112
|
+
}
|
|
113
|
+
this.set[key] = (value) => {
|
|
114
|
+
this.#overridden[key] = true
|
|
115
|
+
write(value)
|
|
116
|
+
return true
|
|
117
|
+
}
|
|
118
|
+
this.get[key] = read
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* @param {string} key The export name to update.
|
|
123
|
+
* @param {() => unknown} readSource Reads the current value from the real module.
|
|
124
|
+
* @param {(value: unknown) => void} write Assigns the wrapper's local binding.
|
|
125
|
+
* @returns {() => boolean} Updater returning whether the value is now settled.
|
|
126
|
+
*/
|
|
127
|
+
#makeUpdater (key, readSource, write) {
|
|
128
|
+
return () => {
|
|
129
|
+
if (this.#overridden[key] === true) return true
|
|
130
|
+
try {
|
|
131
|
+
const value = readSource()
|
|
132
|
+
if (value !== undefined) {
|
|
133
|
+
write(value)
|
|
134
|
+
this.namespace[key] = value
|
|
135
|
+
return true
|
|
136
|
+
}
|
|
137
|
+
return false
|
|
138
|
+
} catch (error) {
|
|
139
|
+
if (error instanceof ReferenceError) return false
|
|
140
|
+
// Only reached if a getter starts throwing a non-ReferenceError after the
|
|
141
|
+
// initial bind read already succeeded or deferred; surfaces in flush's
|
|
142
|
+
// microtask. Kept as-is from the inline wrapper.
|
|
143
|
+
/* c8 ignore next */
|
|
144
|
+
throw error
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
#flushOnce () {
|
|
150
|
+
const next = []
|
|
151
|
+
for (const updater of this.#pending) {
|
|
152
|
+
// If it still throws ReferenceError, keep it for the (single) next attempt.
|
|
153
|
+
if (updater() !== true) next.push(updater)
|
|
154
|
+
}
|
|
155
|
+
this.#pending = next
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Resolves exports deferred by `bind` (undefined or TDZ at wrapper-eval time).
|
|
160
|
+
* Retries on a microtask, then at `RETRY_DELAYS`, giving up afterwards to avoid
|
|
161
|
+
* unbounded retries. A no-op when nothing was deferred.
|
|
162
|
+
*
|
|
163
|
+
* @returns {void}
|
|
164
|
+
*/
|
|
165
|
+
flush () {
|
|
166
|
+
if (this.#pending.length === 0) return
|
|
167
|
+
queueMicrotask(() => {
|
|
168
|
+
this.#flushOnce()
|
|
169
|
+
this.#scheduleRetry(0)
|
|
170
|
+
})
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* @param {number} attempt Index into `RETRY_DELAYS` for the next retry.
|
|
175
|
+
* @returns {void}
|
|
176
|
+
*/
|
|
177
|
+
#scheduleRetry (attempt) {
|
|
178
|
+
if (this.#pending.length === 0) return
|
|
179
|
+
if (attempt >= RETRY_DELAYS.length) {
|
|
180
|
+
// Give up: leave exports as-is to avoid unbounded retries.
|
|
181
|
+
this.#pending = []
|
|
182
|
+
return
|
|
183
|
+
}
|
|
184
|
+
const timer = setTimeout(() => {
|
|
185
|
+
this.#flushOnce()
|
|
186
|
+
this.#scheduleRetry(attempt + 1)
|
|
187
|
+
}, RETRY_DELAYS[attempt])
|
|
188
|
+
// Don't keep the process alive just for best-effort retries.
|
|
189
|
+
if (timer && typeof timer.unref === 'function') timer.unref()
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
58
193
|
exports.register = register
|
|
194
|
+
exports.ModuleBinder = ModuleBinder
|
|
59
195
|
exports.importHooks = importHooks
|
|
60
196
|
exports.specifiers = specifiers
|
|
61
197
|
exports.toHook = toHook
|