| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -908,6 +908,9 @@ export function resolve(specifier, context, nextResolve) { | |||
| 908 | 908 | ||
| 909 | 909 | <!-- YAML | |
| 910 | 910 | changes: | |
| 911 | + - version: REPLACEME | ||
| 912 | + pr-url: https://github.com/nodejs/node/pull/47999 | ||
| 913 | + description: Add support for `source` with format `commonjs`. | ||
| 911 | 914 | - version: | |
| 912 | 915 | - v18.6.0 | |
| 913 | 916 | - v16.17.0 | |
@@ -945,20 +948,43 @@ validating the import assertion. | |||
| 945 | 948 | ||
| 946 | 949 | The final value of `format` must be one of the following: | |
| 947 | 950 | ||
| 948 | - | `format` | Description | Acceptable types for `source` returned by `load` | | ||
| 949 | - | ------------ | ------------------------------ | ----------------------------------------------------- | | ||
| 950 | - | `'builtin'` | Load a Node.js builtin module | Not applicable | | ||
| 951 | - | `'commonjs'` | Load a Node.js CommonJS module | Not applicable | | ||
| 952 | - | `'json'` | Load a JSON file | { [`string`][], [`ArrayBuffer`][], [`TypedArray`][] } | | ||
| 953 | - | `'module'` | Load an ES module | { [`string`][], [`ArrayBuffer`][], [`TypedArray`][] } | | ||
| 954 | - | `'wasm'` | Load a WebAssembly module | { [`ArrayBuffer`][], [`TypedArray`][] } | | ||
| 951 | + | `format` | Description | Acceptable types for `source` returned by `load` | | ||
| 952 | + | ------------ | ------------------------------ | -------------------------------------------------------------------------- | | ||
| 953 | + | `'builtin'` | Load a Node.js builtin module | Not applicable | | ||
| 954 | + | `'commonjs'` | Load a Node.js CommonJS module | { [`string`][], [`ArrayBuffer`][], [`TypedArray`][], `null`, `undefined` } | | ||
| 955 | + | `'json'` | Load a JSON file | { [`string`][], [`ArrayBuffer`][], [`TypedArray`][] } | | ||
| 956 | + | `'module'` | Load an ES module | { [`string`][], [`ArrayBuffer`][], [`TypedArray`][] } | | ||
| 957 | + | `'wasm'` | Load a WebAssembly module | { [`ArrayBuffer`][], [`TypedArray`][] } | | ||
| 955 | 958 | ||
| 956 | 959 | The value of `source` is ignored for type `'builtin'` because currently it is | |
| 957 | - not possible to replace the value of a Node.js builtin (core) module. The value | ||
| 958 | - of `source` is ignored for type `'commonjs'` because the CommonJS module loader | ||
| 959 | - does not provide a mechanism for the ES module loader to override the | ||
| 960 | - [CommonJS module return value](#commonjs-namespaces). This limitation might be | ||
| 961 | - overcome in the future. | ||
| 960 | + not possible to replace the value of a Node.js builtin (core) module. | ||
| 961 | + | ||
| 962 | + The value of `source` can be omitted for type `'commonjs'`. When a `source` is | ||
| 963 | + provided, all `require` calls from this module will be processed by the ESM | ||
| 964 | + loader with registered `resolve` and `load` hooks; all `require.resolve` calls | ||
| 965 | + from this module will be processed by the ESM loader with registered `resolve` | ||
| 966 | + hooks; `require.extensions` and monkey-patching on the CommonJS module loader | ||
| 967 | + will not apply. If `source` is undefined or `null`, it will be handled by the | ||
| 968 | + CommonJS module loader and `require`/`require.resolve` calls will not go through | ||
| 969 | + the registered hooks. This behavior for nullish `source` is temporary — in the | ||
| 970 | + future, nullish `source` will not be supported. | ||
| 971 | + | ||
| 972 | + The Node.js own `load` implementation, which is the value of `next` for the last | ||
| 973 | + loader in the `load` chain, returns `null` for `source` when `format` is | ||
| 974 | + `'commonjs'` for backward compatibility. Here is an example loader that would | ||
| 975 | + opt-in to using the non-default behavior: | ||
| 976 | + | ||
| 977 | + ```js | ||
| 978 | + import { readFile } from 'node:fs/promises'; | ||
| 979 | + | ||
| 980 | + export async function load(url, context, nextLoad) { | ||
| 981 | + const result = await nextLoad(url, context); | ||
| 982 | + if (result.format === 'commonjs') { | ||
| 983 | + result.source ??= await readFile(new URL(result.responseURL ?? url)); | ||
| 984 | + } | ||
| 985 | + return result; | ||
| 986 | + } | ||
| 987 | + ``` | ||
| 962 | 988 | ||
| 963 | 989 | > **Caveat**: The ESM `load` hook and namespaced exports from CommonJS modules | |
| 964 | 990 | > are incompatible. Attempting to use them together will result in an empty | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -10,6 +10,7 @@ const { kEmptyObject } = require('internal/util'); | |||
| 10 | 10 | const { defaultGetFormat } = require('internal/modules/esm/get_format'); | |
| 11 | 11 | const { validateAssertions } = require('internal/modules/esm/assert'); | |
| 12 | 12 | const { getOptionValue } = require('internal/options'); | |
| 13 | + const { readFileSync } = require('fs'); | ||
| 13 | 14 | ||
| 14 | 15 | // Do not eagerly grab .manifest, it may be in TDZ | |
| 15 | 16 | const policy = getOptionValue('--experimental-policy') ? | |
@@ -69,12 +70,35 @@ async function getSource(url, context) { | |||
| 69 | 70 | return { __proto__: null, responseURL, source }; | |
| 70 | 71 | } | |
| 71 | 72 | ||
| 73 | + function getSourceSync(url, context) { | ||
| 74 | + const parsed = new URL(url); | ||
| 75 | + const responseURL = url; | ||
| 76 | + let source; | ||
| 77 | + if (parsed.protocol === 'file:') { | ||
| 78 | + source = readFileSync(parsed); | ||
| 79 | + } else if (parsed.protocol === 'data:') { | ||
| 80 | + const match = RegExpPrototypeExec(DATA_URL_PATTERN, parsed.pathname); | ||
| 81 | + if (!match) { | ||
| 82 | + throw new ERR_INVALID_URL(url); | ||
| 83 | + } | ||
| 84 | + const { 1: base64, 2: body } = match; | ||
| 85 | + source = BufferFrom(decodeURIComponent(body), base64 ? 'base64' : 'utf8'); | ||
| 86 | + } else { | ||
| 87 | + const supportedSchemes = ['file', 'data']; | ||
| 88 | + throw new ERR_UNSUPPORTED_ESM_URL_SCHEME(parsed, supportedSchemes); | ||
| 89 | + } | ||
| 90 | + if (policy?.manifest) { | ||
| 91 | + policy.manifest.assertIntegrity(parsed, source); | ||
| 92 | + } | ||
| 93 | + return { __proto__: null, responseURL, source }; | ||
| 94 | + } | ||
| 95 | + | ||
| 72 | 96 | ||
| 73 | 97 | /** | |
| 74 | 98 | * Node.js default load hook. | |
| 75 | 99 | * @param {string} url | |
| 76 | - * @param {object} context | ||
| 77 | - * @returns {object} | ||
| 100 | + * @param {LoadContext} context | ||
| 101 | + * @returns {LoadReturn} | ||
| 78 | 102 | */ | |
| 79 | 103 | async function defaultLoad(url, context = kEmptyObject) { | |
| 80 | 104 | let responseURL = url; | |
@@ -108,6 +132,51 @@ async function defaultLoad(url, context = kEmptyObject) { | |||
| 108 | 132 | source, | |
| 109 | 133 | }; | |
| 110 | 134 | } | |
| 135 | + /** | ||
| 136 | + * @typedef LoadContext | ||
| 137 | + * @property {string} [format] A hint (possibly returned from `resolve`) | ||
| 138 | + * @property {string | Buffer | ArrayBuffer} [source] source | ||
| 139 | + * @property {Record<string, string>} [importAssertions] import attributes | ||
| 140 | + */ | ||
| 141 | + | ||
| 142 | + /** | ||
| 143 | + * @typedef LoadReturn | ||
| 144 | + * @property {string} format format | ||
| 145 | + * @property {URL['href']} responseURL The module's fully resolved URL | ||
| 146 | + * @property {Buffer} source source | ||
| 147 | + */ | ||
| 148 | + | ||
| 149 | + /** | ||
| 150 | + * @param {URL['href']} url | ||
| 151 | + * @param {LoadContext} [context] | ||
| 152 | + * @returns {LoadReturn} | ||
| 153 | + */ | ||
| 154 | + function defaultLoadSync(url, context = kEmptyObject) { | ||
| 155 | + let responseURL = url; | ||
| 156 | + const { importAssertions } = context; | ||
| 157 | + let { | ||
| 158 | + format, | ||
| 159 | + source, | ||
| 160 | + } = context; | ||
| 161 | + | ||
| 162 | + format ??= defaultGetFormat(new URL(url), context); | ||
| 163 | + | ||
| 164 | + validateAssertions(url, format, importAssertions); | ||
| 165 | + | ||
| 166 | + if (format === 'builtin') { | ||
| 167 | + source = null; | ||
| 168 | + } else if (source == null) { | ||
| 169 | + ({ responseURL, source } = getSourceSync(url, context)); | ||
| 170 | + } | ||
| 171 | + | ||
| 172 | + return { | ||
| 173 | + __proto__: null, | ||
| 174 | + format, | ||
| 175 | + responseURL, | ||
| 176 | + source, | ||
| 177 | + }; | ||
| 178 | + } | ||
| 179 | + | ||
| 111 | 180 | ||
| 112 | 181 | /** | |
| 113 | 182 | * throws an error if the protocol is not one of the protocols | |
@@ -160,5 +229,6 @@ function throwUnknownModuleFormat(url, format) { | |||
| 160 | 229 | ||
| 161 | 230 | module.exports = { | |
| 162 | 231 | defaultLoad, | |
| 232 | + defaultLoadSync, | ||
| 163 | 233 | throwUnknownModuleFormat, | |
| 164 | 234 | }; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -10,6 +10,7 @@ const { | |||
| 10 | 10 | } = primordials; | |
| 11 | 11 | ||
| 12 | 12 | const { | |
| 13 | + ERR_REQUIRE_ESM, | ||
| 13 | 14 | ERR_UNKNOWN_MODULE_FORMAT, | |
| 14 | 15 | } = require('internal/errors').codes; | |
| 15 | 16 | const { getOptionValue } = require('internal/options'); | |
@@ -18,7 +19,7 @@ const { emitExperimentalWarning } = require('internal/util'); | |||
| 18 | 19 | const { | |
| 19 | 20 | getDefaultConditions, | |
| 20 | 21 | } = require('internal/modules/esm/utils'); | |
| 21 | - let defaultResolve, defaultLoad, importMetaInitializer; | ||
| 22 | + let defaultResolve, defaultLoad, defaultLoadSync, importMetaInitializer; | ||
| 22 | 23 | ||
| 23 | 24 | function newResolveCache() { | |
| 24 | 25 | const { ResolveCache } = require('internal/modules/esm/module_map'); | |
@@ -220,7 +221,12 @@ class ModuleLoader { | |||
| 220 | 221 | return this.getJobFromResolveResult(resolveResult, parentURL, importAssertions); | |
| 221 | 222 | } | |
| 222 | 223 | ||
| 223 | - getJobFromResolveResult(resolveResult, parentURL, importAssertions) { | ||
| 224 | + getModuleJobSync(specifier, parentURL, importAssertions) { | ||
| 225 | + const resolveResult = this.resolveSync(specifier, parentURL, importAssertions); | ||
| 226 | + return this.getJobFromResolveResult(resolveResult, parentURL, importAssertions, true); | ||
| 227 | + } | ||
| 228 | + | ||
| 229 | + getJobFromResolveResult(resolveResult, parentURL, importAssertions, sync) { | ||
| 224 | 230 | const { url, format } = resolveResult; | |
| 225 | 231 | const resolvedImportAssertions = resolveResult.importAssertions ?? importAssertions; | |
| 226 | 232 | let job = this.loadCache.get(url, resolvedImportAssertions.type); | |
@@ -231,7 +237,7 @@ class ModuleLoader { | |||
| 231 | 237 | } | |
| 232 | 238 | ||
| 233 | 239 | if (job === undefined) { | |
| 234 | - job = this.#createModuleJob(url, resolvedImportAssertions, parentURL, format); | ||
| 240 | + job = this.#createModuleJob(url, resolvedImportAssertions, parentURL, format, sync); | ||
| 235 | 241 | } | |
| 236 | 242 | ||
| 237 | 243 | return job; | |
@@ -248,17 +254,8 @@ class ModuleLoader { | |||
| 248 | 254 | * `resolve` hook | |
| 249 | 255 | * @returns {Promise<ModuleJob>} The (possibly pending) module job | |
| 250 | 256 | */ | |
| 251 | - #createModuleJob(url, importAssertions, parentURL, format) { | ||
| 252 | - const moduleProvider = async (url, isMain) => { | ||
| 253 | - const { | ||
| 254 | - format: finalFormat, | ||
| 255 | - responseURL, | ||
| 256 | - source, | ||
| 257 | - } = await this.load(url, { | ||
| 258 | - format, | ||
| 259 | - importAssertions, | ||
| 260 | - }); | ||
| 261 | - | ||
| 257 | + #createModuleJob(url, importAssertions, parentURL, format, sync) { | ||
| 258 | + const callTranslator = ({ format: finalFormat, responseURL, source }, isMain) => { | ||
| 262 | 259 | const translator = getTranslators().get(finalFormat); | |
| 263 | 260 | ||
| 264 | 261 | if (!translator) { | |
@@ -267,6 +264,10 @@ class ModuleLoader { | |||
| 267 | 264 | ||
| 268 | 265 | return FunctionPrototypeCall(translator, this, responseURL, source, isMain); | |
| 269 | 266 | }; | |
| 267 | + const context = { format, importAssertions }; | ||
| 268 | + const moduleProvider = sync ? | ||
| 269 | + (url, isMain) => callTranslator(this.loadSync(url, context), isMain) : | ||
| 270 | + async (url, isMain) => callTranslator(await this.load(url, context), isMain); | ||
| 270 | 271 | ||
| 271 | 272 | const inspectBrk = ( | |
| 272 | 273 | parentURL === undefined && | |
@@ -285,6 +286,7 @@ class ModuleLoader { | |||
| 285 | 286 | moduleProvider, | |
| 286 | 287 | parentURL === undefined, | |
| 287 | 288 | inspectBrk, | |
| 289 | + sync, | ||
| 288 | 290 | ); | |
| 289 | 291 | ||
| 290 | 292 | this.loadCache.set(url, importAssertions.type, job); | |
@@ -388,6 +390,24 @@ class ModuleLoader { | |||
| 388 | 390 | return result; | |
| 389 | 391 | } | |
| 390 | 392 | ||
| 393 | + loadSync(url, context) { | ||
| 394 | + defaultLoadSync ??= require('internal/modules/esm/load').defaultLoadSync; | ||
| 395 | + | ||
| 396 | + let result = this.#customizations ? | ||
| 397 | + this.#customizations.loadSync(url, context) : | ||
| 398 | + defaultLoadSync(url, context); | ||
| 399 | + let format = result?.format; | ||
| 400 | + if (format === 'module') { | ||
| 401 | + throw new ERR_REQUIRE_ESM(url, true); | ||
| 402 | + } | ||
| 403 | + if (format === 'commonjs') { | ||
| 404 | + format = 'require-commonjs'; | ||
| 405 | + result = { __proto__: result, format }; | ||
| 406 | + } | ||
| 407 | + this.validateLoadResult(url, format); | ||
| 408 | + return result; | ||
| 409 | + } | ||
| 410 | + | ||
| 391 | 411 | validateLoadResult(url, format) { | |
| 392 | 412 | if (format == null) { | |
| 393 | 413 | require('internal/modules/esm/load').throwUnknownModuleFormat(url, format); | |
@@ -465,6 +485,9 @@ class CustomizedModuleLoader { | |||
| 465 | 485 | load(url, context) { | |
| 466 | 486 | return hooksProxy.makeAsyncRequest('load', undefined, url, context); | |
| 467 | 487 | } | |
| 488 | + loadSync(url, context) { | ||
| 489 | + return hooksProxy.makeSyncRequest('load', undefined, url, context); | ||
| 490 | + } | ||
| 468 | 491 | ||
| 469 | 492 | importMetaInitialize(meta, context, loader) { | |
| 470 | 493 | hooksProxy.importMetaInitialize(meta, context, loader); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -51,17 +51,26 @@ class ModuleJob { | |||
| 51 | 51 | // `loader` is the Loader instance used for loading dependencies. | |
| 52 | 52 | // `moduleProvider` is a function | |
| 53 | 53 | constructor(loader, url, importAssertions = { __proto__: null }, | |
| 54 | - moduleProvider, isMain, inspectBrk) { | ||
| 54 | + moduleProvider, isMain, inspectBrk, sync = false) { | ||
| 55 | 55 | this.loader = loader; | |
| 56 | 56 | this.importAssertions = importAssertions; | |
| 57 | 57 | this.isMain = isMain; | |
| 58 | 58 | this.inspectBrk = inspectBrk; | |
| 59 | 59 | ||
| 60 | + this.url = url; | ||
| 61 | + | ||
| 60 | 62 | this.module = undefined; | |
| 61 | 63 | // Expose the promise to the ModuleWrap directly for linking below. | |
| 62 | 64 | // `this.module` is also filled in below. | |
| 63 | 65 | this.modulePromise = ReflectApply(moduleProvider, loader, [url, isMain]); | |
| 64 | 66 | ||
| 67 | + if (sync) { | ||
| 68 | + this.module = this.modulePromise; | ||
| 69 | + this.modulePromise = PromiseResolve(this.module); | ||
| 70 | + } else { | ||
| 71 | + this.modulePromise = PromiseResolve(this.modulePromise); | ||
| 72 | + } | ||
| 73 | + | ||
| 65 | 74 | // Wait for the ModuleWrap instance being linked with all dependencies. | |
| 66 | 75 | const link = async () => { | |
| 67 | 76 | this.module = await this.modulePromise; | |
@@ -186,6 +195,20 @@ class ModuleJob { | |||
| 186 | 195 | } | |
| 187 | 196 | } | |
| 188 | 197 | ||
| 198 | + runSync() { | ||
| 199 | + assert(this.module instanceof ModuleWrap); | ||
| 200 | + if (this.instantiated !== undefined) { | ||
| 201 | + return { __proto__: null, module: this.module }; | ||
| 202 | + } | ||
| 203 | + | ||
| 204 | + this.module.instantiate(); | ||
| 205 | + this.instantiated = PromiseResolve(); | ||
| 206 | + const timeout = -1; | ||
| 207 | + const breakOnSigint = false; | ||
| 208 | + this.module.evaluate(timeout, breakOnSigint); | ||
| 209 | + return { __proto__: null, module: this.module }; | ||
| 210 | + } | ||
| 211 | + | ||
| 189 | 212 | async run() { | |
| 190 | 213 | await this.instantiate(); | |
| 191 | 214 | const timeout = -1; | |
| Back | FazBrowse Home | New Git URL |
0 commit comments