esm: add chaining to loaders
Notable changes
-
ESMLoader
now provides more detailed error messages for hooks, including the specific file and hook from which the error was caused (ex the hook broke the chain; but not SyntaxError within the hook—which was already working). In order to facilitate this:-
ESMLoader
's internal collections of hooks have changed fromhook fn[]
→Array<{ url: specifier, fn: hook }>
-
ESMLoader::import()
's return has changed fromModuleNamespace[] | ModuleNamespace
→Array<{ url: specifier, fn: hook }> | ModuleNamespace
-
TODOs:
-
hook up next()
s -
custom loaders FIFO → LIFO -
detect & throw on unsignalled short-circuit -
update existing hook output error messages to include newly available specifier -
update code docs -
update Error docs -
update ESM docs -
verify/fix node-land breakages caused the change to ESMLoader::import()
(I think this will not be a breaking change to user-land as the functionality seen in user-land continues to be whereESMLoader::import()
returns a singleModuleNamespace
) -
update current tests -
add new tests for chaining
Trying it out
CLI
./node \
--loader=/PATH/TO/loaderA.mjs \
--loader=/PATH/TO/loaderB.mjs \
--input-type=module \
-e "import fs from 'node:fs'; console.log(2)"
loaderA.mjs
export const resolve = async function resolveA(
specifier,
context,
next,
) {
console.log('resolveA running');
return {
shortCircuit: true,
url: 'file:///foo/bar.js',
};
}
export const load = async function loadA(
resolvedUrl,
context,
next,
) {
console.log('loadA running');
return {
format: 'module',
shortCircuit: true,
source: 'export default 42',
}
}
loaderB.mjs
export const resolve = async function resolveB(
specifier,
context,
next,
) {
console.log('resolveB running');
return next(specifier);
}
export const load = async function loadB(
resolvedUrl,
context,
next,
) {
console.log('loadB running');
return next(resolvedUrl);
}
42
should be printed to stdout when the hooks are properly chained; when disabled, the node:fs module should be printed to stdout.