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 { 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
|
||||||
|
|
|
@ -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 || []),
|
||||||
],
|
],
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
|
@ -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()],
|
||||||
});
|
});
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/svelte": "workspace:*",
|
"@astrojs/svelte": "workspace:*",
|
||||||
|
"@astrojs/react": "workspace:*",
|
||||||
"astro": "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;
|
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>
|
||||||
|
|
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 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
2
pnpm-lock.yaml
generated
|
@ -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:../../..
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue