Remove duplicate CSS in dev (#5917)
* fix(#5817): remove duplicate CSS in dev * chore: add changeset Co-authored-by: Nate Moore <nate@astro.build>
This commit is contained in:
parent
4987d6f44c
commit
7325df4121
11 changed files with 107 additions and 8 deletions
5
.changeset/rotten-cups-happen.md
Normal file
5
.changeset/rotten-cups-happen.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'astro': patch
|
||||
---
|
||||
|
||||
Fix duplicate CSS in dev mode when `vite.css.devSourcemap` is provided
|
38
packages/astro/e2e/css-sourcemaps.test.js
Normal file
38
packages/astro/e2e/css-sourcemaps.test.js
Normal file
|
@ -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);
|
||||
});
|
||||
});
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
export default {
|
||||
vite: {
|
||||
css: {
|
||||
devSourcemap: true,
|
||||
}
|
||||
}
|
||||
};
|
8
packages/astro/e2e/fixtures/css-sourcemaps/package.json
Normal file
8
packages/astro/e2e/fixtures/css-sourcemaps/package.json
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"name": "@e2e/css-sourcemaps",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"astro": "workspace:*"
|
||||
}
|
||||
}
|
1
packages/astro/e2e/fixtures/css-sourcemaps/src/env.d.ts
vendored
Normal file
1
packages/astro/e2e/fixtures/css-sourcemaps/src/env.d.ts
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/// <reference types="astro/client" />
|
|
@ -0,0 +1,9 @@
|
|||
<h1>hello world</h1>
|
||||
|
||||
<style>
|
||||
@import "../styles/main.css";
|
||||
|
||||
h1 {
|
||||
color: var(--h1-color);
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,3 @@
|
|||
:root {
|
||||
--h1-color: red;
|
||||
}
|
|
@ -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,
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
|
|
|
@ -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:*
|
||||
|
|
Loading…
Reference in a new issue