From e5c0ebf046301e42331704eec98efa4b35758b6b Mon Sep 17 00:00:00 2001 From: Daijiro Wachi Date: Mon, 15 Jun 2026 19:24:55 +0900 Subject: [PATCH] lib: lazy-load builtins not used during startup Defer the url_pattern and blob bindings and the typescript builtin until they are first used, instead of requiring them unconditionally during bootstrap. Loading internal/blob lazily also drops the internal/encoding tree it pulled in. This removes 7 builtins from the startup module list (113 -> 106) and reduces bootstrapComplete by ~2% (median 11.70ms -> 11.45ms), measured with performance.nodeTiming over 100 interleaved runs. - fs.openAsBlob() and URL.createObjectURL() load internal/blob and the blob binding on demand - URLPattern is exposed via exposeLazyInterfaces and no longer forces the url_pattern binding at bootstrap - TypeScript type stripping loads internal/modules/typescript only when a .ts source is actually processed Signed-off-by: Daijiro Wachi --- lib/fs.js | 6 +++++- .../bootstrap/web/exposed-window-or-worker.js | 5 ++--- lib/internal/modules/cjs/loader.js | 6 +++++- lib/internal/modules/esm/translators.js | 6 +++++- lib/internal/process/execution.js | 6 +++++- lib/internal/url.js | 16 +++++++++++----- lib/module.js | 4 +++- lib/url.js | 7 +++++-- test/parallel/test-bootstrap-modules.js | 7 ------- 9 files changed, 41 insertions(+), 22 deletions(-) diff --git a/lib/fs.js b/lib/fs.js index 1ea70ff192d6dd..af3bda3e8cc5c7 100644 --- a/lib/fs.js +++ b/lib/fs.js @@ -64,7 +64,10 @@ const { isArrayBufferView } = require('internal/util/types'); const binding = internalBinding('fs'); -const { createBlobFromFilePath } = require('internal/blob'); +let createBlobFromFilePath; +function lazyLoadBlob() { + createBlobFromFilePath ??= require('internal/blob').createBlobFromFilePath; +} const { Buffer } = require('buffer'); const { isBuffer: BufferIsBuffer } = Buffer; @@ -723,6 +726,7 @@ function openAsBlob(path, options = kEmptyObject) { // To give ourselves flexibility to maybe return the Blob asynchronously, // this API returns a Promise. path = getValidatedPath(path); + lazyLoadBlob(); return PromiseResolve(createBlobFromFilePath(path, { type })); } diff --git a/lib/internal/bootstrap/web/exposed-window-or-worker.js b/lib/internal/bootstrap/web/exposed-window-or-worker.js index b138275e92bd4d..25d69408d3f716 100644 --- a/lib/internal/bootstrap/web/exposed-window-or-worker.js +++ b/lib/internal/bootstrap/web/exposed-window-or-worker.js @@ -19,7 +19,6 @@ const { defineLazyProperties, defineReplaceableLazyAttribute, exposeLazyInterfaces, - exposeInterface, } = require('internal/util'); const { @@ -64,9 +63,9 @@ exposeLazyInterfaces(globalThis, 'perf_hooks', [ defineReplaceableLazyAttribute(globalThis, 'perf_hooks', ['performance']); // https://w3c.github.io/FileAPI/#creating-revoking -const { installObjectURLMethods, URLPattern } = require('internal/url'); +const { installObjectURLMethods } = require('internal/url'); installObjectURLMethods(); -exposeInterface(globalThis, 'URLPattern', URLPattern); +exposeLazyInterfaces(globalThis, 'internal/url', ['URLPattern']); let fetchImpl; // https://fetch.spec.whatwg.org/#fetch-method diff --git a/lib/internal/modules/cjs/loader.js b/lib/internal/modules/cjs/loader.js index 219618da8a8a93..ffeb566dd30855 100644 --- a/lib/internal/modules/cjs/loader.js +++ b/lib/internal/modules/cjs/loader.js @@ -179,7 +179,11 @@ const { resolveWithHooks, validateLoadStrict, } = require('internal/modules/customization_hooks'); -const { stripTypeScriptModuleTypes } = require('internal/modules/typescript'); +let _stripTypeScriptModuleTypes; +function stripTypeScriptModuleTypes(source, filename, sourceURL) { + _stripTypeScriptModuleTypes ??= require('internal/modules/typescript').stripTypeScriptModuleTypes; + return _stripTypeScriptModuleTypes(source, filename, sourceURL); +} const packageJsonReader = require('internal/modules/package_json_reader'); const { getOptionValue, getEmbedderOptions } = require('internal/options'); const shouldReportRequiredModules = getLazy(() => process.env.WATCH_REPORT_DEPENDENCIES); diff --git a/lib/internal/modules/esm/translators.js b/lib/internal/modules/esm/translators.js index c453e2b54f5957..47d208da61a93a 100644 --- a/lib/internal/modules/esm/translators.js +++ b/lib/internal/modules/esm/translators.js @@ -30,7 +30,11 @@ const { stripBOM, urlToFilename, } = require('internal/modules/helpers'); -const { stripTypeScriptModuleTypes } = require('internal/modules/typescript'); +let _stripTypeScriptModuleTypes; +function stripTypeScriptModuleTypes(source, filename, sourceURL) { + _stripTypeScriptModuleTypes ??= require('internal/modules/typescript').stripTypeScriptModuleTypes; + return _stripTypeScriptModuleTypes(source, filename, sourceURL); +} const { kIsCachedByESMLoader, Module: CJSModule, diff --git a/lib/internal/process/execution.js b/lib/internal/process/execution.js index 4a2b57790bd7e6..cc8e61c70ca257 100644 --- a/lib/internal/process/execution.js +++ b/lib/internal/process/execution.js @@ -25,7 +25,11 @@ const { kSourcePhase, kEvaluationPhase, } = internalBinding('module_wrap'); -const { stripTypeScriptModuleTypes } = require('internal/modules/typescript'); +let _stripTypeScriptModuleTypes; +function stripTypeScriptModuleTypes(source, filename, sourceURL) { + _stripTypeScriptModuleTypes ??= require('internal/modules/typescript').stripTypeScriptModuleTypes; + return _stripTypeScriptModuleTypes(source, filename, sourceURL); +} const { executionAsyncId, diff --git a/lib/internal/url.js b/lib/internal/url.js index be20127b45f191..139f9321ba5b67 100644 --- a/lib/internal/url.js +++ b/lib/internal/url.js @@ -33,7 +33,10 @@ const { decodeURIComponent, } = primordials; -const { URLPattern } = internalBinding('url_pattern'); +let _URLPattern; +function lazyURLPattern() { + return _URLPattern ??= internalBinding('url_pattern').URLPattern; +} const { inspect } = require('internal/util/inspect'); const { encodeStr, @@ -1210,7 +1213,10 @@ ObjectDefineProperties(URL, { }); function installObjectURLMethods() { - const bindingBlob = internalBinding('blob'); + let bindingBlob; + function lazyBindingBlob() { + return bindingBlob ??= internalBinding('blob'); + } function createObjectURL(obj) { const cryptoRandom = lazyCryptoRandom(); @@ -1223,7 +1229,7 @@ function installObjectURLMethods() { const id = cryptoRandom.randomUUID(); - bindingBlob.storeDataObject(id, obj[blob.kHandle], obj.size, obj.type); + lazyBindingBlob().storeDataObject(id, obj[blob.kHandle], obj.size, obj.type); return `blob:nodedata:${id}`; } @@ -1233,7 +1239,7 @@ function installObjectURLMethods() { throw new ERR_MISSING_ARGS('url'); } - bindingBlob.revokeObjectURL(`${url}`); + lazyBindingBlob().revokeObjectURL(`${url}`); } ObjectDefineProperties(URL, { @@ -1713,7 +1719,7 @@ module.exports = { toPathIfFileURL, installObjectURLMethods, URL, - URLPattern, + get URLPattern() { return lazyURLPattern(); }, URLSearchParams, URLParse: URL.parse, domainToASCII, diff --git a/lib/module.js b/lib/module.js index 1217172afb3ccb..f47904ce3c32c2 100644 --- a/lib/module.js +++ b/lib/module.js @@ -19,7 +19,9 @@ const { const { findPackageJSON, } = require('internal/modules/package_json_reader'); -const { stripTypeScriptTypes } = require('internal/modules/typescript'); +function stripTypeScriptTypes(code, options) { + return require('internal/modules/typescript').stripTypeScriptTypes(code, options); +} Module.register = register; Module.constants = constants; diff --git a/lib/url.js b/lib/url.js index 938f602a353a99..0935280bad6427 100644 --- a/lib/url.js +++ b/lib/url.js @@ -35,7 +35,10 @@ const { decodeURIComponent, } = primordials; -const { URLPattern } = internalBinding('url_pattern'); +let _URLPattern; +function lazyURLPattern() { + return _URLPattern ??= internalBinding('url_pattern').URLPattern; +} const { toASCII } = internalBinding('encoding_binding'); const { encodeStr, hexTable } = require('internal/querystring'); const querystring = require('querystring'); @@ -1032,7 +1035,7 @@ module.exports = { // WHATWG API URL, - URLPattern, + get URLPattern() { return lazyURLPattern(); }, URLSearchParams, domainToASCII, domainToUnicode, diff --git a/test/parallel/test-bootstrap-modules.js b/test/parallel/test-bootstrap-modules.js index 01b7ba07cf6151..71e8cf8fb9b0d8 100644 --- a/test/parallel/test-bootstrap-modules.js +++ b/test/parallel/test-bootstrap-modules.js @@ -75,8 +75,6 @@ expected.beforePreExec = new Set([ 'NativeModule internal/querystring', 'NativeModule querystring', 'Internal Binding url', - 'Internal Binding url_pattern', - 'Internal Binding blob', 'NativeModule internal/url', 'NativeModule util', 'NativeModule internal/webidl', @@ -88,10 +86,6 @@ expected.beforePreExec = new Set([ 'NativeModule internal/v8/startup_snapshot', 'NativeModule internal/process/signal', 'Internal Binding fs', - 'NativeModule internal/encoding', - 'NativeModule internal/encoding/single-byte', - 'NativeModule internal/encoding/util', - 'NativeModule internal/blob', 'NativeModule internal/fs/utils', 'NativeModule fs', 'Internal Binding options', @@ -109,7 +103,6 @@ expected.beforePreExec = new Set([ 'Internal Binding diagnostics_channel', 'Internal Binding wasm_web_api', 'NativeModule internal/events/abort_listener', - 'NativeModule internal/modules/typescript', 'NativeModule internal/data_url', 'NativeModule internal/mime', 'NativeModule internal/modules/esm/utils',