From 3090564616f20cec865a469466e69295a50cdae6 Mon Sep 17 00:00:00 2001 From: Bjorn Lu Date: Tue, 29 Nov 2022 07:13:21 +0800 Subject: [PATCH] feat(ssr)!: remove dedupe and mode support for CJS (#11101) --- packages/vite/src/node/plugins/index.ts | 2 - packages/vite/src/node/plugins/resolve.ts | 3 - .../vite/src/node/plugins/ssrRequireHook.ts | 90 ------------------- packages/vite/src/node/ssr/ssrModuleLoader.ts | 87 +++--------------- 4 files changed, 14 insertions(+), 168 deletions(-) delete mode 100644 packages/vite/src/node/plugins/ssrRequireHook.ts diff --git a/packages/vite/src/node/plugins/index.ts b/packages/vite/src/node/plugins/index.ts index 057f4f1faa5271..17fccd864d0229 100644 --- a/packages/vite/src/node/plugins/index.ts +++ b/packages/vite/src/node/plugins/index.ts @@ -18,7 +18,6 @@ import { modulePreloadPolyfillPlugin } from './modulePreloadPolyfill' import { webWorkerPlugin } from './worker' import { preAliasPlugin } from './preAlias' import { definePlugin } from './define' -import { ssrRequireHookPlugin } from './ssrRequireHook' import { workerImportMetaUrlPlugin } from './workerImportMetaUrl' import { assetImportMetaUrlPlugin } from './assetImportMetaUrl' import { ensureWatchPlugin } from './ensureWatch' @@ -88,7 +87,6 @@ export async function resolvePlugins( wasmFallbackPlugin(), definePlugin(config), cssPostPlugin(config), - isBuild && config.build.ssr ? ssrRequireHookPlugin(config) : null, isBuild && buildHtmlPlugin(config), workerImportMetaUrlPlugin(config), assetImportMetaUrlPlugin(config), diff --git a/packages/vite/src/node/plugins/resolve.ts b/packages/vite/src/node/plugins/resolve.ts index 1c1c80f573e84d..c436f0837386c1 100644 --- a/packages/vite/src/node/plugins/resolve.ts +++ b/packages/vite/src/node/plugins/resolve.ts @@ -105,8 +105,6 @@ export interface InternalResolveOptions extends Required { // Resolve using esbuild deps optimization getDepsOptimizer?: (ssr: boolean) => DepsOptimizer | undefined shouldExternalize?: (id: string) => boolean | undefined - // Check this resolve is called from `hookNodeResolve` in SSR - isHookNodeResolve?: boolean } export function resolvePlugin(resolveOptions: InternalResolveOptions): Plugin { @@ -689,7 +687,6 @@ export function tryNodeResolve( // if import can't be found, check if it's an optional peer dep. // if so, we can resolve to a special id that errors only when imported. if ( - !options.isHookNodeResolve && basedir !== root && // root has no peer dep !isBuiltin(nestedPath) && !nestedPath.includes('\0') && diff --git a/packages/vite/src/node/plugins/ssrRequireHook.ts b/packages/vite/src/node/plugins/ssrRequireHook.ts deleted file mode 100644 index e02f9b48e6b6c7..00000000000000 --- a/packages/vite/src/node/plugins/ssrRequireHook.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { createRequire } from 'node:module' -import MagicString from 'magic-string' -import type { ResolvedConfig } from '..' -import type { Plugin } from '../plugin' -import { arraify } from '../utils' - -/** - * This plugin hooks into Node's module resolution algorithm at runtime, - * so that SSR builds can benefit from `resolve.dedupe` like they do - * in development. - */ -export function ssrRequireHookPlugin(config: ResolvedConfig): Plugin | null { - if ( - config.command !== 'build' || - !config.build.ssr || - !config.resolve.dedupe.length || - config.ssr?.noExternal === true || - config.ssr?.format !== 'cjs' || - isBuildOutputEsm(config) - ) { - return null - } - return { - name: 'vite:ssr-require-hook', - transform(code, id) { - const moduleInfo = this.getModuleInfo(id) - if (moduleInfo?.isEntry) { - const s = new MagicString(code) - s.prepend( - `;(${dedupeRequire.toString()})(${JSON.stringify( - config.resolve.dedupe - )});\n` - ) - return { - code: s.toString(), - map: s.generateMap({ - source: id, - hires: true - }) - } - } - } - } -} - -type NodeResolveFilename = ( - request: string, - parent: NodeModule, - isMain: boolean, - options?: Record -) => string - -/** Respect the `resolve.dedupe` option in production SSR. */ -function dedupeRequire(dedupe: string[]) { - // eslint-disable-next-line no-restricted-globals - const Module = require('node:module') as { - _resolveFilename: NodeResolveFilename - } - const resolveFilename = Module._resolveFilename - Module._resolveFilename = function (request, parent, isMain, options) { - if (request[0] !== '.' && request[0] !== '/') { - const parts = request.split('/') - const pkgName = parts[0][0] === '@' ? parts[0] + '/' + parts[1] : parts[0] - if (dedupe.includes(pkgName)) { - // Use this module as the parent. - parent = module - } - } - return resolveFilename!(request, parent, isMain, options) - } -} - -const _require = createRequire(import.meta.url) -export function hookNodeResolve( - getResolver: (resolveFilename: NodeResolveFilename) => NodeResolveFilename -): () => void { - const Module = _require('module') as { _resolveFilename: NodeResolveFilename } - const prevResolver = Module._resolveFilename - Module._resolveFilename = getResolver(prevResolver) - return () => { - Module._resolveFilename = prevResolver - } -} - -function isBuildOutputEsm(config: ResolvedConfig) { - const outputs = arraify(config.build.rollupOptions?.output) - return outputs.some( - (output) => output?.format === 'es' || output?.format === 'esm' - ) -} diff --git a/packages/vite/src/node/ssr/ssrModuleLoader.ts b/packages/vite/src/node/ssr/ssrModuleLoader.ts index eaae5e73015b46..84b05ac7841973 100644 --- a/packages/vite/src/node/ssr/ssrModuleLoader.ts +++ b/packages/vite/src/node/ssr/ssrModuleLoader.ts @@ -2,7 +2,6 @@ import path from 'node:path' import { pathToFileURL } from 'node:url' import type { ViteDevServer } from '../server' import { - bareImportRE, dynamicImport, isBuiltin, unwrapId, @@ -11,7 +10,6 @@ import { import { transformRequest } from '../server/transformRequest' import type { InternalResolveOptions } from '../plugins/resolve' import { tryNodeResolve } from '../plugins/resolve' -import { hookNodeResolve } from '../plugins/ssrRequireHook' import { ssrDynamicImportKey, ssrExportAllKey, @@ -114,9 +112,6 @@ async function instantiateModule( root } = server.config - // The `extensions` and `mainFields` options are used to ensure that - // CommonJS modules are preferred. We want to avoid ESM->ESM imports - // whenever possible, because `hookNodeResolve` can't intercept them. const resolveOptions: InternalResolveOptions = { mainFields: ['main'], browserField: true, @@ -124,11 +119,9 @@ async function instantiateModule( extensions: ['.js', '.cjs', '.json'], dedupe, preserveSymlinks, - isBuild: true, + isBuild: false, isProduction, - isRequire: true, - root, - isHookNodeResolve: true + root } // Since dynamic imports can happen in parallel, we need to @@ -227,72 +220,17 @@ async function instantiateModule( return Object.freeze(ssrModule) } -// `nodeImport` may run in parallel on multiple `ssrLoadModule` calls. -// We keep track of the current importing count so that the first import -// would `hookNodeResolve`, and the last import would `unhookNodeResolve`. -let importingCount = 0 -let unhookNodeResolve: ReturnType | undefined - // In node@12+ we can use dynamic import to load CJS and ESM async function nodeImport( id: string, importer: string, resolveOptions: InternalResolveOptions ) { - // Node's module resolution is hi-jacked so Vite can ensure the - // configured `resolve.dedupe` and `mode` options are respected. - const viteResolve = ( - id: string, - importer: string, - options = resolveOptions - ) => { - const resolved = tryNodeResolve(id, importer, options, false) - if (!resolved) { - const err: any = new Error( - `Cannot find module '${id}' imported from '${importer}'` - ) - err.code = 'ERR_MODULE_NOT_FOUND' - throw err - } - return resolved.id - } - - if (importingCount === 0) { - // When an ESM module imports an ESM dependency, this hook is *not* used. - unhookNodeResolve = hookNodeResolve( - (nodeResolve) => (id, parent, isMain, options) => { - // Use the Vite resolver only for bare imports while skipping - // any absolute paths, built-in modules and binary modules. - if ( - !bareImportRE.test(id) || - path.isAbsolute(id) || - isBuiltin(id) || - id.endsWith('.node') - ) { - return nodeResolve(id, parent, isMain, options) - } - if (parent) { - let resolved = viteResolve(id, parent.id) - if (resolved) { - // hookNodeResolve must use platform-specific path.normalize - // to be compatible with dynamicImport (#6080) - resolved = path.normalize(resolved) - } - return resolved - } - // Importing a CJS module from an ESM module. In this case, the import - // specifier is already an absolute path, so this is a no-op. - // Options like `resolve.dedupe` and `mode` are not respected. - return id - } - ) - } - let url: string if (id.startsWith('node:') || isBuiltin(id)) { url = id } else { - url = viteResolve( + const resolved = tryNodeResolve( id, importer, // Non-external modules can import ESM-only modules, but only outside @@ -300,23 +238,26 @@ async function nodeImport( // @ts-expect-error typeof jest === 'undefined' ? { ...resolveOptions, tryEsmOnly: true } - : resolveOptions + : resolveOptions, + false ) + if (!resolved) { + const err: any = new Error( + `Cannot find module '${id}' imported from '${importer}'` + ) + err.code = 'ERR_MODULE_NOT_FOUND' + throw err + } + url = resolved.id if (usingDynamicImport) { url = pathToFileURL(url).toString() } } try { - importingCount++ const mod = await dynamicImport(url) return proxyESM(mod) - } finally { - importingCount-- - if (importingCount === 0) { - unhookNodeResolve?.() - } - } + } catch {} } // rollup-style default import interop for cjs