diff --git a/docs/config/index.md b/docs/config/index.md index 6f606857f5bce5..0060f330aa99e2 100644 --- a/docs/config/index.md +++ b/docs/config/index.md @@ -561,6 +561,13 @@ export default async ({ command, mode }) => { - **Default:** `500` Limit for chunk size warnings (in kbs). + +### build.watch + +- **Type:** [`WatcherOptions`](https://rollupjs.org/guide/en/#watch-options)`| null` +- **Default:** `null` + + Set to `{}` to enable rollup watcher. This is mostly used in cases that involve build-only plugins or integrations processes. ## Dep Optimization Options diff --git a/docs/guide/build.md b/docs/guide/build.md index 0f8ea3f681f25e..dd0c940e2fbf50 100644 --- a/docs/guide/build.md +++ b/docs/guide/build.md @@ -46,6 +46,21 @@ module.exports = { For example, you can specify multiple Rollup outputs with plugins that are only applied during build. +## Rebuild on files changes + +You can enable rollup watcher with `vite build --watch`. Or, you can directly adjust the underlying [`WatcherOptions`](https://rollupjs.org/guide/en/#watch-options) via `build.watch`: + +```js +// vite.config.js +module.exports = { + build: { + watch: { + // https://rollupjs.org/guide/en/#watch-options + } + } +} +``` + ## Multi-Page App Suppose you have the following source code structure: diff --git a/packages/vite/src/node/build.ts b/packages/vite/src/node/build.ts index 958f32dd83066a..73d520675206ee 100644 --- a/packages/vite/src/node/build.ts +++ b/packages/vite/src/node/build.ts @@ -12,7 +12,10 @@ import Rollup, { RollupOutput, ExternalOption, GetManualChunk, - GetModuleInfo + GetModuleInfo, + WatcherOptions, + RollupWatcher, + RollupError } from 'rollup' import { buildReporterPlugin } from './plugins/reporter' import { buildHtmlPlugin } from './plugins/html' @@ -176,6 +179,11 @@ export interface BuildOptions { * @default 500 */ chunkSizeWarningLimit?: number + /** + * Rollup watch options + * https://rollupjs.org/guide/en/#watchoptions + */ + watch?: WatcherOptions | null } export interface LibraryOptions { @@ -214,6 +222,7 @@ export function resolveBuildOptions(raw?: BuildOptions): ResolvedBuildOptions { ssrManifest: false, brotliSize: true, chunkSizeWarningLimit: 500, + watch: null, ...raw } @@ -279,7 +288,7 @@ const parallelBuilds: RollupBuild[] = [] */ export async function build( inlineConfig: InlineConfig = {} -): Promise { +): Promise { parallelCallCounts++ try { return await doBuild(inlineConfig) @@ -294,7 +303,7 @@ export async function build( async function doBuild( inlineConfig: InlineConfig = {} -): Promise { +): Promise { const config = await resolveConfig(inlineConfig, 'build', 'production') const options = config.build const ssr = !!options.ssr @@ -355,29 +364,39 @@ async function doBuild( } const rollup = require('rollup') as typeof Rollup + const rollupOptions: RollupOptions = { + input, + preserveEntrySignatures: ssr + ? 'allow-extension' + : libOptions + ? 'strict' + : false, + ...options.rollupOptions, + plugins, + external, + onwarn(warning, warn) { + onRollupWarning(warning, warn, config) + } + } - try { - const bundle = await rollup.rollup({ - input, - preserveEntrySignatures: ssr - ? 'allow-extension' - : libOptions - ? 'strict' - : false, - ...options.rollupOptions, - plugins, - external, - onwarn(warning, warn) { - onRollupWarning(warning, warn, config) - } - }) - - parallelBuilds.push(bundle) + const outputBuildError = (e: RollupError) => { + config.logger.error( + chalk.red(`${e.plugin ? `[${e.plugin}] ` : ''}${e.message}`) + ) + if (e.id) { + const loc = e.loc ? `:${e.loc.line}:${e.loc.column}` : '' + config.logger.error(`file: ${chalk.cyan(`${e.id}${loc}`)}`) + } + if (e.frame) { + config.logger.error(chalk.yellow(e.frame)) + } + } + try { const pkgName = libOptions && getPkgName(config.root) - const generate = (output: OutputOptions = {}) => { - return bundle[options.write ? 'write' : 'generate']({ + const buildOuputOptions = (output: OutputOptions = {}): OutputOptions => { + return { dir: outDir, format: ssr ? 'cjs' : 'es', exports: ssr ? 'named' : 'auto', @@ -406,7 +425,81 @@ async function doBuild( ? createMoveToVendorChunkFn(config) : undefined, ...output + } + } + + // resolve lib mode outputs + const outputs = resolveBuildOutputs( + options.rollupOptions?.output, + libOptions, + config.logger + ) + + // watch file changes with rollup + if (config.build.watch) { + config.logger.info(chalk.cyanBright(`\nwatching for file changes...`)) + + const output: OutputOptions[] = [] + if (Array.isArray(outputs)) { + for (const resolvedOutput of outputs) { + output.push(buildOuputOptions(resolvedOutput)) + } + } else { + output.push(buildOuputOptions(outputs)) + } + + const watcherOptions = config.build.watch + const watcher = rollup.watch({ + ...rollupOptions, + output, + watch: { + ...watcherOptions, + chokidar: { + ignored: [ + '**/node_modules/**', + '**/.git/**', + ...(watcherOptions?.chokidar?.ignored || []) + ], + ignoreInitial: true, + ignorePermissionErrors: true, + ...watcherOptions.chokidar + } + } + }) + + watcher.on('event', (event) => { + if (event.code === 'BUNDLE_START') { + config.logger.info(chalk.cyanBright(`\nbuild started...`)) + + // clean previous files + if (options.write) { + emptyDir(outDir) + if (fs.existsSync(config.publicDir)) { + copyDir(config.publicDir, outDir) + } + } + } else if (event.code === 'BUNDLE_END') { + event.result.close() + config.logger.info(chalk.cyanBright(`built in ${event.duration}ms.`)) + } else if (event.code === 'ERROR') { + outputBuildError(event.error) + } }) + + // stop watching + watcher.close() + + return watcher + } + + // write or generate files with rollup + const bundle = await rollup.rollup(rollupOptions) + parallelBuilds.push(bundle) + + const generate = (output: OutputOptions = {}) => { + return bundle[options.write ? 'write' : 'generate']( + buildOuputOptions(output) + ) } if (options.write) { @@ -434,12 +527,6 @@ async function doBuild( } } - // resolve lib mode outputs - const outputs = resolveBuildOutputs( - options.rollupOptions?.output, - libOptions, - config.logger - ) if (Array.isArray(outputs)) { const res = [] for (const output of outputs) { @@ -450,16 +537,7 @@ async function doBuild( return await generate(outputs) } } catch (e) { - config.logger.error( - chalk.red(`${e.plugin ? `[${e.plugin}] ` : ``}${e.message}`) - ) - if (e.id) { - const loc = e.loc ? `:${e.loc.line}:${e.loc.column}` : `` - config.logger.error(`file: ${chalk.cyan(`${e.id}${loc}`)}`) - } - if (e.frame) { - config.logger.error(chalk.yellow(e.frame)) - } + outputBuildError(e) throw e } } diff --git a/packages/vite/src/node/cli.ts b/packages/vite/src/node/cli.ts index 26384fd6b2511e..41ed363388d7a7 100644 --- a/packages/vite/src/node/cli.ts +++ b/packages/vite/src/node/cli.ts @@ -130,6 +130,7 @@ cli `[boolean] force empty outDir when it's outside of root` ) .option('-m, --mode ', `[string] set env mode`) + .option('-w, --watch', `[boolean] rebuilds when modules have changed on disk`) .action(async (root: string, options: BuildOptions & GlobalCLIOptions) => { const { build } = await import('./build') const buildOptions = cleanOptions(options) as BuildOptions