Skip to content

Commit 50d64ed

Browse files
guybedfordtargos
authored andcommitted
esm: refactor responseURL handling
PR-URL: #43164 Reviewed-By: Antoine du Hamel <[email protected]> Reviewed-By: Minwoo Jung <[email protected]> Reviewed-By: Jacob Smith <[email protected]>
1 parent 254efd9 commit 50d64ed

File tree

9 files changed

+97
-154
lines changed

9 files changed

+97
-154
lines changed

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

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1030,8 +1030,7 @@ function wrapSafe(filename, content, cjsModuleInstance){
10301030
displayErrors: true,
10311031
importModuleDynamically: async(specifier,_,importAssertions)=>{
10321032
constloader=asyncESM.esmLoader;
1033-
returnloader.import(specifier,
1034-
loader.getBaseURL(normalizeReferrerURL(filename)),
1033+
returnloader.import(specifier,normalizeReferrerURL(filename),
10351034
importAssertions);
10361035
},
10371036
});
@@ -1047,8 +1046,7 @@ function wrapSafe(filename, content, cjsModuleInstance){
10471046
filename,
10481047
importModuleDynamically(specifier,_,importAssertions){
10491048
constloader=asyncESM.esmLoader;
1050-
returnloader.import(specifier,
1051-
loader.getBaseURL(normalizeReferrerURL(filename)),
1049+
returnloader.import(specifier,normalizeReferrerURL(filename),
10521050
importAssertions);
10531051
},
10541052
});

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

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -238,17 +238,6 @@ function fetchModule(parsed,{parentURL }){
238238
returnfetchWithRedirects(parsed);
239239
}
240240

241-
/**
242-
* Checks if the given canonical URL exists in the fetch cache
243-
*
244-
* @param{string} key
245-
* @returns{boolean}
246-
*/
247-
functioninFetchCache(key){
248-
returncacheForGET.has(key);
249-
}
250-
251241
module.exports={
252242
fetchModule,
253-
inFetchCache,
254243
};

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

Lines changed: 0 additions & 60 deletions
This file was deleted.

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,13 @@ function createImportMetaResolve(defaultParentUrl){
2626
* @param{{url: string}} context
2727
*/
2828
functioninitializeImportMeta(meta,context){
29-
leturl=context.url;
29+
const{url }=context;
3030

3131
// Alphabetical
3232
if(experimentalImportMetaResolve){
3333
meta.resolve=createImportMetaResolve(url);
3434
}
3535

36-
url=asyncESM.esmLoader.getBaseURL(url);
37-
3836
meta.url=url;
3937
}
4038

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

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,67 @@
11
'use strict';
22

3+
const{
4+
ArrayPrototypePush,
5+
RegExpPrototypeExec,
6+
decodeURIComponent,
7+
}=primordials;
8+
39
const{ defaultGetFormat }=require('internal/modules/esm/get_format');
4-
const{ defaultGetSource }=require('internal/modules/esm/get_source');
510
const{ validateAssertions }=require('internal/modules/esm/assert');
11+
const{ getOptionValue }=require('internal/options');
12+
const{ fetchModule }=require('internal/modules/esm/fetch_module');
13+
14+
// Do not eagerly grab .manifest, it may be in TDZ
15+
constpolicy=getOptionValue('--experimental-policy') ?
16+
require('internal/process/policy') :
17+
null;
18+
constexperimentalNetworkImports=
19+
getOptionValue('--experimental-network-imports');
20+
21+
const{Buffer: {from: BufferFrom}}=require('buffer');
22+
23+
const{readFile: readFileAsync}=require('internal/fs/promises').exports;
24+
const{URL}=require('internal/url');
25+
const{
26+
ERR_INVALID_URL,
27+
ERR_UNSUPPORTED_ESM_URL_SCHEME,
28+
}=require('internal/errors').codes;
29+
30+
constDATA_URL_PATTERN=/^[^/]+\/[^,;]+(?:[^,]*?)(;base64)?,([\s\S]*)$/;
31+
32+
asyncfunctiongetSource(url,context){
33+
constparsed=newURL(url);
34+
letresponseURL=url;
35+
letsource;
36+
if(parsed.protocol==='file:'){
37+
source=awaitreadFileAsync(parsed);
38+
}elseif(parsed.protocol==='data:'){
39+
constmatch=RegExpPrototypeExec(DATA_URL_PATTERN,parsed.pathname);
40+
if(!match){
41+
thrownewERR_INVALID_URL(url);
42+
}
43+
const{1: base64,2: body}=match;
44+
source=BufferFrom(decodeURIComponent(body),base64 ? 'base64' : 'utf8');
45+
}elseif(experimentalNetworkImports&&(
46+
parsed.protocol==='https:'||
47+
parsed.protocol==='http:'
48+
)){
49+
constres=awaitfetchModule(parsed,context);
50+
source=awaitres.body;
51+
responseURL=res.resolvedHREF;
52+
}else{
53+
constsupportedSchemes=['file','data'];
54+
if(experimentalNetworkImports){
55+
ArrayPrototypePush(supportedSchemes,'http','https');
56+
}
57+
thrownewERR_UNSUPPORTED_ESM_URL_SCHEME(parsed,supportedSchemes);
58+
}
59+
if(policy?.manifest){
60+
policy.manifest.assertIntegrity(parsed,source);
61+
}
62+
return{ responseURL, source };
63+
}
64+
665

766
/**
867
* Node.js default load hook.
@@ -11,6 +70,7 @@ const{validateAssertions } = require('internal/modules/esm/assert');
1170
* @returns{object}
1271
*/
1372
asyncfunctiondefaultLoad(url,context){
73+
letresponseURL=url;
1474
const{ importAssertions }=context;
1575
let{
1676
format,
@@ -29,11 +89,12 @@ async function defaultLoad(url, context){
2989
){
3090
source=null;
3191
}elseif(source==null){
32-
source=awaitdefaultGetSource(url,context);
92+
({ responseURL,source }=awaitgetSource(url,context));
3393
}
3494

3595
return{
3696
format,
97+
responseURL,
3798
source,
3899
};
39100
}

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

Lines changed: 28 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,12 @@ const{
1717
RegExpPrototypeExec,
1818
SafeArrayIterator,
1919
SafeWeakMap,
20-
StringPrototypeStartsWith,
2120
globalThis,
2221
}=primordials;
2322
const{ MessageChannel }=require('internal/worker/io');
2423

2524
const{
2625
ERR_LOADER_CHAIN_INCOMPLETE,
27-
ERR_INTERNAL_ASSERTION,
2826
ERR_INVALID_ARG_TYPE,
2927
ERR_INVALID_ARG_VALUE,
3028
ERR_INVALID_RETURN_PROPERTY_VALUE,
@@ -55,11 +53,6 @@ const{defaultLoad } = require('internal/modules/esm/load');
5553
const{ translators }=require(
5654
'internal/modules/esm/translators');
5755
const{ getOptionValue }=require('internal/options');
58-
const{
59-
fetchModule,
60-
inFetchCache,
61-
}=require('internal/modules/esm/fetch_module');
62-
6356

6457
/**
6558
* @typedef{object} ExportedHooks
@@ -306,9 +299,7 @@ class ESMLoader{
306299
constmodule=newModuleWrap(url,undefined,source,0,0);
307300
callbackMap.set(module,{
308301
importModuleDynamically: (specifier,{ url },importAssertions)=>{
309-
returnthis.import(specifier,
310-
this.getBaseURL(url),
311-
importAssertions);
302+
returnthis.import(specifier,url,importAssertions);
312303
}
313304
});
314305

@@ -324,55 +315,6 @@ class ESMLoader{
324315
};
325316
}
326317

327-
/**
328-
* Returns the url to use for the resolution of a given cache key url
329-
* These are not guaranteed to be the same.
330-
*
331-
* In WHATWG HTTP spec for ESM the cache key is the non-I/O bound
332-
* synchronous resolution using only string operations
333-
* ~= resolveImportMap(new URL(specifier, importerHREF))
334-
*
335-
* The url used for subsequent resolution is the response URL after
336-
* all redirects have been resolved.
337-
*
338-
* https://example.com/foo redirecting to https://example.com/bar
339-
* would have a cache key of https://example.com/foo and baseURL
340-
* of https://example.com/bar
341-
*
342-
* ! MUST BE SYNCHRONOUS for import.meta initialization
343-
* ! MUST BE CALLED AFTER receiving the url body due to I/O
344-
* @param{URL['href']} url
345-
* @returns{string|Promise<URL['href']>}
346-
*/
347-
getBaseURL(url){
348-
if(getOptionValue('--experimental-network-imports')&&(
349-
StringPrototypeStartsWith(url,'http:')||
350-
StringPrototypeStartsWith(url,'https:')
351-
)){
352-
// When using network-imports, the request & response have already settled
353-
// so they are in fetchModule's cache, in which case, fetchModule returns
354-
// immediately and synchronously
355-
// Unless a custom loader bypassed the fetch cache, in which case we just
356-
// use the original url
357-
if(inFetchCache(url)){
358-
constmodule=fetchModule(newURL(url),{parentURL: url});
359-
if(typeofmodule?.resolvedHREF==='string'){
360-
returnmodule.resolvedHREF;
361-
}
362-
// Internal error
363-
thrownewERR_INTERNAL_ASSERTION(
364-
`Base url for module ${url} not loaded.`
365-
);
366-
}else{
367-
// A custom loader was used instead of network-imports.
368-
// Adding support for a response URL resolve return in custom loaders is
369-
// pending.
370-
returnurl;
371-
}
372-
}
373-
returnurl;
374-
}
375-
376318
/**
377319
* Get a (possibly still pending) module job from the cache,
378320
* or create one and return its Promise.
@@ -431,6 +373,7 @@ class ESMLoader{
431373
constmoduleProvider=async(url,isMain)=>{
432374
const{
433375
format: finalFormat,
376+
responseURL,
434377
source,
435378
}=awaitthis.load(url,{
436379
format,
@@ -440,10 +383,10 @@ class ESMLoader{
440383
consttranslator=translators.get(finalFormat);
441384

442385
if(!translator){
443-
thrownewERR_UNKNOWN_MODULE_FORMAT(finalFormat,url);
386+
thrownewERR_UNKNOWN_MODULE_FORMAT(finalFormat,responseURL);
444387
}
445388

446-
returnFunctionPrototypeCall(translator,this,url,source,isMain);
389+
returnFunctionPrototypeCall(translator,this,responseURL,source,isMain);
447390
};
448391

449392
constinspectBrk=(
@@ -607,6 +550,29 @@ class ESMLoader{
607550
format,
608551
source,
609552
}=loaded;
553+
letresponseURL=loaded.responseURL;
554+
555+
if(responseURL===undefined){
556+
responseURL=url;
557+
}
558+
559+
letresponseURLObj;
560+
if(typeofresponseURL==='string'){
561+
try{
562+
responseURLObj=newURL(responseURL);
563+
}catch{
564+
// responseURLObj not defined will throw in next branch.
565+
}
566+
}
567+
568+
if(responseURLObj?.href!==responseURL){
569+
thrownewERR_INVALID_RETURN_PROPERTY_VALUE(
570+
'undefined or a fully resolved URL string',
571+
hookErrIdentifier,
572+
'responseURL',
573+
responseURL,
574+
);
575+
}
610576

611577
if(format==null){
612578
constdataUrl=RegExpPrototypeExec(
@@ -644,6 +610,7 @@ class ESMLoader{
644610

645611
return{
646612
format,
613+
responseURL,
647614
source,
648615
};
649616
}

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

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -76,12 +76,7 @@ class ModuleJob{
7676
// these `link` callbacks depending on each other.
7777
constdependencyJobs=[];
7878
constpromises=this.module.link(async(specifier,assertions)=>{
79-
constbase=awaitthis.loader.getBaseURL(url);
80-
constbaseURL=typeofbase==='string' ?
81-
base :
82-
base.resolvedHREF;
83-
84-
constjobPromise=this.loader.getModuleJob(specifier,baseURL,assertions);
79+
constjobPromise=this.loader.getModuleJob(specifier,url,assertions);
8580
ArrayPrototypePush(dependencyJobs,jobPromise);
8681
constjob=awaitjobPromise;
8782
returnjob.modulePromise;

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

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -103,9 +103,7 @@ function errPath(url){
103103
}
104104

105105
asyncfunctionimportModuleDynamically(specifier,{ url },assertions){
106-
returnasyncESM.esmLoader.import(specifier,
107-
asyncESM.esmLoader.getBaseURL(url),
108-
assertions);
106+
returnasyncESM.esmLoader.import(specifier,url,assertions);
109107
}
110108

111109
// Strategy for loading a standard JavaScript module.
@@ -116,9 +114,7 @@ translators.set('module', async function moduleStrategy(url, source, isMain){
116114
debug(`Translating StandardModule ${url}`);
117115
constmodule=newModuleWrap(url,undefined,source,0,0);
118116
moduleWrap.callbackMap.set(module,{
119-
initializeImportMeta: (meta,wrap)=>this.importMetaInitialize(meta,{
120-
url: wrap.url
121-
}),
117+
initializeImportMeta: (meta,wrap)=>this.importMetaInitialize(meta,{ url }),
122118
importModuleDynamically,
123119
});
124120
returnmodule;

0 commit comments

Comments
(0)