Skip to content

Commit 3c04034

Browse files
Revert "esm: convert resolve hook to synchronous"
This reverts commit 90b634a. PR-URL: #43526 Reviewed-By: Geoffrey Booth <webadmin@geoffreybooth.com> Reviewed-By: Guy Bedford <guybedford@gmail.com> Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> Reviewed-By: Mohammed Keyvanzadeh <mohammadkeyvanzade94@gmail.com>
1 parent f5ce6b1 commit 3c04034

30 files changed

Lines changed: 127 additions & 216 deletions

‎doc/api/esm.md‎

Lines changed: 8 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -324,9 +324,6 @@ added:
324324
- v13.9.0
325325
- v12.16.2
326326
changes:
327-
- version: REPLACEME
328-
pr-url: https://github.com/nodejs/node/pull/43363
329-
description: Convert from asynchronous to synchronous.
330327
- version:
331328
- v16.2.0
332329
- v14.18.0
@@ -342,19 +339,15 @@ command flag enabled.
342339
* `specifier` {string} The module specifier to resolve relative to `parent`.
343340
* `parent` {string|URL} The absolute parent module URL to resolve from. If none
344341
is specified, the value of `import.meta.url` is used as the default.
345-
* Returns: {string}
342+
* Returns: {Promise}
346343
347344
Provides a module-relative resolution function scoped to each module, returning
348-
the URL string. In alignment with browser behavior, this now returns
349-
synchronously.
350-
351-
> **Caveat** This can result in synchronous file-system operations, which
352-
> can impact performance similarly to `require.resolve`.
345+
the URL string.
353346
354347
<!-- eslint-skip -->
355348
356349
```js
357-
constdependencyAsset=import.meta.resolve('component-lib/asset.css');
350+
constdependencyAsset=awaitimport.meta.resolve('component-lib/asset.css');
358351
```
359352
360353
`import.meta.resolve` also accepts a second argument which is the parent module
@@ -363,11 +356,11 @@ from which to resolve from:
363356
<!-- eslint-skip -->
364357
365358
```js
366-
import.meta.resolve('./dep', import.meta.url);
359+
awaitimport.meta.resolve('./dep', import.meta.url);
367360
```
368361
369-
This function is synchronous because the ES module resolver in Node.js is
370-
synchronous.
362+
This function is asynchronous because the ES module resolver in Node.js is
363+
allowed to be asynchronous.
371364
372365
## Interoperability with CommonJS
373366
@@ -738,9 +731,6 @@ prevent unintentional breaks in the chain.
738731
739732
<!-- YAML
740733
changes:
741-
- version: REPLACEME
742-
pr-url: https://github.com/nodejs/node/pull/43363
743-
description: Convert hook from asynchronous to synchronous.
744734
- version: REPLACEME
745735
pr-url: https://github.com/nodejs/node/pull/42623
746736
description: Add support for chaining resolve hooks. Each hook must either
@@ -774,9 +764,6 @@ changes:
774764
terminate the chain of `resolve` hooks. **Default:** `false`
775765
* `url` {string} The absolute URL to which this input resolves
776766
777-
> **Caveat** A resolve hook can contain synchronous file-system operations
778-
> (as `defaultResolveHook()` does), which can impact performance.
779-
780767
The `resolve` hook chain is responsible for resolving file URL for a given
781768
module specifier and parent URL, and optionally its format (such as `'module'`)
782769
as a hint to the `load` hook. If a format is specified, the `load` hook is
@@ -803,7 +790,7 @@ Node.js module specifier resolution behavior_ when calling `defaultResolve`, the
803790
`context.conditions` array originally passed into the `resolve` hook.
804791
805792
```js
806-
exportfunctionresolve(specifier, context, nextResolve) {
793+
exportasyncfunctionresolve(specifier, context, nextResolve) {
807794
const { parentURL=null } = context;
808795

809796
if (Math.random() >0.5) { // Some condition.
@@ -1102,7 +1089,7 @@ const baseURL = pathToFileURL(`${cwd()}/`).href;
11021089
// CoffeeScript files end in .coffee, .litcoffee, or .coffee.md.
11031090
const extensionsRegex = /\.coffee$|\.litcoffee$|\.coffee\.md$/;
11041091
1105-
export function resolve(specifier, context, nextResolve) {
1092+
export async function resolve(specifier, context, nextResolve) {
11061093
if (extensionsRegex.test(specifier)) {
11071094
const { parentURL = baseURL } = context;
11081095

‎lib/internal/modules/esm/initialize_import_meta.js‎

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,21 @@
33
const{ getOptionValue }=require('internal/options');
44
constexperimentalImportMetaResolve=
55
getOptionValue('--experimental-import-meta-resolve');
6+
const{
7+
PromisePrototypeThen,
8+
PromiseReject,
9+
}=primordials;
610
constasyncESM=require('internal/process/esm_loader');
711

812
functioncreateImportMetaResolve(defaultParentUrl){
9-
returnfunctionresolve(specifier,parentUrl=defaultParentUrl){
10-
leturl;
11-
12-
try{
13-
({ url }=asyncESM.esmLoader.resolve(specifier,parentUrl));
14-
}catch(error){
15-
if(error.code==='ERR_UNSUPPORTED_DIR_IMPORT'){
16-
({ url }=error);
17-
}else{
18-
throwerror;
19-
}
20-
}
21-
22-
returnurl;
13+
returnasyncfunctionresolve(specifier,parentUrl=defaultParentUrl){
14+
returnPromisePrototypeThen(
15+
asyncESM.esmLoader.resolve(specifier,parentUrl),
16+
({ url })=>url,
17+
(error)=>(
18+
error.code==='ERR_UNSUPPORTED_DIR_IMPORT' ?
19+
error.url : PromiseReject(error))
20+
);
2321
};
2422
}
2523

‎lib/internal/modules/esm/loader.js‎

Lines changed: 31 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@ const {
1515
ObjectDefineProperty,
1616
ObjectSetPrototypeOf,
1717
PromiseAll,
18-
PromiseResolve,
19-
PromisePrototypeThen,
2018
ReflectApply,
2119
RegExpPrototypeExec,
2220
SafeArrayIterator,
@@ -111,14 +109,12 @@ let emittedSpecifierResolutionWarning = false;
111109
* position in the hook chain.
112110
* @param {string} meta.hookName The kind of hook the chain is (ex 'resolve')
113111
* @param {boolean} meta.shortCircuited Whether a hook signaled a short-circuit.
114-
* @param {object} validators A wrapper function
112+
* @param {(hookErrIdentifier, hookArgs) => void} validate A wrapper function
115113
* containing all validation of a custom loader hook's intermediary output. Any
116114
* validation within MUST throw.
117-
* @param {(hookErrIdentifier, hookArgs) => void} validators.validateArgs
118-
* @param {(hookErrIdentifier, output) => void} validators.validateOutput
119115
* @returns {function next<HookName>(...hookArgs)} The next hook in the chain.
120116
*/
121-
functionnextHookFactory(chain,meta,{ validateArgs, validateOutput }){
117+
functionnextHookFactory(chain,meta,validate){
122118
// First, prepare the current
123119
const{ hookName }=meta;
124120
const{
@@ -141,7 +137,7 @@ function nextHookFactory(chain, meta, { validateArgs, validateOutput }) {
141137
// factory generates the next link in the chain.
142138
meta.hookIndex--;
143139

144-
nextNextHook=nextHookFactory(chain,meta,{ validateArgs, validateOutput });
140+
nextNextHook=nextHookFactory(chain,meta,validate);
145141
}else{
146142
// eslint-disable-next-line func-name-matching
147143
nextNextHook=functionchainAdvancedTooFar(){
@@ -152,36 +148,21 @@ function nextHookFactory(chain, meta, { validateArgs, validateOutput }) {
152148
}
153149

154150
returnObjectDefineProperty(
155-
(...args)=>{
151+
async(...args)=>{
156152
// Update only when hook is invoked to avoid fingering the wrong filePath
157153
meta.hookErrIdentifier=`${hookFilePath} '${hookName}'`;
158154

159-
validateArgs(`${meta.hookErrIdentifier} hook's ${nextHookName}()`,args);
160-
161-
constoutputErrIdentifier=`${chain[generatedHookIndex].url} '${hookName}' hook's ${nextHookName}()`;
155+
validate(`${meta.hookErrIdentifier} hook's ${nextHookName}()`,args);
162156

163157
// Set when next<HookName> is actually called, not just generated.
164158
if(generatedHookIndex===0){meta.chainFinished=true;}
165159

166160
ArrayPrototypePush(args,nextNextHook);
167-
constoutput=ReflectApply(hook,undefined,args);
168-
169-
validateOutput(outputErrIdentifier,output);
161+
constoutput=awaitReflectApply(hook,undefined,args);
170162

171-
functioncheckShortCircuited(output){
172-
if(output?.shortCircuit===true){meta.shortCircuited=true;}
173-
174-
returnoutput;
175-
}
176-
177-
if(meta.isChainAsync){
178-
returnPromisePrototypeThen(
179-
PromiseResolve(output),
180-
checkShortCircuited,
181-
);
182-
}
163+
if(output?.shortCircuit===true){meta.shortCircuited=true;}
164+
returnoutput;
183165

184-
returncheckShortCircuited(output);
185166
},
186167
'name',
187168
{__proto__: null,value: nextHookName},
@@ -440,11 +421,8 @@ class ESMLoader {
440421
);
441422
}
442423

443-
const{ format, url }=this.resolve(
444-
specifier,
445-
parentURL,
446-
importAssertionsForResolve,
447-
);
424+
const{ format, url }=
425+
awaitthis.resolve(specifier,parentURL,importAssertionsForResolve);
448426

449427
letjob=this.moduleMap.get(url,importAssertions.type);
450428

@@ -579,11 +557,10 @@ class ESMLoader {
579557
hookErrIdentifier: '',
580558
hookIndex: chain.length-1,
581559
hookName: 'load',
582-
isChainAsync: true,
583560
shortCircuited: false,
584561
};
585562

586-
constvalidateArgs=(hookErrIdentifier,{0: nextUrl,1: ctx})=>{
563+
constvalidate=(hookErrIdentifier,{0: nextUrl,1: ctx})=>{
587564
if(typeofnextUrl!=='string'){
588565
// non-strings can be coerced to a url string
589566
// validateString() throws a less-specific error
@@ -609,22 +586,19 @@ class ESMLoader {
609586

610587
validateObject(ctx,`${hookErrIdentifier} context`);
611588
};
612-
constvalidateOutput=(hookErrIdentifier,output)=>{
613-
if(typeofoutput!=='object'){// [2]
614-
thrownewERR_INVALID_RETURN_VALUE(
615-
'an object',
616-
hookErrIdentifier,
617-
output,
618-
);
619-
}
620-
};
621589

622-
constnextLoad=nextHookFactory(chain,meta,{ validateArgs, validateOutput });
590+
constnextLoad=nextHookFactory(chain,meta,validate);
623591

624592
constloaded=awaitnextLoad(url,context);
625593
const{ hookErrIdentifier }=meta;// Retrieve the value after all settled
626594

627-
validateOutput(hookErrIdentifier,loaded);
595+
if(typeofloaded!=='object'){// [2]
596+
thrownewERR_INVALID_RETURN_VALUE(
597+
'an object',
598+
hookErrIdentifier,
599+
loaded,
600+
);
601+
}
628602

629603
if(loaded?.shortCircuit===true){meta.shortCircuited=true;}
630604

@@ -804,7 +778,7 @@ class ESMLoader {
804778
* statement or expression.
805779
* @returns {{ format: string, url: URL['href'] }}
806780
*/
807-
resolve(
781+
asyncresolve(
808782
originalSpecifier,
809783
parentURL,
810784
importAssertions=ObjectCreate(null)
@@ -828,43 +802,36 @@ class ESMLoader {
828802
hookErrIdentifier: '',
829803
hookIndex: chain.length-1,
830804
hookName: 'resolve',
831-
isChainAsync: false,
832805
shortCircuited: false,
833806
};
807+
834808
constcontext={
835809
conditions: DEFAULT_CONDITIONS,
836810
importAssertions,
837811
parentURL,
838812
};
813+
constvalidate=(hookErrIdentifier,{0: suppliedSpecifier,1: ctx})=>{
839814

840-
constvalidateArgs=(hookErrIdentifier,{0: suppliedSpecifier,1: ctx})=>{
841815
validateString(
842816
suppliedSpecifier,
843817
`${hookErrIdentifier} specifier`,
844818
);// non-strings can be coerced to a url string
845819

846820
validateObject(ctx,`${hookErrIdentifier} context`);
847821
};
848-
constvalidateOutput=(hookErrIdentifier,output)=>{
849-
if(
850-
typeofoutput!=='object'||// [2]
851-
output===null||
852-
(output.url==null&&typeofoutput.then==='function')
853-
){
854-
thrownewERR_INVALID_RETURN_VALUE(
855-
'an object',
856-
hookErrIdentifier,
857-
output,
858-
);
859-
}
860-
};
861822

862-
constnextResolve=nextHookFactory(chain,meta,{ validateArgs, validateOutput });
823+
constnextResolve=nextHookFactory(chain,meta,validate);
863824

864-
constresolution=nextResolve(originalSpecifier,context);
825+
constresolution=awaitnextResolve(originalSpecifier,context);
865826
const{ hookErrIdentifier }=meta;// Retrieve the value after all settled
866827

867-
validateOutput(hookErrIdentifier,resolution);
828+
if(typeofresolution!=='object'){// [2]
829+
thrownewERR_INVALID_RETURN_VALUE(
830+
'an object',
831+
hookErrIdentifier,
832+
resolution,
833+
);
834+
}
868835

869836
if(resolution?.shortCircuit===true){meta.shortCircuited=true;}
870837

‎lib/internal/modules/esm/resolve.js‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1081,7 +1081,7 @@ function throwIfUnsupportedURLScheme(parsed, experimentalNetworkImports) {
10811081
}
10821082
}
10831083

1084-
functiondefaultResolve(specifier,context={}){
1084+
asyncfunctiondefaultResolve(specifier,context={}){
10851085
let{ parentURL, conditions }=context;
10861086
if(parentURL&&policy?.manifest){
10871087
constredirects=policy.manifest.getDependencyMapper(parentURL);
@@ -1227,11 +1227,11 @@ const {
12271227

12281228
if(policy){
12291229
const$defaultResolve=defaultResolve;
1230-
module.exports.defaultResolve=functiondefaultResolve(
1230+
module.exports.defaultResolve=asyncfunctiondefaultResolve(
12311231
specifier,
12321232
context
12331233
){
1234-
constret=$defaultResolve(specifier,context);
1234+
constret=await$defaultResolve(specifier,context,$defaultResolve);
12351235
// This is a preflight check to avoid data exfiltration by query params etc.
12361236
policy.manifest.mightAllow(ret.url,()=>
12371237
newERR_MANIFEST_DEPENDENCY_MISSING(

0 commit comments

Comments
 (0)