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

fix: optimize deps on dev SSR, builtin imports in node #8854

Merged
merged 5 commits into from Jun 29, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 7 additions & 2 deletions packages/vite/src/node/optimizer/esbuildDepPlugin.ts
Expand Up @@ -5,6 +5,7 @@ import { KNOWN_ASSET_TYPES } from '../constants'
import type { ResolvedConfig } from '..'
import {
flattenId,
isBuiltin,
isExternalUrl,
isRunningWithYarnPnp,
moduleListContains,
Expand Down Expand Up @@ -42,7 +43,8 @@ const externalTypes = [
export function esbuildDepPlugin(
qualified: Record<string, string>,
exportsData: Record<string, ExportsData>,
config: ResolvedConfig
config: ResolvedConfig,
ssr: boolean
): Plugin {
// remove optimizable extensions from `externalTypes` list
const allExternalTypes = config.optimizeDeps.extensions
Expand Down Expand Up @@ -77,7 +79,7 @@ export function esbuildDepPlugin(
_importer = importer in qualified ? qualified[importer] : importer
}
const resolver = kind.startsWith('require') ? _resolveRequire : _resolve
return resolver(id, _importer, undefined)
return resolver(id, _importer, undefined, ssr)
}

const resolveResult = (id: string, resolved: string) => {
Expand All @@ -87,6 +89,9 @@ export function esbuildDepPlugin(
namespace: 'browser-external'
}
}
if (ssr && isBuiltin(resolved)) {
return
}
if (isExternalUrl(resolved)) {
return {
path: resolved,
Expand Down
21 changes: 17 additions & 4 deletions packages/vite/src/node/optimizer/index.ts
Expand Up @@ -25,7 +25,11 @@ import { transformWithEsbuild } from '../plugins/esbuild'
import { ESBUILD_MODULES_TARGET } from '../constants'
import { esbuildDepPlugin } from './esbuildDepPlugin'
import { scanImports } from './scan'
export { initDepsOptimizer, getDepsOptimizer } from './optimizer'
export {
initDepsOptimizer,
initDevSsrDepsOptimizer,
getDepsOptimizer
} from './optimizer'

export const debuggerViteDeps = createDebugger('vite:deps')
const debug = debuggerViteDeps
Expand All @@ -46,7 +50,7 @@ export type ExportsData = {
}

export interface DepsOptimizer {
metadata: (options: { ssr: boolean }) => DepOptimizationMetadata
metadata: DepOptimizationMetadata
scanProcessing?: Promise<void>
registerMissingImport: (
id: string,
Expand Down Expand Up @@ -545,6 +549,9 @@ export async function runOptimizeDeps(
: JSON.stringify(process.env.NODE_ENV || config.mode)
}

const platform =
ssr && config.ssr?.target !== 'webworker' ? 'node' : 'browser'

const start = performance.now()

const result = await build({
Expand All @@ -554,9 +561,15 @@ export async function runOptimizeDeps(
// We can't use platform 'neutral', as esbuild has custom handling
// when the platform is 'node' or 'browser' that can't be emulated
// by using mainFields and conditions
platform: ssr && config.ssr?.target !== 'webworker' ? 'node' : 'browser',
platform,
define,
format: 'esm',
banner:
platform === 'node'
? {
js: `import { createRequire } from 'module';const require = createRequire(import.meta.url);`
}
: undefined,
Comment on lines +569 to +574
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can put a comment above about why this is needed.

target: isBuild ? config.build.target || undefined : ESBUILD_MODULES_TARGET,
external: config.optimizeDeps?.exclude,
logLevel: 'error',
Expand All @@ -567,7 +580,7 @@ export async function runOptimizeDeps(
metafile: true,
plugins: [
...plugins,
esbuildDepPlugin(flatIdDeps, flatIdToExports, config)
esbuildDepPlugin(flatIdDeps, flatIdToExports, config, ssr)
],
...esbuildOptions,
supported: {
Expand Down
84 changes: 55 additions & 29 deletions packages/vite/src/node/optimizer/optimizer.ts
Expand Up @@ -24,7 +24,6 @@ import {
runOptimizeDeps
} from '.'
import type {
DepOptimizationMetadata,
DepOptimizationProcessing,
DepsOptimizer,
OptimizedDepInfo
Expand All @@ -39,18 +38,36 @@ const isDebugEnabled = _debug('vite:deps').enabled
const debounceMs = 100

const depsOptimizerMap = new WeakMap<ResolvedConfig, DepsOptimizer>()
const devSsrDepsOptimizerMap = new WeakMap<ResolvedConfig, DepsOptimizer>()
bluwy marked this conversation as resolved.
Show resolved Hide resolved

export function getDepsOptimizer(
config: ResolvedConfig
config: ResolvedConfig,
type: { ssr?: boolean }
): DepsOptimizer | undefined {
// Workers compilation shares the DepsOptimizer from the main build
return depsOptimizerMap.get(config.mainConfig || config)
const isDevSsr = type.ssr && config.command !== 'build'
return (isDevSsr ? devSsrDepsOptimizerMap : depsOptimizerMap).get(
config.mainConfig || config
)
}

export async function initDepsOptimizer(
config: ResolvedConfig,
server?: ViteDevServer
): Promise<DepsOptimizer> {
): Promise<void> {
await createDepsOptimizer(config, server)
}

export async function initDevSsrDepsOptimizer(
config: ResolvedConfig
): Promise<void> {
await createDevSsrDepsOptimizer(config)
}

async function createDepsOptimizer(
config: ResolvedConfig,
server?: ViteDevServer
): Promise<void> {
const { logger } = config
const isBuild = config.command === 'build'

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

let handle: NodeJS.Timeout | undefined

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

const depsOptimizer: DepsOptimizer = {
metadata: (options: { ssr: boolean }) => {
if (isBuild || !options.ssr) {
return _metadata
} else {
return ssrServerDepsMetadata
}
},
metadata,
registerMissingImport,
run: () => debouncedProcessing(0),
isOptimizedDepFile: (id: string) => isOptimizedDepFile(id, config),
Expand All @@ -89,10 +99,6 @@ 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 @@ -137,7 +143,6 @@ export async function initDepsOptimizer(
config,
sessionTimestamp
)
const metadata = _metadata
for (const depInfo of Object.values(discovered)) {
addOptimizedDepInfo(metadata, 'discovered', {
...depInfo,
Expand All @@ -155,8 +160,6 @@ export async function initDepsOptimizer(
try {
debug(colors.green(`scanning for dependencies...`))

const metadata = _metadata

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

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

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 @@ -324,7 +325,7 @@ export async function initDepsOptimizer(
)
}

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

Expand Down Expand Up @@ -418,7 +419,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(_metadata.discovered)
const deps = Object.keys(metadata.discovered)
const depsString = depsLogString(deps)
debug(colors.green(`new dependencies found: ${depsString}`))
runOptimizer()
Expand Down Expand Up @@ -449,7 +450,6 @@ export async function initDepsOptimizer(
`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 Down Expand Up @@ -539,7 +539,7 @@ export async function initDepsOptimizer(
if (!firstRunEnsured && !firstRunCalled && registeredIds.length === 0) {
setTimeout(() => {
if (!firstRunCalled && registeredIds.length === 0) {
getDepsOptimizer(config)?.run()
debouncedProcessing(0) // queue the optimizer run
}
}, runOptimizerIfIdleAfterMs)
}
Expand Down Expand Up @@ -580,7 +580,7 @@ export async function initDepsOptimizer(
if (registeredIds.length > 0) {
runOptimizerWhenIdle()
} else {
getDepsOptimizer(config)?.run()
debouncedProcessing(0) // queue the optimizer run
}
}
}
Expand All @@ -596,8 +596,34 @@ export async function initDepsOptimizer(
}
}
}
}

return depsOptimizer
async function createDevSsrDepsOptimizer(
config: ResolvedConfig
): Promise<void> {
const metadata = await optimizeServerSsrDeps(config)
const depsOptimizer = {
metadata,
isOptimizedDepFile: (id: string) => isOptimizedDepFile(id, config),
isOptimizedDepUrl: createIsOptimizedDepUrl(config),
getOptimizedDepId: (depInfo: OptimizedDepInfo) =>
`${depInfo.file}?v=${depInfo.browserHash}`,

registerMissingImport: () => {
throw new Error(
'Vite Internal Error: registerMissingImport is not supported in dev SSR'
)
},
// noop, there is no scanning during dev SSR
// the optimizer blocks the server start
run: () => {},
registerWorkersSource: (id: string) => {},
delayDepsOptimizerUntil: (id: string, done: () => Promise<any>) => {},
resetRegisteredIds: () => {},
ensureFirstRun: () => {},
options: config.optimizeDeps
}
devSsrDepsOptimizerMap.set(config, depsOptimizer)
}

export async function preTransformOptimizeDepsEntries(
Expand Down
7 changes: 3 additions & 4 deletions packages/vite/src/node/plugins/importAnalysis.ts
Expand Up @@ -214,7 +214,7 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin {
)
}

const depsOptimizer = getDepsOptimizer(config)
const depsOptimizer = getDepsOptimizer(config, { ssr })

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

const needsInterop = await optimizedDepNeedsInterop(
depsOptimizer.metadata({ ssr }),
depsOptimizer.metadata,
file,
config
)
Expand Down
7 changes: 3 additions & 4 deletions packages/vite/src/node/plugins/importAnalysisBuild.ts
Expand Up @@ -154,7 +154,7 @@ export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin {
}

const { root } = config
const depsOptimizer = getDepsOptimizer(config)
const depsOptimizer = getDepsOptimizer(config, { ssr })

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

const needsInterop = await optimizedDepNeedsInterop(
depsOptimizer.metadata({ ssr }),
depsOptimizer.metadata,
file,
config
)
Expand Down
3 changes: 2 additions & 1 deletion packages/vite/src/node/plugins/index.ts
Expand Up @@ -62,7 +62,8 @@ export async function resolvePlugins(
packageCache: config.packageCache,
ssrConfig: config.ssr,
asSrc: true,
getDepsOptimizer: () => getDepsOptimizer(config),
getDepsOptimizer: (type: { ssr?: boolean }) =>
getDepsOptimizer(config, type),
shouldExternalize:
isBuild && config.build.ssr && config.ssr?.format !== 'cjs'
? (id) => shouldExternalizeForSSR(id, config)
Expand Down