diff --git a/.pnp.register.mjs b/.pnp.register.mjs index 51ca28f9ad..430a2293e2 100644 --- a/.pnp.register.mjs +++ b/.pnp.register.mjs @@ -1,6 +1,52 @@ -// Register the PnP ESM loader hook so that ESM imports resolve through Yarn PnP. -// This file is intended to be used with --import (Node >= 20.6). -import { register } from 'node:module' +// Resolve ESM imports through Yarn PnP. +// +// Yarn ships .pnp.loader.mjs for use with module.register(), which runs hooks on a dedicated +// thread — a second V8 isolate costing 50-110MB in every node process, whether or not the +// loader does any work. registerHooks() runs synchronously in-thread instead, and PnP's own +// resolveRequest already applies "exports" conditions, so the hook is just a lookup. +import { registerHooks, isBuiltin, createRequire } from 'node:module' +import { pathToFileURL, fileURLToPath } from 'node:url' +import path from 'node:path' -const loaderURL = new URL('.pnp.loader.mjs', import.meta.url) -register(loaderURL, import.meta.url) +const pnp = createRequire(import.meta.url)('pnpapi') + +registerHooks({ + resolve(specifier, context, nextResolve) { + // require() is patched by .pnp.cjs, and builtins never reach the filesystem. + if (context.conditions.includes('require') || isBuiltin(specifier)) { + return nextResolve(specifier, context) + } + // Only bare specifiers need PnP. The entrypoint arrives as a path, and mocha imports test + // files by file:// URL; both are already resolved. + let url + if (specifier.startsWith('file://')) { + url = specifier + } else if (!context.parentURL) { + url = pathToFileURL(specifier).href + } else { + // Tools installed outside the project — npm, corepack — bring their own node_modules. + // PnP does not own those paths and refuses to resolve from them. + const issuer = path.dirname(fileURLToPath(context.parentURL)) + '/' + if (!pnp.findPackageLocator(issuer)) { + return nextResolve(specifier, context) + } + url = pathToFileURL( + pnp.resolveRequest(specifier, issuer, { + conditions: new Set(context.conditions), + }) + ).href + } + try { + // Keep the resolved path in the chain so loaders registered later can still mock it. + return nextResolve(url, context) + } catch (err) { + // Node cannot look inside PnP's zips, nor at its __virtual__ paths, so PnP's answer is + // final for those. Everything else — a genuinely missing file, an error raised by + // another loader — is a real failure. + if (url.includes('/__virtual__/') || url.includes('.zip/')) { + return { url, shortCircuit: true } + } + throw err + } + }, +}) diff --git a/package.json b/package.json index c9f4d13d07..4d173220e5 100644 --- a/package.json +++ b/package.json @@ -191,8 +191,8 @@ "format:pug:fix": "prettier --cache --cache-location ./.cache/prettier/.prettier-cache --write --check '**/*.pug'", "format:jenkins": "prettier --cache --cache-location ./.cache/prettier/.prettier-cache --check '**/Jenkinsfile'", "format:jenkins:fix": "prettier --cache --cache-location ./.cache/prettier/.prettier-cache --write --check '**/Jenkinsfile'", - "format:monorepo-check": "prettier --cache --cache-location ./.cache/prettier/.prettier-cache --check '**/Jenkinsfile' 'google-ops/packer/jenkins/vm_config/init.groovy.d/*.groovy' '**/*.md' '**/docker-compose.yml' '**/docker-compose.*.yml' '.agents/**/*.js' '.agents/**/*.md' '.github/**/*.md'", - "format:monorepo-check:fix": "prettier --cache --cache-location ./.cache/prettier/.prettier-cache --write --check '**/Jenkinsfile' 'google-ops/packer/jenkins/vm_config/init.groovy.d/*.groovy' '**/*.md' '**/docker-compose.yml' '**/docker-compose.*.yml' '.agents/**/*.js' '.agents/**/*.md' '.github/**/*.md'", + "format:monorepo-check": "prettier --cache --cache-location ./.cache/prettier/.prettier-cache --check '**/Jenkinsfile' 'google-ops/packer/jenkins/vm_config/init.groovy.d/*.groovy' '**/*.md' '**/docker-compose.yml' '**/docker-compose.*.yml' '.agents/**/*.js' '.agents/**/*.md' '.github/**/*.md' .pnp.register.mjs", + "format:monorepo-check:fix": "prettier --cache --cache-location ./.cache/prettier/.prettier-cache --write --check '**/Jenkinsfile' 'google-ops/packer/jenkins/vm_config/init.groovy.d/*.groovy' '**/*.md' '**/docker-compose.yml' '**/docker-compose.*.yml' '.agents/**/*.js' '.agents/**/*.md' '.github/**/*.md' .pnp.register.mjs", "lint": "eslint --cache --cache-location ./.cache/eslint/ --max-warnings 0", "lint:fix": "eslint --cache --cache-location ./.cache/eslint/ --fix" },