This feature is only available with the --experimental-vm-modules command flag enabled.
The vm.Module class provides a low-level interface for using ECMAScript modules in VM contexts. It is the counterpart of the vm.Script class that closely mirrors Module Records as defined in the ECMAScript specification.
Unlike vm.Script however, every vm.Module object is bound to a context from its creation.
Using a vm.Module object requires three distinct steps: creation/parsing, linking, and evaluation. These three steps are illustrated in the following example.
This implementation lies at a lower level than the ECMAScript Module loader. There is also no way to interact with the Loader yet, though support is planned.
import vm from 'node:vm';
const contextifiedObject = vm.createContext({
secret: 42,
print: console.log,
});
// Step 1
//
// Create a Module by constructing a new `vm.SourceTextModule` object. This
// parses the provided source text, throwing a `SyntaxError` if anything goes
// wrong. By default, a Module is created in the top context. But here, we
// specify `contextifiedObject` as the context this Module belongs to.
//
// Here, we attempt to obtain the default export from the module "foo", and
// put it into local binding "secret".
const rootModule = new vm.SourceTextModule(`
import s from 'foo';
s;
print(s);
`, { context: contextifiedObject });
// Step 2
//
// "Link" the imported dependencies of this Module to it.
//
// Obtain the requested dependencies of a SourceTextModule by
// `sourceTextModule.moduleRequests` and resolve them.
//
// Even top-level Modules without dependencies must be explicitly linked. The
// array passed to `sourceTextModule.linkRequests(modules)` can be
// empty, however.
//
// Note: This is a contrived example in that the resolveAndLinkDependencies
// creates a new "foo" module every time it is called. In a full-fledged
// module system, a cache would probably be used to avoid duplicated modules.
const moduleMap = new Map([
['root', rootModule],
]);
function resolveAndLinkDependencies(module) {
const requestedModules = module.moduleRequests.map((request) => {
// In a full-fledged module system, the resolveAndLinkDependencies would
// resolve the module with the module cache key `[specifier, attributes]`.
// In this example, we just use the specifier as the key.
const specifier = request.specifier;
let requestedModule = moduleMap.get(specifier);
if (requestedModule === undefined) {
requestedModule = new vm.SourceTextModule(`
// The "secret" variable refers to the global variable we added to
// "contextifiedObject" when creating the context.
export default secret;
`, { context: referencingModule.context });
moduleMap.set(specifier, linkedModule);
// Resolve the dependencies of the new module as well.
resolveAndLinkDependencies(requestedModule);
}
return requestedModule;
});
module.linkRequests(requestedModules);
}
resolveAndLinkDependencies(rootModule);
rootModule.instantiate();
// Step 3
//
// Evaluate the Module. The evaluate() method returns a promise which will
// resolve after the module has finished evaluating.
// Prints 42.
await rootModule.evaluate();