Skip to content

Commit 57e49ee

Browse files
Guy BedfordRafaelGSS
authored andcommitted
esm: support source phase imports for WebAssembly
PR-URL: #56919 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Michaël Zasso <targos@protonmail.com> Reviewed-By: Stephen Belanger <admin@stephenbelanger.com>
1 parent ab9b439 commit 57e49ee

22 files changed

+580
-95
lines changed

doc/api/errors.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2736,6 +2736,17 @@ The source map could not be parsed because it does not exist, or is corrupt.
27362736

27372737
A file imported from a source map was not found.
27382738

2739+
<a id="ERR_SOURCE_PHASE_NOT_DEFINED"></a>
2740+
2741+
### `ERR_SOURCE_PHASE_NOT_DEFINED`
2742+
2743+
<!-- YAML
2744+
added: REPLACEME
2745+
-->
2746+
2747+
The provided module import does not provide a source phase imports representation for source phase
2748+
import syntax `import source x from 'x'` or `import.source(x)`.
2749+
27392750
<a id="ERR_SQLITE_ERROR"></a>
27402751

27412752
### `ERR_SQLITE_ERROR`

doc/api/esm.md

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -669,17 +669,19 @@ imported from the same path.
669669
670670
> Stability: 1 - Experimental
671671
672-
Importing WebAssembly modules is supported under the
673-
`--experimental-wasm-modules` flag, allowing any `.wasm` files to be
674-
imported as normal modules while also supporting their module imports.
672+
Importing both WebAssembly module instances and WebAssembly source phase
673+
imports are supported under the `--experimental-wasm-modules` flag.
675674
676-
This integration is in line with the
675+
Both of these integrations are in line with the
677676
[ES Module Integration Proposal for WebAssembly][].
678677
679-
For example, an `index.mjs` containing:
678+
Instance imports allow any `.wasm` files to be imported as normal modules,
679+
supporting their module imports in turn.
680+
681+
For example, an `index.js` containing:
680682
681683
```js
682-
import * as M from './module.wasm';
684+
import * as M from './library.wasm';
683685
console.log(M);
684686
```
685687
@@ -689,7 +691,35 @@ executed under:
689691
node --experimental-wasm-modules index.mjs
690692
```
691693
692-
would provide the exports interface for the instantiation of `module.wasm`.
694+
would provide the exports interface for the instantiation of `library.wasm`.
695+
696+
### Wasm Source Phase Imports
697+
698+
<!-- YAML
699+
added: REPLACEME
700+
-->
701+
702+
The [Source Phase Imports][] proposal allows the `import source` keyword
703+
combination to import a `WebAssembly.Module` object directly, instead of getting
704+
a module instance already instantiated with its dependencies.
705+
706+
This is useful when needing custom instantiations for Wasm, while still
707+
resolving and loading it through the ES module integration.
708+
709+
For example, to create multiple instances of a module, or to pass custom imports
710+
into a new instance of `library.wasm`:
711+
712+
```js
713+
import source libraryModule from './library.wasm';
714+
715+
const instance1 = await WebAssembly.instantiate(libraryModule, {
716+
custom: import1,
717+
});
718+
719+
const instance2 = await WebAssembly.instantiate(libraryModule, {
720+
custom: import2,
721+
});
722+
```
693723
694724
<i id="esm_experimental_top_level_await"></i>
695725
@@ -1126,6 +1156,7 @@ resolution for ESM specifiers is [commonjs-extension-resolution-loader][].
11261156
[Loading ECMAScript modules using `require()`]: modules.md#loading-ecmascript-modules-using-require
11271157
[Module customization hooks]: module.md#customization-hooks
11281158
[Node.js Module Resolution And Loading Algorithm]: #resolution-algorithm-specification
1159+
[Source Phase Imports]: https://github.com/tc39/proposal-source-phase-imports
11291160
[Terminology]: #terminology
11301161
[URL]: https://url.spec.whatwg.org/
11311162
[`"exports"`]: packages.md#exports

doc/api/vm.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1908,6 +1908,7 @@ has the following signature:
19081908
* `importAttributes` {Object} The `"with"` value passed to the
19091909
[`optionsExpression`][] optional parameter, or an empty object if no value was
19101910
provided.
1911+
* `phase` {string} The phase of the dynamic import (`"source"` or `"evaluation"`).
19111912
* Returns: {Module Namespace Object|vm.Module} Returning a `vm.Module` is
19121913
recommended in order to take advantage of error tracking, and to avoid issues
19131914
with namespaces that contain `then` function exports.

lib/internal/modules/cjs/loader.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1508,7 +1508,7 @@ function loadESMFromCJS(mod, filename, format, source) {
15081508
if (isMain) {
15091509
require('internal/modules/run_main').runEntryPointWithESMLoader((cascadedLoader) => {
15101510
const mainURL = pathToFileURL(filename).href;
1511-
return cascadedLoader.import(mainURL, undefined, { __proto__: null }, true);
1511+
return cascadedLoader.import(mainURL, undefined, { __proto__: null }, undefined, true);
15121512
});
15131513
// ESM won't be accessible via process.mainModule.
15141514
setOwnProperty(process, 'mainModule', undefined);

lib/internal/modules/esm/loader.js

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ const {
3838
forceDefaultLoader,
3939
} = require('internal/modules/esm/utils');
4040
const { kImplicitTypeAttribute } = require('internal/modules/esm/assert');
41-
const { ModuleWrap, kEvaluating, kEvaluated } = internalBinding('module_wrap');
41+
const { ModuleWrap, kEvaluating, kEvaluated, kEvaluationPhase, kSourcePhase } = internalBinding('module_wrap');
4242
const {
4343
urlToFilename,
4444
} = require('internal/modules/helpers');
@@ -236,8 +236,7 @@ class ModuleLoader {
236236
async executeModuleJob(url, wrap, isEntryPoint = false) {
237237
const { ModuleJob } = require('internal/modules/esm/module_job');
238238
const module = await onImport.tracePromise(async () => {
239-
const job = new ModuleJob(
240-
this, url, undefined, wrap, false, false);
239+
const job = new ModuleJob(this, url, undefined, wrap, kEvaluationPhase, false, false);
241240
this.loadCache.set(url, undefined, job);
242241
const { module } = await job.run(isEntryPoint);
243242
return module;
@@ -273,11 +272,12 @@ class ModuleLoader {
273272
* @param {string} [parentURL] The URL of the module where the module request is initiated.
274273
* It's undefined if it's from the root module.
275274
* @param {ImportAttributes} importAttributes Attributes from the import statement or expression.
275+
* @param {number} phase Import phase.
276276
* @returns {Promise<ModuleJobBase>}
277277
*/
278-
async getModuleJobForImport(specifier, parentURL, importAttributes) {
278+
async getModuleJobForImport(specifier, parentURL, importAttributes, phase) {
279279
const resolveResult = await this.resolve(specifier, parentURL, importAttributes);
280-
return this.#getJobFromResolveResult(resolveResult, parentURL, importAttributes, false);
280+
return this.#getJobFromResolveResult(resolveResult, parentURL, importAttributes, phase, false);
281281
}
282282

283283
/**
@@ -287,11 +287,12 @@ class ModuleLoader {
287287
* @param {string} specifier See {@link getModuleJobForImport}
288288
* @param {string} [parentURL] See {@link getModuleJobForImport}
289289
* @param {ImportAttributes} importAttributes See {@link getModuleJobForImport}
290+
* @param {number} phase Import phase.
290291
* @returns {Promise<ModuleJobBase>}
291292
*/
292-
getModuleJobForRequireInImportedCJS(specifier, parentURL, importAttributes) {
293+
getModuleJobForRequireInImportedCJS(specifier, parentURL, importAttributes, phase) {
293294
const resolveResult = this.resolveSync(specifier, parentURL, importAttributes);
294-
return this.#getJobFromResolveResult(resolveResult, parentURL, importAttributes, true);
295+
return this.#getJobFromResolveResult(resolveResult, parentURL, importAttributes, phase, true);
295296
}
296297

297298
/**
@@ -300,16 +301,21 @@ class ModuleLoader {
300301
* @param {{ format: string, url: string }} resolveResult Resolved module request.
301302
* @param {string} [parentURL] See {@link getModuleJobForImport}
302303
* @param {ImportAttributes} importAttributes See {@link getModuleJobForImport}
304+
* @param {number} phase Import phase.
303305
* @param {boolean} isForRequireInImportedCJS Whether this is done for require() in imported CJS.
304306
* @returns {ModuleJobBase}
305307
*/
306-
#getJobFromResolveResult(resolveResult, parentURL, importAttributes, isForRequireInImportedCJS = false) {
308+
#getJobFromResolveResult(resolveResult, parentURL, importAttributes, phase,
309+
isForRequireInImportedCJS = false) {
307310
const { url, format } = resolveResult;
308311
const resolvedImportAttributes = resolveResult.importAttributes ?? importAttributes;
309312
let job = this.loadCache.get(url, resolvedImportAttributes.type);
310313

311314
if (job === undefined) {
312-
job = this.#createModuleJob(url, resolvedImportAttributes, parentURL, format, isForRequireInImportedCJS);
315+
job = this.#createModuleJob(url, resolvedImportAttributes, phase, parentURL, format,
316+
isForRequireInImportedCJS);
317+
} else {
318+
job.ensurePhase(phase);
313319
}
314320

315321
return job;
@@ -377,7 +383,7 @@ class ModuleLoader {
377383
const inspectBrk = (isMain && getOptionValue('--inspect-brk'));
378384

379385
const { ModuleJobSync } = require('internal/modules/esm/module_job');
380-
job = new ModuleJobSync(this, url, kEmptyObject, wrap, isMain, inspectBrk);
386+
job = new ModuleJobSync(this, url, kEmptyObject, wrap, kEvaluationPhase, isMain, inspectBrk);
381387
this.loadCache.set(url, kImplicitTypeAttribute, job);
382388
mod[kRequiredModuleSymbol] = job.module;
383389
return { wrap: job.module, namespace: job.runSync(parent).namespace };
@@ -389,9 +395,10 @@ class ModuleLoader {
389395
* @param {string} specifier Specifier of the the imported module.
390396
* @param {string} parentURL Where the import comes from.
391397
* @param {object} importAttributes import attributes from the import statement.
398+
* @param {number} phase The import phase.
392399
* @returns {ModuleJobBase}
393400
*/
394-
getModuleJobForRequire(specifier, parentURL, importAttributes) {
401+
getModuleJobForRequire(specifier, parentURL, importAttributes, phase) {
395402
const parsed = URLParse(specifier);
396403
if (parsed != null) {
397404
const protocol = parsed.protocol;
@@ -422,6 +429,7 @@ class ModuleLoader {
422429
}
423430
throw new ERR_REQUIRE_CYCLE_MODULE(message);
424431
}
432+
job.ensurePhase(phase);
425433
// Otherwise the module could be imported before but the evaluation may be already
426434
// completed (e.g. the require call is lazy) so it's okay. We will return the
427435
// module now and check asynchronicity of the entire graph later, after the
@@ -463,7 +471,7 @@ class ModuleLoader {
463471

464472
const inspectBrk = (isMain && getOptionValue('--inspect-brk'));
465473
const { ModuleJobSync } = require('internal/modules/esm/module_job');
466-
job = new ModuleJobSync(this, url, importAttributes, wrap, isMain, inspectBrk);
474+
job = new ModuleJobSync(this, url, importAttributes, wrap, phase, isMain, inspectBrk);
467475

468476
this.loadCache.set(url, importAttributes.type, job);
469477
return job;
@@ -543,13 +551,14 @@ class ModuleLoader {
543551
* by the time this returns. Otherwise it may still have pending module requests.
544552
* @param {string} url The URL that was resolved for this module.
545553
* @param {ImportAttributes} importAttributes See {@link getModuleJobForImport}
554+
* @param {number} phase Import phase.
546555
* @param {string} [parentURL] See {@link getModuleJobForImport}
547556
* @param {string} [format] The format hint possibly returned by the `resolve` hook
548557
* @param {boolean} isForRequireInImportedCJS Whether this module job is created for require()
549558
* in imported CJS.
550559
* @returns {ModuleJobBase} The (possibly pending) module job
551560
*/
552-
#createModuleJob(url, importAttributes, parentURL, format, isForRequireInImportedCJS) {
561+
#createModuleJob(url, importAttributes, phase, parentURL, format, isForRequireInImportedCJS) {
553562
const context = { format, importAttributes };
554563

555564
const isMain = parentURL === undefined;
@@ -575,6 +584,7 @@ class ModuleLoader {
575584
url,
576585
importAttributes,
577586
moduleOrModulePromise,
587+
phase,
578588
isMain,
579589
inspectBrk,
580590
isForRequireInImportedCJS,
@@ -592,11 +602,18 @@ class ModuleLoader {
592602
* @param {string} parentURL Path of the parent importing the module.
593603
* @param {Record<string, string>} importAttributes Validations for the
594604
* module import.
605+
* @param {number} [phase] The phase of the import.
606+
* @param {boolean} [isEntryPoint] Whether this is the realm-level entry point.
595607
* @returns {Promise<ModuleExports>}
596608
*/
597-
async import(specifier, parentURL, importAttributes, isEntryPoint = false) {
609+
async import(specifier, parentURL, importAttributes, phase = kEvaluationPhase, isEntryPoint = false) {
598610
return onImport.tracePromise(async () => {
599-
const moduleJob = await this.getModuleJobForImport(specifier, parentURL, importAttributes);
611+
const moduleJob = await this.getModuleJobForImport(specifier, parentURL, importAttributes,
612+
phase);
613+
if (phase === kSourcePhase) {
614+
const module = await moduleJob.modulePromise;
615+
return module.getModuleSourceObject();
616+
}
600617
const { module } = await moduleJob.run(isEntryPoint);
601618
return module.getNamespace();
602619
}, {

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy