diff --git a/lib/fs.js b/lib/fs.js index 81c4d2b9c884..28e7b28e6a93 100644 --- a/lib/fs.js +++ b/lib/fs.js @@ -64,8 +64,6 @@ const { isArrayBufferView } = require('internal/util/types'); const binding = internalBinding('fs'); -const { createBlobFromFilePath } = require('internal/blob'); - const { Buffer } = require('buffer'); const { isBuffer: BufferIsBuffer } = Buffer; const BufferToString = uncurryThis(Buffer.prototype.toString); @@ -722,6 +720,7 @@ function openAsBlob(path, options = kEmptyObject) { // To give ourselves flexibility to maybe return the Blob asynchronously, // this API returns a Promise. path = getValidatedPath(path); + const { createBlobFromFilePath } = require('internal/blob'); return PromiseResolve(createBlobFromFilePath(path, { type })); } diff --git a/lib/internal/bootstrap/switches/is_main_thread.js b/lib/internal/bootstrap/switches/is_main_thread.js index 06ac27c84869..8cf57b231856 100644 --- a/lib/internal/bootstrap/switches/is_main_thread.js +++ b/lib/internal/bootstrap/switches/is_main_thread.js @@ -292,12 +292,27 @@ rawMethods.resetStdioForTesting = function() { // Needed by the module loader and generally needed everywhere. require('fs'); -require('util'); -require('url'); // eslint-disable-line no-restricted-modules internalBinding('module_wrap'); require('internal/modules/cjs/loader'); -require('internal/modules/esm/loader'); require('internal/modules/esm/utils'); +if (isBuildingSnapshot()) { + // Preloaded so that they are part of the snapshot, where they cost nothing + // at startup. When bootstrapping WITHOUT a snapshot (worker threads, + // embedders that create their own isolate, --no-node-snapshot) they are + // loaded on first use instead: the ESM loader (with its translators, + // resolver and their dependencies) by run_main/import(), the public util + // and url modules by whoever requires them, data: URL and TypeScript + // support by the module loaders, internal/blob by fs.openAsBlob(), and the + // DNS helpers by node:dns or an explicit --dns-result-order (see + // pre_execution). + require('util'); + require('url'); // eslint-disable-line no-restricted-modules + require('internal/modules/esm/loader'); + require('internal/data_url'); + require('internal/modules/typescript'); + require('internal/blob'); + require('internal/dns/utils'); +} // Needed to refresh the time origin. require('internal/perf/utils'); @@ -311,8 +326,6 @@ internalBinding('wasm_web_api'); internalBinding('worker'); // Needed by most execution modes. require('internal/modules/run_main'); -// Needed to refresh DNS configurations. -require('internal/dns/utils'); // Needed by almost all execution modes. It's fine to // load them into the snapshot as long as we don't run // any of the initialization. diff --git a/lib/internal/dns/utils.js b/lib/internal/dns/utils.js index 8e70baffb904..50ec10d3b042 100644 --- a/lib/internal/dns/utils.js +++ b/lib/internal/dns/utils.js @@ -214,15 +214,15 @@ class ResolverBase { } let defaultResolver; -let dnsOrder; +// May already hold a value chosen by the snapshotted application; a +// --dns-result-order flag given at runtime overrides it in initializeDns(). +let dnsOrder = 'verbatim'; const validDnsOrders = ['verbatim', 'ipv4first', 'ipv6first']; const validFamilies = [0, 4, 6]; function initializeDns() { const orderFromCLI = getOptionValue('--dns-result-order'); - if (!orderFromCLI) { - dnsOrder ??= 'verbatim'; - } else { + if (orderFromCLI) { // Allow the deserialized application to override order from CLI. validateOneOf(orderFromCLI, '--dns-result-order', validDnsOrders); dnsOrder = orderFromCLI; diff --git a/lib/internal/modules/cjs/loader.js b/lib/internal/modules/cjs/loader.js index bb466d0b68d5..d5de950e3c8e 100644 --- a/lib/internal/modules/cjs/loader.js +++ b/lib/internal/modules/cjs/loader.js @@ -180,7 +180,7 @@ const { resolveWithHooks, validateLoadStrict, } = require('internal/modules/customization_hooks'); -const { stripTypeScriptModuleTypes } = require('internal/modules/typescript'); +const lazyTypeScript = getLazy(() => require('internal/modules/typescript')); const packageJsonReader = require('internal/modules/package_json_reader'); const { getOptionValue, getEmbedderOptions } = require('internal/options'); const shouldReportRequiredModules = getLazy(() => process.env.WATCH_REPORT_DEPENDENCIES); @@ -1888,7 +1888,7 @@ function wrapSafe(filename, content, cjsModuleInstance, format) { Module.prototype._compile = function(content, filename, format) { if (format === 'commonjs-typescript' || format === 'module-typescript' || format === 'typescript') { this[kURL] ??= convertCJSFilenameToURL(filename); - content = stripTypeScriptModuleTypes(content, filename, this[kURL]); + content = lazyTypeScript().stripTypeScriptModuleTypes(content, filename, this[kURL]); switch (format) { case 'commonjs-typescript': { format = 'commonjs'; diff --git a/lib/internal/modules/esm/load.js b/lib/internal/modules/esm/load.js index 94879761553e..0c38ae082e32 100644 --- a/lib/internal/modules/esm/load.js +++ b/lib/internal/modules/esm/load.js @@ -20,9 +20,6 @@ const { ERR_UNSUPPORTED_ESM_URL_SCHEME, } = require('internal/errors').codes; -const { - dataURLProcessor, -} = require('internal/data_url'); /** * @param {URL} url URL to the module @@ -40,6 +37,7 @@ function getSourceSync(url, context) { // Prefer module.registerHooks() or other more formal fs hooks released in the future. source = fs.readFileSync(url); } else if (protocol === 'data:') { + const { dataURLProcessor } = require('internal/data_url'); // Only for data: URLs. const result = dataURLProcessor(url); if (result === 'failure') { throw new ERR_INVALID_URL(responseURL); diff --git a/lib/internal/modules/esm/translators.js b/lib/internal/modules/esm/translators.js index ad3de25bf6d5..e3baaaefa6c0 100644 --- a/lib/internal/modules/esm/translators.js +++ b/lib/internal/modules/esm/translators.js @@ -30,7 +30,10 @@ const { stripBOM, urlToFilename, } = require('internal/modules/helpers'); -const { stripTypeScriptModuleTypes } = require('internal/modules/typescript'); +function stripTypeScriptModuleTypes(source, url) { + // Only needed for TypeScript sources; keep it out of the loader's startup path. + return require('internal/modules/typescript').stripTypeScriptModuleTypes(source, url); +} const { kIsCachedByESMLoader, Module: CJSModule, diff --git a/lib/internal/process/execution.js b/lib/internal/process/execution.js index 4a2b57790bd7..46cf9f95407a 100644 --- a/lib/internal/process/execution.js +++ b/lib/internal/process/execution.js @@ -25,7 +25,9 @@ const { kSourcePhase, kEvaluationPhase, } = internalBinding('module_wrap'); -const { stripTypeScriptModuleTypes } = require('internal/modules/typescript'); +function stripTypeScriptModuleTypes(source, filename) { + return require('internal/modules/typescript').stripTypeScriptModuleTypes(source, filename); +} const { executionAsyncId, diff --git a/lib/internal/process/pre_execution.js b/lib/internal/process/pre_execution.js index e379fbe34021..d2b48a463b32 100644 --- a/lib/internal/process/pre_execution.js +++ b/lib/internal/process/pre_execution.js @@ -139,7 +139,12 @@ function prepareExecution(options) { initializeConfigFileSupport(); - require('internal/dns/utils').initializeDns(); + // internal/dns/utils (and internal/net behind it) is only needed up front + // to validate an explicit --dns-result-order or to register the resolver's + // snapshot serialization; otherwise it is loaded with node:dns. + if (getOptionValue('--dns-result-order') || isBuildingSnapshot()) { + require('internal/dns/utils').initializeDns(); + } if (isMainThread) { assert(internalBinding('worker').isMainThread); diff --git a/lib/internal/url.js b/lib/internal/url.js index e96df6148f98..876a5d2a3618 100644 --- a/lib/internal/url.js +++ b/lib/internal/url.js @@ -93,7 +93,11 @@ const { kValidateObjectAllowObjects, } = require('internal/validators'); -const { percentDecode } = require('internal/data_url'); +let percentDecode; +function lazyPercentDecode(input) { + percentDecode ??= require('internal/data_url').percentDecode; + return percentDecode(input); +} const querystring = require('querystring'); @@ -1560,7 +1564,7 @@ function getPathBufferFromURLWin32(url) { // percent encoded characters and we take the string as is. Any invalid // percent encodings, e.g. `%ZZ` are ignored and are passed through // literally. - const decodedu8 = percentDecode(Buffer.from(pathname, 'utf8')); + const decodedu8 = lazyPercentDecode(Buffer.from(pathname, 'utf8')); const decodedPathname = Buffer.from(TypedArrayPrototypeGetBuffer(decodedu8), TypedArrayPrototypeGetByteOffset(decodedu8), TypedArrayPrototypeGetByteLength(decodedu8)); @@ -1635,7 +1639,7 @@ function getPathBufferFromURLPosix(url) { // won't scan for the slashes at all, and instead will decode the bytes // literally into the returned Buffer. We're going to do the best we can and // just interpret the input url as a sequence of bytes. - const u8 = percentDecode(Buffer.from(pathname, 'utf8')); + const u8 = lazyPercentDecode(Buffer.from(pathname, 'utf8')); return Buffer.from(TypedArrayPrototypeGetBuffer(u8), TypedArrayPrototypeGetByteOffset(u8), TypedArrayPrototypeGetByteLength(u8)); diff --git a/lib/internal/worker.js b/lib/internal/worker.js index 60e95273ef5f..f3f3c23a5abe 100644 --- a/lib/internal/worker.js +++ b/lib/internal/worker.js @@ -30,10 +30,6 @@ const { const EventEmitter = require('events'); const assert = require('internal/assert'); const path = require('path'); -const { - internalEventLoopUtilization, -} = require('internal/perf/event_loop_utilization'); - const errorCodes = require('internal/errors').codes; const { ERR_WORKER_NOT_RUNNING, @@ -60,7 +56,6 @@ const { WritableWorkerStdio, } = workerIo; const { createMainThreadPort, destroyMainThreadPort } = require('internal/worker/messaging'); -const { deserializeError } = require('internal/error_serdes'); const { fileURLToPath, isURL, pathToFileURL } = require('internal/url'); const { constructSharedArrayBuffer, @@ -417,6 +412,7 @@ class Worker extends EventEmitter { [kOnErrorMessage](serialized) { // This is what is called for uncaught exceptions. + const { deserializeError } = require('internal/error_serdes'); const error = deserializeError(serialized); this.emit('error', error); } @@ -699,6 +695,7 @@ function makeResourceLimits(float64arr) { } function eventLoopUtilization(util1, util2) { + const { internalEventLoopUtilization } = require('internal/perf/event_loop_utilization'); // TODO(trevnorris): Works to solve the thread-safe read/write issue of // loopTime, but has the drawback that it can't be set until the event loop // has had a chance to turn. So it will be impossible to read the ELU of diff --git a/lib/worker_threads.js b/lib/worker_threads.js index 78007d1502b3..622bef3f55c1 100644 --- a/lib/worker_threads.js +++ b/lib/worker_threads.js @@ -1,5 +1,7 @@ 'use strict'; +const { defineLazyProperties } = require('internal/util'); + const { isInternalThread, isMainThread, @@ -30,8 +32,6 @@ const { isMarkedAsUntransferable, } = require('internal/buffer'); -const { locks } = require('internal/locks'); - module.exports = { isInternalThread, isMainThread, @@ -53,5 +53,11 @@ module.exports = { BroadcastChannel, setEnvironmentData, getEnvironmentData, - locks, }; + +// The Web Locks API implementation is only needed once `locks` is used. +defineLazyProperties( + module.exports, + 'internal/locks', + ['locks'], +);