Skip to content

Commit 11d2191

Browse files
authoredJun 18, 2022
feat: support cjs noExternal in SSR dev, fix #2579 (#8430)
1 parent 993b842 commit 11d2191

File tree

16 files changed

+207
-79
lines changed

16 files changed

+207
-79
lines changed
 

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -733,7 +733,7 @@ async function cjsSsrResolveExternal(
733733
): Promise<ExternalOption> {
734734
// see if we have cached deps data available
735735
let knownImports: string[] | undefined
736-
const dataPath = path.join(getDepsCacheDir(config), '_metadata.json')
736+
const dataPath = path.join(getDepsCacheDir(config, false), '_metadata.json')
737737
try {
738738
const data = JSON.parse(
739739
fs.readFileSync(dataPath, 'utf-8')

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

+98-27
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@ import colors from 'picocolors'
66
import type { BuildOptions as EsbuildBuildOptions } from 'esbuild'
77
import { build } from 'esbuild'
88
import { init, parse } from 'es-module-lexer'
9+
import { createFilter } from '@rollup/pluginutils'
910
import type { ResolvedConfig } from '../config'
1011
import {
12+
arraify,
1113
createDebugger,
1214
emptyDir,
1315
flattenId,
@@ -43,10 +45,13 @@ export type ExportsData = {
4345
}
4446

4547
export interface DepsOptimizer {
46-
metadata: DepOptimizationMetadata
48+
metadata: (options: { ssr: boolean }) => DepOptimizationMetadata
4749
scanProcessing?: Promise<void>
48-
49-
registerMissingImport: (id: string, resolved: string) => OptimizedDepInfo
50+
registerMissingImport: (
51+
id: string,
52+
resolved: string,
53+
ssr?: boolean
54+
) => OptimizedDepInfo
5055
run: () => void
5156

5257
isOptimizedDepFile: (id: string) => boolean
@@ -232,6 +237,53 @@ export async function optimizeDeps(
232237
return result.metadata
233238
}
234239

240+
export async function optimizeServerSsrDeps(
241+
config: ResolvedConfig
242+
): Promise<DepOptimizationMetadata> {
243+
const cachedMetadata = loadCachedDepOptimizationMetadata(
244+
config,
245+
config.optimizeDeps.force,
246+
false,
247+
true // ssr
248+
)
249+
if (cachedMetadata) {
250+
return cachedMetadata
251+
}
252+
253+
let alsoInclude: string[] | undefined
254+
let noExternalFilter: ((id: unknown) => boolean) | undefined
255+
256+
const noExternal = config.ssr?.noExternal
257+
if (noExternal) {
258+
alsoInclude = arraify(noExternal).filter(
259+
(ne) => typeof ne === 'string'
260+
) as string[]
261+
noExternalFilter =
262+
noExternal === true
263+
? (dep: unknown) => false
264+
: createFilter(noExternal, config.optimizeDeps?.exclude, {
265+
resolve: false
266+
})
267+
}
268+
269+
const deps: Record<string, string> = {}
270+
271+
await addManuallyIncludedOptimizeDeps(
272+
deps,
273+
config,
274+
alsoInclude,
275+
noExternalFilter
276+
)
277+
278+
const depsInfo = toDiscoveredDependencies(config, deps, true)
279+
280+
const result = await runOptimizeDeps(config, depsInfo, true)
281+
282+
await result.commit()
283+
284+
return result.metadata
285+
}
286+
235287
export function initDepsOptimizerMetadata(
236288
config: ResolvedConfig,
237289
timestamp?: string
@@ -264,7 +316,8 @@ export function addOptimizedDepInfo(
264316
export function loadCachedDepOptimizationMetadata(
265317
config: ResolvedConfig,
266318
force = config.optimizeDeps.force,
267-
asCommand = false
319+
asCommand = false,
320+
ssr = !!config.build.ssr
268321
): DepOptimizationMetadata | undefined {
269322
const log = asCommand ? config.logger.info : debug
270323

@@ -274,7 +327,7 @@ export function loadCachedDepOptimizationMetadata(
274327
emptyDir(config.cacheDir)
275328
}
276329

277-
const depsCacheDir = getDepsCacheDir(config)
330+
const depsCacheDir = getDepsCacheDir(config, ssr)
278331

279332
if (!force) {
280333
let cachedMetadata: DepOptimizationMetadata | undefined
@@ -341,6 +394,15 @@ export async function initialProjectDependencies(
341394

342395
await addManuallyIncludedOptimizeDeps(deps, config)
343396

397+
return toDiscoveredDependencies(config, deps, !!config.build.ssr, timestamp)
398+
}
399+
400+
export function toDiscoveredDependencies(
401+
config: ResolvedConfig,
402+
deps: Record<string, string>,
403+
ssr: boolean,
404+
timestamp?: string
405+
): Record<string, OptimizedDepInfo> {
344406
const browserHash = getOptimizedBrowserHash(
345407
getDepHash(config),
346408
deps,
@@ -351,7 +413,7 @@ export async function initialProjectDependencies(
351413
const src = deps[id]
352414
discovered[id] = {
353415
id,
354-
file: getOptimizedDepPath(id, config),
416+
file: getOptimizedDepPath(id, config, ssr),
355417
src,
356418
browserHash: browserHash,
357419
exportsData: extractExportsData(src, config)
@@ -381,16 +443,17 @@ export function depsLogString(qualifiedIds: string[]): string {
381443
*/
382444
export async function runOptimizeDeps(
383445
resolvedConfig: ResolvedConfig,
384-
depsInfo: Record<string, OptimizedDepInfo>
446+
depsInfo: Record<string, OptimizedDepInfo>,
447+
ssr: boolean = !!resolvedConfig.build.ssr
385448
): Promise<DepOptimizationResult> {
386449
const isBuild = resolvedConfig.command === 'build'
387450
const config: ResolvedConfig = {
388451
...resolvedConfig,
389452
command: 'build'
390453
}
391454

392-
const depsCacheDir = getDepsCacheDir(resolvedConfig)
393-
const processingCacheDir = getProcessingDepsCacheDir(resolvedConfig)
455+
const depsCacheDir = getDepsCacheDir(resolvedConfig, ssr)
456+
const processingCacheDir = getProcessingDepsCacheDir(resolvedConfig, ssr)
394457

395458
// Create a temporal directory so we don't need to delete optimized deps
396459
// until they have been processed. This also avoids leaving the deps cache
@@ -526,7 +589,7 @@ export async function runOptimizeDeps(
526589
const id = path
527590
.relative(processingCacheDirOutputPath, o)
528591
.replace(jsExtensionRE, '')
529-
const file = getOptimizedDepPath(id, resolvedConfig)
592+
const file = getOptimizedDepPath(id, resolvedConfig, ssr)
530593
if (
531594
!findOptimizedDepInfoInRecord(
532595
metadata.optimized,
@@ -561,16 +624,18 @@ export async function findKnownImports(
561624

562625
async function addManuallyIncludedOptimizeDeps(
563626
deps: Record<string, string>,
564-
config: ResolvedConfig
627+
config: ResolvedConfig,
628+
extra?: string[],
629+
filter?: (id: string) => boolean
565630
): Promise<void> {
566-
const include = config.optimizeDeps?.include
631+
const include = [...(config.optimizeDeps?.include ?? []), ...(extra ?? [])]
567632
if (include) {
568633
const resolve = config.createResolver({ asSrc: false, scan: true })
569634
for (const id of include) {
570635
// normalize 'foo >bar` as 'foo > bar' to prevent same id being added
571636
// and for pretty printing
572637
const normalizedId = normalizeId(id)
573-
if (!deps[normalizedId]) {
638+
if (!deps[normalizedId] && filter?.(normalizedId) !== false) {
574639
const entry = await resolve(id)
575640
if (entry) {
576641
deps[normalizedId] = entry
@@ -603,49 +668,55 @@ export function depsFromOptimizedDepInfo(
603668

604669
export function getOptimizedDepPath(
605670
id: string,
606-
config: ResolvedConfig
671+
config: ResolvedConfig,
672+
ssr: boolean = !!config.build.ssr
607673
): string {
608674
return normalizePath(
609-
path.resolve(getDepsCacheDir(config), flattenId(id) + '.js')
675+
path.resolve(getDepsCacheDir(config, ssr), flattenId(id) + '.js')
610676
)
611677
}
612678

613-
function getDepsCacheSuffix(config: ResolvedConfig): string {
679+
function getDepsCacheSuffix(config: ResolvedConfig, ssr: boolean): string {
614680
let suffix = ''
615681
if (config.command === 'build') {
616682
// Differentiate build caches depending on outDir to allow parallel builds
617683
const { outDir } = config.build
618684
const buildId =
619685
outDir.length > 8 || outDir.includes('/') ? getHash(outDir) : outDir
620686
suffix += `_build-${buildId}`
621-
if (config.build.ssr) {
622-
suffix += '_ssr'
623-
}
687+
}
688+
if (ssr) {
689+
suffix += '_ssr'
624690
}
625691
return suffix
626692
}
627-
export function getDepsCacheDir(config: ResolvedConfig): string {
628-
const dirName = 'deps' + getDepsCacheSuffix(config)
629-
return normalizePath(path.resolve(config.cacheDir, dirName))
693+
694+
export function getDepsCacheDir(config: ResolvedConfig, ssr: boolean): string {
695+
return getDepsCacheDirPrefix(config) + getDepsCacheSuffix(config, ssr)
696+
}
697+
698+
function getProcessingDepsCacheDir(config: ResolvedConfig, ssr: boolean) {
699+
return (
700+
getDepsCacheDirPrefix(config) + getDepsCacheSuffix(config, ssr) + '_temp'
701+
)
630702
}
631703

632-
function getProcessingDepsCacheDir(config: ResolvedConfig) {
633-
const dirName = 'deps' + getDepsCacheSuffix(config) + '_temp'
634-
return normalizePath(path.resolve(config.cacheDir, dirName))
704+
export function getDepsCacheDirPrefix(config: ResolvedConfig): string {
705+
return normalizePath(path.resolve(config.cacheDir, 'deps'))
635706
}
636707

637708
export function isOptimizedDepFile(
638709
id: string,
639710
config: ResolvedConfig
640711
): boolean {
641-
return id.startsWith(getDepsCacheDir(config))
712+
return id.startsWith(getDepsCacheDirPrefix(config))
642713
}
643714

644715
export function createIsOptimizedDepUrl(
645716
config: ResolvedConfig
646717
): (url: string) => boolean {
647718
const { root } = config
648-
const depsCacheDir = getDepsCacheDir(config)
719+
const depsCacheDir = getDepsCacheDirPrefix(config)
649720

650721
// determine the url prefix of files inside cache directory
651722
const depsCacheDirRelative = normalizePath(path.relative(root, depsCacheDir))

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

+34-14
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,11 @@ import {
1818
isOptimizedDepFile,
1919
loadCachedDepOptimizationMetadata,
2020
newDepOptimizationProcessing,
21+
optimizeServerSsrDeps,
2122
runOptimizeDeps
2223
} from '.'
2324
import type {
25+
DepOptimizationMetadata,
2426
DepOptimizationProcessing,
2527
DepsOptimizer,
2628
OptimizedDepInfo
@@ -58,9 +60,18 @@ export async function initDepsOptimizer(
5860

5961
let handle: NodeJS.Timeout | undefined
6062

63+
let ssrServerDepsMetadata: DepOptimizationMetadata
64+
let _metadata =
65+
cachedMetadata || initDepsOptimizerMetadata(config, sessionTimestamp)
66+
6167
const depsOptimizer: DepsOptimizer = {
62-
metadata:
63-
cachedMetadata || initDepsOptimizerMetadata(config, sessionTimestamp),
68+
metadata: (options: { ssr: boolean }) => {
69+
if (isBuild || !options.ssr) {
70+
return _metadata
71+
} else {
72+
return ssrServerDepsMetadata
73+
}
74+
},
6475
registerMissingImport,
6576
run: () => debouncedProcessing(0),
6677
isOptimizedDepFile: (id: string) => isOptimizedDepFile(id, config),
@@ -75,6 +86,10 @@ export async function initDepsOptimizer(
7586

7687
depsOptimizerMap.set(config, depsOptimizer)
7788

89+
if (!isBuild && config.ssr) {
90+
ssrServerDepsMetadata = await optimizeServerSsrDeps(config)
91+
}
92+
7893
let newDepsDiscovered = false
7994

8095
let newDepsToLog: string[] = []
@@ -119,7 +134,7 @@ export async function initDepsOptimizer(
119134
config,
120135
sessionTimestamp
121136
)
122-
const { metadata } = depsOptimizer
137+
const metadata = _metadata
123138
for (const depInfo of Object.values(discovered)) {
124139
addOptimizedDepInfo(metadata, 'discovered', {
125140
...depInfo,
@@ -137,7 +152,7 @@ export async function initDepsOptimizer(
137152
try {
138153
debug(colors.green(`scanning for dependencies...`))
139154

140-
const { metadata } = depsOptimizer
155+
const metadata = _metadata
141156

142157
const discovered = await discoverProjectDependencies(
143158
config,
@@ -183,7 +198,7 @@ export async function initDepsOptimizer(
183198
// Ensure that a rerun will not be issued for current discovered deps
184199
if (handle) clearTimeout(handle)
185200

186-
if (Object.keys(depsOptimizer.metadata.discovered).length === 0) {
201+
if (Object.keys(_metadata.discovered).length === 0) {
187202
currentlyProcessing = false
188203
return
189204
}
@@ -193,13 +208,13 @@ export async function initDepsOptimizer(
193208
// a succesful completion of the optimizeDeps rerun will end up
194209
// creating new bundled version of all current and discovered deps
195210
// in the cache dir and a new metadata info object assigned
196-
// to optimizeDeps.metadata. A fullReload is only issued if
197-
// the previous bundled dependencies have changed.
211+
// to _metadata. A fullReload is only issued if the previous bundled
212+
// dependencies have changed.
198213

199-
// if the rerun fails, optimizeDeps.metadata remains untouched,
200-
// current discovered deps are cleaned, and a fullReload is issued
214+
// if the rerun fails, _metadata remains untouched, current discovered
215+
// deps are cleaned, and a fullReload is issued
201216

202-
let { metadata } = depsOptimizer
217+
let metadata = _metadata
203218

204219
// All deps, previous known and newly discovered are rebundled,
205220
// respect insertion order to keep the metadata file stable
@@ -306,7 +321,7 @@ export async function initDepsOptimizer(
306321
)
307322
}
308323

309-
metadata = depsOptimizer.metadata = newData
324+
metadata = _metadata = newData
310325
resolveEnqueuedProcessingPromises()
311326
}
312327

@@ -400,7 +415,7 @@ export async function initDepsOptimizer(
400415
// debounce time to wait for new missing deps finished, issue a new
401416
// optimization of deps (both old and newly found) once the previous
402417
// optimizeDeps processing is finished
403-
const deps = Object.keys(depsOptimizer.metadata.discovered)
418+
const deps = Object.keys(_metadata.discovered)
404419
const depsString = depsLogString(deps)
405420
debug(colors.green(`new dependencies found: ${depsString}`))
406421
runOptimizer()
@@ -426,7 +441,12 @@ export async function initDepsOptimizer(
426441
'Vite internal error: registering missing import before initial scanning is over'
427442
)
428443
}
429-
const { metadata } = depsOptimizer
444+
if (!isBuild && ssr) {
445+
config.logger.error(
446+
`Error: ${id} is a missing dependency in SSR dev server, it needs to be added to optimizeDeps.include`
447+
)
448+
}
449+
const metadata = _metadata
430450
const optimized = metadata.optimized[id]
431451
if (optimized) {
432452
return optimized
@@ -444,7 +464,7 @@ export async function initDepsOptimizer(
444464
newDepsDiscovered = true
445465
missing = addOptimizedDepInfo(metadata, 'discovered', {
446466
id,
447-
file: getOptimizedDepPath(id, config),
467+
file: getOptimizedDepPath(id, config, ssr),
448468
src: resolved,
449469
// Assing a browserHash to this missing dependency that is unique to
450470
// the current state of known + missing deps. If its optimizeDeps run

0 commit comments

Comments
 (0)
Please sign in to comment.