Add client:only CSS to their pages (#3113)
* Add client:only CSS to their pages * Adds a changeset * Use viteID for windows
This commit is contained in:
parent
254048dc82
commit
1687009f31
12 changed files with 97 additions and 8 deletions
5
.changeset/forty-goats-warn.md
Normal file
5
.changeset/forty-goats-warn.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'astro': patch
|
||||
---
|
||||
|
||||
Fixes client:only CSS
|
|
@ -1,7 +1,8 @@
|
|||
import type { RouteData } from '../../@types/astro';
|
||||
import type { AstroConfig, RouteData } from '../../@types/astro';
|
||||
import type { RenderedChunk } from 'rollup';
|
||||
import type { PageBuildData, ViteID } from './types';
|
||||
|
||||
import { fileURLToPath } from 'url';
|
||||
import { viteID } from '../util.js';
|
||||
|
||||
export interface BuildInternals {
|
||||
|
@ -25,6 +26,11 @@ export interface BuildInternals {
|
|||
*/
|
||||
pagesByViteID: Map<ViteID, PageBuildData>;
|
||||
|
||||
/**
|
||||
* A map for page-specific information by a client:only component
|
||||
*/
|
||||
pagesByClientOnly: Map<string, Set<PageBuildData>>;
|
||||
|
||||
/**
|
||||
* chunkToReferenceIdMap maps them to a hash id used to find the final file.
|
||||
* @deprecated This Map is only used for the legacy build.
|
||||
|
@ -73,6 +79,7 @@ export function createBuildInternals(): BuildInternals {
|
|||
|
||||
pagesByComponent: new Map(),
|
||||
pagesByViteID: new Map(),
|
||||
pagesByClientOnly: new Map(),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -88,6 +95,30 @@ export function trackPageData(
|
|||
internals.pagesByViteID.set(viteID(componentURL), pageData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tracks client-only components to the pages they are associated with.
|
||||
*/
|
||||
export function trackClientOnlyPageDatas(
|
||||
internals: BuildInternals,
|
||||
pageData: PageBuildData,
|
||||
clientOnlys: string[],
|
||||
astroConfig: AstroConfig,
|
||||
) {
|
||||
for(const clientOnlyComponent of clientOnlys) {
|
||||
const coPath = viteID(new URL('.' + clientOnlyComponent, astroConfig.root));
|
||||
let pageDataSet: Set<PageBuildData>;
|
||||
if(internals.pagesByClientOnly.has(coPath)) {
|
||||
pageDataSet = internals.pagesByClientOnly.get(coPath)!;
|
||||
} else {
|
||||
pageDataSet = new Set<PageBuildData>();
|
||||
internals.pagesByClientOnly.set(coPath, pageDataSet);
|
||||
}
|
||||
pageDataSet.add(pageData);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
export function* getPageDatasByChunk(
|
||||
internals: BuildInternals,
|
||||
chunk: RenderedChunk
|
||||
|
@ -100,6 +131,22 @@ export function* getPageDatasByChunk(
|
|||
}
|
||||
}
|
||||
|
||||
export function* getPageDatasByClientOnlyChunk(
|
||||
internals: BuildInternals,
|
||||
chunk: RenderedChunk
|
||||
): Generator<PageBuildData, void, unknown> {
|
||||
const pagesByClientOnly = internals.pagesByClientOnly;
|
||||
if(pagesByClientOnly.size) {
|
||||
for (const [modulePath] of Object.entries(chunk.modules)) {
|
||||
if (pagesByClientOnly.has(modulePath)) {
|
||||
for(const pageData of pagesByClientOnly.get(modulePath)!) {
|
||||
yield pageData;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function getPageDataByComponent(
|
||||
internals: BuildInternals,
|
||||
component: string
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import type { RollupOutput } from 'rollup';
|
||||
import type { BuildInternals } from '../../core/build/internal.js';
|
||||
import { BuildInternals, trackClientOnlyPageDatas } from '../../core/build/internal.js';
|
||||
import type { ViteConfigWithSSR } from '../create-vite';
|
||||
import type { PageBuildData, StaticBuildOptions } from './types';
|
||||
import glob from 'fast-glob';
|
||||
|
@ -54,13 +54,17 @@ export async function staticBuild(opts: StaticBuildOptions) {
|
|||
const [renderers, mod] = pageData.preload;
|
||||
const metadata = mod.$$metadata;
|
||||
|
||||
// Track client:only usage so we can map their CSS back to the Page they are used in.
|
||||
const clientOnlys = Array.from(metadata.clientOnlyComponentPaths());
|
||||
trackClientOnlyPageDatas(internals, pageData, clientOnlys, astroConfig);
|
||||
|
||||
const topLevelImports = new Set([
|
||||
// Any component that gets hydrated
|
||||
// 'components/Counter.jsx'
|
||||
// { 'components/Counter.jsx': 'counter.hash.js' }
|
||||
...metadata.hydratedComponentPaths(),
|
||||
// Client-only components
|
||||
...metadata.clientOnlyComponentPaths(),
|
||||
...clientOnlys,
|
||||
// Any hydration directive like astro/client/idle.js
|
||||
...metadata.hydrationDirectiveSpecifiers(),
|
||||
// The client path for each renderer
|
||||
|
@ -148,6 +152,7 @@ async function ssrBuild(opts: StaticBuildOptions, internals: BuildInternals, inp
|
|||
rollupPluginAstroBuildCSS({
|
||||
internals,
|
||||
legacy: false,
|
||||
target: 'server'
|
||||
}),
|
||||
...(viteConfig.plugins || []),
|
||||
// SSR needs to be last
|
||||
|
@ -218,6 +223,7 @@ async function clientBuild(
|
|||
rollupPluginAstroBuildCSS({
|
||||
internals,
|
||||
legacy: false,
|
||||
target: 'client'
|
||||
}),
|
||||
...(viteConfig.plugins || []),
|
||||
],
|
||||
|
|
|
@ -9,6 +9,7 @@ import {
|
|||
getPageDatasByChunk,
|
||||
getPageDataByViteID,
|
||||
hasPageDataByViteID,
|
||||
getPageDatasByClientOnlyChunk,
|
||||
} from '../core/build/internal.js';
|
||||
|
||||
const PLUGIN_NAME = '@astrojs/rollup-plugin-build-css';
|
||||
|
@ -54,6 +55,7 @@ function isRawOrUrlModule(id: string) {
|
|||
interface PluginOptions {
|
||||
internals: BuildInternals;
|
||||
legacy: boolean;
|
||||
target: 'client' | 'server';
|
||||
}
|
||||
|
||||
export function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin {
|
||||
|
@ -172,7 +174,7 @@ export function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin {
|
|||
},
|
||||
|
||||
async renderChunk(_code, chunk) {
|
||||
if (!legacy) return null;
|
||||
if (options.target === 'server') return null;
|
||||
|
||||
let chunkCSS = '';
|
||||
let isPureCSS = true;
|
||||
|
@ -207,6 +209,10 @@ export function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin {
|
|||
for (const pageData of getPageDatasByChunk(internals, chunk)) {
|
||||
pageData.css.add(fileName);
|
||||
}
|
||||
// Adds this CSS for client:only components to the appropriate page
|
||||
for (const pageData of getPageDatasByClientOnlyChunk(internals, chunk)) {
|
||||
pageData.css.add(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { expect } from 'chai';
|
||||
import cheerio from 'cheerio';
|
||||
import { load as cheerioLoad } from 'cheerio';
|
||||
import { loadFixture } from './test-utils.js';
|
||||
|
||||
describe('Client only components', () => {
|
||||
|
@ -14,7 +14,7 @@ describe('Client only components', () => {
|
|||
|
||||
it('Loads pages using client:only hydrator', async () => {
|
||||
const html = await fixture.readFile('/index.html');
|
||||
const $ = cheerio.load(html);
|
||||
const $ = cheerioLoad(html);
|
||||
|
||||
// test 1: <astro-root> is empty
|
||||
expect($('astro-root').html()).to.equal('');
|
||||
|
@ -24,4 +24,10 @@ describe('Client only components', () => {
|
|||
// test 2: svelte renderer is on the page
|
||||
expect(/import\(".\/entry.*/g.test(script)).to.be.ok;
|
||||
});
|
||||
|
||||
it('Adds the CSS to the page', async () => {
|
||||
const html = await fixture.readFile('/index.html');
|
||||
const $ = cheerioLoad(html);
|
||||
expect($('link[rel=stylesheet]')).to.have.lengthOf(2);
|
||||
})
|
||||
});
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import { defineConfig } from 'astro/config';
|
||||
import svelte from '@astrojs/svelte';
|
||||
import react from '@astrojs/react';
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
integrations: [svelte()],
|
||||
integrations: [svelte(), react()],
|
||||
});
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
"private": true,
|
||||
"dependencies": {
|
||||
"@astrojs/svelte": "workspace:*",
|
||||
"@astrojs/react": "workspace:*",
|
||||
"astro": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
|
6
packages/astro/test/fixtures/astro-client-only/src/components/JSXComponent.jsx
vendored
Normal file
6
packages/astro/test/fixtures/astro-client-only/src/components/JSXComponent.jsx
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
import React from 'react';
|
||||
import './global.css';
|
||||
|
||||
export default function() {
|
||||
return <div>i am react</div>
|
||||
}
|
|
@ -12,7 +12,11 @@
|
|||
count -= 1;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
button {
|
||||
background: yellowgreen;
|
||||
}
|
||||
</style>
|
||||
<div class="counter">
|
||||
<button on:click={subtract}>-</button>
|
||||
<pre>{ count }</pre>
|
||||
|
|
3
packages/astro/test/fixtures/astro-client-only/src/components/global.css
vendored
Normal file
3
packages/astro/test/fixtures/astro-client-only/src/components/global.css
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
body {
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
}
|
|
@ -1,9 +1,11 @@
|
|||
---
|
||||
import PersistentCounter from '../components/PersistentCounter.svelte';
|
||||
import ReactComponent from '../components/JSXComponent.jsx';
|
||||
---
|
||||
<html>
|
||||
<head><title>Client only pages</title></head>
|
||||
<body>
|
||||
<PersistentCounter client:only />
|
||||
<ReactComponent client:only="react" />
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -684,9 +684,11 @@ importers:
|
|||
|
||||
packages/astro/test/fixtures/astro-client-only:
|
||||
specifiers:
|
||||
'@astrojs/react': workspace:*
|
||||
'@astrojs/svelte': workspace:*
|
||||
astro: workspace:*
|
||||
dependencies:
|
||||
'@astrojs/react': link:../../../../integrations/react
|
||||
'@astrojs/svelte': link:../../../../integrations/svelte
|
||||
astro: link:../../..
|
||||
|
||||
|
|
Loading…
Reference in a new issue