Skip to content

Commit

Permalink
feat: support cjs noExternal in SSR dev, fix #2579 (#8430)
Browse files Browse the repository at this point in the history
  • Loading branch information
patak-dev committed Jun 18, 2022
1 parent 993b842 commit 11d2191
Show file tree
Hide file tree
Showing 16 changed files with 207 additions and 79 deletions.
2 changes: 1 addition & 1 deletion packages/vite/src/node/build.ts
Expand Up @@ -733,7 +733,7 @@ async function cjsSsrResolveExternal(
): Promise<ExternalOption> {
// see if we have cached deps data available
let knownImports: string[] | undefined
const dataPath = path.join(getDepsCacheDir(config), '_metadata.json')
const dataPath = path.join(getDepsCacheDir(config, false), '_metadata.json')
try {
const data = JSON.parse(
fs.readFileSync(dataPath, 'utf-8')
Expand Down
125 changes: 98 additions & 27 deletions packages/vite/src/node/optimizer/index.ts
Expand Up @@ -6,8 +6,10 @@ import colors from 'picocolors'
import type { BuildOptions as EsbuildBuildOptions } from 'esbuild'
import { build } from 'esbuild'
import { init, parse } from 'es-module-lexer'
import { createFilter } from '@rollup/pluginutils'
import type { ResolvedConfig } from '../config'
import {
arraify,
createDebugger,
emptyDir,
flattenId,
Expand Down Expand Up @@ -43,10 +45,13 @@ export type ExportsData = {
}

export interface DepsOptimizer {
metadata: DepOptimizationMetadata
metadata: (options: { ssr: boolean }) => DepOptimizationMetadata
scanProcessing?: Promise<void>

registerMissingImport: (id: string, resolved: string) => OptimizedDepInfo
registerMissingImport: (
id: string,
resolved: string,
ssr?: boolean
) => OptimizedDepInfo
run: () => void

isOptimizedDepFile: (id: string) => boolean
Expand Down Expand Up @@ -232,6 +237,53 @@ export async function optimizeDeps(
return result.metadata
}

export async function optimizeServerSsrDeps(
config: ResolvedConfig
): Promise<DepOptimizationMetadata> {
const cachedMetadata = loadCachedDepOptimizationMetadata(
config,
config.optimizeDeps.force,
false,
true // ssr
)
if (cachedMetadata) {
return cachedMetadata
}

let alsoInclude: string[] | undefined
let noExternalFilter: ((id: unknown) => boolean) | undefined

const noExternal = config.ssr?.noExternal
if (noExternal) {
alsoInclude = arraify(noExternal).filter(
(ne) => typeof ne === 'string'
) as string[]
noExternalFilter =
noExternal === true
? (dep: unknown) => false
: createFilter(noExternal, config.optimizeDeps?.exclude, {
resolve: false
})
}

const deps: Record<string, string> = {}

await addManuallyIncludedOptimizeDeps(
deps,
config,
alsoInclude,
noExternalFilter
)

const depsInfo = toDiscoveredDependencies(config, deps, true)

const result = await runOptimizeDeps(config, depsInfo, true)

await result.commit()

return result.metadata
}

export function initDepsOptimizerMetadata(
config: ResolvedConfig,
timestamp?: string
Expand Down Expand Up @@ -264,7 +316,8 @@ export function addOptimizedDepInfo(
export function loadCachedDepOptimizationMetadata(
config: ResolvedConfig,
force = config.optimizeDeps.force,
asCommand = false
asCommand = false,
ssr = !!config.build.ssr
): DepOptimizationMetadata | undefined {
const log = asCommand ? config.logger.info : debug

Expand All @@ -274,7 +327,7 @@ export function loadCachedDepOptimizationMetadata(
emptyDir(config.cacheDir)
}

const depsCacheDir = getDepsCacheDir(config)
const depsCacheDir = getDepsCacheDir(config, ssr)

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

await addManuallyIncludedOptimizeDeps(deps, config)

return toDiscoveredDependencies(config, deps, !!config.build.ssr, timestamp)
}

export function toDiscoveredDependencies(
config: ResolvedConfig,
deps: Record<string, string>,
ssr: boolean,
timestamp?: string
): Record<string, OptimizedDepInfo> {
const browserHash = getOptimizedBrowserHash(
getDepHash(config),
deps,
Expand All @@ -351,7 +413,7 @@ export async function initialProjectDependencies(
const src = deps[id]
discovered[id] = {
id,
file: getOptimizedDepPath(id, config),
file: getOptimizedDepPath(id, config, ssr),
src,
browserHash: browserHash,
exportsData: extractExportsData(src, config)
Expand Down Expand Up @@ -381,16 +443,17 @@ export function depsLogString(qualifiedIds: string[]): string {
*/
export async function runOptimizeDeps(
resolvedConfig: ResolvedConfig,
depsInfo: Record<string, OptimizedDepInfo>
depsInfo: Record<string, OptimizedDepInfo>,
ssr: boolean = !!resolvedConfig.build.ssr
): Promise<DepOptimizationResult> {
const isBuild = resolvedConfig.command === 'build'
const config: ResolvedConfig = {
...resolvedConfig,
command: 'build'
}

const depsCacheDir = getDepsCacheDir(resolvedConfig)
const processingCacheDir = getProcessingDepsCacheDir(resolvedConfig)
const depsCacheDir = getDepsCacheDir(resolvedConfig, ssr)
const processingCacheDir = getProcessingDepsCacheDir(resolvedConfig, ssr)

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

async function addManuallyIncludedOptimizeDeps(
deps: Record<string, string>,
config: ResolvedConfig
config: ResolvedConfig,
extra?: string[],
filter?: (id: string) => boolean
): Promise<void> {
const include = config.optimizeDeps?.include
const include = [...(config.optimizeDeps?.include ?? []), ...(extra ?? [])]
if (include) {
const resolve = config.createResolver({ asSrc: false, scan: true })
for (const id of include) {
// normalize 'foo >bar` as 'foo > bar' to prevent same id being added
// and for pretty printing
const normalizedId = normalizeId(id)
if (!deps[normalizedId]) {
if (!deps[normalizedId] && filter?.(normalizedId) !== false) {
const entry = await resolve(id)
if (entry) {
deps[normalizedId] = entry
Expand Down Expand Up @@ -603,49 +668,55 @@ export function depsFromOptimizedDepInfo(

export function getOptimizedDepPath(
id: string,
config: ResolvedConfig
config: ResolvedConfig,
ssr: boolean = !!config.build.ssr
): string {
return normalizePath(
path.resolve(getDepsCacheDir(config), flattenId(id) + '.js')
path.resolve(getDepsCacheDir(config, ssr), flattenId(id) + '.js')
)
}

function getDepsCacheSuffix(config: ResolvedConfig): string {
function getDepsCacheSuffix(config: ResolvedConfig, ssr: boolean): string {
let suffix = ''
if (config.command === 'build') {
// Differentiate build caches depending on outDir to allow parallel builds
const { outDir } = config.build
const buildId =
outDir.length > 8 || outDir.includes('/') ? getHash(outDir) : outDir
suffix += `_build-${buildId}`
if (config.build.ssr) {
suffix += '_ssr'
}
}
if (ssr) {
suffix += '_ssr'
}
return suffix
}
export function getDepsCacheDir(config: ResolvedConfig): string {
const dirName = 'deps' + getDepsCacheSuffix(config)
return normalizePath(path.resolve(config.cacheDir, dirName))

export function getDepsCacheDir(config: ResolvedConfig, ssr: boolean): string {
return getDepsCacheDirPrefix(config) + getDepsCacheSuffix(config, ssr)
}

function getProcessingDepsCacheDir(config: ResolvedConfig, ssr: boolean) {
return (
getDepsCacheDirPrefix(config) + getDepsCacheSuffix(config, ssr) + '_temp'
)
}

function getProcessingDepsCacheDir(config: ResolvedConfig) {
const dirName = 'deps' + getDepsCacheSuffix(config) + '_temp'
return normalizePath(path.resolve(config.cacheDir, dirName))
export function getDepsCacheDirPrefix(config: ResolvedConfig): string {
return normalizePath(path.resolve(config.cacheDir, 'deps'))
}

export function isOptimizedDepFile(
id: string,
config: ResolvedConfig
): boolean {
return id.startsWith(getDepsCacheDir(config))
return id.startsWith(getDepsCacheDirPrefix(config))
}

export function createIsOptimizedDepUrl(
config: ResolvedConfig
): (url: string) => boolean {
const { root } = config
const depsCacheDir = getDepsCacheDir(config)
const depsCacheDir = getDepsCacheDirPrefix(config)

// determine the url prefix of files inside cache directory
const depsCacheDirRelative = normalizePath(path.relative(root, depsCacheDir))
Expand Down
48 changes: 34 additions & 14 deletions packages/vite/src/node/optimizer/optimizer.ts
Expand Up @@ -18,9 +18,11 @@ import {
isOptimizedDepFile,
loadCachedDepOptimizationMetadata,
newDepOptimizationProcessing,
optimizeServerSsrDeps,
runOptimizeDeps
} from '.'
import type {
DepOptimizationMetadata,
DepOptimizationProcessing,
DepsOptimizer,
OptimizedDepInfo
Expand Down Expand Up @@ -58,9 +60,18 @@ export async function initDepsOptimizer(

let handle: NodeJS.Timeout | undefined

let ssrServerDepsMetadata: DepOptimizationMetadata
let _metadata =
cachedMetadata || initDepsOptimizerMetadata(config, sessionTimestamp)

const depsOptimizer: DepsOptimizer = {
metadata:
cachedMetadata || initDepsOptimizerMetadata(config, sessionTimestamp),
metadata: (options: { ssr: boolean }) => {
if (isBuild || !options.ssr) {
return _metadata
} else {
return ssrServerDepsMetadata
}
},
registerMissingImport,
run: () => debouncedProcessing(0),
isOptimizedDepFile: (id: string) => isOptimizedDepFile(id, config),
Expand All @@ -75,6 +86,10 @@ export async function initDepsOptimizer(

depsOptimizerMap.set(config, depsOptimizer)

if (!isBuild && config.ssr) {
ssrServerDepsMetadata = await optimizeServerSsrDeps(config)
}

let newDepsDiscovered = false

let newDepsToLog: string[] = []
Expand Down Expand Up @@ -119,7 +134,7 @@ export async function initDepsOptimizer(
config,
sessionTimestamp
)
const { metadata } = depsOptimizer
const metadata = _metadata
for (const depInfo of Object.values(discovered)) {
addOptimizedDepInfo(metadata, 'discovered', {
...depInfo,
Expand All @@ -137,7 +152,7 @@ export async function initDepsOptimizer(
try {
debug(colors.green(`scanning for dependencies...`))

const { metadata } = depsOptimizer
const metadata = _metadata

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

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

// if the rerun fails, optimizeDeps.metadata remains untouched,
// current discovered deps are cleaned, and a fullReload is issued
// if the rerun fails, _metadata remains untouched, current discovered
// deps are cleaned, and a fullReload is issued

let { metadata } = depsOptimizer
let metadata = _metadata

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

metadata = depsOptimizer.metadata = newData
metadata = _metadata = newData
resolveEnqueuedProcessingPromises()
}

Expand Down Expand Up @@ -400,7 +415,7 @@ export async function initDepsOptimizer(
// debounce time to wait for new missing deps finished, issue a new
// optimization of deps (both old and newly found) once the previous
// optimizeDeps processing is finished
const deps = Object.keys(depsOptimizer.metadata.discovered)
const deps = Object.keys(_metadata.discovered)
const depsString = depsLogString(deps)
debug(colors.green(`new dependencies found: ${depsString}`))
runOptimizer()
Expand All @@ -426,7 +441,12 @@ export async function initDepsOptimizer(
'Vite internal error: registering missing import before initial scanning is over'
)
}
const { metadata } = depsOptimizer
if (!isBuild && ssr) {
config.logger.error(
`Error: ${id} is a missing dependency in SSR dev server, it needs to be added to optimizeDeps.include`
)
}
const metadata = _metadata
const optimized = metadata.optimized[id]
if (optimized) {
return optimized
Expand All @@ -444,7 +464,7 @@ export async function initDepsOptimizer(
newDepsDiscovered = true
missing = addOptimizedDepInfo(metadata, 'discovered', {
id,
file: getOptimizedDepPath(id, config),
file: getOptimizedDepPath(id, config, ssr),
src: resolved,
// Assing a browserHash to this missing dependency that is unique to
// the current state of known + missing deps. If its optimizeDeps run
Expand Down

0 comments on commit 11d2191

Please sign in to comment.