Skip to content
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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
"@types/picomatch": "^4.0.3",
"@types/stylus": "^0.48.43",
"@types/ws": "^8.18.1",
"@vitejs/release-scripts": "^1.7.0",
"@vitejs/release-scripts": "^1.7.1",
"eslint": "^9.39.4",
"eslint-plugin-import-x": "^4.16.2",
"eslint-plugin-n": "^18.0.1",
Expand Down
23 changes: 23 additions & 0 deletions packages/plugin-legacy/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,26 @@
## <small>[8.0.2](https://github.com/vitejs/vite/compare/plugin-legacy@8.0.1...plugin-legacy@8.0.2) (2026-05-14)</small>
### Bug Fixes

* **deps:** update all non-major dependencies ([#22143](https://github.com/vitejs/vite/issues/22143)) ([22b0166](https://github.com/vitejs/vite/commit/22b016612703320db45c64a2fe44472051ef5ec5))
* **deps:** update all non-major dependencies ([#22382](https://github.com/vitejs/vite/issues/22382)) ([5c0cfcb](https://github.com/vitejs/vite/commit/5c0cfcb83dde2c6e25b6c3215dd622956bf29631))
* **plugin-legacy:** remove modulepreload links for legacy-only builds ([#22332](https://github.com/vitejs/vite/issues/22332)) ([f3a0bc9](https://github.com/vitejs/vite/commit/f3a0bc90bcc529a12a520469b9d0fb6fa751107c))

### Miscellaneous Chores

* **deps:** update dependency tsdown to ^0.21.10 ([#22333](https://github.com/vitejs/vite/issues/22333)) ([3b51e05](https://github.com/vitejs/vite/commit/3b51e050214c5a817c163838ab8643fe34c7d0c3))
* **deps:** update dependency tsdown to ^0.21.9 ([#22267](https://github.com/vitejs/vite/issues/22267)) ([a0aef50](https://github.com/vitejs/vite/commit/a0aef50f6b51120df95cc11a7354af2afabe6a4a))
* **deps:** update rolldown-related dependencies ([#21989](https://github.com/vitejs/vite/issues/21989)) ([0ded627](https://github.com/vitejs/vite/commit/0ded6274579e8bda6b22a7ba93b15d15b4c28b78))
* **deps:** update rolldown-related dependencies ([#22421](https://github.com/vitejs/vite/issues/22421)) ([66b9eb3](https://github.com/vitejs/vite/commit/66b9eb35188007e0e9a1bd03b4be820016cad60b))

### Code Refactoring

* upgrade to typescript 6 ([#22110](https://github.com/vitejs/vite/issues/22110)) ([cc41398](https://github.com/vitejs/vite/commit/cc41398c2cf0bb5061cf0ca5dc3b408ae7e41191))

### Build System

* handle tsdown inlineOnly deprecation ([#22111](https://github.com/vitejs/vite/issues/22111)) ([86cbc6e](https://github.com/vitejs/vite/commit/86cbc6e5571aefd6278d6ecbbfb5ede8d4061940))
* improve typecheck and linting ([#22230](https://github.com/vitejs/vite/issues/22230)) ([3770a53](https://github.com/vitejs/vite/commit/3770a53064e8da27d4cb8d595a2b038413e6abdb))

## <small>[8.0.1](https://github.com/vitejs/vite/compare/plugin-legacy@8.0.0...plugin-legacy@8.0.1) (2026-03-26)</small>
### Bug Fixes

Expand Down
2 changes: 1 addition & 1 deletion packages/plugin-legacy/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@vitejs/plugin-legacy",
"version": "8.0.1",
"version": "8.0.2",
"type": "module",
"license": "MIT",
"author": "Evan You",
Expand Down
45 changes: 45 additions & 0 deletions packages/plugin-legacy/src/__tests__/index.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { describe, expect, test } from 'vitest'
import { modulePreloadLinkRE } from '../index'

describe('modulePreloadLinkRE', () => {
const matches: Array<[string, string]> = [
['rel first', '<link rel="modulepreload" crossorigin href="/assets/x.js">'],
[
'rel after other attributes',
'<link href="/assets/x.js" rel="modulepreload">',
],
['rel only', '<link rel="modulepreload">'],
['self-closing', '<link rel="modulepreload"/>'],
['self-closing with space', '<link rel="modulepreload" />'],
['single quotes', "<link rel='modulepreload' href='/assets/x.js'>"],
[
'attributes across multiple lines',
'<link\n rel="modulepreload"\n href="/assets/x.js"\n>',
],
]

for (const [name, html] of matches) {
test(`matches: ${name}`, () => {
expect(html.replace(modulePreloadLinkRE, '')).toBe('')
})
}

const nonMatches: Array<[string, string]> = [
['tag name with suffix', '<linkfoo rel="modulepreload">'],
['custom element with hyphen', '<link-preview rel="modulepreload">'],
['bare link tag', '<link>'],
['stylesheet link', '<link rel="stylesheet" href="/assets/x.css">'],
['preload (not modulepreload)', '<link rel="preload" href="/assets/x.js">'],
[
'attribute name ending in rel',
'<link xrel="modulepreload" href="/assets/x.js">',
],
['mismatched quotes', `<link rel="modulepreload'>`],
]

for (const [name, html] of nonMatches) {
test(`does not match: ${name}`, () => {
expect(html.replace(modulePreloadLinkRE, '')).toBe(html)
})
}
})
6 changes: 5 additions & 1 deletion packages/plugin-legacy/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@ const _require = createRequire(import.meta.url)

const nonLeadingHashInFileNameRE = /[^/]+\[hash(?::\d+)?\]/
const prefixedHashInFileNameRE = /\W?\[hash(?::\d+)?\]/
export const modulePreloadLinkRE: RegExp =
/<link(?![\w-])[^>]*?\srel=(['"])modulepreload\1[^>]*>/g

// browsers supporting dynamic import + import.meta.resolve + async generator
const modernTargetsEsbuild = [
Expand Down Expand Up @@ -641,7 +643,9 @@ function viteLegacyPlugin(options: Options = {}): Plugin[] {
}
}
if (!genModern) {
html = html.replace(/<script type="module".*?<\/script>/g, '')
html = html
.replace(/<script type="module".*?<\/script>/g, '')
.replace(modulePreloadLinkRE, '')
}

const tags: HtmlTagDescriptor[] = []
Expand Down
20 changes: 20 additions & 0 deletions packages/vite/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,23 @@
## <small>[8.0.13](https://github.com/vitejs/vite/compare/v8.0.12...v8.0.13) (2026-05-14)</small>
### Features

* **bundled-dev:** add lazy bundling support ([#21406](https://github.com/vitejs/vite/issues/21406)) ([4f0949f](https://github.com/vitejs/vite/commit/4f0949f3f13e4b2b34d32bf7b2b4de5f26bea192))
* **optimizer:** improve the esbuild plugin converter to pass some properties of build result to `onEnd` ([#22357](https://github.com/vitejs/vite/issues/22357)) ([47071ce](https://github.com/vitejs/vite/commit/47071ce53f21726cf39e999c4407c4828ecbe957))
* update rolldown to 1.0.1 ([#22444](https://github.com/vitejs/vite/issues/22444)) ([8c766a6](https://github.com/vitejs/vite/commit/8c766a6c5ee014969c4e32f29cc265e8e2c96e18))

### Bug Fixes

* **build:** copy public directory after building same environment with `write=false` ([#22328](https://github.com/vitejs/vite/issues/22328)) ([158e8ae](https://github.com/vitejs/vite/commit/158e8ae8efdf7075ab295727e36b5ff68da3243e))
* **css:** await sass/less/styl worker disposal on teardown (fix [#22274](https://github.com/vitejs/vite/issues/22274)) ([#22275](https://github.com/vitejs/vite/issues/22275)) ([b7edcb7](https://github.com/vitejs/vite/commit/b7edcb7d0dd17ddfeef4ace78d610c099216dade))
* **css:** keep deprecated `name`/`originalFileName` in synthetic `assetFileNames` call ([#22439](https://github.com/vitejs/vite/issues/22439)) ([8e59c97](https://github.com/vitejs/vite/commit/8e59c97a44d923c4c06f67287a793c9aa5a4ebaa))
* make `isBundled` per environment ([#22257](https://github.com/vitejs/vite/issues/22257)) ([a576326](https://github.com/vitejs/vite/commit/a5763266170f8606836da5c6f987b4b2fd6ddc55))
* **ssr:** avoid rewriting labels that collide with imports ([#22451](https://github.com/vitejs/vite/issues/22451)) ([d9b18e0](https://github.com/vitejs/vite/commit/d9b18e0387a253628d3d834288e79c5f7e85d566))

### Miscellaneous Chores

* remove irrelevant commits from changelog ([#22430](https://github.com/vitejs/vite/issues/22430)) ([6ea3838](https://github.com/vitejs/vite/commit/6ea383859aaf0ef8e673b458f164e84aeb6ff51d))
* update changelog ([#22413](https://github.com/vitejs/vite/issues/22413)) ([fcdc87c](https://github.com/vitejs/vite/commit/fcdc87cc6799857e2bab0f44f333a681694fff74))

## <small>[8.0.12](https://github.com/vitejs/vite/compare/v8.0.11...v8.0.12) (2026-05-11)</small>
### Features

Expand Down
2 changes: 1 addition & 1 deletion packages/vite/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "vite",
"version": "8.0.12",
"version": "8.0.13",
"type": "module",
"license": "MIT",
"author": "Evan You",
Expand Down
12 changes: 10 additions & 2 deletions packages/vite/src/client/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -644,6 +644,15 @@ if (isBundleMode && typeof DevRuntime !== 'undefined') {
}
}

const clientId = nanoid()

// notify client id
transport.send({
type: 'custom',
event: 'vite:module-loaded',
data: { modules: [], clientId },
})

const wrappedSocket: Messenger = {
send(message) {
switch (message.type) {
Expand All @@ -652,7 +661,7 @@ if (isBundleMode && typeof DevRuntime !== 'undefined') {
type: 'custom',
event: 'vite:module-loaded',
// clone array as the runtime reuses the array instance
data: { modules: message.modules.slice() },
data: { modules: message.modules.slice(), clientId },
})
break
}
Expand All @@ -661,7 +670,6 @@ if (isBundleMode && typeof DevRuntime !== 'undefined') {
}
},
}
const clientId = nanoid()
;(globalThis as any).__rolldown_runtime__ ??= new ViteDevRuntime(
wrappedSocket,
clientId,
Expand Down
23 changes: 23 additions & 0 deletions packages/vite/src/node/__tests__/build.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1187,6 +1187,29 @@ test('watch rebuild manifest', async (ctx) => {
`)
})

test('copies public directory after building same environment with write false first', async (ctx) => {
const root = resolve(dirname, 'fixtures/public-dir-write-false')
ctx.onTestFinished(() =>
fsp.rm(resolve(root, 'dist'), { recursive: true, force: true }),
)

const builder = await createBuilder({
root,
configFile: false,
logLevel: 'silent',
})

builder.environments.client.config.build.write = false
await builder.build(builder.environments.client)

builder.environments.client.config.build.write = true
await builder.build(builder.environments.client)

await expect(
fsp.readFile(resolve(root, 'dist/favicon.svg'), 'utf-8'),
).resolves.toBe('<svg></svg>')
})

/**
* for each chunks in output1, if there's a chunk in output2 with the same fileName,
* ensure that the chunk code is the same. if not, the chunk hash should have changed.
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
34 changes: 34 additions & 0 deletions packages/vite/src/node/__tests__/optimizer/pluginConverter.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import type * as esbuild from 'esbuild'
import { describe, expect, test } from 'vitest'
import { convertEsbuildPluginToRolldownPlugin } from '../../optimizer/pluginConverter'
import type { Plugin } from '../../plugin'

type ConvertedPluginHooks = {
options: Extract<Plugin['options'], Function>
generateBundle: Extract<Plugin['generateBundle'], Function>
}

describe('convertEsbuildPluginToRolldownPlugin', () => {
test('passes a BuildResult to onEnd callbacks', async () => {
let buildResult: esbuild.BuildResult | undefined

const plugin = convertEsbuildPluginToRolldownPlugin({
name: 'read-metafile',
setup(build) {
build.onEnd((result) => {
buildResult = result
expect(result.metafile).toBeUndefined()
})
},
}) as ConvertedPluginHooks

await plugin.options.call({} as any, { plugins: [], platform: 'browser' })
await plugin.generateBundle.call({} as any, {} as any, {} as any, true)

expect(buildResult).toEqual({
outputFiles: undefined,
metafile: undefined,
mangleCache: undefined,
})
})
})
17 changes: 12 additions & 5 deletions packages/vite/src/node/optimizer/pluginConverter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,15 +134,22 @@ export function convertEsbuildPluginToRolldownPlugin(
cb()
}
},
generateBundle() {
generateBundle(_outputOpts, _bundle, isWrite) {
const buildResult = new Proxy(
{},
{
get(_target, _prop) {
throw new Error('Not implemented')
metafile: undefined,
mangleCache: undefined,
...(isWrite ? { outputFiles: undefined } : {}),
} as esbuild.BuildResult,
{
get(_target, prop) {
if (prop in _target || typeof prop === 'symbol') {
return (_target as any)[prop]
}
throw new Error('Not implemented property: ' + prop)
},
},
) as esbuild.BuildResult
)
for (const cb of onEndCallbacks) {
cb(buildResult)
}
Expand Down
2 changes: 1 addition & 1 deletion packages/vite/src/node/plugins/prepareOutDir.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ export function prepareOutDirPlugin(): Plugin {
if (rendered.has(this.environment)) {
return
}
rendered.add(this.environment)

const { config } = this.environment
if (config.build.write) {
rendered.add(this.environment)
const { root, build: options } = config
const resolvedOutDirs = getResolvedOutDirs(
root,
Expand Down
44 changes: 33 additions & 11 deletions packages/vite/src/node/server/environments/fullBundleEnvironment.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { randomUUID } from 'node:crypto'
import { setTimeout } from 'node:timers/promises'
import {
type BindingClientHmrUpdate,
Expand Down Expand Up @@ -61,6 +60,7 @@ export class MemoryFiles {

export class FullBundleDevEnvironment extends DevEnvironment {
private devEngine!: DevEngine
private initialBuildCompleted = false
private clients = new Clients()
private invalidateCalledModules = new Map<
NormalizedHotChannelClient,
Expand Down Expand Up @@ -106,8 +106,8 @@ export class FullBundleDevEnvironment extends DevEnvironment {
)!

this.hot.on('vite:module-loaded', (payload, client) => {
const clientId = this.clients.setupIfNeeded(client)
this.devEngine.registerModules(clientId, payload.modules)
this.clients.setupIfNeeded(client, payload.clientId)
this.devEngine.registerModules(payload.clientId, payload.modules)
})
this.hot.on('vite:client:disconnect', (_payload, client) => {
const clientId = this.clients.delete(client)
Expand Down Expand Up @@ -184,6 +184,7 @@ export class FullBundleDevEnvironment extends DevEnvironment {
this.waitForInitialBuildFinish().then(() => {
debug?.('INITIAL: build done')
this.hot.send({ type: 'full-reload', path: '*' })
this.initialBuildCompleted = true
})
}

Expand Down Expand Up @@ -260,7 +261,9 @@ export class FullBundleDevEnvironment extends DevEnvironment {
async triggerBundleRegenerationIfStale(): Promise<boolean> {
const bundleState = await this.devEngine.getBundleState()
const shouldTrigger =
bundleState.hasStaleOutput && !bundleState.lastFullBuildFailed
bundleState.hasStaleOutput &&
!bundleState.lastFullBuildFailed &&
this.initialBuildCompleted
if (shouldTrigger) {
this.devEngine.ensureLatestBuildOutput().then(() => {
this.debouncedFullReload()
Expand All @@ -270,16 +273,34 @@ export class FullBundleDevEnvironment extends DevEnvironment {
return shouldTrigger
}

async triggerLazyBundling(
moduleId: string | null,
clientId: string | null,
): Promise<string | undefined> {
if (!moduleId || !clientId) {
return
}
debug?.(
`TRIGGER-LAZY: trigger lazy bundling for module ${moduleId} for client ${clientId}`,
)
return await this.devEngine.compileEntry(moduleId, clientId)
}

override async close(): Promise<void> {
this.memoryFiles.clear()
await Promise.all([super.close(), this.devEngine.close()])
this.initialBuildCompleted = false
}

private async getRolldownOptions() {
const chunkMetadataMap = new ChunkMetadataMap()
const rolldownOptions = resolveRolldownOptions(this, chunkMetadataMap)
rolldownOptions.experimental ??= {}
rolldownOptions.experimental.devMode = {
lazy: true,
...(typeof rolldownOptions.experimental.devMode === 'object'
? rolldownOptions.experimental.devMode
: {}),
implement: await getHmrImplementation(this.getTopLevelConfig()),
}

Expand Down Expand Up @@ -382,14 +403,15 @@ class Clients {
private clientToId = new Map<NormalizedHotChannelClient, string>()
private idToClient = new Map<string, NormalizedHotChannelClient>()

setupIfNeeded(client: NormalizedHotChannelClient): string {
setupIfNeeded(client: NormalizedHotChannelClient, clientId: string) {
const id = this.clientToId.get(client)
if (id) return id

const newId = randomUUID()
this.clientToId.set(client, newId)
this.idToClient.set(newId, client)
return newId
if (id && id !== clientId) {
throw new Error(
'client ID conflict detected. Please restart the dev server.',
)
}
this.clientToId.set(client, clientId)
this.idToClient.set(clientId, client)
}

get(id: string): NormalizedHotChannelClient | undefined {
Expand Down
2 changes: 2 additions & 0 deletions packages/vite/src/node/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ import type { DevEnvironment } from './environment'
import { hostValidationMiddleware } from './middlewares/hostCheck'
import { rejectInvalidRequestMiddleware } from './middlewares/rejectInvalidRequest'
import { memoryFilesMiddleware } from './middlewares/memoryFiles'
import { triggerLazyBundlingMiddleware } from './middlewares/triggerLazyBundling'

const usedConfigs = new WeakSet<ResolvedConfig>()

Expand Down Expand Up @@ -993,6 +994,7 @@ export async function _createServer(
}

if (config.experimental.bundledDev) {
middlewares.use(triggerLazyBundlingMiddleware(server))
middlewares.use(memoryFilesMiddleware(server))
} else {
// main transform middleware
Expand Down
Loading
Loading