Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support cjs noExternal in SSR dev, fix #2579 #8430

Merged
merged 6 commits into from
Jun 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/vite/src/node/build.ts
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Loading