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

Lines changed: 580 additions & 95 deletions

‎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+
<aid="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
<aid="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*asMfrom'./module.wasm';
684+
import*asMfrom'./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+
importsourcelibraryModulefrom'./library.wasm';
714+
715+
constinstance1=awaitWebAssembly.instantiate(libraryModule, {
716+
custom: import1,
717+
});
718+
719+
constinstance2=awaitWebAssembly.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
constmainURL=pathToFileURL(filename).href;
1511-
returncascadedLoader.import(mainURL,undefined,{__proto__: null},true);
1511+
returncascadedLoader.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
asyncexecuteModuleJob(url,wrap,isEntryPoint=false){
237237
const{ ModuleJob }=require('internal/modules/esm/module_job');
238238
constmodule=awaitonImport.tracePromise(async()=>{
239-
constjob=newModuleJob(
240-
this,url,undefined,wrap,false,false);
239+
constjob=newModuleJob(this,url,undefined,wrap,kEvaluationPhase,false,false);
241240
this.loadCache.set(url,undefined,job);
242241
const{ module }=awaitjob.run(isEntryPoint);
243242
returnmodule;
@@ -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-
asyncgetModuleJobForImport(specifier,parentURL,importAttributes){
278+
asyncgetModuleJobForImport(specifier,parentURL,importAttributes,phase){
279279
constresolveResult=awaitthis.resolve(specifier,parentURL,importAttributes);
280-
returnthis.#getJobFromResolveResult(resolveResult,parentURL,importAttributes,false);
280+
returnthis.#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
constresolveResult=this.resolveSync(specifier,parentURL,importAttributes);
294-
returnthis.#getJobFromResolveResult(resolveResult,parentURL,importAttributes,true);
295+
returnthis.#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
constresolvedImportAttributes=resolveResult.importAttributes??importAttributes;
309312
letjob=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
returnjob;
@@ -377,7 +383,7 @@ class ModuleLoader {
377383
constinspectBrk=(isMain&&getOptionValue('--inspect-brk'));
378384

379385
const{ ModuleJobSync }=require('internal/modules/esm/module_job');
380-
job=newModuleJobSync(this,url,kEmptyObject,wrap,isMain,inspectBrk);
386+
job=newModuleJobSync(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
constparsed=URLParse(specifier);
396403
if(parsed!=null){
397404
constprotocol=parsed.protocol;
@@ -422,6 +429,7 @@ class ModuleLoader {
422429
}
423430
thrownewERR_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
constinspectBrk=(isMain&&getOptionValue('--inspect-brk'));
465473
const{ ModuleJobSync }=require('internal/modules/esm/module_job');
466-
job=newModuleJobSync(this,url,importAttributes,wrap,isMain,inspectBrk);
474+
job=newModuleJobSync(this,url,importAttributes,wrap,phase,isMain,inspectBrk);
467475

468476
this.loadCache.set(url,importAttributes.type,job);
469477
returnjob;
@@ -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
constcontext={ format, importAttributes };
554563

555564
constisMain=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-
asyncimport(specifier,parentURL,importAttributes,isEntryPoint=false){
609+
asyncimport(specifier,parentURL,importAttributes,phase=kEvaluationPhase,isEntryPoint=false){
598610
returnonImport.tracePromise(async()=>{
599-
constmoduleJob=awaitthis.getModuleJobForImport(specifier,parentURL,importAttributes);
611+
constmoduleJob=awaitthis.getModuleJobForImport(specifier,parentURL,importAttributes,
612+
phase);
613+
if(phase===kSourcePhase){
614+
constmodule=awaitmoduleJob.modulePromise;
615+
returnmodule.getModuleSourceObject();
616+
}
600617
const{ module }=awaitmoduleJob.run(isEntryPoint);
601618
returnmodule.getNamespace();
602619
},{

0 commit comments

Comments
 (0)