diff --git a/packages/vite/src/node/optimizer/index.ts b/packages/vite/src/node/optimizer/index.ts index 9289c5327feb69..fde636e21363ca 100644 --- a/packages/vite/src/node/optimizer/index.ts +++ b/packages/vite/src/node/optimizer/index.ts @@ -272,6 +272,10 @@ export interface DepOptimizationMetadata { * OptimizedDepInfo list */ depInfoList: OptimizedDepInfo[] + /** + * Directories and files to watch for changes + */ + watchFiles?: string[] } /** @@ -728,6 +732,29 @@ export function runOptimizeDeps( } } + // Gather directories of linked monorepo packages to watch for changes + metadata.watchFiles ??= [] + for (const depInfo of Object.values(metadata.optimized)) { + if (depInfo.src) { + try { + const realPath = fs.realpathSync(depInfo.src) + if (!realPath.includes('node_modules')) { + const pkgPath = lookupFile(path.dirname(realPath), [ + 'package.json', + ]) + if (pkgPath) { + const pkgDir = path.dirname(pkgPath) + if (!metadata.watchFiles.includes(pkgDir)) { + metadata.watchFiles.push(pkgDir) + } + } + } + } catch (_e) { + // Ignore + } + } + } + clearTimeout(bundleTimer) debug?.( @@ -1014,15 +1041,22 @@ function parseDepsOptimizerMetadata( jsonMetadata: string, depsCacheDir: string, ): DepOptimizationMetadata | undefined { - const { hash, lockfileHash, configHash, browserHash, optimized, chunks } = - JSON.parse(jsonMetadata, (key: string, value: string) => { - // Paths can be absolute or relative to the deps cache dir where - // the _metadata.json is located - if (key === 'file' || key === 'src') { - return normalizePath(path.resolve(depsCacheDir, value)) - } - return value - }) + const { + hash, + lockfileHash, + configHash, + browserHash, + optimized, + chunks, + watchFiles, + } = JSON.parse(jsonMetadata, (key: string, value: string) => { + // Paths can be absolute or relative to the deps cache dir where + // the _metadata.json is located + if (key === 'file' || key === 'src') { + return normalizePath(path.resolve(depsCacheDir, value)) + } + return value + }) if ( !chunks || Object.values(optimized).some((depInfo: any) => !depInfo.fileHash) @@ -1039,6 +1073,7 @@ function parseDepsOptimizerMetadata( discovered: {}, chunks: {}, depInfoList: [], + watchFiles: watchFiles || [], } for (const id of Object.keys(optimized)) { addOptimizedDepInfo(metadata, 'optimized', { @@ -1068,14 +1103,22 @@ function stringifyDepsOptimizerMetadata( metadata: DepOptimizationMetadata, depsCacheDir: string, ) { - const { hash, configHash, lockfileHash, browserHash, optimized, chunks } = - metadata + const { + hash, + configHash, + lockfileHash, + browserHash, + optimized, + chunks, + watchFiles, + } = metadata return JSON.stringify( { hash, configHash, lockfileHash, browserHash, + watchFiles, optimized: Object.fromEntries( Object.values(optimized).map( ({ id, src, file, fileHash, needsInterop, isDynamicEntry }) => [ diff --git a/packages/vite/src/node/server/environment.ts b/packages/vite/src/node/server/environment.ts index b76ad894525bc7..19a5ad9007e924 100644 --- a/packages/vite/src/node/server/environment.ts +++ b/packages/vite/src/node/server/environment.ts @@ -271,6 +271,11 @@ export class DevEnvironment extends BaseEnvironment { async listen(server: ViteDevServer): Promise { this.hot.listen() await Promise.all([this.bundledDev?.listen(), this.depsOptimizer?.init()]) + + if (this.depsOptimizer?.metadata.watchFiles) { + server.watcher.add(this.depsOptimizer.metadata.watchFiles) + } + warmupFiles(server, this) } diff --git a/packages/vite/src/node/server/index.ts b/packages/vite/src/node/server/index.ts index c93bbb6eb1e2d5..e00a5cfa3ba81f 100644 --- a/packages/vite/src/node/server/index.ts +++ b/packages/vite/src/node/server/index.ts @@ -903,8 +903,34 @@ export async function _createServer( } } + const checkOptimizedDepSource = async (file: string) => { + let isOptimizedDepSource = false + for (const environment of Object.values(server.environments)) { + if ( + environment.depsOptimizer?.metadata.watchFiles?.some( + (id) => file === id || file.startsWith(id + '/'), + ) + ) { + isOptimizedDepSource = true + break + } + } + if (isOptimizedDepSource) { + server.config.logger.info( + colors.green( + `optimized dependency source changed, restarting server...`, + ), + { clear: true, timestamp: true }, + ) + await server.restart(true) + return true + } + return false + } + const onFileAddUnlink = async (file: string, isUnlink: boolean) => { file = normalizePath(file) + if (await checkOptimizedDepSource(file)) return reloadOnTsconfigChange(server, file) await Promise.all( @@ -943,6 +969,7 @@ export async function _createServer( const onFileChange = async (file: string) => { file = normalizePath(file) + if (await checkOptimizedDepSource(file)) return reloadOnTsconfigChange(server, file) await Promise.all(