Skip to content

Commit f280dd9

Browse files
authored
feat: ssr.optimizeDeps (#8917)
1 parent 722f514 commit f280dd9

20 files changed

Lines changed: 285 additions & 229 deletions

packages/vite/src/node/build.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -398,7 +398,7 @@ async function doBuild(
398398
external = await cjsSsrResolveExternal(config, userExternal)
399399
}
400400

401-
if (isDepsOptimizerEnabled(config)) {
401+
if (isDepsOptimizerEnabled(config, ssr)) {
402402
await initDepsOptimizer(config)
403403
}
404404

@@ -739,7 +739,7 @@ async function cjsSsrResolveExternal(
739739
} catch (e) {}
740740
if (!knownImports) {
741741
// no dev deps optimization data, do a fresh scan
742-
knownImports = await findKnownImports(config)
742+
knownImports = await findKnownImports(config, false) // needs to use non-ssr
743743
}
744744
const ssrExternals = cjsSsrResolveExternals(config, knownImports)
745745

packages/vite/src/node/config.ts

Lines changed: 26 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import colors from 'picocolors'
77
import type { Alias, AliasOptions } from 'types/alias'
88
import aliasPlugin from '@rollup/plugin-alias'
99
import { build } from 'esbuild'
10-
import type { Plugin as ESBuildPlugin } from 'esbuild'
1110
import type { RollupOptions } from 'rollup'
1211
import type { Plugin } from './plugin'
1312
import type {
@@ -47,7 +46,7 @@ import type { InternalResolveOptions, ResolveOptions } from './plugins/resolve'
4746
import { resolvePlugin } from './plugins/resolve'
4847
import type { LogLevel, Logger } from './logger'
4948
import { createLogger } from './logger'
50-
import type { DepOptimizationOptions } from './optimizer'
49+
import type { DepOptimizationConfig, DepOptimizationOptions } from './optimizer'
5150
import type { JsonOptions } from './plugins/json'
5251
import type { PluginContainer } from './server/pluginContainer'
5352
import { createPluginContainer } from './server/pluginContainer'
@@ -334,7 +333,7 @@ export type ResolvedConfig = Readonly<
334333
server: ResolvedServerOptions
335334
build: ResolvedBuildOptions
336335
preview: ResolvedPreviewOptions
337-
ssr: ResolvedSSROptions | undefined
336+
ssr: ResolvedSSROptions
338337
assetsInclude: (file: string) => boolean
339338
logger: Logger
340339
createResolver: (options?: Partial<InternalResolveOptions>) => ResolveFn
@@ -560,15 +559,14 @@ export async function resolveConfig(
560559
: ''
561560

562561
const server = resolveServerOptions(resolvedRoot, config.server, logger)
563-
let ssr = resolveSSROptions(config.ssr)
564-
if (config.legacy?.buildSsrCjsExternalHeuristics) {
565-
if (ssr) ssr.format = 'cjs'
566-
else ssr = { target: 'node', format: 'cjs' }
567-
}
562+
const ssr = resolveSSROptions(
563+
config.ssr,
564+
config.legacy?.buildSsrCjsExternalHeuristics,
565+
config.resolve?.preserveSymlinks
566+
)
568567

569568
const middlewareMode = config?.server?.middlewareMode
570569

571-
config = mergeConfig(config, externalConfigCompat(config, configEnv))
572570
const optimizeDeps = config.optimizeDeps || {}
573571

574572
if (process.env.VITE_TEST_LEGACY_CJS_PLUGIN) {
@@ -668,6 +666,12 @@ export async function resolveConfig(
668666
} else if (optimizerDisabled === 'dev') {
669667
resolved.optimizeDeps.disabled = true // Also disabled during build
670668
}
669+
const ssrOptimizerDisabled = resolved.ssr.optimizeDeps.disabled
670+
if (!ssrOptimizerDisabled) {
671+
resolved.ssr.optimizeDeps.disabled = 'build'
672+
} else if (ssrOptimizerDisabled === 'dev') {
673+
resolved.ssr.optimizeDeps.disabled = true // Also disabled during build
674+
}
671675
}
672676

673677
// Some plugins that aren't intended to work in the bundling of workers (doing post-processing at build time for example).
@@ -1004,92 +1008,21 @@ async function loadConfigFromBundledFile(
10041008
return raw.__esModule ? raw.default : raw
10051009
}
10061010

1007-
export function isDepsOptimizerEnabled(config: ResolvedConfig): boolean {
1008-
const { command, optimizeDeps } = config
1009-
const { disabled } = optimizeDeps
1011+
export function getDepOptimizationConfig(
1012+
config: ResolvedConfig,
1013+
ssr: boolean
1014+
): DepOptimizationConfig {
1015+
return ssr ? config.ssr.optimizeDeps : config.optimizeDeps
1016+
}
1017+
export function isDepsOptimizerEnabled(
1018+
config: ResolvedConfig,
1019+
ssr: boolean
1020+
): boolean {
1021+
const { command } = config
1022+
const { disabled } = getDepOptimizationConfig(config, ssr)
10101023
return !(
10111024
disabled === true ||
10121025
(command === 'build' && disabled === 'build') ||
1013-
(command === 'serve' && optimizeDeps.disabled === 'dev')
1026+
(command === 'serve' && disabled === 'dev')
10141027
)
10151028
}
1016-
1017-
// esbuild doesn't transpile `require('foo')` into `import` statements if 'foo' is externalized
1018-
// https://github.com/evanw/esbuild/issues/566#issuecomment-735551834
1019-
function esbuildCjsExternalPlugin(externals: string[]): ESBuildPlugin {
1020-
return {
1021-
name: 'cjs-external',
1022-
setup(build) {
1023-
const escape = (text: string) =>
1024-
`^${text.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')}$`
1025-
const filter = new RegExp(externals.map(escape).join('|'))
1026-
1027-
build.onResolve({ filter: /.*/, namespace: 'external' }, (args) => ({
1028-
path: args.path,
1029-
external: true
1030-
}))
1031-
1032-
build.onResolve({ filter }, (args) => ({
1033-
path: args.path,
1034-
namespace: 'external'
1035-
}))
1036-
1037-
build.onLoad({ filter: /.*/, namespace: 'external' }, (args) => ({
1038-
contents: `export * from ${JSON.stringify(args.path)}`
1039-
}))
1040-
}
1041-
}
1042-
}
1043-
1044-
// Support `rollupOptions.external` when `legacy.buildRollupPluginCommonjs` is disabled
1045-
function externalConfigCompat(config: UserConfig, { command }: ConfigEnv) {
1046-
// Only affects the build command
1047-
if (command !== 'build') {
1048-
return {}
1049-
}
1050-
1051-
// Skip if using Rollup CommonJS plugin
1052-
if (
1053-
config.legacy?.buildRollupPluginCommonjs ||
1054-
config.optimizeDeps?.disabled === 'build'
1055-
) {
1056-
return {}
1057-
}
1058-
1059-
// Skip if no `external` configured
1060-
const external = config?.build?.rollupOptions?.external
1061-
if (!external) {
1062-
return {}
1063-
}
1064-
1065-
let normalizedExternal = external
1066-
if (typeof external === 'string') {
1067-
normalizedExternal = [external]
1068-
}
1069-
1070-
// TODO: decide whether to support RegExp and function options
1071-
// They're not supported yet because `optimizeDeps.exclude` currently only accepts strings
1072-
if (
1073-
!Array.isArray(normalizedExternal) ||
1074-
normalizedExternal.some((ext) => typeof ext !== 'string')
1075-
) {
1076-
throw new Error(
1077-
`[vite] 'build.rollupOptions.external' can only be an array of strings or a string.\n` +
1078-
`You can turn on 'legacy.buildRollupPluginCommonjs' to support more advanced options.`
1079-
)
1080-
}
1081-
1082-
const additionalConfig: UserConfig = {
1083-
optimizeDeps: {
1084-
exclude: normalizedExternal as string[],
1085-
esbuildOptions: {
1086-
plugins: [
1087-
// TODO: maybe it can be added globally/unconditionally?
1088-
esbuildCjsExternalPlugin(normalizedExternal as string[])
1089-
]
1090-
}
1091-
}
1092-
}
1093-
1094-
return additionalConfig
1095-
}

packages/vite/src/node/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export type {
3535
export type {
3636
DepOptimizationMetadata,
3737
DepOptimizationOptions,
38+
DepOptimizationConfig,
3839
DepOptimizationResult,
3940
DepOptimizationProcessing,
4041
OptimizedDepInfo,
@@ -43,6 +44,7 @@ export type {
4344
} from './optimizer'
4445
export type {
4546
ResolvedSSROptions,
47+
SsrDepOptimizationOptions,
4648
SSROptions,
4749
SSRFormat,
4850
SSRTarget

packages/vite/src/node/optimizer/esbuildDepPlugin.ts

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import path from 'node:path'
22
import { promises as fs } from 'node:fs'
33
import type { ImportKind, Plugin } from 'esbuild'
44
import { KNOWN_ASSET_TYPES } from '../constants'
5+
import { getDepOptimizationConfig } from '..'
56
import type { ResolvedConfig } from '..'
67
import {
78
flattenId,
@@ -43,14 +44,15 @@ const externalTypes = [
4344
export function esbuildDepPlugin(
4445
qualified: Record<string, string>,
4546
exportsData: Record<string, ExportsData>,
47+
external: string[],
4648
config: ResolvedConfig,
4749
ssr: boolean
4850
): Plugin {
51+
const { extensions } = getDepOptimizationConfig(config, ssr)
52+
4953
// remove optimizable extensions from `externalTypes` list
50-
const allExternalTypes = config.optimizeDeps.extensions
51-
? externalTypes.filter(
52-
(type) => !config.optimizeDeps.extensions?.includes('.' + type)
53-
)
54+
const allExternalTypes = extensions
55+
? externalTypes.filter((type) => !extensions?.includes('.' + type))
5456
: externalTypes
5557

5658
// default resolver which prefers ESM
@@ -163,7 +165,7 @@ export function esbuildDepPlugin(
163165
build.onResolve(
164166
{ filter: /^[\w@][^:]/ },
165167
async ({ path: id, importer, kind }) => {
166-
if (moduleListContains(config.optimizeDeps?.exclude, id)) {
168+
if (moduleListContains(external, id)) {
167169
return {
168170
path: id,
169171
external: true
@@ -301,3 +303,30 @@ module.exports = Object.create(new Proxy({}, {
301303
}
302304
}
303305
}
306+
307+
// esbuild doesn't transpile `require('foo')` into `import` statements if 'foo' is externalized
308+
// https://github.com/evanw/esbuild/issues/566#issuecomment-735551834
309+
export function esbuildCjsExternalPlugin(externals: string[]): Plugin {
310+
return {
311+
name: 'cjs-external',
312+
setup(build) {
313+
const escape = (text: string) =>
314+
`^${text.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')}$`
315+
const filter = new RegExp(externals.map(escape).join('|'))
316+
317+
build.onResolve({ filter: /.*/, namespace: 'external' }, (args) => ({
318+
path: args.path,
319+
external: true
320+
}))
321+
322+
build.onResolve({ filter }, (args) => ({
323+
path: args.path,
324+
namespace: 'external'
325+
}))
326+
327+
build.onLoad({ filter: /.*/, namespace: 'external' }, (args) => ({
328+
contents: `export * from ${JSON.stringify(args.path)}`
329+
}))
330+
}
331+
}
332+
}

0 commit comments

Comments
 (0)