Expose the default module resolver in a package
Currently we can access the default resolver when in a --loader loader. However we can't access this loader from a regular program. I'd like to propose exposing this function as a regular function that can be imported at runtime for use with vm.SourceTextModule to create in-process loaders.
For example:
import vm from 'vm';
import process from 'process';
import url from 'url';
import { resolver } from 'module';
const cwd = url.pathToFileURL(process.cwd() + '/');
class TestLoader {
#cache = new Map();
#realm = vm.createContext({});
async #loadModule(url) {
return new vm.SourceTextModule(await fsp.readFile(resolvedUrl, 'utf8'), {
url: resolvedUrl,
context: this.#realm,
});
}
async #linker(module, specifier) {
// --- Use node's resolution algorithm ---
const resolvedUrl = resolver(module.url, specifier);
if (this.#cache.has(resolvedUrl)) {
return this.#cache.get(resolvedUrl);
}
const module = this.#loadModule(resolvedUrl);
this.#cache.set(resolvedUrl, module);
return module;
}
constructor() {
// Setup test environment
this.#realm.evaluate(`function deepEqual(a, b) { /* ... */ }`);
}
async runTest(file) {
const module = this.#loadModule(resolver(cwd, file));
await module.link((...args) => this.#linker(...args));
module.instantiate();
try {
await module.evaluate();
return { passed: true };
} catch (error) {
return { passed: false, error };
}
}
}
Maybe worth noting, in some of my early --loader experiments, I was inclined to chain loaders by wrapping the resolvers and passing them as the defaultResolve argument…
So I think we might want to first verify if defaultResolve will not be specific to a loader or loader-chain in the newer loader design(s).
The default resolver hook is sync, should this instead be async? If it's ever desired to support loader stacking node --loader=./loader1.js --loader=./loader2.js I think this would be a blocker as the 3rd argument to the resolve in loader2.js wouldn't be defaultResolve, it would be the resolve from loader1.js. Regardless of multiple loaders ever being supported or not I think the default resolver should return a Promise to be consistent but especially if it's going to be exposed in this way.
Another question, do you think the default resolver should be exposed, or should a function which wraps the current resolver be exposed?
The default resolver hook is sync, should this instead be async?
I have no strong opinions on the matter, getting some more feedback from library authors for things like bundlers would probably be helpful. I would be fine with async for most my own use cases.
Another question, do you think the default resolver should be exposed, or should a function which wraps the current resolver be exposed?
Tools will probably want either depending on their use case.