From e5fe1c65203a3509e096baeed1ef176d21f93851 Mon Sep 17 00:00:00 2001 From: patak Date: Wed, 4 May 2022 18:35:58 +0200 Subject: [PATCH] fix: optimized processing folder renaming in win (fix #7939) (#8019) --- packages/vite/src/node/optimizer/index.ts | 23 ++++----- .../src/node/optimizer/registerMissing.ts | 8 ++-- packages/vite/src/node/utils.ts | 48 +++++++++++++++++++ 3 files changed, 61 insertions(+), 18 deletions(-) diff --git a/packages/vite/src/node/optimizer/index.ts b/packages/vite/src/node/optimizer/index.ts index 4e4fb5a8c895ae..03df61ae3e3ac2 100644 --- a/packages/vite/src/node/optimizer/index.ts +++ b/packages/vite/src/node/optimizer/index.ts @@ -13,7 +13,9 @@ import { normalizePath, writeFile, flattenId, - normalizeId + normalizeId, + removeDirSync, + renameDir } from '../utils' import { esbuildDepPlugin } from './esbuildDepPlugin' import { init, parse } from 'es-module-lexer' @@ -116,7 +118,7 @@ export interface DepOptimizationResult { * the page reload will be delayed until the next rerun so we need * to be able to discard the result */ - commit: () => void + commit: () => Promise cancel: () => void } @@ -194,7 +196,7 @@ export async function optimizeDeps( const result = await runOptimizeDeps(config, depsInfo) - result.commit() + await result.commit() return result.metadata } @@ -376,7 +378,7 @@ export async function runOptimizeDeps( metadata, commit() { // Write metadata file, delete `deps` folder and rename the `processing` folder to `deps` - commitProcessingDepsCacheSync() + return commitProcessingDepsCacheSync() }, cancel } @@ -529,16 +531,16 @@ export async function runOptimizeDeps( metadata, commit() { // Write metadata file, delete `deps` folder and rename the new `processing` folder to `deps` in sync - commitProcessingDepsCacheSync() + return commitProcessingDepsCacheSync() }, cancel } - function commitProcessingDepsCacheSync() { + async function commitProcessingDepsCacheSync() { // Processing is done, we can now replace the depsCacheDir with processingCacheDir // Rewire the file paths from the temporal processing dir to the final deps cache dir removeDirSync(depsCacheDir) - fs.renameSync(processingCacheDir, depsCacheDir) + await renameDir(processingCacheDir, depsCacheDir) } function cancel() { @@ -546,13 +548,6 @@ export async function runOptimizeDeps( } } -function removeDirSync(dir: string) { - if (fs.existsSync(dir)) { - const rmSync = fs.rmSync ?? fs.rmdirSync // TODO: Remove after support for Node 12 is dropped - rmSync(dir, { recursive: true }) - } -} - export async function findKnownImports( config: ResolvedConfig ): Promise { diff --git a/packages/vite/src/node/optimizer/registerMissing.ts b/packages/vite/src/node/optimizer/registerMissing.ts index ee4824389c202b..2262c7be96bb0a 100644 --- a/packages/vite/src/node/optimizer/registerMissing.ts +++ b/packages/vite/src/node/optimizer/registerMissing.ts @@ -189,8 +189,8 @@ export function createOptimizedDeps(server: ViteDevServer): OptimizedDeps { ) }) - const commitProcessing = () => { - processingResult.commit() + const commitProcessing = async () => { + await processingResult.commit() // While optimizeDeps is running, new missing deps may be discovered, // in which case they will keep being added to metadata.discovered @@ -240,7 +240,7 @@ export function createOptimizedDeps(server: ViteDevServer): OptimizedDeps { } if (!needsReload) { - commitProcessing() + await commitProcessing() if (!isDebugEnabled) { if (newDepsToLogHandle) clearTimeout(newDepsToLogHandle) @@ -270,7 +270,7 @@ export function createOptimizedDeps(server: ViteDevServer): OptimizedDeps { } ) } else { - commitProcessing() + await commitProcessing() if (!isDebugEnabled) { if (newDepsToLogHandle) clearTimeout(newDepsToLogHandle) diff --git a/packages/vite/src/node/utils.ts b/packages/vite/src/node/utils.ts index 688e4c0f9cd44d..d41dd6850ebb56 100644 --- a/packages/vite/src/node/utils.ts +++ b/packages/vite/src/node/utils.ts @@ -3,6 +3,7 @@ import colors from 'picocolors' import fs from 'fs' import os from 'os' import path from 'path' +import { promisify } from 'util' import { pathToFileURL, URL } from 'url' import { FS_PREFIX, @@ -522,6 +523,15 @@ export function copyDir(srcDir: string, destDir: string): void { } } +export function removeDirSync(dir: string) { + if (fs.existsSync(dir)) { + const rmSync = fs.rmSync ?? fs.rmdirSync // TODO: Remove after support for Node 12 is dropped + rmSync(dir, { recursive: true }) + } +} + +export const renameDir = isWindows ? promisify(gracefulRename) : fs.renameSync + export function ensureWatchedFile( watcher: FSWatcher, file: string | null, @@ -737,3 +747,41 @@ export function parseRequest(id: string): Record | null { } export const blankReplacer = (match: string) => ' '.repeat(match.length) + +// Based on node-graceful-fs + +// The ISC License +// Copyright (c) 2011-2022 Isaac Z. Schlueter, Ben Noordhuis, and Contributors +// https://github.com/isaacs/node-graceful-fs/blob/main/LICENSE + +// On Windows, A/V software can lock the directory, causing this +// to fail with an EACCES or EPERM if the directory contains newly +// created files. The original tried for up to 60 seconds, we only +// wait for 5 seconds, as a longer time would be seen as an error + +const GRACEFUL_RENAME_TIMEOUT = 5000 +function gracefulRename( + from: string, + to: string, + cb: (error: NodeJS.ErrnoException | null) => void +) { + const start = Date.now() + let backoff = 0 + fs.rename(from, to, function CB(er) { + if ( + er && + (er.code === 'EACCES' || er.code === 'EPERM') && + Date.now() - start < GRACEFUL_RENAME_TIMEOUT + ) { + setTimeout(function () { + fs.stat(to, function (stater, st) { + if (stater && stater.code === 'ENOENT') gracefulRename(from, to, CB) + else cb(er) + }) + }, backoff) + if (backoff < 100) backoff += 10 + return + } + if (cb) cb(er) + }) +}