diff --git a/packages/playground/hmr/__tests__/hmr.spec.ts b/packages/playground/hmr/__tests__/hmr.spec.ts index 8244ecb20472e6..1f28763a90df94 100644 --- a/packages/playground/hmr/__tests__/hmr.spec.ts +++ b/packages/playground/hmr/__tests__/hmr.spec.ts @@ -139,4 +139,20 @@ if (!isBuild) { 'title2' ) }) + + test('CSS update preserves query params', async () => { + await page.goto(viteTestUrl) + + editFile('global.css', (code) => code.replace('white', 'tomato')) + + const elprev = await page.$('.css-prev') + const elpost = await page.$('.css-post') + await untilUpdated(() => elprev.textContent(), 'param=required') + await untilUpdated(() => elpost.textContent(), 'param=required') + const textprev = await elprev.textContent() + const textpost = await elpost.textContent() + expect(textprev).not.toBe(textpost) + expect(textprev).not.toMatch('direct') + expect(textpost).not.toMatch('direct') + }) } diff --git a/packages/playground/hmr/global.css b/packages/playground/hmr/global.css new file mode 100644 index 00000000000000..5b6976fbff5506 --- /dev/null +++ b/packages/playground/hmr/global.css @@ -0,0 +1,3 @@ +body { + background: white; +} diff --git a/packages/playground/hmr/hmr.js b/packages/playground/hmr/hmr.js index e8da1ecbabf98b..01dca20f5dd71c 100644 --- a/packages/playground/hmr/hmr.js +++ b/packages/playground/hmr/hmr.js @@ -35,6 +35,19 @@ if (import.meta.hot) { import.meta.hot.on('vite:beforeUpdate', (event) => { console.log(`>>> vite:beforeUpdate -- ${event.type}`) + + const cssUpdate = event.updates.find( + (update) => + update.type === 'css-update' && update.path.match('global.css') + ) + if (cssUpdate) { + const el = document.querySelector('#global-css') + text('.css-prev', el.href) + // We don't have a vite:afterUpdate event, but updates are currently sync + setTimeout(() => { + text('.css-post', el.href) + }, 0) + } }) import.meta.hot.on('vite:error', (event) => { diff --git a/packages/playground/hmr/index.html b/packages/playground/hmr/index.html index 2b060949d38af0..766338598e51ad 100644 --- a/packages/playground/hmr/index.html +++ b/packages/playground/hmr/index.html @@ -1,6 +1,9 @@ +
+
+
diff --git a/packages/vite/src/client/client.ts b/packages/vite/src/client/client.ts index c9a95a66604bb3..8d647cee52c881 100644 --- a/packages/vite/src/client/client.ts +++ b/packages/vite/src/client/client.ts @@ -39,6 +39,12 @@ function warnFailedFetch(err: Error, path: string | string[]) { ) } +function cleanUrl(pathname: string): string { + const url = new URL(pathname, location.toString()) + url.searchParams.delete('direct') + return url.pathname + url.search +} + // Listen for messages socket.addEventListener('message', async ({ data }) => { handleMessage(JSON.parse(data)) @@ -73,21 +79,21 @@ async function handleMessage(payload: HMRPayload) { } else { // css-update // this is only sent when a css file referenced with is updated - let { path, timestamp } = update - path = path.replace(/\?.*/, '') + const { path, timestamp } = update + const searchUrl = cleanUrl(path) // can't use querySelector with `[href*=]` here since the link may be // using relative paths so we need to use link.href to grab the full // URL for the include check. const el = Array.from( document.querySelectorAll('link') - ).find((e) => e.href.includes(path)) + ).find((e) => cleanUrl(e.href).includes(searchUrl)) if (el) { - const newPath = `${base}${path.slice(1)}${ - path.includes('?') ? '&' : '?' + const newPath = `${base}${searchUrl.slice(1)}${ + searchUrl.includes('?') ? '&' : '?' }t=${timestamp}` el.href = new URL(newPath, el.href).href } - console.log(`[vite] css hot updated: ${path}`) + console.log(`[vite] css hot updated: ${searchUrl}`) } }) break