Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

esm: refactor responseURL handling #43164

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions doc/api/esm.md
Original file line number Diff line number Diff line change
Expand Up @@ -749,7 +749,7 @@ changes:
* `context` {Object}
* `conditions` {string\[]} Export conditions of the relevant `package.json`
* `importAssertions` {Object}
* `parentURL` {string|undefined} The module importing this one, or undefined
* `parentUrl` {string|undefined} The module importing this one, or undefined
guybedford marked this conversation as resolved.
Show resolved Hide resolved
if this is the Node.js entry point
* `nextResolve` {Function} The subsequent `resolve` hook in the chain, or the
Node.js default `resolve` hook after the last user-supplied `resolve` hook
Expand Down Expand Up @@ -790,15 +790,15 @@ Node.js module specifier resolution behavior_ when calling `defaultResolve`, the

```js
export async function resolve(specifier, context, nextResolve) {
const { parentURL = null } = context;
const { parentUrl = null } = context;

if (Math.random() > 0.5) { // Some condition.
// For some or all specifiers, do some custom logic for resolving.
// Always return an object of the form {url: <string>}.
return {
shortCircuit: true,
url: parentURL ?
new URL(specifier, parentURL).href :
url: parentUrl ?
new URL(specifier, parentUrl).href :
new URL(specifier).href,
};
}
Expand Down Expand Up @@ -1007,7 +1007,7 @@ and there is no security.
import { get } from 'node:https';

export function resolve(specifier, context, nextResolve) {
const { parentURL = null } = context;
const { parentUrl = null } = context;

// Normally Node.js would error on specifiers starting with 'https://', so
// this hook intercepts them and converts them into absolute URLs to be
Expand All @@ -1017,10 +1017,10 @@ export function resolve(specifier, context, nextResolve) {
shortCircuit: true,
url: specifier
};
} else if (parentURL && parentURL.startsWith('https://')) {
} else if (parentUrl && parentUrl.startsWith('https://')) {
return {
shortCircuit: true,
url: new URL(specifier, parentURL).href,
url: new URL(specifier, parentUrl).href,
};
}

Expand Down Expand Up @@ -1083,20 +1083,20 @@ import { cwd } from 'node:process';
import { fileURLToPath, pathToFileURL } from 'node:url';
import CoffeeScript from 'coffeescript';

const baseURL = pathToFileURL(`${cwd()}/`).href;
const baseUrl = pathToFileURL(`${cwd()}/`).href;

// CoffeeScript files end in .coffee, .litcoffee or .coffee.md.
const extensionsRegex = /\.coffee$|\.litcoffee$|\.coffee\.md$/;

export async function resolve(specifier, context, nextResolve) {
if (extensionsRegex.test(specifier)) {
const { parentURL = baseURL } = context;
const { parentUrl = baseUrl } = context;

// Node.js normally errors on unknown file extensions, so return a URL for
// specifiers ending in the CoffeeScript file extensions.
return {
shortCircuit: true,
url: new URL(specifier, parentURL).href
url: new URL(specifier, parentUrl).href
};
}

Expand Down
6 changes: 2 additions & 4 deletions lib/internal/modules/cjs/loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -1023,8 +1023,7 @@ function wrapSafe(filename, content, cjsModuleInstance) {
displayErrors: true,
importModuleDynamically: async (specifier, _, importAssertions) => {
const loader = asyncESM.esmLoader;
return loader.import(specifier,
loader.getBaseURL(normalizeReferrerURL(filename)),
return loader.import(specifier, normalizeReferrerURL(filename),
importAssertions);
},
});
Expand All @@ -1040,8 +1039,7 @@ function wrapSafe(filename, content, cjsModuleInstance) {
filename,
importModuleDynamically(specifier, _, importAssertions) {
const loader = asyncESM.esmLoader;
return loader.import(specifier,
loader.getBaseURL(normalizeReferrerURL(filename)),
return loader.import(specifier, normalizeReferrerURL(filename),
importAssertions);
},
});
Expand Down
15 changes: 2 additions & 13 deletions lib/internal/modules/esm/fetch_module.js
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ async function isLocalAddress(hostname) {
* @param {ESModuleContext} context
* @returns {ReturnType<typeof fetchWithRedirects>}
*/
function fetchModule(parsed, { parentURL }) {
function fetchModule(parsed, { parentUrl }) {
const { href } = parsed;
const existing = cacheForGET.get(href);
if (existing) {
Expand All @@ -228,7 +228,7 @@ function fetchModule(parsed, { parentURL }) {
if (is !== true) {
throw new ERR_NETWORK_IMPORT_DISALLOWED(
href,
parentURL,
parentUrl,
'http can only be used to load local resources (use https instead).'
);
}
Expand All @@ -238,17 +238,6 @@ function fetchModule(parsed, { parentURL }) {
return fetchWithRedirects(parsed);
}

/**
* Checks if the given canonical URL exists in the fetch cache
*
* @param {string} key
* @returns {boolean}
*/
function inFetchCache(key) {
return cacheForGET.has(key);
}

module.exports = {
fetchModule,
inFetchCache,
};
8 changes: 4 additions & 4 deletions lib/internal/modules/esm/get_format.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ function getDataProtocolModuleFormat(parsed) {

/**
* @param {URL} url
* @param {{parentURL: string}} context
* @param {{parentUrl: string}} context
* @param {boolean} ignoreErrors
* @returns {string}
*/
Expand Down Expand Up @@ -85,7 +85,7 @@ function getFileProtocolModuleFormat(url, context, ignoreErrors) {

/**
* @param {URL} url
* @param {{parentURL: string}} context
* @param {{parentUrl: string}} context
* @returns {Promise<string> | undefined} only works when enabled
*/
function getHttpProtocolModuleFormat(url, context) {
Expand All @@ -101,7 +101,7 @@ function getHttpProtocolModuleFormat(url, context) {

/**
* @param {URL | URL['href']} url
* @param {{parentURL: string}} context
* @param {{parentUrl: string}} context
* @returns {Promise<string> | string | undefined} only works when enabled
*/
function defaultGetFormatWithoutErrors(url, context) {
Expand All @@ -113,7 +113,7 @@ function defaultGetFormatWithoutErrors(url, context) {

/**
* @param {URL | URL['href']} url
* @param {{parentURL: string}} context
* @param {{parentUrl: string}} context
* @returns {Promise<string> | string | undefined} only works when enabled
*/
function defaultGetFormat(url, context) {
Expand Down
60 changes: 0 additions & 60 deletions lib/internal/modules/esm/get_source.js

This file was deleted.

4 changes: 1 addition & 3 deletions lib/internal/modules/esm/initialize_import_meta.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,13 @@ function createImportMetaResolve(defaultParentUrl) {
* @param {{url: string}} context
*/
function initializeImportMeta(meta, context) {
let url = context.url;
const { url } = context;

// Alphabetical
if (experimentalImportMetaResolve) {
meta.resolve = createImportMetaResolve(url);
}

url = asyncESM.esmLoader.getBaseURL(url);

meta.url = url;
}

Expand Down
65 changes: 63 additions & 2 deletions lib/internal/modules/esm/load.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,67 @@
'use strict';

const {
ArrayPrototypePush,
RegExpPrototypeExec,
decodeURIComponent,
} = primordials;

const { defaultGetFormat } = require('internal/modules/esm/get_format');
const { defaultGetSource } = require('internal/modules/esm/get_source');
const { validateAssertions } = require('internal/modules/esm/assert');
const { getOptionValue } = require('internal/options');
const { fetchModule } = require('internal/modules/esm/fetch_module');

// Do not eagerly grab .manifest, it may be in TDZ
const policy = getOptionValue('--experimental-policy') ?
require('internal/process/policy') :
null;
const experimentalNetworkImports =
getOptionValue('--experimental-network-imports');

const { Buffer: { from: BufferFrom } } = require('buffer');

const { readFile: readFileAsync } = require('internal/fs/promises').exports;
const { URL } = require('internal/url');
const {
ERR_INVALID_URL,
ERR_UNSUPPORTED_ESM_URL_SCHEME,
} = require('internal/errors').codes;

const DATA_URL_PATTERN = /^[^/]+\/[^,;]+(?:[^,]*?)(;base64)?,([\s\S]*)$/;

async function getSource(url, context) {
guybedford marked this conversation as resolved.
Show resolved Hide resolved
const parsed = new URL(url);
let responseUrl = url;
let source;
if (parsed.protocol === 'file:') {
source = await readFileAsync(parsed);
} else if (parsed.protocol === 'data:') {
const match = RegExpPrototypeExec(DATA_URL_PATTERN, parsed.pathname);
if (!match) {
throw new ERR_INVALID_URL(url);
}
const { 1: base64, 2: body } = match;
source = BufferFrom(decodeURIComponent(body), base64 ? 'base64' : 'utf8');
} else if (experimentalNetworkImports && (
parsed.protocol === 'https:' ||
parsed.protocol === 'http:'
)) {
const res = await fetchModule(parsed, context);
source = await res.body;
responseUrl = res.resolvedHREF;
} else {
const supportedSchemes = ['file', 'data'];
if (experimentalNetworkImports) {
ArrayPrototypePush(supportedSchemes, 'http', 'https');
}
throw new ERR_UNSUPPORTED_ESM_URL_SCHEME(parsed, supportedSchemes);
}
if (policy?.manifest) {
policy.manifest.assertIntegrity(parsed, source);
}
return { responseUrl, source };
}


/**
* Node.js default load hook.
Expand All @@ -11,6 +70,7 @@ const { validateAssertions } = require('internal/modules/esm/assert');
* @returns {object}
*/
async function defaultLoad(url, context) {
let responseUrl = url;
const { importAssertions } = context;
let {
format,
Expand All @@ -29,11 +89,12 @@ async function defaultLoad(url, context) {
) {
source = null;
} else if (source == null) {
source = await defaultGetSource(url, context);
({ responseUrl, source } = await getSource(url, context));
}

return {
format,
responseUrl,
source,
};
}
Expand Down
Loading