diff --git a/docs/config/ssr-options.md b/docs/config/ssr-options.md index d8b65f1ea1c910..356c4e66eabad6 100644 --- a/docs/config/ssr-options.md +++ b/docs/config/ssr-options.md @@ -20,12 +20,3 @@ Prevent listed dependencies from being externalized for SSR. If `true`, no depen - **Default:** `node` Build target for the SSR server. - -## ssr.format - -- **Experimental:** [CJS support to be removed in Vite 5](https://github.com/vitejs/vite/discussions/13816) -- **Deprecated** Only ESM output will be supported in Vite 5. -- **Type:** `'esm' | 'cjs'` -- **Default:** `esm` - -Build format for the SSR server. Since Vite v3 the SSR build generates ESM by default. `'cjs'` can be selected to generate a CJS build, but it isn't recommended. The option is left marked as experimental to give users more time to update to ESM. CJS builds require complex externalization heuristics that aren't present in the ESM format. diff --git a/docs/guide/ssr.md b/docs/guide/ssr.md index f70b0385a4096a..03fb7cf30e4c55 100644 --- a/docs/guide/ssr.md +++ b/docs/guide/ssr.md @@ -266,11 +266,3 @@ The CLI commands `$ vite dev` and `$ vite preview` can also be used for SSR apps :::tip Note Use a post hook so that your SSR middleware runs _after_ Vite's middlewares. ::: - -## SSR Format - -By default, Vite generates the SSR bundle in ESM. There is experimental support for configuring `ssr.format`, but it isn't recommended. Future efforts around SSR development will be based on ESM, and CommonJS remains available for backward compatibility. If using ESM for SSR isn't possible in your project, you can set `legacy.buildSsrCjsExternalHeuristics: true` to generate a CJS bundle using the same [externalization heuristics of Vite v2](https://v2.vitejs.dev/guide/ssr.html#ssr-externals). - -:::warning Warning -Experimental `legacy.buildSsrCjsExternalHeuristics` and `ssr.format: 'cjs'` are going to be removed in Vite 5. Find more information and give feedback [in this discussion](https://github.com/vitejs/vite/discussions/13816). -::: diff --git a/packages/vite/src/node/build.ts b/packages/vite/src/node/build.ts index 9caedc02958aa4..f90566ca5f91e0 100644 --- a/packages/vite/src/node/build.ts +++ b/packages/vite/src/node/build.ts @@ -41,17 +41,8 @@ import { manifestPlugin } from './plugins/manifest' import type { Logger } from './logger' import { dataURIPlugin } from './plugins/dataUri' import { buildImportAnalysisPlugin } from './plugins/importAnalysisBuild' -import { - cjsShouldExternalizeForSSR, - cjsSsrResolveExternals, -} from './ssr/ssrExternal' import { ssrManifestPlugin } from './ssr/ssrManifestPlugin' -import type { DepOptimizationMetadata } from './optimizer' -import { - findKnownImports, - getDepsCacheDir, - initDepsOptimizer, -} from './optimizer' +import { initDepsOptimizer } from './optimizer' import { loadFallbackPlugin } from './plugins/loadFallback' import { findNearestPackageData } from './packages' import type { PackageCache } from './packages' @@ -518,16 +509,6 @@ export async function build( ssr ? config.plugins.map((p) => injectSsrFlagToHooks(p)) : config.plugins ) as Plugin[] - const userExternal = options.rollupOptions?.external - let external = userExternal - - // In CJS, we can pass the externals to rollup as is. In ESM, we need to - // do it in the resolve plugin so we can add the resolved extension for - // deep node_modules imports - if (ssr && config.legacy?.buildSsrCjsExternalHeuristics) { - external = await cjsSsrResolveExternal(config, userExternal) - } - if (isDepsOptimizerEnabled(config, ssr)) { await initDepsOptimizer(config) } @@ -542,7 +523,7 @@ export async function build( ...options.rollupOptions, input, plugins, - external, + external: options.rollupOptions?.external, onwarn(warning, warn) { onRollupWarning(warning, warn, config) }, @@ -575,9 +556,8 @@ export async function build( const ssrNodeBuild = ssr && config.ssr.target === 'node' const ssrWorkerBuild = ssr && config.ssr.target === 'webworker' - const cjsSsrBuild = ssr && config.ssr.format === 'cjs' - const format = output.format || (cjsSsrBuild ? 'cjs' : 'es') + const format = output.format || 'es' const jsExt = ssrNodeBuild || libOptions ? resolveOutputJsExtension( @@ -590,7 +570,7 @@ export async function build( dir: outDir, // Default format is 'es' for regular and for SSR builds format, - exports: cjsSsrBuild ? 'named' : 'auto', + exports: 'auto', sourcemap: options.sourcemap, name: libOptions ? libOptions.name : undefined, // es2015 enables `generatedCode.symbols` @@ -945,36 +925,6 @@ export function onRollupWarning( } } -async function cjsSsrResolveExternal( - config: ResolvedConfig, - user: ExternalOption | undefined, -): Promise { - // see if we have cached deps data available - let knownImports: string[] | undefined - const dataPath = path.join(getDepsCacheDir(config, false), '_metadata.json') - try { - const data = JSON.parse( - fs.readFileSync(dataPath, 'utf-8'), - ) as DepOptimizationMetadata - knownImports = Object.keys(data.optimized) - } catch (e) {} - if (!knownImports) { - // no dev deps optimization data, do a fresh scan - knownImports = await findKnownImports(config, false) // needs to use non-ssr - } - const ssrExternals = cjsSsrResolveExternals(config, knownImports) - - return (id, parentId, isResolved) => { - const isExternal = cjsShouldExternalizeForSSR(id, ssrExternals) - if (isExternal) { - return true - } - if (user) { - return resolveUserExternal(user, id, parentId, isResolved) - } - } -} - export function resolveUserExternal( user: ExternalOption, id: string, diff --git a/packages/vite/src/node/config.ts b/packages/vite/src/node/config.ts index 2f69d74f20b09d..4f48af3bfd922f 100644 --- a/packages/vite/src/node/config.ts +++ b/packages/vite/src/node/config.ts @@ -322,13 +322,8 @@ export interface ExperimentalOptions { export interface LegacyOptions { /** - * Revert vite build --ssr to the v2.9 strategy. Use CJS SSR build and v2.9 externalization heuristics - * - * @experimental - * @deprecated - * @default false + * No longer needed for now, but kept for backwards compatibility. */ - buildSsrCjsExternalHeuristics?: boolean } export interface ResolveWorkerOptions extends PluginHookUtils { @@ -642,11 +637,7 @@ export async function resolveConfig( : '' const server = resolveServerOptions(resolvedRoot, config.server, logger) - const ssr = resolveSSROptions( - config.ssr, - resolveOptions.preserveSymlinks, - config.legacy?.buildSsrCjsExternalHeuristics, - ) + const ssr = resolveSSROptions(config.ssr, resolveOptions.preserveSymlinks) const middlewareMode = config?.server?.middlewareMode @@ -851,13 +842,15 @@ assetFileNames isn't equal for every build.rollupOptions.output. A single patter // Warn about removal of experimental features if ( + // @ts-expect-error Option removed config.legacy?.buildSsrCjsExternalHeuristics || + // @ts-expect-error Option removed config.ssr?.format === 'cjs' ) { resolved.logger.warn( colors.yellow(` -(!) Experimental legacy.buildSsrCjsExternalHeuristics and ssr.format: 'cjs' are going to be removed in Vite 5. - Find more information and give feedback at https://github.com/vitejs/vite/discussions/13816. +(!) Experimental legacy.buildSsrCjsExternalHeuristics and ssr.format were be removed in Vite 5. + The only SSR Output format is ESM. Find more information at https://github.com/vitejs/vite/discussions/13816. `), ) } diff --git a/packages/vite/src/node/index.ts b/packages/vite/src/node/index.ts index 0ab0747fade26c..b64e99942a9b1e 100644 --- a/packages/vite/src/node/index.ts +++ b/packages/vite/src/node/index.ts @@ -51,7 +51,6 @@ export type { ResolvedSSROptions, SsrDepOptimizationOptions, SSROptions, - SSRFormat, SSRTarget, } from './ssr' export type { Plugin, HookHandler } from './plugin' diff --git a/packages/vite/src/node/optimizer/index.ts b/packages/vite/src/node/optimizer/index.ts index ebc7e3c350638f..6ae227d5db156f 100644 --- a/packages/vite/src/node/optimizer/index.ts +++ b/packages/vite/src/node/optimizer/index.ts @@ -822,15 +822,6 @@ async function prepareEsbuildOptimizerRun( return { context, idToExports } } -export async function findKnownImports( - config: ResolvedConfig, - ssr: boolean, -): Promise { - const { deps } = await scanImports(config).result - await addManuallyIncludedOptimizeDeps(deps, config, ssr) - return Object.keys(deps) -} - export async function addManuallyIncludedOptimizeDeps( deps: Record, config: ResolvedConfig, diff --git a/packages/vite/src/node/plugins/importAnalysis.ts b/packages/vite/src/node/plugins/importAnalysis.ts index 814aa9187a9ce0..d809967e7dfbed 100644 --- a/packages/vite/src/node/plugins/importAnalysis.ts +++ b/packages/vite/src/node/plugins/importAnalysis.ts @@ -50,10 +50,7 @@ import { import { getDepOptimizationConfig } from '../config' import type { ResolvedConfig } from '../config' import type { Plugin } from '../plugin' -import { - cjsShouldExternalizeForSSR, - shouldExternalizeForSSR, -} from '../ssr/ssrExternal' +import { shouldExternalizeForSSR } from '../ssr/ssrExternal' import { getDepsOptimizer, optimizedDepNeedsInterop } from '../optimizer' import { ERR_CLOSED_SERVER } from '../server/pluginContainer' import { checkPublicFile, urlRE } from './asset' @@ -487,13 +484,7 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin { } // skip ssr external if (ssr) { - if (config.legacy?.buildSsrCjsExternalHeuristics) { - if ( - cjsShouldExternalizeForSSR(specifier, server._ssrExternals) - ) { - return - } - } else if (shouldExternalizeForSSR(specifier, importer, config)) { + if (shouldExternalizeForSSR(specifier, importer, config)) { return } if (isBuiltin(specifier)) { diff --git a/packages/vite/src/node/plugins/index.ts b/packages/vite/src/node/plugins/index.ts index 45b9416363a9cb..28d88c825fbb3a 100644 --- a/packages/vite/src/node/plugins/index.ts +++ b/packages/vite/src/node/plugins/index.ts @@ -67,7 +67,7 @@ export async function resolvePlugins( asSrc: true, getDepsOptimizer: (ssr: boolean) => getDepsOptimizer(config, ssr), shouldExternalize: - isBuild && config.build.ssr && config.ssr?.format !== 'cjs' + isBuild && config.build.ssr ? (id, importer) => shouldExternalizeForSSR(id, importer, config) : undefined, }), diff --git a/packages/vite/src/node/server/index.ts b/packages/vite/src/node/server/index.ts index 7510e740a7bfad..da9bb062f3602d 100644 --- a/packages/vite/src/node/server/index.ts +++ b/packages/vite/src/node/server/index.ts @@ -32,7 +32,6 @@ import { resolveServerUrls, } from '../utils' import { ssrLoadModule } from '../ssr/ssrModuleLoader' -import { cjsSsrResolveExternals } from '../ssr/ssrExternal' import { ssrFixStacktrace, ssrRewriteStacktrace } from '../ssr/ssrStacktrace' import { ssrTransform } from '../ssr/ssrTransform' import { @@ -290,11 +289,6 @@ export interface ViteDevServer { * @internal */ _importGlobMap: Map - /** - * Deps that are externalized - * @internal - */ - _ssrExternals: string[] | null /** * @internal */ @@ -400,9 +394,6 @@ export async function _createServer( if (isDepsOptimizerEnabled(config, true)) { await initDevSsrDepsOptimizer(config, server) } - if (config.legacy?.buildSsrCjsExternalHeuristics) { - await updateCjsSsrExternals(server) - } return ssrLoadModule( url, server, @@ -508,7 +499,6 @@ export async function _createServer( return server._restartPromise }, - _ssrExternals: null, _restartPromise: null, _importGlobMap: new Map(), _forceOptimizeOnRestart: false, @@ -886,26 +876,3 @@ async function restartServer(server: ViteDevServer) { bindCLIShortcuts(newServer, shortcutsOptions) } } - -async function updateCjsSsrExternals(server: ViteDevServer) { - if (!server._ssrExternals) { - let knownImports: string[] = [] - - // Important! We use the non-ssr optimized deps to find known imports - // Only the explicitly defined deps are optimized during dev SSR, so - // we use the generated list from the scanned deps in regular dev. - // This is part of the v2 externalization heuristics and it is kept - // for backwards compatibility in case user needs to fallback to the - // legacy scheme. It may be removed in a future v3 minor. - const depsOptimizer = getDepsOptimizer(server.config, false) // non-ssr - - if (depsOptimizer) { - await depsOptimizer.scanProcessing - knownImports = [ - ...Object.keys(depsOptimizer.metadata.optimized), - ...Object.keys(depsOptimizer.metadata.discovered), - ] - } - server._ssrExternals = cjsSsrResolveExternals(server.config, knownImports) - } -} diff --git a/packages/vite/src/node/ssr/__tests__/ssrExternal.spec.ts b/packages/vite/src/node/ssr/__tests__/ssrExternal.spec.ts deleted file mode 100644 index 579ba997da5272..00000000000000 --- a/packages/vite/src/node/ssr/__tests__/ssrExternal.spec.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { expect, test } from 'vitest' -import { stripNesting } from '../ssrExternal' - -test('stripNesting', async () => { - expect(stripNesting(['c', 'p1>c1', 'p2 > c2'])).toEqual(['c', 'c1', 'c2']) -}) diff --git a/packages/vite/src/node/ssr/index.ts b/packages/vite/src/node/ssr/index.ts index ba4597177170c0..60d5fd1ee8be86 100644 --- a/packages/vite/src/node/ssr/index.ts +++ b/packages/vite/src/node/ssr/index.ts @@ -1,7 +1,6 @@ import type { DepOptimizationConfig } from '../optimizer' export type SSRTarget = 'node' | 'webworker' -export type SSRFormat = 'esm' | 'cjs' export type SsrDepOptimizationOptions = DepOptimizationConfig @@ -14,16 +13,6 @@ export interface SSROptions { * @default 'node' */ target?: SSRTarget - /** - * Define the format for the ssr build. Since Vite v3 the SSR build generates ESM by default. - * `'cjs'` can be selected to generate a CJS build, but it isn't recommended. This option is - * left marked as experimental to give users more time to update to ESM. CJS builds requires - * complex externalization heuristics that aren't present in the ESM format. - * @experimental - * @deprecated - * @default 'esm' - */ - format?: SSRFormat /** * Control over which dependencies are optimized during SSR and esbuild options * During build: @@ -37,21 +26,17 @@ export interface SSROptions { export interface ResolvedSSROptions extends SSROptions { target: SSRTarget - format: SSRFormat optimizeDeps: SsrDepOptimizationOptions } export function resolveSSROptions( ssr: SSROptions | undefined, preserveSymlinks: boolean, - buildSsrCjsExternalHeuristics?: boolean, ): ResolvedSSROptions { ssr ??= {} const optimizeDeps = ssr.optimizeDeps ?? {} - const format: SSRFormat = buildSsrCjsExternalHeuristics ? 'cjs' : 'esm' const target: SSRTarget = 'node' return { - format, target, ...ssr, optimizeDeps: { diff --git a/packages/vite/src/node/ssr/ssrExternal.ts b/packages/vite/src/node/ssr/ssrExternal.ts index 0f03fe272e4ad2..9ee38434c18a16 100644 --- a/packages/vite/src/node/ssr/ssrExternal.ts +++ b/packages/vite/src/node/ssr/ssrExternal.ts @@ -1,7 +1,5 @@ -import fs from 'node:fs' import path from 'node:path' -import { createRequire } from 'node:module' -import type { InternalResolveOptions, ResolveOptions } from '../plugins/resolve' +import type { InternalResolveOptions } from '../plugins/resolve' import { tryNodeResolve } from '../plugins/resolve' import { bareImportRE, @@ -9,88 +7,11 @@ import { createFilter, getNpmPackageName, isBuiltin, - isDefined, - isInNodeModules, - lookupFile, - normalizePath, - withTrailingSlash, } from '../utils' -import type { Logger, ResolvedConfig } from '..' -import { resolvePackageData } from '../packages' +import type { ResolvedConfig } from '..' const debug = createDebugger('vite:ssr-external') -/** - * Converts "parent > child" syntax to just "child" - */ -export function stripNesting(packages: string[]): string[] { - return packages.map((s) => { - const arr = s.split('>') - return arr[arr.length - 1].trim() - }) -} - -/** - * Heuristics for determining whether a dependency should be externalized for - * server-side rendering. - */ -export function cjsSsrResolveExternals( - config: ResolvedConfig, - knownImports: string[], -): string[] { - // strip nesting since knownImports may be passed in from optimizeDeps which - // supports a "parent > child" syntax - knownImports = stripNesting(knownImports) - - const ssrConfig = config.ssr - if (ssrConfig?.noExternal === true) { - return [] - } - - const ssrExternals: Set = new Set() - const seen: Set = new Set() - ssrConfig?.external?.forEach((id) => { - ssrExternals.add(id) - seen.add(id) - }) - - cjsSsrCollectExternals( - config.root, - config.resolve, - ssrExternals, - seen, - config.logger, - ) - - const importedDeps = knownImports.map(getNpmPackageName).filter(isDefined) - for (const dep of importedDeps) { - // Assume external if not yet seen - // At this point, the project root and any linked packages have had their dependencies checked, - // so we can safely mark any knownImports not yet seen as external. They are guaranteed to be - // dependencies of packages in node_modules. - if (!seen.has(dep)) { - ssrExternals.add(dep) - } - } - - // ensure `vite/dynamic-import-polyfill` is bundled (issue #1865) - ssrExternals.delete('vite') - - let externals = [...ssrExternals] - if (ssrConfig?.noExternal) { - externals = externals.filter( - createFilter(undefined, ssrConfig.noExternal, { resolve: false }), - ) - } - return externals -} - -const CJS_CONTENT_RE = - /\bmodule\.exports\b|\bexports[.[]|\brequire\s*\(|\bObject\.(?:defineProperty|defineProperties|assign)\s*\(\s*exports\b/ - -// TODO: use import() -const _require = createRequire(import.meta.url) - const isSsrExternalCache = new WeakMap< ResolvedConfig, (id: string, importer?: string) => boolean | undefined @@ -213,140 +134,3 @@ function createIsSsrExternal( return external } } - -// When config.experimental.buildSsrCjsExternalHeuristics is enabled, this function -// is used reverting to the Vite 2.9 SSR externalization heuristics -function cjsSsrCollectExternals( - root: string, - resolveOptions: Required, - ssrExternals: Set, - seen: Set, - logger: Logger, -) { - const rootPkgPath = lookupFile(root, ['package.json']) - if (!rootPkgPath) { - return - } - const rootPkgContent = fs.readFileSync(rootPkgPath, 'utf-8') - if (!rootPkgContent) { - return - } - - const rootPkg = JSON.parse(rootPkgContent) - const deps = { - ...rootPkg.devDependencies, - ...rootPkg.dependencies, - } - - const internalResolveOptions: InternalResolveOptions = { - ...resolveOptions, - root, - isProduction: false, - isBuild: true, - } - - const depsToTrace = new Set() - - for (const id in deps) { - if (seen.has(id)) continue - seen.add(id) - - let esmEntry: string | undefined - let requireEntry: string - - try { - esmEntry = tryNodeResolve( - id, - undefined, - internalResolveOptions, - true, // we set `targetWeb` to `true` to get the ESM entry - undefined, - true, - )?.id - // normalizePath required for windows. tryNodeResolve uses normalizePath - // which returns with '/', require.resolve returns with '\\' - requireEntry = normalizePath(_require.resolve(id, { paths: [root] })) - } catch (e) { - // no main entry, but deep imports may be allowed - const pkgDir = resolvePackageData(id, root)?.dir - if (pkgDir) { - if (isInNodeModules(pkgDir)) { - ssrExternals.add(id) - } else { - depsToTrace.add(path.dirname(pkgDir)) - } - continue - } - - // resolve failed, assume include - debug?.(`Failed to resolve entries for package "${id}"\n`, e) - continue - } - // no esm entry but has require entry - if (!esmEntry) { - ssrExternals.add(id) - } - // trace the dependencies of linked packages - else if (!isInNodeModules(esmEntry)) { - const pkgDir = resolvePackageData(id, root)?.dir - if (pkgDir) { - depsToTrace.add(pkgDir) - } - } - // has separate esm/require entry, assume require entry is cjs - else if (esmEntry !== requireEntry) { - ssrExternals.add(id) - } - // if we're externalizing ESM and CJS should basically just always do it? - // or are there others like SystemJS / AMD that we'd need to handle? - // for now, we'll just leave this as is - else if (/\.m?js$/.test(esmEntry)) { - const pkg = resolvePackageData(id, root)?.data - if (!pkg) { - continue - } - - if (pkg.type === 'module' || esmEntry.endsWith('.mjs')) { - ssrExternals.add(id) - continue - } - // check if the entry is cjs - const content = fs.readFileSync(esmEntry, 'utf-8') - if (CJS_CONTENT_RE.test(content)) { - ssrExternals.add(id) - continue - } - - logger.warn( - `${id} doesn't appear to be written in CJS, but also doesn't appear to be a valid ES module (i.e. it doesn't have "type": "module" or an .mjs extension for the entry point). Please contact the package author to fix.`, - ) - } - } - - for (const depRoot of depsToTrace) { - cjsSsrCollectExternals(depRoot, resolveOptions, ssrExternals, seen, logger) - } -} - -export function cjsShouldExternalizeForSSR( - id: string, - externals: string[] | null, -): boolean { - if (!externals) { - return false - } - const should = externals.some((e) => { - if (id === e) { - return true - } - // deep imports, check ext before externalizing - only externalize - // extension-less imports and explicit .js imports - if ( - id.startsWith(withTrailingSlash(e)) && - (!path.extname(id) || id.endsWith('.js')) - ) { - return true - } - }) - return should -}