diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 3bd831cd2c5..6be6a0a6d5c 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -61,7 +61,7 @@ jobs: fail-fast: true matrix: os: [ubuntu-latest, windows-latest, macos-latest] - node-version: [20.x, 22.x, 24.x, 25.x] + node-version: [20.x, 22.x, 24.x, 25.x, 26.x] webpack-version: [latest] dev-server-version: [latest] diff --git a/test/build/config-format/typescript-mjs/typescript.test.mjs b/test/build/config-format/typescript-mjs/typescript.test.mjs index 03acf17b9a9..857678ae870 100644 --- a/test/build/config-format/typescript-mjs/typescript.test.mjs +++ b/test/build/config-format/typescript-mjs/typescript.test.mjs @@ -32,7 +32,7 @@ describe("typescript ecma modules configuration", () => { // Fallback to `ts-node/esm` for old Node.js versions nodeOptions: major >= 22 - ? ["--experimental-transform-types", "--no-warnings"] + ? ["--no-warnings", ...(major < 26 ? ["--experimental-transform-types"] : [])] : [ "--no-deprecation", "--import=data:text/javascript,import { register } from 'node:module'; import { pathToFileURL } from 'node:url'; register('ts-node/esm', pathToFileURL('./'));", diff --git a/test/external-command/my-loader.mjs b/test/external-command/my-loader.mjs index e1d2f1665a0..2f5280dd2b7 100644 --- a/test/external-command/my-loader.mjs +++ b/test/external-command/my-loader.mjs @@ -1,23 +1,40 @@ import { join } from "node:path"; import { pathToFileURL } from "node:url"; +/** + * Resolves a bare specifier from the `node_modules` directory of the current working directory. + * @param {string} specifier module specifier + * @param {Error & { code?: string }} err error thrown by the next resolve hook + * @returns {{ url: string, shortCircuit: boolean }} resolution result + */ +function resolveFromCwd(specifier, err) { + if (err.code === "ERR_MODULE_NOT_FOUND" && !specifier.startsWith(".")) { + const baseDir = join(process.cwd(), "node_modules/"); + const resolved = join(baseDir, specifier, "index.js"); + + return { + url: pathToFileURL(resolved).href, + shortCircuit: true, + }; + } + + throw err; +} + +// Asynchronous hook, used with `module.register()` export async function resolve(specifier, context, nextResolve) { try { return await nextResolve(specifier, context); } catch (err) { - if (err.code === "ERR_MODULE_NOT_FOUND" && !specifier.startsWith(".")) { - try { - const baseDir = join(process.cwd(), "node_modules/"); - const resolved = join(baseDir, specifier, "index.js"); + return resolveFromCwd(specifier, err); + } +} - return { - url: pathToFileURL(resolved).href, - shortCircuit: true, - }; - } catch { - throw err; - } - } - throw err; +// Synchronous hook, used with `module.registerHooks()` +export function resolveSync(specifier, context, nextResolve) { + try { + return nextResolve(specifier, context); + } catch (err) { + return resolveFromCwd(specifier, err); } } diff --git a/test/external-command/register-loader.mjs b/test/external-command/register-loader.mjs index d3c8a88dce4..3dd8d195ed3 100644 --- a/test/external-command/register-loader.mjs +++ b/test/external-command/register-loader.mjs @@ -1,4 +1,11 @@ -import { register } from "node:module"; +import nodeModule from "node:module"; import { pathToFileURL } from "node:url"; +import { resolveSync } from "./my-loader.mjs"; -register("./my-loader.mjs", pathToFileURL("./")); +// `module.register()` is deprecated (DEP0205) since Node.js 26, +// fallback to it only for Node.js versions without `module.registerHooks()` +if (typeof nodeModule.registerHooks === "function") { + nodeModule.registerHooks({ resolve: resolveSync }); +} else { + nodeModule.register("./my-loader.mjs", pathToFileURL("./")); +}