From f49804399e482783c5b09a5b1d955e6e69153be9 Mon Sep 17 00:00:00 2001 From: Uzini <43294422+Uziniii@users.noreply.github.com> Date: Wed, 11 Nov 2026 00:56:53 +0100 Subject: [PATCH] module: reduce ESM ModuleJob memory Share empty dependency arrays for leaf ModuleJobs instead of allocating separate arrays for each job. Remove unused per-job fields and avoid retaining a default import attributes object when it is not provided. Assisted-by: OpenCode Signed-off-by: Uzini <43294422+Uziniii@users.noreply.github.com> --- lib/internal/modules/esm/module_job.js | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/lib/internal/modules/esm/module_job.js b/lib/internal/modules/esm/module_job.js index c8506b42ff3f..7fa2347db124 100644 --- a/lib/internal/modules/esm/module_job.js +++ b/lib/internal/modules/esm/module_job.js @@ -10,6 +10,7 @@ const { ArrayPrototypeSort, FunctionPrototype, ObjectAssign, + ObjectFreeze, ObjectSetPrototypeOf, PromisePrototypeThen, PromiseResolve, @@ -61,6 +62,9 @@ const { } = require('internal/modules/helpers'); const { getOptionValue } = require('internal/options'); const noop = FunctionPrototype; +// Module jobs retain both `linked` and `commonJsDeps`. Share their empty value +// so leaf modules do not each retain two arrays. +const kEmptyModuleDependencies = ObjectFreeze([]); const { ERR_REQUIRE_ASYNC_MODULE, ERR_REQUIRE_ESM_RACE_CONDITION, @@ -296,9 +300,7 @@ class ModuleJobBase { constructor(loader, url, importAttributes, phase, isMain, inspectBrk) { assert(typeof phase === 'number'); this.loader = loader; - this.importAttributes = importAttributes; this.phase = phase; - this.isMain = isMain; this.inspectBrk = inspectBrk; this.url = url; @@ -317,9 +319,12 @@ class ModuleJobBase { this.loader.loadCache.set(this.url, this.type, this); const moduleRequests = this.module.getModuleRequests(); // Modules should be aligned with the moduleRequests array in order. - const modules = Array(moduleRequests.length); - const evaluationDepJobs = []; - this.commonJsDeps = Array(moduleRequests.length); + const modules = moduleRequests.length === 0 ? + kEmptyModuleDependencies : Array(moduleRequests.length); + const evaluationDepJobs = moduleRequests.length === 0 ? + kEmptyModuleDependencies : []; + this.commonJsDeps = moduleRequests.length === 0 ? + kEmptyModuleDependencies : Array(moduleRequests.length); try { for (let idx = 0; idx < moduleRequests.length; idx++) { const request = moduleRequests[idx]; @@ -402,7 +407,7 @@ class ModuleJob extends ModuleJobBase { * first line paused in the debugger (because --inspect-brk is passed). * @param {ModuleRequestType} requestType Type of the module request. */ - constructor(loader, url, importAttributes = { __proto__: null }, moduleOrModulePromise, + constructor(loader, url, importAttributes, moduleOrModulePromise, phase = kEvaluationPhase, isMain, inspectBrk, requestType) { super(loader, url, importAttributes, phase, isMain, inspectBrk); @@ -452,9 +457,12 @@ class ModuleJob extends ModuleJobBase { // Create an ArrayLike to avoid calling into userspace with `.then` // when returned from the async function. // Modules should be aligned with the moduleRequests array in order. - const modulePromises = Array(moduleRequests.length); - const evaluationDepJobs = []; - this.commonJsDeps = Array(moduleRequests.length); + const modulePromises = moduleRequests.length === 0 ? + kEmptyModuleDependencies : Array(moduleRequests.length); + const evaluationDepJobs = moduleRequests.length === 0 ? + kEmptyModuleDependencies : []; + this.commonJsDeps = moduleRequests.length === 0 ? + kEmptyModuleDependencies : Array(moduleRequests.length); for (let idx = 0; idx < moduleRequests.length; idx++) { const request = moduleRequests[idx]; // Explicitly keeping track of dependency jobs is needed in order