From b50b72819cd17e4d722f192e39db8241983b7704 Mon Sep 17 00:00:00 2001 From: qmhc <40221744+qmhc@users.noreply.github.com> Date: Fri, 19 Aug 2022 14:27:50 +0800 Subject: [PATCH 1/8] fix: support process each out dir when there are two or more --- packages/vite/src/node/build.ts | 106 +++++++++++++++++++------------- 1 file changed, 64 insertions(+), 42 deletions(-) diff --git a/packages/vite/src/node/build.ts b/packages/vite/src/node/build.ts index 174e5a33d9169a..bb7832dafc58bd 100644 --- a/packages/vite/src/node/build.ts +++ b/packages/vite/src/node/build.ts @@ -491,20 +491,22 @@ async function doBuild( libOptions, config.logger ) + const normalizedOutputs: OutputOptions[] = [] + + if (Array.isArray(outputs)) { + for (const resolvedOutput of outputs) { + normalizedOutputs.push(buildOutputOptions(resolvedOutput)) + } + } else { + normalizedOutputs.push(buildOutputOptions(outputs)) + } + + const outDirs = normalizedOutputs.map(({ dir }) => resolve(dir!)) // watch file changes with rollup if (config.build.watch) { config.logger.info(colors.cyan(`\nwatching for file changes...`)) - const output: OutputOptions[] = [] - if (Array.isArray(outputs)) { - for (const resolvedOutput of outputs) { - output.push(buildOutputOptions(resolvedOutput)) - } - } else { - output.push(buildOutputOptions(outputs)) - } - const resolvedChokidarOptions = resolveChokidarOptions( config.build.watch.chokidar ) @@ -512,7 +514,7 @@ async function doBuild( const { watch } = await import('rollup') const watcher = watch({ ...rollupOptions, - output, + output: normalizedOutputs, watch: { ...config.build.watch, chokidar: resolvedChokidarOptions @@ -523,7 +525,7 @@ async function doBuild( if (event.code === 'BUNDLE_START') { config.logger.info(colors.cyan(`\nbuild started...`)) if (options.write) { - prepareOutDir(outDir, options.emptyOutDir, config) + prepareOutDir(outDirs, options.emptyOutDir, config) } } else if (event.code === 'BUNDLE_END') { event.result.close() @@ -542,55 +544,75 @@ async function doBuild( parallelBuilds.push(bundle) const generate = (output: OutputOptions = {}) => { - return bundle[options.write ? 'write' : 'generate']( - buildOutputOptions(output) - ) + return bundle[options.write ? 'write' : 'generate'](output) } if (options.write) { - prepareOutDir(outDir, options.emptyOutDir, config) + prepareOutDir(outDirs, options.emptyOutDir, config) } - if (Array.isArray(outputs)) { - const res = [] - for (const output of outputs) { - res.push(await generate(output)) - } - return res - } else { - return await generate(outputs) + const res = [] + for (const output of normalizedOutputs) { + res.push(await generate(output)) } + return res } catch (e) { outputBuildError(e) throw e } } +function parseShouldEmptyDirs(outDirs: string[]) { + const sortedOutDirs = Array.from(new Set(outDirs)) + .filter(Boolean) + .sort((p, n) => p.length - n.length) + const removedIndex = new Set() + + // if there have dirs like ['a/b/c', 'b/c', 'c'], only the `a/b/c` should be emptied + for (let i = 0; i < sortedOutDirs.length - 1; ++i) { + if (removedIndex.has(i)) continue + + for (let j = i + 1; j < sortedOutDirs.length; ++j) { + if (removedIndex.has(j)) continue + + if (sortedOutDirs[i].includes(sortedOutDirs[j])) { + removedIndex.add(j) + } + } + } + + return sortedOutDirs.filter((_, i) => !removedIndex.has(i)) +} + function prepareOutDir( - outDir: string, + outDirs: string[], emptyOutDir: boolean | null, config: ResolvedConfig ) { - if (fs.existsSync(outDir)) { - if ( - emptyOutDir == null && - !normalizePath(outDir).startsWith(config.root + '/') - ) { - // warn if outDir is outside of root - config.logger.warn( - colors.yellow( - `\n${colors.bold(`(!)`)} outDir ${colors.white( - colors.dim(outDir) - )} is not inside project root and will not be emptied.\n` + - `Use --emptyOutDir to override.\n` + const shouldEmptyDirs = parseShouldEmptyDirs(outDirs) + + for (const outDir of outDirs) { + if (fs.existsSync(outDir)) { + if ( + emptyOutDir == null && + !normalizePath(outDir).startsWith(config.root + '/') + ) { + // warn if outDir is outside of root + config.logger.warn( + colors.yellow( + `\n${colors.bold(`(!)`)} outDir ${colors.white( + colors.dim(outDir) + )} is not inside project root and will not be emptied.\n` + + `Use --emptyOutDir to override.\n` + ) ) - ) - } else if (emptyOutDir !== false) { - emptyDir(outDir, ['.git']) + } else if (emptyOutDir !== false && shouldEmptyDirs.includes(outDir)) { + emptyDir(outDir, ['.git']) + } + } + if (config.publicDir && fs.existsSync(config.publicDir)) { + copyDir(config.publicDir, outDir) } - } - if (config.publicDir && fs.existsSync(config.publicDir)) { - copyDir(config.publicDir, outDir) } } From 300e0f98febddcb95c01694e8752a20d1f04c945 Mon Sep 17 00:00:00 2001 From: qmhc <40221744+qmhc@users.noreply.github.com> Date: Fri, 19 Aug 2022 15:41:13 +0800 Subject: [PATCH 2/8] fix: ensure doBuild consistency of return --- packages/vite/src/node/build.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vite/src/node/build.ts b/packages/vite/src/node/build.ts index bb7832dafc58bd..4fab92e72d4bf3 100644 --- a/packages/vite/src/node/build.ts +++ b/packages/vite/src/node/build.ts @@ -555,7 +555,7 @@ async function doBuild( for (const output of normalizedOutputs) { res.push(await generate(output)) } - return res + return Array.isArray(outputs) ? res : res[0] } catch (e) { outputBuildError(e) throw e From 1f14f81730161985c8c19ed8c3583127e4c6c26a Mon Sep 17 00:00:00 2001 From: qmhc <544022268@qq.com> Date: Fri, 19 Aug 2022 21:58:35 +0800 Subject: [PATCH 3/8] fix: skip .git when nested --- packages/vite/src/node/build.ts | 42 +++++++++++++-------------------- packages/vite/src/node/utils.ts | 19 ++++++++++++--- 2 files changed, 32 insertions(+), 29 deletions(-) diff --git a/packages/vite/src/node/build.ts b/packages/vite/src/node/build.ts index 4fab92e72d4bf3..4fb38728c1e634 100644 --- a/packages/vite/src/node/build.ts +++ b/packages/vite/src/node/build.ts @@ -562,35 +562,11 @@ async function doBuild( } } -function parseShouldEmptyDirs(outDirs: string[]) { - const sortedOutDirs = Array.from(new Set(outDirs)) - .filter(Boolean) - .sort((p, n) => p.length - n.length) - const removedIndex = new Set() - - // if there have dirs like ['a/b/c', 'b/c', 'c'], only the `a/b/c` should be emptied - for (let i = 0; i < sortedOutDirs.length - 1; ++i) { - if (removedIndex.has(i)) continue - - for (let j = i + 1; j < sortedOutDirs.length; ++j) { - if (removedIndex.has(j)) continue - - if (sortedOutDirs[i].includes(sortedOutDirs[j])) { - removedIndex.add(j) - } - } - } - - return sortedOutDirs.filter((_, i) => !removedIndex.has(i)) -} - function prepareOutDir( outDirs: string[], emptyOutDir: boolean | null, config: ResolvedConfig ) { - const shouldEmptyDirs = parseShouldEmptyDirs(outDirs) - for (const outDir of outDirs) { if (fs.existsSync(outDir)) { if ( @@ -606,8 +582,22 @@ function prepareOutDir( `Use --emptyOutDir to override.\n` ) ) - } else if (emptyOutDir !== false && shouldEmptyDirs.includes(outDir)) { - emptyDir(outDir, ['.git']) + } else if (emptyOutDir !== false) { + // skip those other outDirs which are nested in current outDir + const skipDirs = outDirs + .map((dir) => { + const relative = path.relative(outDir, dir) + if ( + relative && + !relative.startsWith('..') && + !path.isAbsolute(relative) + ) { + return relative + } + return '' + }) + .filter(Boolean) + emptyDir(outDir, [...skipDirs, '.git']) } } if (config.publicDir && fs.existsSync(config.publicDir)) { diff --git a/packages/vite/src/node/utils.ts b/packages/vite/src/node/utils.ts index 85f85adf3cf550..b928feed07870b 100644 --- a/packages/vite/src/node/utils.ts +++ b/packages/vite/src/node/utils.ts @@ -534,15 +534,28 @@ export function isFileReadable(filename: string): boolean { /** * Delete every file and subdirectory. **The given directory must exist.** - * Pass an optional `skip` array to preserve files in the root directory. + * Pass an optional `skip` array to preserve files under the root directory. */ -export function emptyDir(dir: string, skip?: string[]): void { +export function emptyDir(dir: string, skip: string[] = []): void { + const nested: string[] = [] for (const file of fs.readdirSync(dir)) { - if (skip?.includes(file)) { + const matched = skip.find((f) => f.startsWith(file)) + if (matched) { + if (matched !== file) { + nested.push(`${dir}/${file}`) + } continue } fs.rmSync(path.resolve(dir, file), { recursive: true, force: true }) } + if (nested.length) { + skip = skip + .filter((f) => path.dirname(f) !== '.') + .map((f) => f.replace(/.+?[\\/]/, '')) + for (const dir of nested) { + emptyDir(dir, skip) + } + } } export function copyDir(srcDir: string, destDir: string): void { From 6daa79ac36468595c3645a5e70678873399bae77 Mon Sep 17 00:00:00 2001 From: qmhc <544022268@qq.com> Date: Sun, 21 Aug 2022 00:14:28 +0800 Subject: [PATCH 4/8] fix: nested skip should depend on each nedted dir --- packages/vite/src/node/utils.ts | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/packages/vite/src/node/utils.ts b/packages/vite/src/node/utils.ts index b928feed07870b..0a20d8c41340b5 100644 --- a/packages/vite/src/node/utils.ts +++ b/packages/vite/src/node/utils.ts @@ -537,23 +537,30 @@ export function isFileReadable(filename: string): boolean { * Pass an optional `skip` array to preserve files under the root directory. */ export function emptyDir(dir: string, skip: string[] = []): void { - const nested: string[] = [] + let nested: Map | null = null + skip = skip.map(slash) for (const file of fs.readdirSync(dir)) { - const matched = skip.find((f) => f.startsWith(file)) + const matched = skip.find((f) => f === file || f.startsWith(`${file}/`)) if (matched) { if (matched !== file) { - nested.push(`${dir}/${file}`) + nested ??= new Map() + let nestedSkip = nested.get(`${dir}/${file}`) + if (!nestedSkip) { + nestedSkip = [] + nested.set(`${dir}/${file}`, nestedSkip) + } + const skipPath = matched.replace(`${file}/`, '') + if (!nestedSkip.includes(skipPath)) { + nestedSkip.push(skipPath) + } } continue } fs.rmSync(path.resolve(dir, file), { recursive: true, force: true }) } - if (nested.length) { - skip = skip - .filter((f) => path.dirname(f) !== '.') - .map((f) => f.replace(/.+?[\\/]/, '')) - for (const dir of nested) { - emptyDir(dir, skip) + if (nested?.size) { + for (const [dir, nestedSkip] of nested) { + emptyDir(dir, nestedSkip) } } } From e3268b22351c3367b5b036fbf17d82d4bb373b15 Mon Sep 17 00:00:00 2001 From: qmhc <544022268@qq.com> Date: Sun, 21 Aug 2022 12:32:11 +0800 Subject: [PATCH 5/8] fix: pre-precess skip paths --- packages/vite/src/node/utils.ts | 49 +++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/packages/vite/src/node/utils.ts b/packages/vite/src/node/utils.ts index 0a20d8c41340b5..c80e45169bbaa0 100644 --- a/packages/vite/src/node/utils.ts +++ b/packages/vite/src/node/utils.ts @@ -532,35 +532,44 @@ export function isFileReadable(filename: string): boolean { } } +const splitFirstDirRE = /(.+)?[\\/](.+)/ + /** * Delete every file and subdirectory. **The given directory must exist.** * Pass an optional `skip` array to preserve files under the root directory. */ -export function emptyDir(dir: string, skip: string[] = []): void { +export function emptyDir(dir: string, skip?: string[]): void { + const skipInDir: string[] = [] let nested: Map | null = null - skip = skip.map(slash) - for (const file of fs.readdirSync(dir)) { - const matched = skip.find((f) => f === file || f.startsWith(`${file}/`)) - if (matched) { - if (matched !== file) { - nested ??= new Map() - let nestedSkip = nested.get(`${dir}/${file}`) - if (!nestedSkip) { - nestedSkip = [] - nested.set(`${dir}/${file}`, nestedSkip) - } - const skipPath = matched.replace(`${file}/`, '') - if (!nestedSkip.includes(skipPath)) { - nestedSkip.push(skipPath) + if (skip?.length) { + for (const file of skip) { + if (path.dirname(file) !== '.') { + const matched = file.match(splitFirstDirRE) + if (matched) { + nested ??= new Map() + const [, nestedDir, skipPath] = matched + let nestedSkip = nested.get(nestedDir) + if (!nestedSkip) { + nestedSkip = [] + nested.set(nestedDir, nestedSkip) + } + if (!nestedSkip.includes(skipPath)) { + nestedSkip.push(skipPath) + } } + } else { + skipInDir.push(file) } - continue } - fs.rmSync(path.resolve(dir, file), { recursive: true, force: true }) } - if (nested?.size) { - for (const [dir, nestedSkip] of nested) { - emptyDir(dir, nestedSkip) + for (const file of fs.readdirSync(dir)) { + if (skipInDir.includes(file)) { + continue + } + if (nested?.has(file)) { + emptyDir(path.resolve(dir, file), nested.get(file)) + } else { + fs.rmSync(path.resolve(dir, file), { recursive: true, force: true }) } } } From 8fd02747fc50fb2263109ceb9773174e49ab112e Mon Sep 17 00:00:00 2001 From: qmhc <40221744+qmhc@users.noreply.github.com> Date: Tue, 23 Aug 2022 08:49:42 +0800 Subject: [PATCH 6/8] fix: correct split regexp --- packages/vite/src/node/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vite/src/node/utils.ts b/packages/vite/src/node/utils.ts index c80e45169bbaa0..4dd73fb47e6eee 100644 --- a/packages/vite/src/node/utils.ts +++ b/packages/vite/src/node/utils.ts @@ -532,7 +532,7 @@ export function isFileReadable(filename: string): boolean { } } -const splitFirstDirRE = /(.+)?[\\/](.+)/ +const splitFirstDirRE = /(.+?)[\\/](.+)/ /** * Delete every file and subdirectory. **The given directory must exist.** From 8fbd43c126e74fd5c933d8010501d6123f0de433 Mon Sep 17 00:00:00 2001 From: qmhc <544022268@qq.com> Date: Thu, 25 Aug 2022 23:25:58 +0800 Subject: [PATCH 7/8] fix: dedupe outDirs when prepare --- packages/vite/src/node/build.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vite/src/node/build.ts b/packages/vite/src/node/build.ts index 4fb38728c1e634..3f024a562dce25 100644 --- a/packages/vite/src/node/build.ts +++ b/packages/vite/src/node/build.ts @@ -567,7 +567,7 @@ function prepareOutDir( emptyOutDir: boolean | null, config: ResolvedConfig ) { - for (const outDir of outDirs) { + for (const outDir of new Set(outDirs)) { if (fs.existsSync(outDir)) { if ( emptyOutDir == null && From 6eae0c857e7f13eecfb09b3786da901213282337 Mon Sep 17 00:00:00 2001 From: qmhc <544022268@qq.com> Date: Sun, 11 Sep 2022 13:31:41 +0800 Subject: [PATCH 8/8] fix: disable emptying if there is any outDir outside root --- packages/vite/src/node/build.ts | 45 +++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/packages/vite/src/node/build.ts b/packages/vite/src/node/build.ts index 3f024a562dce25..bfac5bda214469 100644 --- a/packages/vite/src/node/build.ts +++ b/packages/vite/src/node/build.ts @@ -567,10 +567,12 @@ function prepareOutDir( emptyOutDir: boolean | null, config: ResolvedConfig ) { - for (const outDir of new Set(outDirs)) { - if (fs.existsSync(outDir)) { + const nonDuplicateDirs = new Set(outDirs) + let outside = false + if (emptyOutDir == null) { + for (const outDir of nonDuplicateDirs) { if ( - emptyOutDir == null && + fs.existsSync(outDir) && !normalizePath(outDir).startsWith(config.root + '/') ) { // warn if outDir is outside of root @@ -582,24 +584,29 @@ function prepareOutDir( `Use --emptyOutDir to override.\n` ) ) - } else if (emptyOutDir !== false) { - // skip those other outDirs which are nested in current outDir - const skipDirs = outDirs - .map((dir) => { - const relative = path.relative(outDir, dir) - if ( - relative && - !relative.startsWith('..') && - !path.isAbsolute(relative) - ) { - return relative - } - return '' - }) - .filter(Boolean) - emptyDir(outDir, [...skipDirs, '.git']) + outside = true + break } } + } + for (const outDir of nonDuplicateDirs) { + if (!outside && emptyOutDir !== false && fs.existsSync(outDir)) { + // skip those other outDirs which are nested in current outDir + const skipDirs = outDirs + .map((dir) => { + const relative = path.relative(outDir, dir) + if ( + relative && + !relative.startsWith('..') && + !path.isAbsolute(relative) + ) { + return relative + } + return '' + }) + .filter(Boolean) + emptyDir(outDir, [...skipDirs, '.git']) + } if (config.publicDir && fs.existsSync(config.publicDir)) { copyDir(config.publicDir, outDir) }