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)');
|
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());
|
const html = await astro.fetch('/').then((res) => res.text());
|
||||||
|
// style[data-vite-dev-id] should exist in initial SSR'd markup
|
||||||
// style[data-astro-dev-id] should exist in initial SSR'd markup
|
expect(html).toMatch('data-vite-dev-id');
|
||||||
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);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -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 }) => {
|
test('client:only styles are retained on transition', async ({ page, astro }) => {
|
||||||
const totalExpectedStyles = 8;
|
const totalExpectedStyles = 7;
|
||||||
|
|
||||||
// Go to page 1
|
// Go to page 1
|
||||||
await page.goto(astro.resolveUrl('/client-only-one'));
|
await page.goto(astro.resolveUrl('/client-only-one'));
|
||||||
|
|
|
@ -1,46 +1,7 @@
|
||||||
/// <reference types="vite/client" />
|
/// <reference types="vite/client" />
|
||||||
|
|
||||||
if (import.meta.hot) {
|
if (import.meta.hot) {
|
||||||
// Vite injects `<style data-vite-dev-id>` for ESM imports of styles
|
// HMR temporarily not needed for now, but kept here in case we need it again.
|
||||||
// but Astro also SSRs with `<style data-astro-dev-id>` blocks. This MutationObserver
|
// To re-instate this module again, update `vite-plugin-astro-server/route.ts`
|
||||||
// removes any duplicates as soon as they are hydrated client-side.
|
// to add this module as a script similar to `/@vite/client`
|
||||||
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');
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -208,7 +208,7 @@ async function updateDOM(
|
||||||
const devId = el.dataset.viteDevId;
|
const devId = el.dataset.viteDevId;
|
||||||
// If this same style tag exists, remove it from the new page
|
// If this same style tag exists, remove it from the new page
|
||||||
return (
|
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.
|
// Otherwise, keep it anyways. This is client:only styles.
|
||||||
noopEl
|
noopEl
|
||||||
);
|
);
|
||||||
|
|
|
@ -28,7 +28,7 @@ export async function getStylesForURL(
|
||||||
mode === 'development' && // only inline in development
|
mode === 'development' && // only inline in development
|
||||||
typeof ssrModule?.default === 'string' // ignore JS module styles
|
typeof ssrModule?.default === 'string' // ignore JS module styles
|
||||||
) {
|
) {
|
||||||
importedStylesMap.set(importedModule.url, ssrModule.default);
|
importedStylesMap.set(importedModule.id ?? importedModule.url, ssrModule.default);
|
||||||
} else {
|
} else {
|
||||||
// NOTE: We use the `url` property here. `id` would break Windows.
|
// NOTE: We use the `url` property here. `id` would break Windows.
|
||||||
importedCssUrls.add(importedModule.url);
|
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 { createRenderContext, getParamsAndProps, type SSROptions } from '../core/render/index.js';
|
||||||
import { createRequest } from '../core/request.js';
|
import { createRequest } from '../core/request.js';
|
||||||
import { matchAllRoutes } from '../core/routing/index.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 { getSortedPreloadedMatches } from '../prerender/routing.js';
|
||||||
import { isServerLikeOutput } from '../prerender/utils.js';
|
import { isServerLikeOutput } from '../prerender/utils.js';
|
||||||
import { PAGE_SCRIPT_ID } from '../vite-plugin-scripts/index.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' },
|
props: { type: 'module', src: '/@vite/client' },
|
||||||
children: '',
|
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
|
// 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: '',
|
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({
|
styles.add({
|
||||||
props: {
|
props: {
|
||||||
// Track the ID so we can match it to Vite's injected style later
|
'data-vite-dev-id': url,
|
||||||
'data-astro-dev-id': viteID(new URL(`.${url}`, settings.config.root)),
|
|
||||||
},
|
},
|
||||||
children: content,
|
children: content,
|
||||||
});
|
});
|
||||||
|
|
Loading…
Add table
Reference in a new issue