Skip to content

Commit ff92f2f

Browse files
authored
fix: avoid temporal optimize deps dirs (#12582)
1 parent 24b91d5 commit ff92f2f

File tree

3 files changed

+66
-203
lines changed

3 files changed

+66
-203
lines changed

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

Lines changed: 66 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,8 @@ import {
2020
lookupFile,
2121
normalizeId,
2222
normalizePath,
23-
removeDir,
2423
removeLeadingSlash,
25-
renameDir,
2624
tryStatSync,
27-
writeFile,
2825
} from '../utils'
2926
import { transformWithEsbuild } from '../plugins/esbuild'
3027
import { ESBUILD_MODULES_TARGET } from '../constants'
@@ -164,6 +161,9 @@ export interface DepOptimizationResult {
164161
* to be able to discard the result
165162
*/
166163
commit: () => Promise<void>
164+
/**
165+
* @deprecated noop
166+
*/
167167
cancel: () => void
168168
}
169169

@@ -474,23 +474,6 @@ export function runOptimizeDeps(
474474
}
475475

476476
const depsCacheDir = getDepsCacheDir(resolvedConfig, ssr)
477-
const processingCacheDir = getProcessingDepsCacheDir(resolvedConfig, ssr)
478-
479-
// Create a temporal directory so we don't need to delete optimized deps
480-
// until they have been processed. This also avoids leaving the deps cache
481-
// directory in a corrupted state if there is an error
482-
if (fs.existsSync(processingCacheDir)) {
483-
emptyDir(processingCacheDir)
484-
} else {
485-
fs.mkdirSync(processingCacheDir, { recursive: true })
486-
}
487-
488-
// a hint for Node.js
489-
// all files in the cache directory should be recognized as ES modules
490-
writeFile(
491-
path.resolve(processingCacheDir, 'package.json'),
492-
JSON.stringify({ type: 'module' }),
493-
)
494477

495478
const metadata = initDepsOptimizerMetadata(config, ssr)
496479

@@ -505,38 +488,16 @@ export function runOptimizeDeps(
505488

506489
const qualifiedIds = Object.keys(depsInfo)
507490

508-
let cleaned = false
509-
const cleanUp = () => {
510-
if (!cleaned) {
511-
cleaned = true
512-
// No need to wait, we can clean up in the background because temp folders
513-
// are unique per run
514-
fsp.rm(processingCacheDir, { recursive: true, force: true }).catch(() => {
515-
// Ignore errors
516-
})
517-
}
518-
}
519-
const createProcessingResult = () => ({
491+
const createEmptyProcessingResult = () => ({
520492
metadata,
521-
async commit() {
522-
if (cleaned) {
523-
throw new Error(
524-
`Vite Internal Error: Can't commit optimizeDeps processing result, it has already been cancelled.`,
525-
)
526-
}
527-
// Write metadata file, delete `deps` folder and rename the `processing` folder to `deps`
528-
// Processing is done, we can now replace the depsCacheDir with processingCacheDir
529-
// Rewire the file paths from the temporal processing dir to the final deps cache dir
530-
await removeDir(depsCacheDir)
531-
await renameDir(processingCacheDir, depsCacheDir)
532-
},
533-
cancel: cleanUp,
493+
commit: async () => {},
494+
cancel: async () => {},
534495
})
535496

536497
if (!qualifiedIds.length) {
537498
return {
538-
cancel: async () => cleanUp(),
539-
result: Promise.resolve(createProcessingResult()),
499+
result: Promise.resolve(createEmptyProcessingResult()),
500+
cancel: async () => {},
540501
}
541502
}
542503

@@ -546,19 +507,19 @@ export function runOptimizeDeps(
546507
resolvedConfig,
547508
depsInfo,
548509
ssr,
549-
processingCacheDir,
510+
depsCacheDir,
550511
optimizerContext,
551512
)
552513

553-
const result = preparedRun.then(({ context, idToExports }) => {
514+
const runResult = preparedRun.then(({ context, idToExports }) => {
554515
function disposeContext() {
555516
return context?.dispose().catch((e) => {
556517
config.logger.error('Failed to dispose esbuild context', { error: e })
557518
})
558519
}
559520
if (!context || optimizerContext.cancelled) {
560521
disposeContext()
561-
return createProcessingResult()
522+
return createEmptyProcessingResult()
562523
}
563524

564525
return context
@@ -569,15 +530,11 @@ export function runOptimizeDeps(
569530
// the paths in `meta.outputs` are relative to `process.cwd()`
570531
const processingCacheDirOutputPath = path.relative(
571532
process.cwd(),
572-
processingCacheDir,
533+
depsCacheDir,
573534
)
574535

575536
for (const id in depsInfo) {
576-
const output = esbuildOutputFromId(
577-
meta.outputs,
578-
id,
579-
processingCacheDir,
580-
)
537+
const output = esbuildOutputFromId(meta.outputs, id, depsCacheDir)
581538

582539
const { exportsData, ...info } = depsInfo[id]
583540
addOptimizedDepInfo(metadata, 'optimized', {
@@ -624,23 +581,64 @@ export function runOptimizeDeps(
624581
}
625582
}
626583

627-
const dataPath = path.join(processingCacheDir, '_metadata.json')
628-
writeFile(
629-
dataPath,
630-
stringifyDepsOptimizerMetadata(metadata, depsCacheDir),
631-
)
632-
633584
debug(
634585
`Dependencies bundled in ${(performance.now() - start).toFixed(2)}ms`,
635586
)
636587

637-
return createProcessingResult()
588+
return {
589+
metadata,
590+
async commit() {
591+
// Write this run of pre-bundled dependencies to the deps cache
592+
593+
// Get a list of old files in the deps directory to delete the stale ones
594+
const oldFilesPaths: string[] = []
595+
if (!fs.existsSync(depsCacheDir)) {
596+
fs.mkdirSync(depsCacheDir, { recursive: true })
597+
} else {
598+
oldFilesPaths.push(
599+
...(await fsp.readdir(depsCacheDir)).map((f) =>
600+
path.join(depsCacheDir, f),
601+
),
602+
)
603+
}
604+
605+
const newFilesPaths = new Set<string>()
606+
const files: Promise<void>[] = []
607+
const write = (filePath: string, content: string) => {
608+
newFilesPaths.add(filePath)
609+
files.push(fsp.writeFile(filePath, content))
610+
}
611+
612+
// a hint for Node.js
613+
// all files in the cache directory should be recognized as ES modules
614+
write(
615+
path.resolve(depsCacheDir, 'package.json'),
616+
'{\n "type": "module"\n}\n',
617+
)
618+
619+
write(
620+
path.join(depsCacheDir, '_metadata.json'),
621+
stringifyDepsOptimizerMetadata(metadata, depsCacheDir),
622+
)
623+
624+
for (const outputFile of result.outputFiles!)
625+
write(outputFile.path, outputFile.text)
626+
627+
// Clean up old files in the background
628+
for (const filePath of oldFilesPaths)
629+
if (!newFilesPaths.has(filePath)) fsp.unlink(filePath)
630+
631+
await Promise.all(files)
632+
},
633+
cancel: () => {},
634+
}
638635
})
636+
639637
.catch((e) => {
640638
if (e.errors && e.message.includes('The build was canceled')) {
641639
// esbuild logs an error when cancelling, but this is expected so
642640
// return an empty result instead
643-
return createProcessingResult()
641+
return createEmptyProcessingResult()
644642
}
645643
throw e
646644
})
@@ -649,18 +647,13 @@ export function runOptimizeDeps(
649647
})
650648
})
651649

652-
result.catch(() => {
653-
cleanUp()
654-
})
655-
656650
return {
657651
async cancel() {
658652
optimizerContext.cancelled = true
659653
const { context } = await preparedRun
660654
await context?.cancel()
661-
cleanUp()
662655
},
663-
result,
656+
result: runResult,
664657
}
665658
}
666659

@@ -760,6 +753,9 @@ async function prepareEsbuildOptimizerRun(
760753
absWorkingDir: process.cwd(),
761754
entryPoints: Object.keys(flatIdDeps),
762755
bundle: true,
756+
// Don't write to disk, we'll only write the files if the build isn't invalidated
757+
// by newly discovered dependencies
758+
write: false,
763759
// We can't use platform 'neutral', as esbuild has custom handling
764760
// when the platform is 'node' or 'browser' that can't be emulated
765761
// by using mainFields and conditions
@@ -934,15 +930,6 @@ export function getDepsCacheDir(config: ResolvedConfig, ssr: boolean): string {
934930
return getDepsCacheDirPrefix(config) + getDepsCacheSuffix(config, ssr)
935931
}
936932

937-
function getProcessingDepsCacheDir(config: ResolvedConfig, ssr: boolean) {
938-
return (
939-
getDepsCacheDirPrefix(config) +
940-
getDepsCacheSuffix(config, ssr) +
941-
'_temp_' +
942-
getHash(Date.now().toString())
943-
)
944-
}
945-
946933
export function getDepsCacheDirPrefix(config: ResolvedConfig): string {
947934
return normalizePath(path.resolve(config.cacheDir, 'deps'))
948935
}
@@ -1305,29 +1292,3 @@ export async function optimizedDepNeedsInterop(
13051292
}
13061293
return depInfo?.needsInterop
13071294
}
1308-
1309-
const MAX_TEMP_DIR_AGE_MS = 24 * 60 * 60 * 1000
1310-
export async function cleanupDepsCacheStaleDirs(
1311-
config: ResolvedConfig,
1312-
): Promise<void> {
1313-
try {
1314-
const cacheDir = path.resolve(config.cacheDir)
1315-
if (fs.existsSync(cacheDir)) {
1316-
const dirents = await fsp.readdir(cacheDir, { withFileTypes: true })
1317-
for (const dirent of dirents) {
1318-
if (dirent.isDirectory() && dirent.name.includes('_temp_')) {
1319-
const tempDirPath = path.resolve(config.cacheDir, dirent.name)
1320-
const stats = await fsp.stat(tempDirPath).catch((_) => null)
1321-
if (
1322-
stats?.mtime &&
1323-
Date.now() - stats.mtime.getTime() > MAX_TEMP_DIR_AGE_MS
1324-
) {
1325-
await removeDir(tempDirPath)
1326-
}
1327-
}
1328-
}
1329-
}
1330-
} catch (err) {
1331-
config.logger.error(err)
1332-
}
1333-
}

packages/vite/src/node/server/index.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ import { cjsSsrResolveExternals } from '../ssr/ssrExternal'
3535
import { ssrFixStacktrace, ssrRewriteStacktrace } from '../ssr/ssrStacktrace'
3636
import { ssrTransform } from '../ssr/ssrTransform'
3737
import {
38-
cleanupDepsCacheStaleDirs,
3938
getDepsOptimizer,
4039
initDepsOptimizer,
4140
initDevSsrDepsOptimizer,
@@ -693,10 +692,6 @@ export async function createServer(
693692
await initServer()
694693
}
695694

696-
// Fire a clean up of stale cache dirs, in case old processes didn't
697-
// terminate correctly. Don't await this promise
698-
cleanupDepsCacheStaleDirs(config)
699-
700695
return server
701696
}
702697

0 commit comments

Comments
 (0)