Fix duplicated Astro and Vite injected styles (#8706)
This commit is contained in:
parent
31c59ad8b6
commit
345808170f
13 changed files with 18 additions and 135 deletions
5
.changeset/olive-bags-think.md
Normal file
5
.changeset/olive-bags-think.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'astro': patch
|
||||
---
|
||||
|
||||
Fix duplicated Astro and Vite injected styles
|
|
@ -1,36 +0,0 @@
|
|||
import { expect } from '@playwright/test';
|
||||
import { 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('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);
|
||||
});
|
||||
});
|
|
@ -29,21 +29,9 @@ test.describe('CSS HMR', () => {
|
|||
await expect(h).toHaveCSS('color', 'rgb(0, 128, 0)');
|
||||
});
|
||||
|
||||
test('removes Astro-injected CSS once Vite-injected CSS loads', async ({ page, astro }) => {
|
||||
test('removes Astro-injected CSS once Vite-injected CSS loads', async ({ 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);
|
||||
// style[data-vite-dev-id] should exist in initial SSR'd markup
|
||||
expect(html).toMatch('data-vite-dev-id');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
export default {
|
||||
vite: {
|
||||
css: {
|
||||
devSourcemap: true,
|
||||
}
|
||||
}
|
||||
};
|
|
@ -1,8 +0,0 @@
|
|||
{
|
||||
"name": "@e2e/css-sourcemaps",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"astro": "workspace:*"
|
||||
}
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
/// <reference types="astro/client" />
|
|
@ -1,9 +0,0 @@
|
|||
<h1>hello world</h1>
|
||||
|
||||
<style>
|
||||
@import "../styles/main.css";
|
||||
|
||||
h1 {
|
||||
color: var(--h1-color);
|
||||
}
|
||||
</style>
|
|
@ -1,3 +0,0 @@
|
|||
:root {
|
||||
--h1-color: red;
|
||||
}
|
|
@ -631,7 +631,7 @@ test.describe('View Transitions', () => {
|
|||
});
|
||||
|
||||
test('client:only styles are retained on transition', async ({ page, astro }) => {
|
||||
const totalExpectedStyles = 8;
|
||||
const totalExpectedStyles = 7;
|
||||
|
||||
// Go to page 1
|
||||
await page.goto(astro.resolveUrl('/client-only-one'));
|
||||
|
|
|
@ -1,46 +1,7 @@
|
|||
/// <reference types="vite/client" />
|
||||
|
||||
if (import.meta.hot) {
|
||||
// Vite injects `<style data-vite-dev-id>` for ESM imports of styles
|
||||
// but Astro also SSRs with `<style 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.getAttribute('data-vite-dev-id')!)?.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
mo.observe(document.documentElement, { subtree: true, childList: true });
|
||||
|
||||
// Vue `link` styles need to be manually refreshed in Firefox
|
||||
import.meta.hot.on('vite:beforeUpdate', async (payload) => {
|
||||
for (const file of payload.updates) {
|
||||
if (file.acceptedPath.includes('vue&type=style')) {
|
||||
const link = document.querySelector(`link[href="${file.acceptedPath}"]`);
|
||||
if (link) {
|
||||
link.replaceWith(link.cloneNode(true));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getInjectedStyles() {
|
||||
const injectedStyles = new Map<string, Element>();
|
||||
document.querySelectorAll<HTMLStyleElement>('style[data-astro-dev-id]').forEach((el) => {
|
||||
injectedStyles.set(el.getAttribute('data-astro-dev-id')!, el);
|
||||
});
|
||||
return injectedStyles;
|
||||
}
|
||||
|
||||
function isStyle(node: Node): node is HTMLStyleElement {
|
||||
return node.nodeType === node.ELEMENT_NODE && (node as Element).tagName.toLowerCase() === 'style';
|
||||
}
|
||||
|
||||
function isViteInjectedStyle(node: Node): node is HTMLStyleElement {
|
||||
return isStyle(node) && !!node.getAttribute('data-vite-dev-id');
|
||||
// HMR temporarily not needed for now, but kept here in case we need it again.
|
||||
// To re-instate this module again, update `vite-plugin-astro-server/route.ts`
|
||||
// to add this module as a script similar to `/@vite/client`
|
||||
}
|
||||
|
|
|
@ -208,7 +208,7 @@ async function updateDOM(
|
|||
const devId = el.dataset.viteDevId;
|
||||
// If this same style tag exists, remove it from the new page
|
||||
return (
|
||||
newDocument.querySelector(`style[data-astro-dev-id="${devId}"]`) ||
|
||||
newDocument.querySelector(`style[data-vite-dev-id="${devId}"]`) ||
|
||||
// Otherwise, keep it anyways. This is client:only styles.
|
||||
noopEl
|
||||
);
|
||||
|
|
|
@ -28,7 +28,7 @@ export async function getStylesForURL(
|
|||
mode === 'development' && // only inline in development
|
||||
typeof ssrModule?.default === 'string' // ignore JS module styles
|
||||
) {
|
||||
importedStylesMap.set(importedModule.url, ssrModule.default);
|
||||
importedStylesMap.set(importedModule.id ?? importedModule.url, ssrModule.default);
|
||||
} else {
|
||||
// NOTE: We use the `url` property here. `id` would break Windows.
|
||||
importedCssUrls.add(importedModule.url);
|
||||
|
|
|
@ -12,7 +12,7 @@ import { loadMiddleware } from '../core/middleware/loadMiddleware.js';
|
|||
import { createRenderContext, getParamsAndProps, type SSROptions } from '../core/render/index.js';
|
||||
import { createRequest } from '../core/request.js';
|
||||
import { matchAllRoutes } from '../core/routing/index.js';
|
||||
import { isPage, resolveIdToUrl, viteID } from '../core/util.js';
|
||||
import { isPage } from '../core/util.js';
|
||||
import { getSortedPreloadedMatches } from '../prerender/routing.js';
|
||||
import { isServerLikeOutput } from '../prerender/utils.js';
|
||||
import { PAGE_SCRIPT_ID } from '../vite-plugin-scripts/index.js';
|
||||
|
@ -275,13 +275,6 @@ async function getScriptsAndStyles({ pipeline, filePath }: GetScriptsAndStylesPa
|
|||
props: { type: 'module', src: '/@vite/client' },
|
||||
children: '',
|
||||
});
|
||||
scripts.add({
|
||||
props: {
|
||||
type: 'module',
|
||||
src: await resolveIdToUrl(moduleLoader, 'astro/runtime/client/hmr.js'),
|
||||
},
|
||||
children: '',
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: We should allow adding generic HTML elements to the head, not just scripts
|
||||
|
@ -322,11 +315,11 @@ async function getScriptsAndStyles({ pipeline, filePath }: GetScriptsAndStylesPa
|
|||
},
|
||||
children: '',
|
||||
});
|
||||
// But we still want to inject the styles to avoid FOUC
|
||||
// But we still want to inject the styles to avoid FOUC. The style tags
|
||||
// should emulate what Vite injects so further HMR works as expected.
|
||||
styles.add({
|
||||
props: {
|
||||
// Track the ID so we can match it to Vite's injected style later
|
||||
'data-astro-dev-id': viteID(new URL(`.${url}`, settings.config.root)),
|
||||
'data-vite-dev-id': url,
|
||||
},
|
||||
children: content,
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue