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 { 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

View file

@ -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 || []),
],

View file

@ -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;

View file

@ -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);
})
});

View file

@ -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()],
});

View file

@ -4,6 +4,7 @@
"private": true,
"dependencies": {
"@astrojs/svelte": "workspace:*",
"@astrojs/react": "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;
}
</script>
<style>
button {
background: yellowgreen;
}
</style>
<div class="counter">
<button on:click={subtract}>-</button>
<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 ReactComponent from '../components/JSXComponent.jsx';
---
<html>
<head><title>Client only pages</title></head>
<body>
<PersistentCounter client:only />
<ReactComponent client:only="react" />
</body>
</html>

View file

@ -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:../../..