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:
Matthew Phillips 2022-04-14 12:19:03 -04:00 committed by GitHub
parent 254048dc82
commit 1687009f31
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 97 additions and 8 deletions

View file

@ -0,0 +1,5 @@
---
'astro': patch
---
Fixes client:only CSS

View file

@ -1,7 +1,8 @@
import type { RouteData } from '../../@types/astro'; import type { AstroConfig, RouteData } from '../../@types/astro';
import type { RenderedChunk } from 'rollup'; import type { RenderedChunk } from 'rollup';
import type { PageBuildData, ViteID } from './types'; import type { PageBuildData, ViteID } from './types';
import { fileURLToPath } from 'url';
import { viteID } from '../util.js'; import { viteID } from '../util.js';
export interface BuildInternals { export interface BuildInternals {
@ -25,6 +26,11 @@ export interface BuildInternals {
*/ */
pagesByViteID: Map<ViteID, PageBuildData>; 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. * chunkToReferenceIdMap maps them to a hash id used to find the final file.
* @deprecated This Map is only used for the legacy build. * @deprecated This Map is only used for the legacy build.
@ -73,6 +79,7 @@ export function createBuildInternals(): BuildInternals {
pagesByComponent: new Map(), pagesByComponent: new Map(),
pagesByViteID: new Map(), pagesByViteID: new Map(),
pagesByClientOnly: new Map(),
}; };
} }
@ -88,6 +95,30 @@ export function trackPageData(
internals.pagesByViteID.set(viteID(componentURL), pageData); 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( export function* getPageDatasByChunk(
internals: BuildInternals, internals: BuildInternals,
chunk: RenderedChunk 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( export function getPageDataByComponent(
internals: BuildInternals, internals: BuildInternals,
component: string component: string

View file

@ -1,5 +1,5 @@
import type { RollupOutput } from 'rollup'; 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 { ViteConfigWithSSR } from '../create-vite';
import type { PageBuildData, StaticBuildOptions } from './types'; import type { PageBuildData, StaticBuildOptions } from './types';
import glob from 'fast-glob'; import glob from 'fast-glob';
@ -54,13 +54,17 @@ export async function staticBuild(opts: StaticBuildOptions) {
const [renderers, mod] = pageData.preload; const [renderers, mod] = pageData.preload;
const metadata = mod.$$metadata; 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([ const topLevelImports = new Set([
// Any component that gets hydrated // Any component that gets hydrated
// 'components/Counter.jsx' // 'components/Counter.jsx'
// { 'components/Counter.jsx': 'counter.hash.js' } // { 'components/Counter.jsx': 'counter.hash.js' }
...metadata.hydratedComponentPaths(), ...metadata.hydratedComponentPaths(),
// Client-only components // Client-only components
...metadata.clientOnlyComponentPaths(), ...clientOnlys,
// Any hydration directive like astro/client/idle.js // Any hydration directive like astro/client/idle.js
...metadata.hydrationDirectiveSpecifiers(), ...metadata.hydrationDirectiveSpecifiers(),
// The client path for each renderer // The client path for each renderer
@ -148,6 +152,7 @@ async function ssrBuild(opts: StaticBuildOptions, internals: BuildInternals, inp
rollupPluginAstroBuildCSS({ rollupPluginAstroBuildCSS({
internals, internals,
legacy: false, legacy: false,
target: 'server'
}), }),
...(viteConfig.plugins || []), ...(viteConfig.plugins || []),
// SSR needs to be last // SSR needs to be last
@ -218,6 +223,7 @@ async function clientBuild(
rollupPluginAstroBuildCSS({ rollupPluginAstroBuildCSS({
internals, internals,
legacy: false, legacy: false,
target: 'client'
}), }),
...(viteConfig.plugins || []), ...(viteConfig.plugins || []),
], ],

View file

@ -9,6 +9,7 @@ import {
getPageDatasByChunk, getPageDatasByChunk,
getPageDataByViteID, getPageDataByViteID,
hasPageDataByViteID, hasPageDataByViteID,
getPageDatasByClientOnlyChunk,
} from '../core/build/internal.js'; } from '../core/build/internal.js';
const PLUGIN_NAME = '@astrojs/rollup-plugin-build-css'; const PLUGIN_NAME = '@astrojs/rollup-plugin-build-css';
@ -54,6 +55,7 @@ function isRawOrUrlModule(id: string) {
interface PluginOptions { interface PluginOptions {
internals: BuildInternals; internals: BuildInternals;
legacy: boolean; legacy: boolean;
target: 'client' | 'server';
} }
export function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin { export function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin {
@ -172,7 +174,7 @@ export function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin {
}, },
async renderChunk(_code, chunk) { async renderChunk(_code, chunk) {
if (!legacy) return null; if (options.target === 'server') return null;
let chunkCSS = ''; let chunkCSS = '';
let isPureCSS = true; let isPureCSS = true;
@ -207,6 +209,10 @@ export function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin {
for (const pageData of getPageDatasByChunk(internals, chunk)) { for (const pageData of getPageDatasByChunk(internals, chunk)) {
pageData.css.add(fileName); 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; return null;

View file

@ -1,5 +1,5 @@
import { expect } from 'chai'; import { expect } from 'chai';
import cheerio from 'cheerio'; import { load as cheerioLoad } from 'cheerio';
import { loadFixture } from './test-utils.js'; import { loadFixture } from './test-utils.js';
describe('Client only components', () => { describe('Client only components', () => {
@ -14,7 +14,7 @@ describe('Client only components', () => {
it('Loads pages using client:only hydrator', async () => { it('Loads pages using client:only hydrator', async () => {
const html = await fixture.readFile('/index.html'); const html = await fixture.readFile('/index.html');
const $ = cheerio.load(html); const $ = cheerioLoad(html);
// test 1: <astro-root> is empty // test 1: <astro-root> is empty
expect($('astro-root').html()).to.equal(''); expect($('astro-root').html()).to.equal('');
@ -24,4 +24,10 @@ describe('Client only components', () => {
// test 2: svelte renderer is on the page // test 2: svelte renderer is on the page
expect(/import\(".\/entry.*/g.test(script)).to.be.ok; 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);
})
}); });

View file

@ -1,7 +1,8 @@
import { defineConfig } from 'astro/config'; import { defineConfig } from 'astro/config';
import svelte from '@astrojs/svelte'; import svelte from '@astrojs/svelte';
import react from '@astrojs/react';
// https://astro.build/config // https://astro.build/config
export default defineConfig({ export default defineConfig({
integrations: [svelte()], integrations: [svelte(), react()],
}); });

View file

@ -4,6 +4,7 @@
"private": true, "private": true,
"dependencies": { "dependencies": {
"@astrojs/svelte": "workspace:*", "@astrojs/svelte": "workspace:*",
"@astrojs/react": "workspace:*",
"astro": "workspace:*" "astro": "workspace:*"
} }
} }

View file

@ -0,0 +1,6 @@
import React from 'react';
import './global.css';
export default function() {
return <div>i am react</div>
}

View file

@ -12,7 +12,11 @@
count -= 1; count -= 1;
} }
</script> </script>
<style>
button {
background: yellowgreen;
}
</style>
<div class="counter"> <div class="counter">
<button on:click={subtract}>-</button> <button on:click={subtract}>-</button>
<pre>{ count }</pre> <pre>{ count }</pre>

View file

@ -0,0 +1,3 @@
body {
font-family: 'Courier New', Courier, monospace;
}

View file

@ -1,9 +1,11 @@
--- ---
import PersistentCounter from '../components/PersistentCounter.svelte'; import PersistentCounter from '../components/PersistentCounter.svelte';
import ReactComponent from '../components/JSXComponent.jsx';
--- ---
<html> <html>
<head><title>Client only pages</title></head> <head><title>Client only pages</title></head>
<body> <body>
<PersistentCounter client:only /> <PersistentCounter client:only />
<ReactComponent client:only="react" />
</body> </body>
</html> </html>

2
pnpm-lock.yaml generated
View file

@ -684,9 +684,11 @@ importers:
packages/astro/test/fixtures/astro-client-only: packages/astro/test/fixtures/astro-client-only:
specifiers: specifiers:
'@astrojs/react': workspace:*
'@astrojs/svelte': workspace:* '@astrojs/svelte': workspace:*
astro: workspace:* astro: workspace:*
dependencies: dependencies:
'@astrojs/react': link:../../../../integrations/react
'@astrojs/svelte': link:../../../../integrations/svelte '@astrojs/svelte': link:../../../../integrations/svelte
astro: link:../../.. astro: link:../../..