From db125c6ddea16f05b84b284ef21b3b66e67dc3b0 Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Thu, 23 Apr 2026 00:45:47 +0200 Subject: [PATCH] fs: restore fs patchability in ESM loader MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Temporarily restore fs patchability in ESM loader as a workaround for helping downstream projects that depend on this undocumented hidden contract transition into using hook proper APIs. This patch intentionally avoids adding a test and instead adds warning comments to hopefully steer new code away from depending on it. PR-URL: https://github.com/nodejs/node/pull/62835 Backport-PR-URL: https://github.com/nodejs/node/pull/64722 Refs: https://github.com/nodejs/node/issues/62012 Reviewed-By: Michaƫl Zasso Reviewed-By: Matteo Collina Signed-off-by: Ash <92314878+ash2228@users.noreply.github.com> --- lib/internal/modules/esm/load.js | 8 ++++++-- lib/internal/modules/esm/resolve.js | 8 ++++++-- lib/internal/modules/esm/translators.js | 8 ++++++-- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/lib/internal/modules/esm/load.js b/lib/internal/modules/esm/load.js index 2fc344765d81..f372eef16135 100644 --- a/lib/internal/modules/esm/load.js +++ b/lib/internal/modules/esm/load.js @@ -10,7 +10,7 @@ const { const { defaultGetFormat } = require('internal/modules/esm/get_format'); const { validateAttributes, emitImportAssertionWarning } = require('internal/modules/esm/assert'); const { getOptionValue } = require('internal/options'); -const { readFileSync } = require('fs'); +const fs = require('fs'); const defaultType = getOptionValue('--experimental-default-type'); @@ -38,7 +38,11 @@ function getSourceSync(url, context) { const responseURL = href; let source; if (protocol === 'file:') { - source = readFileSync(url); + // If you are reading this code to figure out how to patch Node.js module loading + // behavior - DO NOT depend on the patchability in new code: Node.js + // internals may stop going through the JavaScript fs module entirely. + // Prefer module.registerHooks() or other more formal fs hooks released in the future. + source = fs.readFileSync(url); } else if (protocol === 'data:') { const result = dataURLProcessor(url); if (result === 'failure') { diff --git a/lib/internal/modules/esm/resolve.js b/lib/internal/modules/esm/resolve.js index 62b842d61cda..8c917ad60eb8 100644 --- a/lib/internal/modules/esm/resolve.js +++ b/lib/internal/modules/esm/resolve.js @@ -25,7 +25,7 @@ const { const assert = require('internal/assert'); const internalFS = require('internal/fs/utils'); const { BuiltinModule } = require('internal/bootstrap/realm'); -const { realpathSync } = require('fs'); +const fs = require('fs'); const { getOptionValue } = require('internal/options'); // Do not eagerly grab .manifest, it may be in TDZ const { sep, posix: { relative: relativePosixPath }, resolve } = require('path'); @@ -277,7 +277,11 @@ function finalizeResolution(resolved, base, preserveSymlinks) { } if (!preserveSymlinks) { - const real = realpathSync(path, { + // If you are reading this code to figure out how to patch Node.js module loading + // behavior - DO NOT depend on the patchability in new code: Node.js + // internals may stop going through the JavaScript fs module entirely. + // Prefer module.registerHooks() or other more formal fs hooks released in the future. + const real = fs.realpathSync(path, { [internalFS.realpathCacheKey]: realpathCache, }); const { search, hash } = resolved; diff --git a/lib/internal/modules/esm/translators.js b/lib/internal/modules/esm/translators.js index a8cb010f470a..8ffe61ce7a0c 100644 --- a/lib/internal/modules/esm/translators.js +++ b/lib/internal/modules/esm/translators.js @@ -24,7 +24,7 @@ const { const { BuiltinModule } = require('internal/bootstrap/realm'); const assert = require('internal/assert'); -const { readFileSync } = require('fs'); +const fs = require('fs'); const { dirname, extname } = require('path'); const { assertBufferSource, @@ -355,7 +355,11 @@ translators.set('commonjs', function commonjsStrategy(url, translateContext, par try { // We still need to read the FS to detect the exports. - translateContext.source ??= readFileSync(new URL(url), 'utf8'); + // If you are reading this code to figure out how to patch Node.js module loading + // behavior - DO NOT depend on the patchability in new code: Node.js + // internals may stop going through the JavaScript fs module entirely. + // Prefer module.registerHooks() or other more formal fs hooks released in the future. + translateContext.source ??= fs.readFileSync(new URL(url), 'utf8'); } catch { // Continue regardless of error. }