Skip to content

Commit

Permalink
vm: refactor SourceTextModule
Browse files Browse the repository at this point in the history
- Removes redundant `instantiate` method
- Refactors `link` to match the spec linking steps more accurately
- Removes URL validation from SourceTextModule specifiers
- DRYs some dynamic import logic

Closes #29030

Co-Authored-By: Michaël Zasso <targos@protonmail.com>
  • Loading branch information
devsnek and targos committed Oct 1, 2019
1 parent 1c5a3f0 commit c002456
Show file tree
Hide file tree
Showing 18 changed files with 269 additions and 335 deletions.
10 changes: 5 additions & 5 deletions doc/api/errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -1984,11 +1984,6 @@ than the parent module. Linked modules must share the same context.

The linker function returned a module for which linking has failed.

<a id="ERR_VM_MODULE_NOT_LINKED"></a>
### ERR_VM_MODULE_NOT_LINKED

The module must be successfully linked before instantiation.

<a id="ERR_VM_MODULE_NOT_MODULE"></a>
### ERR_VM_MODULE_NOT_MODULE

Expand Down Expand Up @@ -2275,6 +2270,11 @@ removed: v10.0.0

Used when a given value is out of the accepted range.

<a id="ERR_VM_MODULE_NOT_LINKED"></a>
### ERR_VM_MODULE_NOT_LINKED

The module must be successfully linked before instantiation.

<a id="ERR_ZLIB_BINDING_CLOSED"></a>
### ERR_ZLIB_BINDING_CLOSED
<!-- YAML
Expand Down
103 changes: 27 additions & 76 deletions doc/api/vm.md
Original file line number Diff line number Diff line change
Expand Up @@ -322,9 +322,9 @@ intrinsically asynchronous, in contrast with the synchronous nature of
`vm.Script` objects. With the help of async functions, however, manipulating
`vm.SourceTextModule` objects is fairly straightforward.

Using a `vm.SourceTextModule` object requires four distinct steps:
creation/parsing, linking, instantiation, and evaluation. These four steps are
illustrated in the following example.
Using a `vm.SourceTextModule` 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 currently no way to interact with the Loader, though
Expand Down Expand Up @@ -391,15 +391,6 @@ const contextifiedSandbox = vm.createContext({ secret: 42 });

// Step 3
//
// Instantiate the top-level Module.
//
// Only the top-level Module needs to be explicitly instantiated; its
// dependencies will be recursively instantiated by instantiate().

bar.instantiate();

// Step 4
//
// Evaluate the Module. The evaluate() method returns a Promise with a single
// property "result" that contains the result of the very last statement
// executed in the Module. In the case of `bar`, it is `s;`, which refers to
Expand All @@ -417,8 +408,9 @@ const contextifiedSandbox = vm.createContext({ secret: 42 });

* `code` {string} JavaScript Module code to parse
* `options`
* `url` {string} URL used in module resolution and stack traces. **Default:**
`'vm:module(i)'` where `i` is a context-specific ascending index.
* `identifier` {string} String used in stack traces.
**Default:** `'vm:module(i)'` where `i` is a context-specific ascending
index.
* `context` {Object} The [contextified][] object as returned by the
`vm.createContext()` method, to compile and evaluate this `Module` in.
* `lineOffset` {integer} Specifies the line number offset that is displayed
Expand Down Expand Up @@ -465,7 +457,6 @@ const contextifiedSandbox = vm.createContext({ secret: 42 });
});
// Since module has no dependencies, the linker function will never be called.
await module.link(() => {});
module.instantiate();
await module.evaluate();

// Now, Object.prototype.secret will be equal to 42.
Expand Down Expand Up @@ -516,7 +507,7 @@ in the ECMAScript specification.

Evaluate the module.

This must be called after the module has been instantiated; otherwise it will
This must be called after the module has been linked; otherwise it will
throw an error. It could be called also when the module has already been
evaluated, in which case it will do one of the following two things:

Expand All @@ -531,23 +522,6 @@ This method cannot be called while the module is being evaluated
Corresponds to the [Evaluate() concrete method][] field of [Source Text Module
Record][]s in the ECMAScript specification.

### module.instantiate()

Instantiate the module. This must be called after linking has completed
(`linkingStatus` is `'linked'`); otherwise it will throw an error. It may also
throw an exception if one of the dependencies does not provide an export the
parent module requires.

However, if this function succeeded, further calls to this function after the
initial instantiation will be no-ops, to be consistent with the ECMAScript
specification.

Unlike other methods operating on `Module`, this function completes
synchronously and returns nothing.

Corresponds to the [Instantiate() concrete method][] field of [Source Text
Module Record][]s in the ECMAScript specification.

### module.link(linker)

* `linker` {Function}
Expand All @@ -563,17 +537,17 @@ Module Record][]s in the ECMAScript specification.
* Returns: {vm.SourceTextModule|Promise}
* Returns: {Promise}

Link module dependencies. This method must be called before instantiation, and
Link module dependencies. This method must be called before evaluation, and
can only be called once per module.

The function is expected to return a `Module` object or a `Promise` that
eventually resolves to a `Module` object. The returned `Module` must satisfy the
following two invariants:

* It must belong to the same context as the parent `Module`.
* Its `linkingStatus` must not be `'errored'`.
* Its `status` must not be `'errored'`.

If the returned `Module`'s `linkingStatus` is `'unlinked'`, this method will be
If the returned `Module`'s `status` is `'unlinked'`, this method will be
recursively called on the returned `Module` with the same provided `linker`
function.

Expand All @@ -587,37 +561,22 @@ specification, with a few key differences:

* The linker function is allowed to be asynchronous while
[HostResolveImportedModule][] is synchronous.
* The linker function is executed during linking, a Node.js-specific stage
before instantiation, while [HostResolveImportedModule][] is called during
instantiation.

The actual [HostResolveImportedModule][] implementation used during module
instantiation is one that returns the modules linked during linking. Since at
linking is one that returns the modules linked during linking. Since at
that point all modules would have been fully linked already, the
[HostResolveImportedModule][] implementation is fully synchronous per
specification.

### module.linkingStatus

* {string}

The current linking status of `module`. It will be one of the following values:

* `'unlinked'`: `module.link()` has not yet been called.
* `'linking'`: `module.link()` has been called, but not all Promises returned by
the linker function have been resolved yet.
* `'linked'`: `module.link()` has been called, and all its dependencies have
been successfully linked.
* `'errored'`: `module.link()` has been called, but at least one of its
dependencies failed to link, either because the callback returned a `Promise`
that is rejected, or because the `Module` the callback returned is invalid.
Corresponds to the [Link() concrete method][] field of [Source Text Module
Record][]s in the ECMAScript specification.

### module.namespace

* {Object}

The namespace object of the module. This is only available after instantiation
(`module.instantiate()`) has completed.
The namespace object of the module. This is only available after linking
(`module.link()`) has completed.

Corresponds to the [GetModuleNamespace][] abstract operation in the ECMAScript
specification.
Expand All @@ -628,21 +587,13 @@ specification.

The current status of the module. Will be one of:

* `'uninstantiated'`: The module is not instantiated. It may because of any of
the following reasons:

* The module was just created.
* `module.instantiate()` has been called on this module, but it failed for
some reason.

This status does not convey any information regarding if `module.link()` has
been called. See `module.linkingStatus` for that.
* `'unlinked'`: `module.link()` has not yet been called.

* `'instantiating'`: The module is currently being instantiated through a
`module.instantiate()` call on itself or a parent module.
* `'linking'`: `module.link()` has been called, but not all Promises returned
by the linker function have been resolved yet.

* `'instantiated'`: The module has been instantiated successfully, but
`module.evaluate()` has not yet been called.
* `'linked'`: The module has been linked successfully, and all of its
dependencies are linked, but `module.evaluate()` has not yet been called.

* `'evaluating'`: The module is being evaluated through a `module.evaluate()` on
itself or a parent module.
Expand All @@ -656,11 +607,11 @@ Other than `'errored'`, this status string corresponds to the specification's
`'evaluated'` in the specification, but with `[[EvaluationError]]` set to a
value that is not `undefined`.

### module.url
### module.identifier

* {string}

The URL of the current module, as set in the constructor.
The identifier of the current module, as set in the constructor.

## vm.compileFunction(code[, params[, options]])
<!-- YAML
Expand Down Expand Up @@ -1127,11 +1078,11 @@ queues.
[`vm.runInContext()`]: #vm_vm_runincontext_code_contextifiedsandbox_options
[`vm.runInThisContext()`]: #vm_vm_runinthiscontext_code_options
[ECMAScript Module Loader]: esm.html#esm_ecmascript_modules
[Evaluate() concrete method]: https://tc39.github.io/ecma262/#sec-moduleevaluation
[GetModuleNamespace]: https://tc39.github.io/ecma262/#sec-getmodulenamespace
[HostResolveImportedModule]: https://tc39.github.io/ecma262/#sec-hostresolveimportedmodule
[Instantiate() concrete method]: https://tc39.github.io/ecma262/#sec-moduledeclarationinstantiation
[Source Text Module Record]: https://tc39.github.io/ecma262/#sec-source-text-module-records
[Evaluate() concrete method]: https://tc39.es/ecma262/#sec-moduleevaluation
[GetModuleNamespace]: https://tc39.es/ecma262/#sec-getmodulenamespace
[HostResolveImportedModule]: https://tc39.es/ecma262/#sec-hostresolveimportedmodule
[Link() concrete method]: https://tc39.es/ecma262/#sec-moduledeclarationlinking
[Source Text Module Record]: https://tc39.es/ecma262/#sec-source-text-module-records
[V8 Embedder's Guide]: https://v8.dev/docs/embed#contexts
[contextified]: #vm_what_does_it_mean_to_contextify_an_object
[global object]: https://es5.github.io/#x15.1
Expand Down
2 changes: 0 additions & 2 deletions lib/internal/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -1211,8 +1211,6 @@ E('ERR_VM_MODULE_DIFFERENT_CONTEXT',
'Linked modules must use the same context', Error);
E('ERR_VM_MODULE_LINKING_ERRORED',
'Linking has already failed for the provided module', Error);
E('ERR_VM_MODULE_NOT_LINKED',
'Module must be linked before it can be instantiated', Error);
E('ERR_VM_MODULE_NOT_MODULE',
'Provided module is not an instance of Module', Error);
E('ERR_VM_MODULE_STATUS', 'Module status %s', Error);
Expand Down
4 changes: 2 additions & 2 deletions lib/internal/modules/esm/loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,15 +127,15 @@ class Loader {
this.moduleMap.set(url, job);
const { module, result } = await job.run();
return {
namespace: module.namespace(),
namespace: module.getNamespace(),
result
};
}

async import(specifier, parent) {
const job = await this.getModuleJob(specifier, parent);
const { module } = await job.run();
return module.namespace();
return module.getNamespace();
}

hook({ resolve, dynamicInstantiate }) {
Expand Down
Loading

0 comments on commit c002456

Please sign in to comment.