diff options
-rw-r--r-- | .changeset/rotten-cups-happen.md | 5 | ||||
-rw-r--r-- | packages/astro/e2e/css-sourcemaps.test.js | 38 | ||||
-rw-r--r-- | packages/astro/e2e/css.test.js | 18 | ||||
-rw-r--r-- | packages/astro/e2e/fixtures/css-sourcemaps/astro.config.mjs | 7 | ||||
-rw-r--r-- | packages/astro/e2e/fixtures/css-sourcemaps/package.json | 8 | ||||
-rw-r--r-- | packages/astro/e2e/fixtures/css-sourcemaps/src/env.d.ts | 1 | ||||
-rw-r--r-- | packages/astro/e2e/fixtures/css-sourcemaps/src/pages/index.astro | 9 | ||||
-rw-r--r-- | packages/astro/e2e/fixtures/css-sourcemaps/src/styles/main.css | 3 | ||||
-rw-r--r-- | packages/astro/src/core/render/dev/index.ts | 8 | ||||
-rw-r--r-- | packages/astro/src/runtime/client/hmr.ts | 12 | ||||
-rw-r--r-- | pnpm-lock.yaml | 6 |
11 files changed, 107 insertions, 8 deletions
diff --git a/.changeset/rotten-cups-happen.md b/.changeset/rotten-cups-happen.md new file mode 100644 index 000000000..e484c098f --- /dev/null +++ b/.changeset/rotten-cups-happen.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Fix duplicate CSS in dev mode when `vite.css.devSourcemap` is provided diff --git a/packages/astro/e2e/css-sourcemaps.test.js b/packages/astro/e2e/css-sourcemaps.test.js new file mode 100644 index 000000000..07cea4cb0 --- /dev/null +++ b/packages/astro/e2e/css-sourcemaps.test.js @@ -0,0 +1,38 @@ +import { expect } from '@playwright/test'; +import { getColor, isWindows, testFactory } from './test-utils.js'; + +const test = testFactory({ + root: './fixtures/css/', +}); + +let devServer; + +test.beforeAll(async ({ astro }) => { + devServer = await astro.startDevServer(); +}); + +test.afterAll(async () => { + await devServer.stop(); +}); + +test.describe('CSS Sourcemap HMR', () => { + test.skip(isWindows, 'TODO: fix css hmr in windows'); + + test('removes Astro-injected CSS once Vite-injected CSS loads', async ({ page, astro }) => { + const html = await astro.fetch('/').then(res => res.text()); + + // style[data-astro-dev-id] should exist in initial SSR'd markup + expect(html).toMatch('data-astro-dev-id'); + + await page.goto(astro.resolveUrl('/')); + + // Ensure JS has initialized + await page.waitForTimeout(500); + + // style[data-astro-dev-id] should NOT exist once JS runs + expect(await page.locator('style[data-astro-dev-id]').count()).toEqual(0); + + // style[data-vite-dev-id] should exist now + expect(await page.locator('style[data-vite-dev-id]').count()).toBeGreaterThan(0); + }); +}); diff --git a/packages/astro/e2e/css.test.js b/packages/astro/e2e/css.test.js index b817c419a..745a540ee 100644 --- a/packages/astro/e2e/css.test.js +++ b/packages/astro/e2e/css.test.js @@ -30,4 +30,22 @@ test.describe('CSS HMR', () => { expect(await getColor(h)).toBe('rgb(0, 128, 0)'); }); + + test('removes Astro-injected CSS once Vite-injected CSS loads', async ({ page, astro }) => { + const html = await astro.fetch('/').then(res => res.text()); + + // style[data-astro-dev-id] should exist in initial SSR'd markup + expect(html).toMatch('data-astro-dev-id'); + + await page.goto(astro.resolveUrl('/')); + + // Ensure JS has initialized + await page.waitForTimeout(500); + + // style[data-astro-dev-id] should NOT exist once JS runs + expect(await page.locator('style[data-astro-dev-id]').count()).toEqual(0); + + // style[data-vite-dev-id] should exist now + expect(await page.locator('style[data-vite-dev-id]').count()).toBeGreaterThan(0); + }); }); diff --git a/packages/astro/e2e/fixtures/css-sourcemaps/astro.config.mjs b/packages/astro/e2e/fixtures/css-sourcemaps/astro.config.mjs new file mode 100644 index 000000000..7e8fac1e7 --- /dev/null +++ b/packages/astro/e2e/fixtures/css-sourcemaps/astro.config.mjs @@ -0,0 +1,7 @@ +export default { + vite: { + css: { + devSourcemap: true, + } + } +}; diff --git a/packages/astro/e2e/fixtures/css-sourcemaps/package.json b/packages/astro/e2e/fixtures/css-sourcemaps/package.json new file mode 100644 index 000000000..1fa4c2c79 --- /dev/null +++ b/packages/astro/e2e/fixtures/css-sourcemaps/package.json @@ -0,0 +1,8 @@ +{ + "name": "@e2e/css-sourcemaps", + "version": "0.0.0", + "private": true, + "dependencies": { + "astro": "workspace:*" + } +} diff --git a/packages/astro/e2e/fixtures/css-sourcemaps/src/env.d.ts b/packages/astro/e2e/fixtures/css-sourcemaps/src/env.d.ts new file mode 100644 index 000000000..8c34fb45e --- /dev/null +++ b/packages/astro/e2e/fixtures/css-sourcemaps/src/env.d.ts @@ -0,0 +1 @@ +/// <reference types="astro/client" />
\ No newline at end of file diff --git a/packages/astro/e2e/fixtures/css-sourcemaps/src/pages/index.astro b/packages/astro/e2e/fixtures/css-sourcemaps/src/pages/index.astro new file mode 100644 index 000000000..7275177f9 --- /dev/null +++ b/packages/astro/e2e/fixtures/css-sourcemaps/src/pages/index.astro @@ -0,0 +1,9 @@ +<h1>hello world</h1> + +<style> + @import "../styles/main.css"; + + h1 { + color: var(--h1-color); + } +</style> diff --git a/packages/astro/e2e/fixtures/css-sourcemaps/src/styles/main.css b/packages/astro/e2e/fixtures/css-sourcemaps/src/styles/main.css new file mode 100644 index 000000000..c80a6cde1 --- /dev/null +++ b/packages/astro/e2e/fixtures/css-sourcemaps/src/styles/main.css @@ -0,0 +1,3 @@ +:root { + --h1-color: red; +} diff --git a/packages/astro/src/core/render/dev/index.ts b/packages/astro/src/core/render/dev/index.ts index 7b5df9482..7ea008d69 100644 --- a/packages/astro/src/core/render/dev/index.ts +++ b/packages/astro/src/core/render/dev/index.ts @@ -10,7 +10,7 @@ import { PAGE_SCRIPT_ID } from '../../../vite-plugin-scripts/index.js'; import { enhanceViteSSRError } from '../../errors/dev/index.js'; import { AggregateError, CSSError, MarkdownError } from '../../errors/index.js'; import type { ModuleLoader } from '../../module-loader/index'; -import { isPage, resolveIdToUrl } from '../../util.js'; +import { isPage, resolveIdToUrl, viteID } from '../../util.js'; import { createRenderContext, renderPage as coreRenderPage } from '../index.js'; import { filterFoundRenderers, loadRenderer } from '../renderer.js'; import { getStylesForURL } from './css.js'; @@ -133,7 +133,11 @@ async function getScriptsAndStyles({ env, filePath }: GetScriptsAndStylesParams) }); // But we still want to inject the styles to avoid FOUC styles.add({ - props: {}, + props: { + type: 'text/css', + // Track the ID so we can match it to Vite's injected style later + 'data-astro-dev-id': viteID(new URL(`.${url}`, env.settings.config.root)) + }, children: content, }); }); diff --git a/packages/astro/src/runtime/client/hmr.ts b/packages/astro/src/runtime/client/hmr.ts index f3a3074f3..b71c39990 100644 --- a/packages/astro/src/runtime/client/hmr.ts +++ b/packages/astro/src/runtime/client/hmr.ts @@ -1,15 +1,15 @@ /// <reference types="vite/client" /> if (import.meta.hot) { - // Vite injects `<style type="text/css">` for ESM imports of styles - // but Astro also SSRs with `<style>` blocks. This MutationObserver + // Vite injects `<style type="text/css" data-vite-dev-id>` for ESM imports of styles + // but Astro also SSRs with `<style type="text/css" data-astro-dev-id>` blocks. This MutationObserver // removes any duplicates as soon as they are hydrated client-side. const injectedStyles = getInjectedStyles(); const mo = new MutationObserver((records) => { for (const record of records) { for (const node of record.addedNodes) { if (isViteInjectedStyle(node)) { - injectedStyles.get(node.innerHTML.trim())?.remove(); + injectedStyles.get(node.getAttribute('data-vite-dev-id')!)?.remove(); } } } @@ -31,8 +31,8 @@ if (import.meta.hot) { function getInjectedStyles() { const injectedStyles = new Map<string, Element>(); - document.querySelectorAll<HTMLStyleElement>('style').forEach((el) => { - injectedStyles.set(el.innerHTML.trim(), el); + document.querySelectorAll<HTMLStyleElement>('style[data-astro-dev-id]').forEach((el) => { + injectedStyles.set(el.getAttribute('data-astro-dev-id')!, el); }); return injectedStyles; } @@ -42,5 +42,5 @@ function isStyle(node: Node): node is HTMLStyleElement { } function isViteInjectedStyle(node: Node): node is HTMLStyleElement { - return isStyle(node) && node.getAttribute('type') === 'text/css'; + return isStyle(node) && node.getAttribute('type') === 'text/css' && !!node.getAttribute('data-vite-dev-id'); } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 35de7b913..c59bac079 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -657,6 +657,12 @@ importers: dependencies: astro: link:../../.. + packages/astro/e2e/fixtures/css-sourcemaps: + specifiers: + astro: workspace:* + dependencies: + astro: link:../../.. + packages/astro/e2e/fixtures/error-cyclic: specifiers: '@astrojs/preact': workspace:* |