From fe83d2b0e25e6e05f6ae49407031f7fd2b969f8c Mon Sep 17 00:00:00 2001 From: btea <2356281422@qq.com> Date: Fri, 7 Aug 2026 16:20:25 +0800 Subject: [PATCH 1/3] fix(bundled-dev): refresh after editing inline scripts in html entries --- packages/vite/src/node/plugins/html.ts | 10 +++-- packages/vite/src/node/server/bundledDev.ts | 16 +++++++ playground/html/__tests__/html.spec.ts | 46 +++++++++++++++++++++ playground/html/inline-classic-script.html | 5 +++ playground/html/vite.config.js | 1 + 5 files changed, 74 insertions(+), 4 deletions(-) create mode 100644 playground/html/inline-classic-script.html diff --git a/packages/vite/src/node/plugins/html.ts b/packages/vite/src/node/plugins/html.ts index 36dfafd4fca36b..21427c97ab069a 100644 --- a/packages/vite/src/node/plugins/html.ts +++ b/packages/vite/src/node/plugins/html.ts @@ -58,7 +58,7 @@ interface ScriptAssetsUrl { } const htmlProxyRE = - /[?&]html-proxy=?(?:&inline-css)?(?:&style-attr)?&index=(\d+)\.(?:js|css)$/ + /[?&]html-proxy=?(?:&inline-css)?(?:&style-attr)?&index=(\d+)(?:&h=[a-z0-9]+)?\.(?:js|css)$/ const isHtmlProxyRE = /[?&]html-proxy\b/ const inlineCSSRE = /__VITE_INLINE_CSS__([a-z\d]{8}_\d+)__/g @@ -592,7 +592,9 @@ export function buildHtmlPlugin(config: ResolvedConfig): Plugin { addToHTMLProxyCache(config, filePath, inlineModuleIndex, { code: contents, }) - js += `\nimport "${id}?html-proxy&index=${inlineModuleIndex}.js"` + // include a content hash in the proxy id so that editing the + // inline script is detected as a change by the bundler + js += `\nimport "${id}?html-proxy&index=${inlineModuleIndex}&h=${getHash(contents)}.js"` shouldRemove = true } @@ -713,7 +715,7 @@ export function buildHtmlPlugin(config: ResolvedConfig): Plugin { const filePath = id.replace(normalizePath(config.root), '') addToHTMLProxyCache(config, filePath, inlineModuleIndex, { code }) // will transform with css plugin and cache result with css-post plugin - js += `\nimport "${id}?html-proxy&inline-css&style-attr&index=${inlineModuleIndex}.css"` + js += `\nimport "${id}?html-proxy&inline-css&style-attr&index=${inlineModuleIndex}&h=${getHash(code)}.css"` const hash = getHash(cleanUrl(id)) // will transform in `applyHtmlTransforms` overwriteAttrValue( @@ -732,7 +734,7 @@ export function buildHtmlPlugin(config: ResolvedConfig): Plugin { addToHTMLProxyCache(config, filePath, inlineModuleIndex, { code: styleNode.value, }) - js += `\nimport "${id}?html-proxy&inline-css&index=${inlineModuleIndex}.css"` + js += `\nimport "${id}?html-proxy&inline-css&index=${inlineModuleIndex}&h=${getHash(styleNode.value)}.css"` const hash = getHash(cleanUrl(id)) // will transform in `applyHtmlTransforms` s.update( diff --git a/packages/vite/src/node/server/bundledDev.ts b/packages/vite/src/node/server/bundledDev.ts index f5b6ca0c4c06dc..8c3452bc95f7cc 100644 --- a/packages/vite/src/node/server/bundledDev.ts +++ b/packages/vite/src/node/server/bundledDev.ts @@ -10,6 +10,7 @@ import getEtag from 'etag' import { ChunkMetadataMap, resolveRolldownOptions } from '../build' import { BUNDLED_DEV_CLIENT_FILENAME } from '../constants' import { getHmrImplementation } from '../plugins/clientInjections' +import { isHTMLRequest } from '../plugins/html' import { createDebugger, formatAndTruncateFileList } from '../utils' import type { DevEnvironment } from './environment' import { type NormalizedHotChannelClient, debugHmr, getShortName } from './hmr' @@ -195,6 +196,21 @@ export class BundledDev { if (changedFiles.length === 0) { return } + // Edits to an HTML entry may leave its transformed module code + // unchanged (classic inline scripts and markup stay in the html + // output; only module scripts/styles turn into imports), in which + // case rolldown reports Noop updates and nothing would happen. + // Always rebuild and reload on html changes, like the unbundled + // dev server does. + if (changedFiles.some((file) => isHTMLRequest(file))) { + debug?.(`TRIGGER: html entry changed, forcing full rebuild`) + this.devEngine.triggerFullBuild() + this.devEngine.ensureLatestBuildOutput().then( + () => this.debouncedFullReload(), + () => {}, + ) + return + } if (updates.every((update) => update.update.type === 'Noop')) { debug?.(`ignored file change for ${changedFiles.join(', ')}`) return diff --git a/playground/html/__tests__/html.spec.ts b/playground/html/__tests__/html.spec.ts index 08c453ff9354c6..9045f84ff3c412 100644 --- a/playground/html/__tests__/html.spec.ts +++ b/playground/html/__tests__/html.spec.ts @@ -529,6 +529,52 @@ test('invalidate inline proxy module on reload', async () => { expect(await page.textContent('.test')).toContain('ok') }) +test.runIf(isServe)( + 'editing inline script in html reloads with updated content', + async () => { + await page.goto(viteTestUrl + '/a á.html') + try { + await untilBrowserLogAfter( + () => + editFile('a á.html', (code) => + code.replace('special character', 'special character edited'), + ), + 'special character edited', + ) + } finally { + editFile('a á.html', (code) => + code.replace('special character edited', 'special character'), + ) + } + }, +) + +// TODO: enable for bundledDev once rolldown includes +// https://github.com/rolldown/rolldown/pull/10637 — classic inline scripts stay +// in the html output, and rolldown <1.2.4 drops re-emitted assets with changed +// content from rebuild output, so bundledDev would serve the stale html. +test.skipIf(isBundled)( + 'editing classic inline script in html reloads with updated content', + async () => { + await page.goto(`${viteTestUrl}/inline-classic-script.html`) + expect(await page.textContent('.classic-script-content')).toContain( + 'classic before', + ) + editFile('inline-classic-script.html', (code) => + code.replace('classic before', 'classic after edit'), + ) + try { + await expect + .poll(() => page.textContent('.classic-script-content')) + .toContain('classic after edit') + } finally { + editFile('inline-classic-script.html', (code) => + code.replace('classic after edit', 'classic before'), + ) + } + }, +) + test.runIf(isServe)( 'malformed URLs in src attributes should show errors', async () => { diff --git a/playground/html/inline-classic-script.html b/playground/html/inline-classic-script.html new file mode 100644 index 00000000000000..98eb499da634b9 --- /dev/null +++ b/playground/html/inline-classic-script.html @@ -0,0 +1,5 @@ +
placeholder
+ diff --git a/playground/html/vite.config.js b/playground/html/vite.config.js index 4cbf0c145a41c6..390a04b7d1c5b7 100644 --- a/playground/html/vite.config.js +++ b/playground/html/vite.config.js @@ -34,6 +34,7 @@ const input = { serveBothFolder: resolve(dirname, 'serve/both/index.html'), write: resolve(dirname, 'write.html'), 'transform-inline-js': resolve(dirname, 'transform-inline-js.html'), + 'inline-classic-script': resolve(dirname, 'inline-classic-script.html'), malformedUrl: resolve(dirname, 'malformed-url.html'), // resolved from `process.cwd()` by Rolldown (resolved from `root` by vite's resolver first) relativeInput: relative( From b231463a86dc83bcc77a1e2a8e067107fa7e74cc Mon Sep 17 00:00:00 2001 From: btea <2356281422@qq.com> Date: Tue, 18 Aug 2026 17:48:47 +0800 Subject: [PATCH 2/3] fix: update --- packages/vite/src/node/server/bundledDev.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/vite/src/node/server/bundledDev.ts b/packages/vite/src/node/server/bundledDev.ts index 8c3452bc95f7cc..63c40090acb488 100644 --- a/packages/vite/src/node/server/bundledDev.ts +++ b/packages/vite/src/node/server/bundledDev.ts @@ -204,7 +204,6 @@ export class BundledDev { // dev server does. if (changedFiles.some((file) => isHTMLRequest(file))) { debug?.(`TRIGGER: html entry changed, forcing full rebuild`) - this.devEngine.triggerFullBuild() this.devEngine.ensureLatestBuildOutput().then( () => this.debouncedFullReload(), () => {}, From 9cd15579eaaa3c50d2c7ecf1753448da86246f86 Mon Sep 17 00:00:00 2001 From: btea <2356281422@qq.com> Date: Tue, 18 Aug 2026 18:19:03 +0800 Subject: [PATCH 3/3] Update packages/vite/src/node/server/bundledDev.ts Co-authored-by: andrew --- packages/vite/src/node/server/bundledDev.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vite/src/node/server/bundledDev.ts b/packages/vite/src/node/server/bundledDev.ts index 63c40090acb488..4b7322ff35ab16 100644 --- a/packages/vite/src/node/server/bundledDev.ts +++ b/packages/vite/src/node/server/bundledDev.ts @@ -203,7 +203,7 @@ export class BundledDev { // Always rebuild and reload on html changes, like the unbundled // dev server does. if (changedFiles.some((file) => isHTMLRequest(file))) { - debug?.(`TRIGGER: html entry changed, forcing full rebuild`) + debug?.(`TRIGGER: html entry changed, forcing rebuild`) this.devEngine.ensureLatestBuildOutput().then( () => this.debouncedFullReload(), () => {},