From 7b26be5639d17c302257c846f8da8b96235e7f69 Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Tue, 26 Jan 2021 15:41:26 +0100 Subject: [PATCH] doc: add top-level await syntax in vm.md --- doc/api/vm.md | 125 ++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 115 insertions(+), 10 deletions(-) diff --git a/doc/api/vm.md b/doc/api/vm.md index 0fc796450739ac..fc1269f5ac9c9d 100644 --- a/doc/api/vm.md +++ b/doc/api/vm.md @@ -408,7 +408,78 @@ 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. -```js +```mjs +import vm from '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 bar = new vm.SourceTextModule(` + import s from 'foo'; + s; + print(s); +`, { context: contextifiedObject }); + +// Step 2 +// +// "Link" the imported dependencies of this Module to it. +// +// The provided linking callback (the "linker") accepts two arguments: the +// parent module (`bar` in this case) and the string that is the specifier of +// the imported module. The callback is expected to return a Module that +// corresponds to the provided specifier, with certain requirements documented +// in `module.link()`. +// +// If linking has not started for the returned Module, the same linker +// callback will be called on the returned Module. +// +// Even top-level Modules without dependencies must be explicitly linked. The +// callback provided would never be called, however. +// +// The link() method returns a Promise that will be resolved when all the +// Promises returned by the linker resolve. +// +// Note: This is a contrived example in that the linker function 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. + +async function linker(specifier, referencingModule) { + if (specifier === 'foo') { + return 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 }); + + // Using `contextifiedObject` instead of `referencingModule.context` + // here would work as well. + } + throw new Error(`Unable to resolve dependency: ${specifier}`); +} +await bar.link(linker); + +// Step 3 +// +// Evaluate the Module. The evaluate() method returns a promise which will +// resolve after the module has finished evaluating. + +// Prints 42. +await bar.evaluate(); +``` + +```cjs const vm = require('vm'); const contextifiedObject = vm.createContext({ @@ -542,8 +613,7 @@ The identifier of the current module, as set in the constructor. * `linker` {Function} * `specifier` {string} The specifier of the requested module: - - ```js + ```mjs import foo from 'foo'; // ^^^^^ the module specifier ``` @@ -673,11 +743,37 @@ Properties assigned to the `import.meta` object that are objects may allow the module to access information outside the specified `context`. Use `vm.runInContext()` to create objects in a specific context. -```js -const vm = require('vm'); +```mjs +import vm from 'vm'; const contextifiedObject = vm.createContext({ secret: 42 }); +const module = new vm.SourceTextModule( + 'Object.getPrototypeOf(import.meta.prop).secret = secret;', + { + initializeImportMeta(meta) { + // Note: this object is created in the top context. As such, + // Object.getPrototypeOf(import.meta.prop) points to the + // Object.prototype in the top context rather than that in + // the contextified object. + meta.prop = {}; + } + }); +// Since module has no dependencies, the linker function will never be called. +await module.link(() => {}); +await module.evaluate(); + +// Now, Object.prototype.secret will be equal to 42. +// +// To fix this problem, replace +// meta.prop = {}; +// above with +// meta.prop = vm.runInContext('{}', contextifiedObject); +``` + +```cjs +const vm = require('vm'); +const contextifiedObject = vm.createContext({ secret: 42 }); (async () => { const module = new vm.SourceTextModule( 'Object.getPrototypeOf(import.meta.prop).secret = secret;', @@ -693,7 +789,6 @@ const contextifiedObject = vm.createContext({ secret: 42 }); // Since module has no dependencies, the linker function will never be called. await module.link(() => {}); await module.evaluate(); - // Now, Object.prototype.secret will be equal to 42. // // To fix this problem, replace @@ -794,17 +889,27 @@ This method is used after the module is linked to set the values of exports. If it is called before the module is linked, an [`ERR_VM_MODULE_STATUS`][] error will be thrown. -```js -const vm = require('vm'); +```mjs +import vm from 'vm'; +const m = new vm.SyntheticModule(['x'], () => { + m.setExport('x', 1); +}); + +await m.link(() => {}); +await m.evaluate(); + +assert.strictEqual(m.namespace.x, 1); +``` + +```cjs +const vm = require('vm'); (async () => { const m = new vm.SyntheticModule(['x'], () => { m.setExport('x', 1); }); - await m.link(() => {}); await m.evaluate(); - assert.strictEqual(m.namespace.x, 1); })(); ```