Skip to content

feat: use Rolldown's watch API #67

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
May 1, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 51 additions & 48 deletions packages/vite/src/node/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ import type {
RolldownBuild,
RolldownOptions,
RolldownOutput,
RolldownWatcher,
RollupError,
RollupLog,
// RollupWatcher,
WarningHandlerWithDefault,
// WatcherOptions,
WatcherOptions,
} from 'rolldown'
import {
loadFallbackPlugin as nativeLoadFallbackPlugin,
Expand Down Expand Up @@ -72,8 +72,9 @@ import { buildLoadFallbackPlugin } from './plugins/loadFallback'
import { findNearestMainPackageData, findNearestPackageData } from './packages'
import type { PackageCache } from './packages'
import {
convertToNotifyOptions,
getResolvedOutDirs,
// resolveChokidarOptions,
resolveChokidarOptions,
resolveEmptyOutDir,
} from './watch'
import { completeSystemWrapPlugin } from './plugins/completeSystemWrap'
Expand Down Expand Up @@ -280,7 +281,7 @@ export interface BuildEnvironmentOptions {
* https://rollupjs.org/configuration-options/#watch
* @default null
*/
// watch?: WatcherOptions | null
watch?: WatcherOptions | null
/**
* create the Build Environment instance
*/
Expand Down Expand Up @@ -537,7 +538,7 @@ export async function resolveBuildPlugins(config: ResolvedConfig): Promise<{
*/
export async function build(
inlineConfig: InlineConfig = {},
): Promise<RolldownOutput | RolldownOutput[] /* | RollupWatcher */> {
): Promise<RolldownOutput | RolldownOutput[] | RolldownWatcher> {
const builder = await createBuilder(inlineConfig, true)
const environment = Object.values(builder.environments)[0]
if (!environment) throw new Error('No environment found')
Expand Down Expand Up @@ -565,7 +566,7 @@ function resolveConfigToBuild(
**/
async function buildEnvironment(
environment: BuildEnvironment,
): Promise<RolldownOutput | RolldownOutput[] /* | RollupWatcher */> {
): Promise<RolldownOutput | RolldownOutput[] | RolldownWatcher> {
const { root, packageCache } = environment.config
const options = environment.config.build
const libOptions = options.lib
Expand Down Expand Up @@ -711,11 +712,11 @@ async function buildEnvironment(
}
}

// const outputBuildError = (e: RollupError) => {
// enhanceRollupError(e)
// clearLine()
// logger.error(e.message, { error: e })
// }
const outputBuildError = (e: RollupError) => {
enhanceRollupError(e)
clearLine()
logger.error(e.message, { error: e })
}

const isSsrTargetWebworkerEnvironment =
environment.name === 'ssr' &&
Expand Down Expand Up @@ -831,42 +832,44 @@ async function buildEnvironment(
)

// watch file changes with rollup
// if (options.watch) {
// logger.info(colors.cyan(`\nwatching for file changes...`))

// const resolvedChokidarOptions = resolveChokidarOptions(
// options.watch.chokidar,
// resolvedOutDirs,
// emptyOutDir,
// environment.config.cacheDir,
// )

// const { watch } = await import('rollup')
// const watcher = watch({
// ...rollupOptions,
// output: normalizedOutputs,
// watch: {
// ...options.watch,
// chokidar: resolvedChokidarOptions,
// },
// })

// watcher.on('event', (event) => {
// if (event.code === 'BUNDLE_START') {
// logger.info(colors.cyan(`\nbuild started...`))
// if (options.write) {
// prepareOutDir(resolvedOutDirs, emptyOutDir, environment)
// }
// } else if (event.code === 'BUNDLE_END') {
// event.result.close()
// logger.info(colors.cyan(`built in ${event.duration}ms.`))
// } else if (event.code === 'ERROR') {
// outputBuildError(event.error)
// }
// })

// return watcher
// }
if (options.watch) {
logger.info(colors.cyan(`\nwatching for file changes...`))

const resolvedChokidarOptions = resolveChokidarOptions(
// @ts-expect-error chokidar option does not exist in rolldown but used for backward compat
options.watch.chokidar,
resolvedOutDirs,
emptyOutDir,
environment.config.cacheDir,
)

const { watch } = await import('rolldown')
const watcher = watch({
...rollupOptions,
output: normalizedOutputs,
watch: {
...options.watch,
notify: convertToNotifyOptions(resolvedChokidarOptions),
},
})

watcher.on('event', (event) => {
if (event.code === 'BUNDLE_START') {
logger.info(colors.cyan(`\nbuild started...`))
if (options.write) {
prepareOutDir(resolvedOutDirs, emptyOutDir, environment)
}
} else if (event.code === 'BUNDLE_END') {
// FIXME: https://github.com/rolldown/rolldown/issues/4380
// event.result.close()
logger.info(colors.cyan(`built in ${event.duration}ms.`))
} else if (event.code === 'ERROR') {
outputBuildError(event.error)
}
})

return watcher
}

// write or generate files with rolldown
const { rolldown } = await import('rolldown')
Expand Down Expand Up @@ -1613,7 +1616,7 @@ export interface ViteBuilder {
buildApp(): Promise<void>
build(
environment: BuildEnvironment,
): Promise<RolldownOutput | RolldownOutput[] /* | RollupWatcher */>
): Promise<RolldownOutput | RolldownOutput[] | RolldownWatcher>
}

export interface BuilderOptions {
Expand Down
12 changes: 11 additions & 1 deletion packages/vite/src/node/watch.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { EventEmitter } from 'node:events'
import path from 'node:path'
import type { FSWatcher, WatchOptions } from 'dep-types/chokidar'
import type { OutputOptions } from 'rolldown'
import type { OutputOptions, WatcherOptions } from 'rolldown'
import colors from 'picocolors'
import { escapePath } from 'tinyglobby'
import { withTrailingSlash } from '../shared/utils'
Expand Down Expand Up @@ -78,6 +78,16 @@ export function resolveChokidarOptions(
return resolvedWatchOptions
}

export function convertToNotifyOptions(
options: WatchOptions | undefined,
): WatcherOptions['notify'] {
if (!options) return

return {
pollInterval: options.usePolling ? (options.interval ?? 100) : undefined,
}
}

class NoopWatcher extends EventEmitter implements FSWatcher {
constructor(public options: WatchOptions) {
super()
Expand Down
52 changes: 28 additions & 24 deletions playground/assets/__tests__/assets.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
getColor,
isBuild,
isServe,
isWindows,
listAssets,
notifyRebuildComplete,
page,
Expand Down Expand Up @@ -633,32 +634,35 @@ test.runIf(isBuild)('manifest', async () => {
}
})

// TODO: rolldown does not support rebuild
describe.runIf(isBuild).skip('css and assets in css in build watch', () => {
test('css will not be lost and css does not contain undefined', async () => {
editFile('index.html', (code) => code.replace('Assets', 'assets'))
await notifyRebuildComplete(watcher)
const cssFile = findAssetFile(/index-[-\w]+\.css$/, 'foo')
expect(cssFile).not.toBe('')
expect(cssFile).not.toMatch(/undefined/)
})
// TODO: skip on Windows due to https://github.com/rolldown/rolldown/issues/4385
describe.runIf(isBuild && !isWindows)(
'css and assets in css in build watch',
() => {
test('css will not be lost and css does not contain undefined', async () => {
editFile('index.html', (code) => code.replace('Assets', 'assets'))
await notifyRebuildComplete(watcher)
const cssFile = findAssetFile(/index-[-\w]+\.css$/, 'foo')
expect(cssFile).not.toBe('')
expect(cssFile).not.toMatch(/undefined/)
})

test('import module.css', async () => {
expect(await getColor('#foo')).toBe('red')
editFile('css/foo.module.css', (code) => code.replace('red', 'blue'))
await notifyRebuildComplete(watcher)
await page.reload()
expect(await getColor('#foo')).toBe('blue')
})
test('import module.css', async () => {
expect(await getColor('#foo')).toBe('red')
editFile('css/foo.module.css', (code) => code.replace('red', 'blue'))
await notifyRebuildComplete(watcher)
await page.reload()
expect(await getColor('#foo')).toBe('blue')
})

test('import with raw query', async () => {
expect(await page.textContent('.raw-query')).toBe('foo')
editFile('static/foo.txt', (code) => code.replace('foo', 'zoo'))
await notifyRebuildComplete(watcher)
await page.reload()
expect(await page.textContent('.raw-query')).toBe('zoo')
})
})
test('import with raw query', async () => {
expect(await page.textContent('.raw-query')).toBe('foo')
editFile('static/foo.txt', (code) => code.replace('foo', 'zoo'))
await notifyRebuildComplete(watcher)
await page.reload()
expect(await page.textContent('.raw-query')).toBe('zoo')
})
},
)

test('inline style test', async () => {
expect(await getBg('.inline-style')).toMatch(assetMatch)
Expand Down
2 changes: 1 addition & 1 deletion playground/assets/vite.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,6 @@ export default defineConfig({
outDir: 'dist/foo',
assetsInlineLimit: 8000, // 8 kB
manifest: true,
// watch: {},
watch: {},
},
})
3 changes: 2 additions & 1 deletion playground/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"devDependencies": {
"convert-source-map": "^2.0.0",
"css-color-names": "^1.0.1",
"kill-port": "^1.6.1"
"kill-port": "^1.6.1",
"rolldown": "1.0.0-beta.8-commit.a720367"
}
}
42 changes: 25 additions & 17 deletions playground/vitestSetup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import type {
InlineConfig,
Logger,
PluginOption,
// ResolvedConfig,
ResolvedConfig,
UserConfig,
ViteDevServer,
} from 'vite'
Expand All @@ -20,7 +20,11 @@ import {
preview,
} from 'vite'
import type { Browser, Page } from 'playwright-chromium'
import type { RollupError, RollupWatcher, RollupWatcherEvent } from 'rollup'
import type {
RolldownWatcher,
RolldownWatcherEvent,
RollupError,
} from 'rolldown'
import type { RunnerTestFile } from 'vitest'
import { beforeAll, inject } from 'vitest'

Expand Down Expand Up @@ -70,7 +74,7 @@ export const browserErrors: Error[] = []
export let page: Page = undefined!
export let browser: Browser = undefined!
export let viteTestUrl: string = ''
export const watcher: RollupWatcher | undefined = undefined
export let watcher: RolldownWatcher | undefined = undefined

export function setViteUrl(url: string): void {
viteTestUrl = url
Expand Down Expand Up @@ -242,12 +246,12 @@ export async function startDefaultServe(): Promise<void> {
await page.goto(viteTestUrl)
} else {
process.env.VITE_INLINE = 'inline-build'
// let resolvedConfig: ResolvedConfig
let resolvedConfig: ResolvedConfig
// determine build watch
const resolvedPlugin: () => PluginOption = () => ({
name: 'vite-plugin-watcher',
configResolved(_config) {
// resolvedConfig = config
configResolved(config) {
resolvedConfig = config
},
})
const buildConfig = mergeConfig(
Expand All @@ -260,13 +264,13 @@ export async function startDefaultServe(): Promise<void> {
const builder = await createBuilder(buildConfig)
await builder.buildApp()
} else {
/* const rollupOutput = */ await build(buildConfig)
// const isWatch = !!resolvedConfig!.build.watch
// // in build watch,call startStaticServer after the build is complete
// if (isWatch) {
// watcher = rollupOutput as RollupWatcher
// await notifyRebuildComplete(watcher)
// }
const rollupOutput = await build(buildConfig)
const isWatch = !!resolvedConfig!.build.watch
// in build watch,call startStaticServer after the build is complete
if (isWatch) {
watcher = rollupOutput as RolldownWatcher
await notifyRebuildComplete(watcher)
}
if (buildConfig.__test__) {
buildConfig.__test__()
}
Expand All @@ -293,19 +297,23 @@ export async function startDefaultServe(): Promise<void> {
* Send the rebuild complete message in build watch
*/
export async function notifyRebuildComplete(
watcher: RollupWatcher,
): Promise<RollupWatcher> {
watcher: RolldownWatcher,
): Promise<RolldownWatcher> {
let resolveFn: undefined | (() => void)
const callback = (event: RollupWatcherEvent): void => {
const callback = (event: RolldownWatcherEvent): void => {
if (event.code === 'END') {
resolveFn?.()
resolveFn = undefined // set to undefined instead of watcher.off for now
}
}
watcher.on('event', callback)
await new Promise<void>((resolve) => {
resolveFn = resolve
})
return watcher.off('event', callback)

// TODO: not supported yet (https://github.com/rolldown/rolldown/issues/4382)
// return watcher.off('event', callback)
return watcher
}

export function createInMemoryLogger(logs: string[]): Logger {
Expand Down
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.