Skip to content

Commit d49856c

Browse files
authoredJun 29, 2022
fix: optimize deps on dev SSR, builtin imports in node (#8854)
1 parent cd8d63b commit d49856c

21 files changed

+226
-129
lines changed
 

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

+7-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { KNOWN_ASSET_TYPES } from '../constants'
55
import type { ResolvedConfig } from '..'
66
import {
77
flattenId,
8+
isBuiltin,
89
isExternalUrl,
910
isRunningWithYarnPnp,
1011
moduleListContains,
@@ -42,7 +43,8 @@ const externalTypes = [
4243
export function esbuildDepPlugin(
4344
qualified: Record<string, string>,
4445
exportsData: Record<string, ExportsData>,
45-
config: ResolvedConfig
46+
config: ResolvedConfig,
47+
ssr: boolean
4648
): Plugin {
4749
// remove optimizable extensions from `externalTypes` list
4850
const allExternalTypes = config.optimizeDeps.extensions
@@ -77,7 +79,7 @@ export function esbuildDepPlugin(
7779
_importer = importer in qualified ? qualified[importer] : importer
7880
}
7981
const resolver = kind.startsWith('require') ? _resolveRequire : _resolve
80-
return resolver(id, _importer, undefined)
82+
return resolver(id, _importer, undefined, ssr)
8183
}
8284

8385
const resolveResult = (id: string, resolved: string) => {
@@ -87,6 +89,9 @@ export function esbuildDepPlugin(
8789
namespace: 'browser-external'
8890
}
8991
}
92+
if (ssr && isBuiltin(resolved)) {
93+
return
94+
}
9095
if (isExternalUrl(resolved)) {
9196
return {
9297
path: resolved,

‎packages/vite/src/node/optimizer/index.ts

+18-4
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,11 @@ import { transformWithEsbuild } from '../plugins/esbuild'
2626
import { ESBUILD_MODULES_TARGET } from '../constants'
2727
import { esbuildDepPlugin } from './esbuildDepPlugin'
2828
import { scanImports } from './scan'
29-
export { initDepsOptimizer, getDepsOptimizer } from './optimizer'
29+
export {
30+
initDepsOptimizer,
31+
initDevSsrDepsOptimizer,
32+
getDepsOptimizer
33+
} from './optimizer'
3034

3135
export const debuggerViteDeps = createDebugger('vite:deps')
3236
const debug = debuggerViteDeps
@@ -47,7 +51,7 @@ export type ExportsData = {
4751
}
4852

4953
export interface DepsOptimizer {
50-
metadata: (options: { ssr: boolean }) => DepOptimizationMetadata
54+
metadata: DepOptimizationMetadata
5155
scanProcessing?: Promise<void>
5256
registerMissingImport: (
5357
id: string,
@@ -546,6 +550,9 @@ export async function runOptimizeDeps(
546550
: JSON.stringify(process.env.NODE_ENV || config.mode)
547551
}
548552

553+
const platform =
554+
ssr && config.ssr?.target !== 'webworker' ? 'node' : 'browser'
555+
549556
const start = performance.now()
550557

551558
const result = await build({
@@ -555,9 +562,16 @@ export async function runOptimizeDeps(
555562
// We can't use platform 'neutral', as esbuild has custom handling
556563
// when the platform is 'node' or 'browser' that can't be emulated
557564
// by using mainFields and conditions
558-
platform: ssr && config.ssr?.target !== 'webworker' ? 'node' : 'browser',
565+
platform,
559566
define,
560567
format: 'esm',
568+
// See https://github.com/evanw/esbuild/issues/1921#issuecomment-1152991694
569+
banner:
570+
platform === 'node'
571+
? {
572+
js: `import { createRequire } from 'module';const require = createRequire(import.meta.url);`
573+
}
574+
: undefined,
561575
target: isBuild ? config.build.target || undefined : ESBUILD_MODULES_TARGET,
562576
external: config.optimizeDeps?.exclude,
563577
logLevel: 'error',
@@ -568,7 +582,7 @@ export async function runOptimizeDeps(
568582
metafile: true,
569583
plugins: [
570584
...plugins,
571-
esbuildDepPlugin(flatIdDeps, flatIdToExports, config)
585+
esbuildDepPlugin(flatIdDeps, flatIdToExports, config, ssr)
572586
],
573587
...esbuildOptions,
574588
supported: {

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

+55-29
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import {
2424
runOptimizeDeps
2525
} from '.'
2626
import type {
27-
DepOptimizationMetadata,
2827
DepOptimizationProcessing,
2928
DepsOptimizer,
3029
OptimizedDepInfo
@@ -39,18 +38,36 @@ const isDebugEnabled = _debug('vite:deps').enabled
3938
const debounceMs = 100
4039

4140
const depsOptimizerMap = new WeakMap<ResolvedConfig, DepsOptimizer>()
41+
const devSsrDepsOptimizerMap = new WeakMap<ResolvedConfig, DepsOptimizer>()
4242

4343
export function getDepsOptimizer(
44-
config: ResolvedConfig
44+
config: ResolvedConfig,
45+
type: { ssr?: boolean }
4546
): DepsOptimizer | undefined {
4647
// Workers compilation shares the DepsOptimizer from the main build
47-
return depsOptimizerMap.get(config.mainConfig || config)
48+
const isDevSsr = type.ssr && config.command !== 'build'
49+
return (isDevSsr ? devSsrDepsOptimizerMap : depsOptimizerMap).get(
50+
config.mainConfig || config
51+
)
4852
}
4953

5054
export async function initDepsOptimizer(
5155
config: ResolvedConfig,
5256
server?: ViteDevServer
53-
): Promise<DepsOptimizer> {
57+
): Promise<void> {
58+
await createDepsOptimizer(config, server)
59+
}
60+
61+
export async function initDevSsrDepsOptimizer(
62+
config: ResolvedConfig
63+
): Promise<void> {
64+
await createDevSsrDepsOptimizer(config)
65+
}
66+
67+
async function createDepsOptimizer(
68+
config: ResolvedConfig,
69+
server?: ViteDevServer
70+
): Promise<void> {
5471
const { logger } = config
5572
const isBuild = config.command === 'build'
5673

@@ -62,18 +79,11 @@ export async function initDepsOptimizer(
6279

6380
let handle: NodeJS.Timeout | undefined
6481

65-
let ssrServerDepsMetadata: DepOptimizationMetadata
66-
let _metadata =
82+
let metadata =
6783
cachedMetadata || initDepsOptimizerMetadata(config, sessionTimestamp)
6884

6985
const depsOptimizer: DepsOptimizer = {
70-
metadata: (options: { ssr: boolean }) => {
71-
if (isBuild || !options.ssr) {
72-
return _metadata
73-
} else {
74-
return ssrServerDepsMetadata
75-
}
76-
},
86+
metadata,
7787
registerMissingImport,
7888
run: () => debouncedProcessing(0),
7989
isOptimizedDepFile: (id: string) => isOptimizedDepFile(id, config),
@@ -89,10 +99,6 @@ export async function initDepsOptimizer(
8999

90100
depsOptimizerMap.set(config, depsOptimizer)
91101

92-
if (!isBuild && config.ssr) {
93-
ssrServerDepsMetadata = await optimizeServerSsrDeps(config)
94-
}
95-
96102
let newDepsDiscovered = false
97103

98104
let newDepsToLog: string[] = []
@@ -137,7 +143,6 @@ export async function initDepsOptimizer(
137143
config,
138144
sessionTimestamp
139145
)
140-
const metadata = _metadata
141146
for (const depInfo of Object.values(discovered)) {
142147
addOptimizedDepInfo(metadata, 'discovered', {
143148
...depInfo,
@@ -155,8 +160,6 @@ export async function initDepsOptimizer(
155160
try {
156161
debug(colors.green(`scanning for dependencies...`))
157162

158-
const metadata = _metadata
159-
160163
const discovered = await discoverProjectDependencies(
161164
config,
162165
sessionTimestamp
@@ -201,7 +204,7 @@ export async function initDepsOptimizer(
201204
// Ensure that a rerun will not be issued for current discovered deps
202205
if (handle) clearTimeout(handle)
203206

204-
if (Object.keys(_metadata.discovered).length === 0) {
207+
if (Object.keys(metadata.discovered).length === 0) {
205208
currentlyProcessing = false
206209
return
207210
}
@@ -217,8 +220,6 @@ export async function initDepsOptimizer(
217220
// if the rerun fails, _metadata remains untouched, current discovered
218221
// deps are cleaned, and a fullReload is issued
219222

220-
let metadata = _metadata
221-
222223
// All deps, previous known and newly discovered are rebundled,
223224
// respect insertion order to keep the metadata file stable
224225

@@ -324,7 +325,7 @@ export async function initDepsOptimizer(
324325
)
325326
}
326327

327-
metadata = _metadata = newData
328+
metadata = depsOptimizer.metadata = newData
328329
resolveEnqueuedProcessingPromises()
329330
}
330331

@@ -418,7 +419,7 @@ export async function initDepsOptimizer(
418419
// debounce time to wait for new missing deps finished, issue a new
419420
// optimization of deps (both old and newly found) once the previous
420421
// optimizeDeps processing is finished
421-
const deps = Object.keys(_metadata.discovered)
422+
const deps = Object.keys(metadata.discovered)
422423
const depsString = depsLogString(deps)
423424
debug(colors.green(`new dependencies found: ${depsString}`))
424425
runOptimizer()
@@ -449,7 +450,6 @@ export async function initDepsOptimizer(
449450
`Error: ${id} is a missing dependency in SSR dev server, it needs to be added to optimizeDeps.include`
450451
)
451452
}
452-
const metadata = _metadata
453453
const optimized = metadata.optimized[id]
454454
if (optimized) {
455455
return optimized
@@ -539,7 +539,7 @@ export async function initDepsOptimizer(
539539
if (!firstRunEnsured && !firstRunCalled && registeredIds.length === 0) {
540540
setTimeout(() => {
541541
if (!firstRunCalled && registeredIds.length === 0) {
542-
getDepsOptimizer(config)?.run()
542+
debouncedProcessing(0) // queue the optimizer run
543543
}
544544
}, runOptimizerIfIdleAfterMs)
545545
}
@@ -580,7 +580,7 @@ export async function initDepsOptimizer(
580580
if (registeredIds.length > 0) {
581581
runOptimizerWhenIdle()
582582
} else {
583-
getDepsOptimizer(config)?.run()
583+
debouncedProcessing(0) // queue the optimizer run
584584
}
585585
}
586586
}
@@ -596,8 +596,34 @@ export async function initDepsOptimizer(
596596
}
597597
}
598598
}
599+
}
599600

600-
return depsOptimizer
601+
async function createDevSsrDepsOptimizer(
602+
config: ResolvedConfig
603+
): Promise<void> {
604+
const metadata = await optimizeServerSsrDeps(config)
605+
const depsOptimizer = {
606+
metadata,
607+
isOptimizedDepFile: (id: string) => isOptimizedDepFile(id, config),
608+
isOptimizedDepUrl: createIsOptimizedDepUrl(config),
609+
getOptimizedDepId: (depInfo: OptimizedDepInfo) =>
610+
`${depInfo.file}?v=${depInfo.browserHash}`,
611+
612+
registerMissingImport: () => {
613+
throw new Error(
614+
'Vite Internal Error: registerMissingImport is not supported in dev SSR'
615+
)
616+
},
617+
// noop, there is no scanning during dev SSR
618+
// the optimizer blocks the server start
619+
run: () => {},
620+
registerWorkersSource: (id: string) => {},
621+
delayDepsOptimizerUntil: (id: string, done: () => Promise<any>) => {},
622+
resetRegisteredIds: () => {},
623+
ensureFirstRun: () => {},
624+
options: config.optimizeDeps
625+
}
626+
devSsrDepsOptimizerMap.set(config, depsOptimizer)
601627
}
602628

603629
export async function preTransformOptimizeDepsEntries(

‎packages/vite/src/node/plugins/importAnalysis.ts

+3-4
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin {
214214
)
215215
}
216216

217-
const depsOptimizer = getDepsOptimizer(config)
217+
const depsOptimizer = getDepsOptimizer(config, { ssr })
218218

219219
const { moduleGraph } = server
220220
// since we are already in the transform phase of the importer, it must
@@ -275,8 +275,7 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin {
275275
// the dependency needs to be resolved starting from the original source location of the optimized file
276276
// because starting from node_modules/.vite will not find the dependency if it was not hoisted
277277
// (that is, if it is under node_modules directory in the package source of the optimized file)
278-
for (const optimizedModule of depsOptimizer.metadata({ ssr })
279-
.depInfoList) {
278+
for (const optimizedModule of depsOptimizer.metadata.depInfoList) {
280279
if (!optimizedModule.src) continue // Ignore chunks
281280
if (optimizedModule.file === importerModule.file) {
282281
importerFile = optimizedModule.src
@@ -479,7 +478,7 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin {
479478
const file = cleanUrl(resolvedId) // Remove ?v={hash}
480479

481480
const needsInterop = await optimizedDepNeedsInterop(
482-
depsOptimizer.metadata({ ssr }),
481+
depsOptimizer.metadata,
483482
file,
484483
config
485484
)

‎packages/vite/src/node/plugins/importAnalysisBuild.ts

+3-4
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin {
154154
}
155155

156156
const { root } = config
157-
const depsOptimizer = getDepsOptimizer(config)
157+
const depsOptimizer = getDepsOptimizer(config, { ssr })
158158

159159
const normalizeUrl = async (
160160
url: string,
@@ -170,8 +170,7 @@ export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin {
170170
// the dependency needs to be resolved starting from the original source location of the optimized file
171171
// because starting from node_modules/.vite will not find the dependency if it was not hoisted
172172
// (that is, if it is under node_modules directory in the package source of the optimized file)
173-
for (const optimizedModule of depsOptimizer.metadata({ ssr })
174-
.depInfoList) {
173+
for (const optimizedModule of depsOptimizer.metadata.depInfoList) {
175174
if (!optimizedModule.src) continue // Ignore chunks
176175
if (optimizedModule.file === importer) {
177176
importerFile = optimizedModule.src
@@ -262,7 +261,7 @@ export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin {
262261
const file = cleanUrl(resolvedId) // Remove ?v={hash}
263262

264263
const needsInterop = await optimizedDepNeedsInterop(
265-
depsOptimizer.metadata({ ssr }),
264+
depsOptimizer.metadata,
266265
file,
267266
config
268267
)

‎packages/vite/src/node/plugins/index.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,8 @@ export async function resolvePlugins(
6262
packageCache: config.packageCache,
6363
ssrConfig: config.ssr,
6464
asSrc: true,
65-
getDepsOptimizer: () => getDepsOptimizer(config),
65+
getDepsOptimizer: (type: { ssr?: boolean }) =>
66+
getDepsOptimizer(config, type),
6667
shouldExternalize:
6768
isBuild && config.build.ssr && config.ssr?.format !== 'cjs'
6869
? (id) => shouldExternalizeForSSR(id, config)

‎packages/vite/src/node/plugins/optimizedDeps.ts

+58-55
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ export function optimizedDepsPlugin(config: ResolvedConfig): Plugin {
1717
return {
1818
name: 'vite:optimized-deps',
1919

20-
async resolveId(id) {
21-
if (getDepsOptimizer(config)?.isOptimizedDepFile(id)) {
20+
async resolveId(id, source, { ssr }) {
21+
if (getDepsOptimizer(config, { ssr })?.isOptimizedDepFile(id)) {
2222
return id
2323
}
2424
},
@@ -28,51 +28,49 @@ export function optimizedDepsPlugin(config: ResolvedConfig): Plugin {
2828
// is in importAnalysis, see call to delayDepsOptimizerUntil
2929

3030
async load(id, options) {
31-
const ssr = options?.ssr ?? false
32-
const depsOptimizer = getDepsOptimizer(config)
31+
const ssr = options?.ssr === true
32+
const depsOptimizer = getDepsOptimizer(config, { ssr })
3333
if (depsOptimizer?.isOptimizedDepFile(id)) {
34-
const metadata = depsOptimizer?.metadata({ ssr })
35-
if (metadata) {
36-
const file = cleanUrl(id)
37-
const versionMatch = id.match(DEP_VERSION_RE)
38-
const browserHash = versionMatch
39-
? versionMatch[1].split('=')[1]
40-
: undefined
41-
42-
// Search in both the currently optimized and newly discovered deps
43-
const info = optimizedDepInfoFromFile(metadata, file)
44-
if (info) {
45-
if (browserHash && info.browserHash !== browserHash) {
46-
throwOutdatedRequest(id)
47-
}
48-
try {
49-
// This is an entry point, it may still not be bundled
50-
await info.processing
51-
} catch {
52-
// If the refresh has not happened after timeout, Vite considers
53-
// something unexpected has happened. In this case, Vite
54-
// returns an empty response that will error.
55-
throwProcessingError(id)
56-
return
57-
}
58-
const newMetadata = depsOptimizer.metadata({ ssr })
59-
if (metadata !== newMetadata) {
60-
const currentInfo = optimizedDepInfoFromFile(newMetadata!, file)
61-
if (info.browserHash !== currentInfo?.browserHash) {
62-
throwOutdatedRequest(id)
63-
}
64-
}
34+
const metadata = depsOptimizer.metadata
35+
const file = cleanUrl(id)
36+
const versionMatch = id.match(DEP_VERSION_RE)
37+
const browserHash = versionMatch
38+
? versionMatch[1].split('=')[1]
39+
: undefined
40+
41+
// Search in both the currently optimized and newly discovered deps
42+
const info = optimizedDepInfoFromFile(metadata, file)
43+
if (info) {
44+
if (browserHash && info.browserHash !== browserHash) {
45+
throwOutdatedRequest(id)
6546
}
66-
isDebug && debug(`load ${colors.cyan(file)}`)
67-
// Load the file from the cache instead of waiting for other plugin
68-
// load hooks to avoid race conditions, once processing is resolved,
69-
// we are sure that the file has been properly save to disk
7047
try {
71-
return await fs.readFile(file, 'utf-8')
72-
} catch (e) {
73-
// Outdated non-entry points (CHUNK), loaded after a rerun
74-
throwOutdatedRequest(id)
48+
// This is an entry point, it may still not be bundled
49+
await info.processing
50+
} catch {
51+
// If the refresh has not happened after timeout, Vite considers
52+
// something unexpected has happened. In this case, Vite
53+
// returns an empty response that will error.
54+
throwProcessingError(id)
55+
return
7556
}
57+
const newMetadata = depsOptimizer.metadata
58+
if (metadata !== newMetadata) {
59+
const currentInfo = optimizedDepInfoFromFile(newMetadata!, file)
60+
if (info.browserHash !== currentInfo?.browserHash) {
61+
throwOutdatedRequest(id)
62+
}
63+
}
64+
}
65+
isDebug && debug(`load ${colors.cyan(file)}`)
66+
// Load the file from the cache instead of waiting for other plugin
67+
// load hooks to avoid race conditions, once processing is resolved,
68+
// we are sure that the file has been properly save to disk
69+
try {
70+
return await fs.readFile(file, 'utf-8')
71+
} catch (e) {
72+
// Outdated non-entry points (CHUNK), loaded after a rerun
73+
throwOutdatedRequest(id)
7674
}
7775
}
7876
}
@@ -85,27 +83,32 @@ export function optimizedDepsBuildPlugin(config: ResolvedConfig): Plugin {
8583

8684
buildStart() {
8785
if (!config.isWorker) {
88-
getDepsOptimizer(config)?.resetRegisteredIds()
86+
// This will be run for the current active optimizer, during build
87+
// it will be the SSR optimizer if config.build.ssr is defined
88+
getDepsOptimizer(config, { ssr: undefined })?.resetRegisteredIds()
8989
}
9090
},
9191

92-
async resolveId(id) {
93-
if (getDepsOptimizer(config)?.isOptimizedDepFile(id)) {
92+
async resolveId(id, importer, { ssr }) {
93+
if (getDepsOptimizer(config, { ssr })?.isOptimizedDepFile(id)) {
9494
return id
9595
}
9696
},
9797

98-
transform(_code, id) {
99-
getDepsOptimizer(config)?.delayDepsOptimizerUntil(id, async () => {
100-
await this.load({ id })
101-
})
98+
transform(_code, id, options) {
99+
const ssr = options?.ssr === true
100+
getDepsOptimizer(config, { ssr })?.delayDepsOptimizerUntil(
101+
id,
102+
async () => {
103+
await this.load({ id })
104+
}
105+
)
102106
},
103107

104108
async load(id, options) {
105-
const ssr = options?.ssr ?? false
106-
const depsOptimizer = getDepsOptimizer(config)
107-
const metadata = depsOptimizer?.metadata({ ssr })
108-
if (!metadata || !depsOptimizer?.isOptimizedDepFile(id)) {
109+
const ssr = options?.ssr === true
110+
const depsOptimizer = getDepsOptimizer(config, { ssr })
111+
if (!depsOptimizer?.isOptimizedDepFile(id)) {
109112
return
110113
}
111114

@@ -114,7 +117,7 @@ export function optimizedDepsBuildPlugin(config: ResolvedConfig): Plugin {
114117
const file = cleanUrl(id)
115118
// Search in both the currently optimized and newly discovered deps
116119
// If all the inputs are dependencies, we aren't going to get any
117-
const info = optimizedDepInfoFromFile(metadata, file)
120+
const info = optimizedDepInfoFromFile(depsOptimizer.metadata, file)
118121
if (info) {
119122
try {
120123
// This is an entry point, it may still not be bundled

‎packages/vite/src/node/plugins/preAlias.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ export function preAliasPlugin(config: ResolvedConfig): Plugin {
1111
return {
1212
name: 'vite:pre-alias',
1313
async resolveId(id, importer, options) {
14-
const ssr = options?.ssr ?? false
15-
const depsOptimizer = getDepsOptimizer(config)
14+
const ssr = options?.ssr === true
15+
const depsOptimizer = getDepsOptimizer(config, { ssr })
1616
if (depsOptimizer && bareImportRE.test(id) && !options?.scan) {
17-
return await tryOptimizedResolve(depsOptimizer, ssr, id, importer)
17+
return await tryOptimizedResolve(depsOptimizer, id, importer)
1818
}
1919
}
2020
}

‎packages/vite/src/node/plugins/resolve.ts

+8-12
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ export interface InternalResolveOptions extends ResolveOptions {
8383
// True when resolving during the scan phase to discover dependencies
8484
scan?: boolean
8585
// Resolve using esbuild deps optimization
86-
getDepsOptimizer?: () => DepsOptimizer | undefined
86+
getDepsOptimizer?: (type: { ssr?: boolean }) => DepsOptimizer | undefined
8787
shouldExternalize?: (id: string) => boolean | undefined
8888
}
8989

@@ -102,11 +102,11 @@ export function resolvePlugin(resolveOptions: InternalResolveOptions): Plugin {
102102
name: 'vite:resolve',
103103

104104
async resolveId(id, importer, resolveOpts) {
105+
const ssr = resolveOpts?.ssr === true
106+
105107
// We need to delay depsOptimizer until here instead of passing it as an option
106108
// the resolvePlugin because the optimizer is created on server listen during dev
107-
const depsOptimizer = resolveOptions.getDepsOptimizer?.()
108-
109-
const ssr = resolveOpts?.ssr === true
109+
const depsOptimizer = resolveOptions.getDepsOptimizer?.({ ssr })
110110

111111
if (id.startsWith(browserExternalId)) {
112112
return id
@@ -186,7 +186,7 @@ export function resolvePlugin(resolveOptions: InternalResolveOptions): Plugin {
186186
// Inject the current browserHash version if the path doesn't have one
187187
if (!normalizedFsPath.match(DEP_VERSION_RE)) {
188188
const browserHash = optimizedDepInfoFromFile(
189-
depsOptimizer.metadata({ ssr }),
189+
depsOptimizer.metadata,
190190
normalizedFsPath
191191
)?.browserHash
192192
if (browserHash) {
@@ -269,7 +269,7 @@ export function resolvePlugin(resolveOptions: InternalResolveOptions): Plugin {
269269
asSrc &&
270270
depsOptimizer &&
271271
!options.scan &&
272-
(res = await tryOptimizedResolve(depsOptimizer, ssr, id, importer))
272+
(res = await tryOptimizedResolve(depsOptimizer, id, importer))
273273
) {
274274
return res
275275
}
@@ -690,7 +690,7 @@ export function tryNodeResolve(
690690
// otherwise we may introduce duplicated modules for externalized files
691691
// from pre-bundled deps.
692692
if (!isBuild) {
693-
const versionHash = depsOptimizer.metadata({ ssr }).browserHash
693+
const versionHash = depsOptimizer.metadata.browserHash
694694
if (versionHash && isJsType) {
695695
resolved = injectQuery(resolved, `v=${versionHash}`)
696696
}
@@ -716,16 +716,12 @@ export function tryNodeResolve(
716716

717717
export async function tryOptimizedResolve(
718718
depsOptimizer: DepsOptimizer,
719-
ssr: boolean,
720719
id: string,
721720
importer?: string
722721
): Promise<string | undefined> {
723722
await depsOptimizer.scanProcessing
724723

725-
const metadata = depsOptimizer.metadata({ ssr })
726-
if (!metadata) {
727-
return
728-
}
724+
const metadata = depsOptimizer.metadata
729725

730726
const depInfo = optimizedDepInfoFromId(metadata, id)
731727
if (depInfo) {

‎packages/vite/src/node/plugins/worker.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,8 @@ export function webWorkerPlugin(config: ResolvedConfig): Plugin {
222222
}
223223
},
224224

225-
async transform(raw, id) {
225+
async transform(raw, id, options) {
226+
const ssr = options?.ssr === true
226227
const query = parseRequest(id)
227228
if (query && query[WORKER_FILE_ID] != null) {
228229
// if import worker by worker constructor will had query.type
@@ -269,7 +270,7 @@ export function webWorkerPlugin(config: ResolvedConfig): Plugin {
269270
: 'module'
270271
const workerOptions = workerType === 'classic' ? '' : ',{type: "module"}'
271272
if (isBuild) {
272-
getDepsOptimizer(config)?.registerWorkersSource(id)
273+
getDepsOptimizer(config, { ssr })?.registerWorkersSource(id)
273274
if (query.inline != null) {
274275
const chunk = await bundleWorkerEntry(config, id, query)
275276
// inline as blob data url

‎packages/vite/src/node/plugins/workerImportMetaUrl.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ export function workerImportMetaUrlPlugin(config: ResolvedConfig): Plugin {
7676
name: 'vite:worker-import-meta-url',
7777

7878
async transform(code, id, options) {
79+
const ssr = options?.ssr === true
7980
if (
8081
!options?.ssr &&
8182
(code.includes('new Worker') || code.includes('new SharedWorker')) &&
@@ -117,7 +118,7 @@ export function workerImportMetaUrlPlugin(config: ResolvedConfig): Plugin {
117118

118119
let url: string
119120
if (isBuild) {
120-
getDepsOptimizer(config)?.registerWorkersSource(id)
121+
getDepsOptimizer(config, { ssr })?.registerWorkersSource(id)
121122
url = await workerFileToUrl(config, file, query)
122123
} else {
123124
url = await fileToUrl(cleanUrl(file), config, this)

‎packages/vite/src/node/server/index.ts

+25-6
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,11 @@ import {
2828
ssrRewriteStacktrace
2929
} from '../ssr/ssrStacktrace'
3030
import { ssrTransform } from '../ssr/ssrTransform'
31-
import { getDepsOptimizer, initDepsOptimizer } from '../optimizer'
31+
import {
32+
getDepsOptimizer,
33+
initDepsOptimizer,
34+
initDevSsrDepsOptimizer
35+
} from '../optimizer'
3236
import { CLIENT_DIR } from '../constants'
3337
import type { Logger } from '../logger'
3438
import { printCommonServerUrls } from '../logger'
@@ -306,6 +310,8 @@ export async function createServer(
306310

307311
let exitProcess: () => void
308312

313+
let creatingDevSsrOptimizer: Promise<void> | null = null
314+
309315
const server: ViteDevServer = {
310316
config,
311317
middlewares,
@@ -324,6 +330,13 @@ export async function createServer(
324330
},
325331
transformIndexHtml: null!, // to be immediately set
326332
async ssrLoadModule(url, opts?: { fixStacktrace?: boolean }) {
333+
if (!getDepsOptimizer(config, { ssr: true })) {
334+
if (!creatingDevSsrOptimizer) {
335+
creatingDevSsrOptimizer = initDevSsrDepsOptimizer(config)
336+
}
337+
await creatingDevSsrOptimizer
338+
creatingDevSsrOptimizer = null
339+
}
327340
await updateCjsSsrExternals(server)
328341
return ssrLoadModule(
329342
url,
@@ -752,15 +765,21 @@ async function restartServer(server: ViteDevServer) {
752765

753766
async function updateCjsSsrExternals(server: ViteDevServer) {
754767
if (!server._ssrExternals) {
755-
// We use the non-ssr optimized deps to find known imports
756768
let knownImports: string[] = []
757-
const depsOptimizer = getDepsOptimizer(server.config)
769+
770+
// Important! We use the non-ssr optimized deps to find known imports
771+
// Only the explicitly defined deps are optimized during dev SSR, so
772+
// we use the generated list from the scanned deps in regular dev.
773+
// This is part of the v2 externalization heuristics and it is kept
774+
// for backwards compatibility in case user needs to fallback to the
775+
// legacy scheme. It may be removed in a future v3 minor.
776+
const depsOptimizer = getDepsOptimizer(server.config, { ssr: false })
777+
758778
if (depsOptimizer) {
759779
await depsOptimizer.scanProcessing
760-
const metadata = depsOptimizer.metadata({ ssr: false })
761780
knownImports = [
762-
...Object.keys(metadata.optimized),
763-
...Object.keys(metadata.discovered)
781+
...Object.keys(depsOptimizer.metadata.optimized),
782+
...Object.keys(depsOptimizer.metadata.discovered)
764783
]
765784
}
766785
server._ssrExternals = cjsSsrResolveExternals(server.config, knownImports)

‎packages/vite/src/node/server/middlewares/transform.ts

+8-2
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,11 @@ export function transformMiddleware(
7171
const isSourceMap = withoutQuery.endsWith('.map')
7272
// since we generate source map references, handle those requests here
7373
if (isSourceMap) {
74-
if (getDepsOptimizer(server.config)?.isOptimizedDepUrl(url)) {
74+
if (
75+
getDepsOptimizer(server.config, { ssr: false })?.isOptimizedDepUrl(
76+
url
77+
)
78+
) {
7579
// If the browser is requesting a source map for an optimized dep, it
7680
// means that the dependency has already been pre-bundled and loaded
7781
const mapFile = url.startsWith(FS_PREFIX)
@@ -188,7 +192,9 @@ export function transformMiddleware(
188192
const type = isDirectCSSRequest(url) ? 'css' : 'js'
189193
const isDep =
190194
DEP_VERSION_RE.test(url) ||
191-
getDepsOptimizer(server.config)?.isOptimizedDepUrl(url)
195+
getDepsOptimizer(server.config, { ssr: false })?.isOptimizedDepUrl(
196+
url
197+
)
192198
return send(req, res, result.code, type, {
193199
etag: result.etag,
194200
// allow browser to cache npm deps!

‎packages/vite/src/node/server/transformRequest.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ async function doTransform(
145145

146146
const result = loadAndTransform(id, url, server, options, timestamp)
147147

148-
const depsOptimizer = getDepsOptimizer(config)
148+
const depsOptimizer = getDepsOptimizer(config, { ssr })
149149
if (depsOptimizer && !config.legacy?.devDepsScanner) {
150150
depsOptimizer.delayDepsOptimizerUntil(id, () => result)
151151
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
exports.stream = require('stream')
2+
3+
exports.hello = function () {
4+
return 'Hello World!'
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"name": "import-builtin",
3+
"private": true,
4+
"type": "commonjs",
5+
"version": "0.0.0"
6+
}

‎playground/ssr-deps/no-external-cjs/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"name": "primitive-export",
2+
"name": "no-external-cjs",
33
"private": true,
44
"type": "commonjs",
55
"version": "0.0.0"

‎playground/ssr-deps/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"require-absolute": "file:./require-absolute",
2121
"ts-transpiled-exports": "file:./ts-transpiled-exports",
2222
"no-external-cjs": "file:./no-external-cjs",
23+
"import-builtin-cjs": "file:./import-builtin-cjs",
2324
"no-external-css": "file:./no-external-css"
2425
},
2526
"devDependencies": {

‎playground/ssr-deps/server.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export async function createServer(root = process.cwd(), hmrPort) {
3535
},
3636
appType: 'custom',
3737
ssr: {
38-
noExternal: ['no-external-cjs', 'no-external-css']
38+
noExternal: ['no-external-cjs', 'import-builtin-cjs', 'no-external-css']
3939
}
4040
})
4141
// use vite's connect instance as middleware

‎playground/ssr-deps/src/app.js

+4
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import definePropertyExports from 'define-property-exports'
1010
import onlyObjectAssignedExports from 'only-object-assigned-exports'
1111
import requireAbsolute from 'require-absolute'
1212
import noExternalCjs from 'no-external-cjs'
13+
import importBuiltinCjs from 'import-builtin-cjs'
1314

1415
export async function render(url, rootDir) {
1516
let html = ''
@@ -49,5 +50,8 @@ export async function render(url, rootDir) {
4950
const noExternalCjsMessage = noExternalCjs.hello()
5051
html += `\n<p class="no-external-cjs-msg">message from no-external-cjs: ${noExternalCjsMessage}</p>`
5152

53+
const importBuiltinCjsMessage = importBuiltinCjs.hello()
54+
html += `\n<p class="import-builtin-cjs-msg">message from import-builtin-cjs: ${importBuiltinCjsMessage}</p>`
55+
5256
return html + '\n'
5357
}

‎pnpm-lock.yaml

+12-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)
Please sign in to comment.