Skip to content

Commit

Permalink
Remove duplicate CSS in dev (#5917)
Browse files Browse the repository at this point in the history
* fix(#5817): remove duplicate CSS in dev

* chore: add changeset

Co-authored-by: Nate Moore <nate@astro.build>
  • Loading branch information
natemoo-re and natemoo-re committed Jan 23, 2023
1 parent 4987d6f commit 7325df4
Show file tree
Hide file tree
Showing 11 changed files with 107 additions and 8 deletions.
5 changes: 5 additions & 0 deletions .changeset/rotten-cups-happen.md
@@ -0,0 +1,5 @@
---
'astro': patch
---

Fix duplicate CSS in dev mode when `vite.css.devSourcemap` is provided
38 changes: 38 additions & 0 deletions 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);
});
});
18 changes: 18 additions & 0 deletions packages/astro/e2e/css.test.js
Expand Up @@ -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);
});
});
7 changes: 7 additions & 0 deletions packages/astro/e2e/fixtures/css-sourcemaps/astro.config.mjs
@@ -0,0 +1,7 @@
export default {
vite: {
css: {
devSourcemap: true,
}
}
};
8 changes: 8 additions & 0 deletions 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:*"
}
}
1 change: 1 addition & 0 deletions packages/astro/e2e/fixtures/css-sourcemaps/src/env.d.ts
@@ -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;
}
8 changes: 6 additions & 2 deletions packages/astro/src/core/render/dev/index.ts
Expand Up @@ -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';
Expand Down Expand Up @@ -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,
});
});
Expand Down
12 changes: 6 additions & 6 deletions 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();
}
}
}
Expand All @@ -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;
}
Expand All @@ -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');
}
6 changes: 6 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 7325df4

Please sign in to comment.