Remove unused CSS for client:load
components (#4566)
This commit is contained in:
parent
6f1fbd8be0
commit
9ad307a9fc
6 changed files with 48 additions and 9 deletions
5
.changeset/tough-pandas-sneeze.md
Normal file
5
.changeset/tough-pandas-sneeze.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
'astro': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Remove unused CSS for `client:load` components
|
|
@ -5,8 +5,11 @@ import { prependForwardSlash } from '../path.js';
|
||||||
import { viteID } from '../util.js';
|
import { viteID } from '../util.js';
|
||||||
|
|
||||||
export interface BuildInternals {
|
export interface BuildInternals {
|
||||||
// Pure CSS chunks are chunks that only contain CSS.
|
/**
|
||||||
pureCSSChunks: Set<RenderedChunk>;
|
* The module ids of all CSS chunks, used to deduplicate CSS assets between
|
||||||
|
* SSR build and client build in vite-plugin-css.
|
||||||
|
*/
|
||||||
|
cssChunkModuleIds: Set<string>;
|
||||||
|
|
||||||
// A mapping of hoisted script ids back to the exact hoisted scripts it references
|
// A mapping of hoisted script ids back to the exact hoisted scripts it references
|
||||||
hoistedScriptIdToHoistedMap: Map<string, Set<string>>;
|
hoistedScriptIdToHoistedMap: Map<string, Set<string>>;
|
||||||
|
@ -59,10 +62,6 @@ export interface BuildInternals {
|
||||||
* @returns {BuildInternals}
|
* @returns {BuildInternals}
|
||||||
*/
|
*/
|
||||||
export function createBuildInternals(): BuildInternals {
|
export function createBuildInternals(): BuildInternals {
|
||||||
// Pure CSS chunks are chunks that only contain CSS.
|
|
||||||
// This is all of them, and chunkToReferenceIdMap maps them to a hash id used to find the final file.
|
|
||||||
const pureCSSChunks = new Set<RenderedChunk>();
|
|
||||||
|
|
||||||
// These are for tracking hoisted script bundling
|
// These are for tracking hoisted script bundling
|
||||||
const hoistedScriptIdToHoistedMap = new Map<string, Set<string>>();
|
const hoistedScriptIdToHoistedMap = new Map<string, Set<string>>();
|
||||||
|
|
||||||
|
@ -70,7 +69,7 @@ export function createBuildInternals(): BuildInternals {
|
||||||
const hoistedScriptIdToPagesMap = new Map<string, Set<string>>();
|
const hoistedScriptIdToPagesMap = new Map<string, Set<string>>();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
pureCSSChunks,
|
cssChunkModuleIds: new Set(),
|
||||||
hoistedScriptIdToHoistedMap,
|
hoistedScriptIdToHoistedMap,
|
||||||
hoistedScriptIdToPagesMap,
|
hoistedScriptIdToPagesMap,
|
||||||
entrySpecifierToBundleMap: new Map<string, string>(),
|
entrySpecifierToBundleMap: new Map<string, string>(),
|
||||||
|
|
|
@ -134,11 +134,30 @@ export function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin[]
|
||||||
|
|
||||||
// Chunks that have the viteMetadata.importedCss are CSS chunks
|
// Chunks that have the viteMetadata.importedCss are CSS chunks
|
||||||
if (meta.importedCss.size) {
|
if (meta.importedCss.size) {
|
||||||
|
// In the SSR build, keep track of all CSS chunks' modules as the client build may
|
||||||
|
// duplicate them, e.g. for `client:load` components that render in SSR and client
|
||||||
|
// for hydation.
|
||||||
|
if (options.target === 'server') {
|
||||||
|
for (const id of Object.keys(c.modules)) {
|
||||||
|
internals.cssChunkModuleIds.add(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// In the client build, we bail if the chunk is a duplicated CSS chunk tracked from
|
||||||
|
// above. We remove all the importedCss to prevent emitting the CSS asset.
|
||||||
|
if (options.target === 'client') {
|
||||||
|
if (Object.keys(c.modules).every((id) => internals.cssChunkModuleIds.has(id))) {
|
||||||
|
for (const importedCssImport of meta.importedCss) {
|
||||||
|
delete bundle[importedCssImport];
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// For the client build, client:only styles need to be mapped
|
// For the client build, client:only styles need to be mapped
|
||||||
// over to their page. For this chunk, determine if it's a child of a
|
// over to their page. For this chunk, determine if it's a child of a
|
||||||
// client:only component and if so, add its CSS to the page it belongs to.
|
// client:only component and if so, add its CSS to the page it belongs to.
|
||||||
if (options.target === 'client') {
|
if (options.target === 'client') {
|
||||||
for (const [id] of Object.entries(c.modules)) {
|
for (const id of Object.keys(c.modules)) {
|
||||||
for (const pageData of getParentClientOnlys(id, this)) {
|
for (const pageData of getParentClientOnlys(id, this)) {
|
||||||
for (const importedCssImport of meta.importedCss) {
|
for (const importedCssImport of meta.importedCss) {
|
||||||
pageData.css.set(importedCssImport, { depth: -1 });
|
pageData.css.set(importedCssImport, { depth: -1 });
|
||||||
|
@ -148,7 +167,7 @@ export function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin[]
|
||||||
}
|
}
|
||||||
|
|
||||||
// For this CSS chunk, walk parents until you find a page. Add the CSS to that page.
|
// For this CSS chunk, walk parents until you find a page. Add the CSS to that page.
|
||||||
for (const [id] of Object.entries(c.modules)) {
|
for (const id of Object.keys(c.modules)) {
|
||||||
for (const [pageInfo, depth] of walkParentInfos(id, this)) {
|
for (const [pageInfo, depth] of walkParentInfos(id, this)) {
|
||||||
if (moduleIsTopLevelPage(pageInfo)) {
|
if (moduleIsTopLevelPage(pageInfo)) {
|
||||||
const pageViteID = pageInfo.id;
|
const pageViteID = pageInfo.id;
|
||||||
|
|
|
@ -354,5 +354,12 @@ describe('CSS', function () {
|
||||||
expect(allInjectedStyles).to.contain('.vue-scss{');
|
expect(allInjectedStyles).to.contain('.vue-scss{');
|
||||||
expect(allInjectedStyles).to.contain('.vue-scoped[data-v-');
|
expect(allInjectedStyles).to.contain('.vue-scoped[data-v-');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('remove unused styles from client:load components', async () => {
|
||||||
|
const bundledAssets = await fixture.readdir('./assets');
|
||||||
|
// SvelteDynamic styles is already included in the main page css asset
|
||||||
|
const unusedCssAsset = bundledAssets.find((asset) => /SvelteDynamic\..*\.css/.test(asset));
|
||||||
|
expect(unusedCssAsset, 'Found unused style ' + unusedCssAsset).to.be.undefined;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
7
packages/astro/test/fixtures/0-css/src/components/SvelteDynamic.svelte
vendored
Normal file
7
packages/astro/test/fixtures/0-css/src/components/SvelteDynamic.svelte
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<h1 id="svelte-dynamic" class="svelte-dynamic">Svelte Dynamic</h1>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.svelte-dynamic {
|
||||||
|
font-family: Courier, monospace;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -18,6 +18,7 @@ import VueSass from '../components/VueSass.vue';
|
||||||
import VueScoped from '../components/VueScoped.vue';
|
import VueScoped from '../components/VueScoped.vue';
|
||||||
import VueScss from '../components/VueScss.vue';
|
import VueScss from '../components/VueScss.vue';
|
||||||
import ReactDynamic from '../components/ReactDynamic.jsx';
|
import ReactDynamic from '../components/ReactDynamic.jsx';
|
||||||
|
import SvelteDynamic from '../components/SvelteDynamic.svelte';
|
||||||
|
|
||||||
import '../styles/imported-url.css';
|
import '../styles/imported-url.css';
|
||||||
import '../styles/imported.sass';
|
import '../styles/imported.sass';
|
||||||
|
@ -69,6 +70,7 @@ import '../styles/imported.scss';
|
||||||
<VueScoped />
|
<VueScoped />
|
||||||
<VueScss />
|
<VueScss />
|
||||||
<ReactDynamic client:load />
|
<ReactDynamic client:load />
|
||||||
|
<SvelteDynamic client:load />
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
Loading…
Reference in a new issue