Skip to content

Commit 916e45e

Browse files
guybedforddanielleadams
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 7f8f61a commit 916e45e

File tree

8 files changed

+97
-126
lines changed

8 files changed

+97
-126
lines changed

lib/internal/modules/cjs/loader.js

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1026,8 +1026,7 @@ function wrapSafe(filename, content, cjsModuleInstance) {
10261026
displayErrors: true,
10271027
importModuleDynamically: async (specifier, _, importAssertions) => {
10281028
const loader = asyncESM.esmLoader;
1029-
return loader.import(specifier,
1030-
loader.getBaseURL(normalizeReferrerURL(filename)),
1029+
return loader.import(specifier, normalizeReferrerURL(filename),
10311030
importAssertions);
10321031
},
10331032
});
@@ -1043,8 +1042,7 @@ function wrapSafe(filename, content, cjsModuleInstance) {
10431042
filename,
10441043
importModuleDynamically(specifier, _, importAssertions) {
10451044
const loader = asyncESM.esmLoader;
1046-
return loader.import(specifier,
1047-
loader.getBaseURL(normalizeReferrerURL(filename)),
1045+
return loader.import(specifier, normalizeReferrerURL(filename),
10481046
importAssertions);
10491047
},
10501048
});

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
function initializeImportMeta(meta, context) {
29-
let url = 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+
const policy = getOptionValue('--experimental-policy') ?
16+
require('internal/process/policy') :
17+
null;
18+
const experimentalNetworkImports =
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+
const DATA_URL_PATTERN = /^[^/]+\/[^,;]+(?:[^,]*?)(;base64)?,([\s\S]*)$/;
31+
32+
async function getSource(url, context) {
33+
const parsed = new URL(url);
34+
let responseURL = url;
35+
let source;
36+
if (parsed.protocol === 'file:') {
37+
source = await readFileAsync(parsed);
38+
} else if (parsed.protocol === 'data:') {
39+
const match = RegExpPrototypeExec(DATA_URL_PATTERN, parsed.pathname);
40+
if (!match) {
41+
throw new ERR_INVALID_URL(url);
42+
}
43+
const { 1: base64, 2: body } = match;
44+
source = BufferFrom(decodeURIComponent(body), base64 ? 'base64' : 'utf8');
45+
} else if (experimentalNetworkImports && (
46+
parsed.protocol === 'https:' ||
47+
parsed.protocol === 'http:'
48+
)) {
49+
const res = await fetchModule(parsed, context);
50+
source = await res.body;
51+
responseURL = res.resolvedHREF;
52+
} else {
53+
const supportedSchemes = ['file', 'data'];
54+
if (experimentalNetworkImports) {
55+
ArrayPrototypePush(supportedSchemes, 'http', 'https');
56+
}
57+
throw new ERR_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
async function defaultLoad(url, context) {
73+
let responseURL = url;
1474
const { importAssertions } = context;
1575
let {
1676
format,
@@ -29,11 +89,12 @@ async function defaultLoad(url, context) {
2989
) {
3090
source = null;
3191
} else if (source == null) {
32-
source = await defaultGetSource(url, context);
92+
({ responseURL, source } = await getSource(url, context));
3393
}
3494

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

lib/internal/modules/esm/loader.js

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

2524
const {
26-
ERR_INTERNAL_ASSERTION,
2725
ERR_INVALID_ARG_TYPE,
2826
ERR_INVALID_ARG_VALUE,
2927
ERR_INVALID_RETURN_PROPERTY_VALUE,
@@ -50,10 +48,6 @@ const { defaultLoad } = require('internal/modules/esm/load');
5048
const { translators } = require(
5149
'internal/modules/esm/translators');
5250
const { getOptionValue } = require('internal/options');
53-
const {
54-
fetchModule,
55-
} = require('internal/modules/esm/fetch_module');
56-
5751

5852
/**
5953
* Prevent the specifier resolution warning from being printed twice
@@ -238,9 +232,7 @@ class ESMLoader {
238232
const module = new ModuleWrap(url, undefined, source, 0, 0);
239233
callbackMap.set(module, {
240234
importModuleDynamically: (specifier, { url }, importAssertions) => {
241-
return this.import(specifier,
242-
this.getBaseURL(url),
243-
importAssertions);
235+
return this.import(specifier, url, importAssertions);
244236
}
245237
});
246238

@@ -256,43 +248,6 @@ class ESMLoader {
256248
};
257249
}
258250

259-
/**
260-
* Returns the url to use for the resolution of a given cache key url
261-
* These are not guaranteed to be the same.
262-
*
263-
* In WHATWG HTTP spec for ESM the cache key is the non-I/O bound
264-
* synchronous resolution using only string operations
265-
* ~= resolveImportMap(new URL(specifier, importerHREF))
266-
*
267-
* The url used for subsequent resolution is the response URL after
268-
* all redirects have been resolved.
269-
*
270-
* https://example.com/foo redirecting to https://example.com/bar
271-
* would have a cache key of https://example.com/foo and baseURL
272-
* of https://example.com/bar
273-
*
274-
* MUST BE SYNCHRONOUS for import.meta initialization
275-
* MUST BE CALLED AFTER receiving the url body due to I/O
276-
* @param {string} url
277-
* @returns {string}
278-
*/
279-
getBaseURL(url) {
280-
if (
281-
StringPrototypeStartsWith(url, 'http:') ||
282-
StringPrototypeStartsWith(url, 'https:')
283-
) {
284-
// The request & response have already settled, so they are in
285-
// fetchModule's cache, in which case, fetchModule returns
286-
// immediately and synchronously
287-
url = fetchModule(new URL(url), { parentURL: url }).resolvedHREF;
288-
// This should only occur if the module hasn't been fetched yet
289-
if (typeof url !== 'string') {
290-
throw new ERR_INTERNAL_ASSERTION(`Base url for module ${url} not loaded.`);
291-
}
292-
}
293-
return url;
294-
}
295-
296251
/**
297252
* Get a (possibly still pending) module job from the cache,
298253
* or create one and return its Promise.
@@ -346,6 +301,7 @@ class ESMLoader {
346301
const moduleProvider = async (url, isMain) => {
347302
const {
348303
format: finalFormat,
304+
responseURL,
349305
source,
350306
} = await this.load(url, {
351307
format,
@@ -355,10 +311,10 @@ class ESMLoader {
355311
const translator = translators.get(finalFormat);
356312

357313
if (!translator) {
358-
throw new ERR_UNKNOWN_MODULE_FORMAT(finalFormat, url);
314+
throw new ERR_UNKNOWN_MODULE_FORMAT(finalFormat, responseURL);
359315
}
360316

361-
return FunctionPrototypeCall(translator, this, url, source, isMain);
317+
return FunctionPrototypeCall(translator, this, responseURL, source, isMain);
362318
};
363319

364320
const inspectBrk = (
@@ -442,6 +398,29 @@ class ESMLoader {
442398
format,
443399
source,
444400
} = loaded;
401+
let responseURL = loaded.responseURL;
402+
403+
if (responseURL === undefined) {
404+
responseURL = url;
405+
}
406+
407+
let responseURLObj;
408+
if (typeof responseURL === 'string') {
409+
try {
410+
responseURLObj = new URL(responseURL);
411+
} catch {
412+
// responseURLObj not defined will throw in next branch.
413+
}
414+
}
415+
416+
if (responseURLObj?.href !== responseURL) {
417+
throw new ERR_INVALID_RETURN_PROPERTY_VALUE(
418+
'undefined or a fully resolved URL string',
419+
hookErrIdentifier,
420+
'responseURL',
421+
responseURL,
422+
);
423+
}
445424

446425
if (format == null) {
447426
const dataUrl = RegExpPrototypeExec(
@@ -477,6 +456,7 @@ class ESMLoader {
477456

478457
return {
479458
format,
459+
responseURL,
480460
source,
481461
};
482462
}

lib/internal/modules/esm/module_job.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,7 @@ class ModuleJob {
7676
// these `link` callbacks depending on each other.
7777
const dependencyJobs = [];
7878
const promises = this.module.link(async (specifier, assertions) => {
79-
const baseURL = this.loader.getBaseURL(url);
80-
const jobPromise = this.loader.getModuleJob(specifier, baseURL, assertions);
79+
const jobPromise = this.loader.getModuleJob(specifier, url, assertions);
8180
ArrayPrototypePush(dependencyJobs, jobPromise);
8281
const job = await jobPromise;
8382
return job.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
async function importModuleDynamically(specifier, { url }, assertions) {
106-
return asyncESM.esmLoader.import(specifier,
107-
asyncESM.esmLoader.getBaseURL(url),
108-
assertions);
106+
return asyncESM.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
const module = new ModuleWrap(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
return module;

test/parallel/test-bootstrap-modules.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,6 @@ const expectedModules = new Set([
7878
'NativeModule internal/modules/esm/fetch_module',
7979
'NativeModule internal/modules/esm/formats',
8080
'NativeModule internal/modules/esm/get_format',
81-
'NativeModule internal/modules/esm/get_source',
8281
'NativeModule internal/modules/esm/handle_process_exit',
8382
'NativeModule internal/modules/esm/initialize_import_meta',
8483
'NativeModule internal/modules/esm/load',

0 commit comments

Comments
 (0)