Skip to content

Commit 13bf25e

Browse files
authored
Revert "Replace internal cssScopeTo implementation to vite.cssScopeTo (#13347)"
This reverts commit d83f92a.
1 parent be866a1 commit 13bf25e

13 files changed

+115
-66
lines changed

packages/astro/src/core/build/plugins/plugin-css.ts

+51-2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import type { GetModuleInfo } from 'rollup';
2-
import type { BuildOptions, ResolvedConfig, Plugin as VitePlugin } from 'vite';
2+
import type { BuildOptions, ResolvedConfig, Rollup, Plugin as VitePlugin } from 'vite';
33
import { isBuildableCSSRequest } from '../../../vite-plugin-astro-server/util.js';
44
import type { BuildInternals } from '../internal.js';
55
import type { AstroBuildPlugin, BuildTarget } from '../plugin.js';
66
import type { PageBuildData, StaticBuildOptions, StylesheetAsset } from '../types.js';
77

88
import { hasAssetPropagationFlag } from '../../../content/index.js';
9+
import type { AstroPluginCssMetadata } from '../../../vite-plugin-astro/index.js';
910
import * as assetName from '../css-asset-name.js';
1011
import {
1112
getParentExtendedModuleInfos,
@@ -155,6 +156,32 @@ function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin[] {
155156
},
156157
};
157158

159+
/**
160+
* This plugin is a port of https://github.com/vitejs/vite/pull/16058. It enables removing unused
161+
* scoped CSS from the bundle if the scoped target (e.g. Astro files) were not bundled.
162+
* Once/If that PR is merged, we can refactor this away, renaming `meta.astroCss` to `meta.vite`.
163+
*/
164+
const cssScopeToPlugin: VitePlugin = {
165+
name: 'astro:rollup-plugin-css-scope-to',
166+
renderChunk(_, chunk, __, meta) {
167+
for (const id in chunk.modules) {
168+
// If this CSS is scoped to its importers exports, check if those importers exports
169+
// are rendered in the chunks. If they are not, we can skip bundling this CSS.
170+
const modMeta = this.getModuleInfo(id)?.meta as AstroPluginCssMetadata | undefined;
171+
const cssScopeTo = modMeta?.astroCss?.cssScopeTo;
172+
if (cssScopeTo && !isCssScopeToRendered(cssScopeTo, Object.values(meta.chunks))) {
173+
// If this CSS is not used, delete it from the chunk modules so that Vite is unable
174+
// to trace that it's used
175+
delete chunk.modules[id];
176+
const moduleIdsIndex = chunk.moduleIds.indexOf(id);
177+
if (moduleIdsIndex > -1) {
178+
chunk.moduleIds.splice(moduleIdsIndex, 1);
179+
}
180+
}
181+
}
182+
},
183+
};
184+
158185
const singleCssPlugin: VitePlugin = {
159186
name: 'astro:rollup-plugin-single-css',
160187
enforce: 'post',
@@ -246,7 +273,7 @@ function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin[] {
246273
},
247274
};
248275

249-
return [cssBuildPlugin, singleCssPlugin, inlineStylesheetsPlugin];
276+
return [cssBuildPlugin, cssScopeToPlugin, singleCssPlugin, inlineStylesheetsPlugin];
250277
}
251278

252279
/***** UTILITY FUNCTIONS *****/
@@ -294,3 +321,25 @@ function appendCSSToPage(
294321
}
295322
}
296323
}
324+
325+
/**
326+
* `cssScopeTo` is a map of `importer`s to its `export`s. This function iterate each `cssScopeTo` entries
327+
* and check if the `importer` and its `export`s exists in the final chunks. If at least one matches,
328+
* `cssScopeTo` is considered "rendered" by Rollup and we return true.
329+
*/
330+
function isCssScopeToRendered(
331+
cssScopeTo: Record<string, string[]>,
332+
chunks: Rollup.RenderedChunk[],
333+
) {
334+
for (const moduleId in cssScopeTo) {
335+
const exports = cssScopeTo[moduleId];
336+
// Find the chunk that renders this `moduleId` and get the rendered module
337+
const renderedModule = chunks.find((c) => c.moduleIds.includes(moduleId))?.modules[moduleId];
338+
// Return true if `renderedModule` exists and one of its exports is rendered
339+
if (renderedModule?.renderedExports.some((e) => exports.includes(e))) {
340+
return true;
341+
}
342+
}
343+
344+
return false;
345+
}

packages/astro/src/core/build/static-build.ts

+1-12
Original file line numberDiff line numberDiff line change
@@ -188,18 +188,7 @@ async function ssrBuild(
188188
const encoded = encodeName(name);
189189
return [prefix, encoded, suffix].join('');
190190
},
191-
assetFileNames(chunkInfo) {
192-
const { names } = chunkInfo;
193-
const name = names[0] ?? '';
194-
195-
// Sometimes chunks have the `@_@astro` suffix due to SSR logic. Remove it!
196-
// TODO: refactor our build logic to avoid this
197-
if (name.includes(ASTRO_PAGE_EXTENSION_POST_PATTERN)) {
198-
const [sanitizedName] = name.split(ASTRO_PAGE_EXTENSION_POST_PATTERN);
199-
return `${settings.config.build.assets}/${sanitizedName}.[hash][extname]`;
200-
}
201-
return `${settings.config.build.assets}/[name].[hash][extname]`;
202-
},
191+
assetFileNames: `${settings.config.build.assets}/[name].[hash][extname]`,
203192
...viteConfig.build?.rollupOptions?.output,
204193
entryFileNames(chunkInfo) {
205194
if (chunkInfo.facadeModuleId?.startsWith(ASTRO_PAGE_RESOLVED_MODULE_ID)) {

packages/astro/src/vite-plugin-astro/index.ts

+17-5
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@ import type { SourceDescription } from 'rollup';
22
import type * as vite from 'vite';
33
import type { Logger } from '../core/logger/core.js';
44
import type { AstroSettings } from '../types/astro.js';
5-
import type { PluginMetadata as AstroPluginMetadata, CompileMetadata } from './types.js';
5+
import type {
6+
PluginCssMetadata as AstroPluginCssMetadata,
7+
PluginMetadata as AstroPluginMetadata,
8+
CompileMetadata,
9+
} from './types.js';
610

711
import { defaultClientConditions, defaultServerConditions, normalizePath } from 'vite';
812
import type { AstroConfig } from '../types/public/config.js';
@@ -12,7 +16,7 @@ import { handleHotUpdate } from './hmr.js';
1216
import { parseAstroRequest } from './query.js';
1317
import { loadId } from './utils.js';
1418
export { getAstroMetadata } from './metadata.js';
15-
export type { AstroPluginMetadata };
19+
export type { AstroPluginMetadata, AstroPluginCssMetadata };
1620

1721
interface AstroPluginOptions {
1822
settings: AstroSettings;
@@ -134,9 +138,17 @@ export default function astro({ settings, logger }: AstroPluginOptions): vite.Pl
134138

135139
return {
136140
code: result.code,
137-
// `vite.cssScopeTo` is a Vite feature that allows this CSS to be treeshaken
138-
// if the Astro component's default export is not used
139-
meta: result.isGlobal ? undefined : { vite: { cssScopeTo: [filename, 'default'] } },
141+
// This metadata is used by `cssScopeToPlugin` to remove this module from the bundle
142+
// if the `filename` default export (the Astro component) is unused.
143+
meta: result.isGlobal
144+
? undefined
145+
: ({
146+
astroCss: {
147+
cssScopeTo: {
148+
[filename]: ['default'],
149+
},
150+
},
151+
} satisfies AstroPluginCssMetadata),
140152
};
141153
}
142154
case 'script': {

packages/astro/src/vite-plugin-astro/types.ts

+21
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,27 @@ export interface PluginMetadata {
1818
};
1919
}
2020

21+
export interface PluginCssMetadata {
22+
astroCss: {
23+
/**
24+
* For Astro CSS virtual modules, it can scope to the main Astro module's default export
25+
* so that if those exports are treeshaken away, the CSS module will also be treeshaken.
26+
*
27+
* Example config if the CSS id is `/src/Foo.astro?astro&type=style&lang.css`:
28+
* ```js
29+
* cssScopeTo: {
30+
* '/src/Foo.astro': ['default']
31+
* }
32+
* ```
33+
*
34+
* The above is the only config we use today, but we're exposing as a `Record` to follow the
35+
* upstream Vite implementation: https://github.com/vitejs/vite/pull/16058. When/If that lands,
36+
* we can also remove our custom implementation.
37+
*/
38+
cssScopeTo: Record<string, string[]>;
39+
};
40+
}
41+
2142
export interface CompileMetadata {
2243
/** Used for HMR to compare code changes */
2344
originalCode: string;

packages/astro/test/0-css.test.js

+8-13
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,10 @@ import { after, before, describe, it } from 'node:test';
99
import * as cheerio from 'cheerio';
1010
import { loadFixture } from './test-utils.js';
1111

12-
async function getCssContent($, fixture) {
13-
const contents = await Promise.all(
14-
$('link[rel=stylesheet][href^=/_astro/]').map((_, el) =>
15-
fixture.readFile(el.attribs.href.replace(/^\/?/, '/')),
16-
),
17-
);
18-
return contents.join('').replace(/\s/g, '').replace('/n', '');
19-
}
12+
/** @type {import('./test-utils').Fixture} */
13+
let fixture;
2014

2115
describe('CSS', function () {
22-
/** @type {import('./test-utils').Fixture} */
23-
let fixture;
24-
2516
before(async () => {
2617
fixture = await loadFixture({ root: './fixtures/0-css/' });
2718
});
@@ -39,7 +30,10 @@ describe('CSS', function () {
3930
// get bundled CSS (will be hashed, hence DOM query)
4031
html = await fixture.readFile('/index.html');
4132
$ = cheerio.load(html);
42-
bundledCSS = await getCssContent($, fixture);
33+
const bundledCSSHREF = $('link[rel=stylesheet][href^=/_astro/]').attr('href');
34+
bundledCSS = (await fixture.readFile(bundledCSSHREF.replace(/^\/?/, '/')))
35+
.replace(/\s/g, '')
36+
.replace('/n', '');
4337
},
4438
{
4539
timeout: 45000,
@@ -105,7 +99,8 @@ describe('CSS', function () {
10599
it('Styles through barrel files should only include used Astro scoped styles', async () => {
106100
const barrelHtml = await fixture.readFile('/barrel-styles/index.html');
107101
const barrel$ = cheerio.load(barrelHtml);
108-
const style = await getCssContent(barrel$, fixture);
102+
const barrelBundledCssHref = barrel$('link[rel=stylesheet][href^=/_astro/]').attr('href');
103+
const style = await fixture.readFile(barrelBundledCssHref.replace(/^\/?/, '/'));
109104
assert.match(style, /\.comp-a\[data-astro-cid/);
110105
assert.match(style, /\.comp-c\{/);
111106
assert.doesNotMatch(style, /\.comp-b/);

packages/astro/test/astro-css-bundling.test.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,9 @@ describe('CSS Bundling', function () {
6363
}
6464
});
6565

66-
it('there are 5 css files', async () => {
66+
it('there are 4 css files', async () => {
6767
const dir = await fixture.readdir('/_astro');
68-
assert.equal(dir.length, 5);
68+
assert.equal(dir.length, 4);
6969
});
7070

7171
it('CSS includes hashes', async () => {
@@ -96,9 +96,9 @@ describe('CSS Bundling', function () {
9696
await fixture.build({ mode: 'production' });
9797
});
9898

99-
it('there are 5 css files', async () => {
99+
it('there are 4 css files', async () => {
100100
const dir = await fixture.readdir('/assets');
101-
assert.equal(dir.length, 5);
101+
assert.equal(dir.length, 4);
102102
});
103103

104104
it('CSS does not include hashes', async () => {

packages/astro/test/config-vite.test.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ describe('Vite Config', async () => {
2020
it('Allows overriding bundle naming options in the build', async () => {
2121
const html = await fixture.readFile('/index.html');
2222
const $ = cheerio.load(html);
23-
assert.match($('link').attr('href'), /\/assets\/testing-.+\.css/);
23+
assert.match($('link').attr('href'), /\/assets\/testing-[a-z\d]+\.css/);
2424
});
2525
});
2626

packages/astro/test/css-inline-stylesheets.test.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ describe('Setting inlineStylesheets to auto in static output', () => {
113113

114114
// the count of style/link tags depends on our css chunking logic
115115
// this test should be updated if it changes
116-
assert.equal($('style').length, 2);
116+
assert.equal($('style').length, 3);
117117
assert.equal($('link[rel=stylesheet]').length, 1);
118118
});
119119

@@ -162,7 +162,7 @@ describe('Setting inlineStylesheets to auto in server output', () => {
162162

163163
// the count of style/link tags depends on our css chunking logic
164164
// this test should be updated if it changes
165-
assert.equal($('style').length, 2);
165+
assert.equal($('style').length, 3);
166166
assert.equal($('link[rel=stylesheet]').length, 1);
167167
});
168168

packages/astro/test/css-order-import.test.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -147,9 +147,9 @@ describe('CSS ordering - import order', () => {
147147
const content = await Promise.all(
148148
getLinks(html).map((href) => getLinkContent(href, fixture)),
149149
);
150-
let [link1, , link3] = content;
150+
let [link1, link2] = content;
151151
assert.ok(link1.css.includes('f0f8ff')); // aliceblue minified
152-
assert.ok(link3.css.includes('ff0')); // yellow minified
152+
assert.ok(link2.css.includes('ff0')); // yellow minified
153153
});
154154
});
155155
});

packages/astro/test/css-order.test.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ describe('CSS production ordering', () => {
9090
);
9191

9292
assert.ok(content.length, 3, 'there are 3 stylesheets');
93-
const [, pageStyles, sharedStyles] = content;
93+
const [, sharedStyles, pageStyles] = content;
9494

9595
assert.ok(/red/.exec(sharedStyles.css));
9696
assert.ok(/#00f/.exec(pageStyles.css));

packages/astro/test/postcss.test.js

+4-10
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,6 @@ import * as cheerio from 'cheerio';
44
import eol from 'eol';
55
import { loadFixture } from './test-utils.js';
66

7-
async function getCssContent($, fixture) {
8-
const contents = await Promise.all(
9-
$('link[rel=stylesheet][href^=/_astro/]').map((_, el) =>
10-
fixture.readFile(el.attribs.href.replace(/^\/?/, '/')),
11-
),
12-
);
13-
return contents.join('').replace(/\s/g, '').replace('/n', '');
14-
}
15-
167
describe('PostCSS', () => {
178
let fixture;
189
let bundledCSS;
@@ -28,7 +19,10 @@ describe('PostCSS', () => {
2819
// get bundled CSS (will be hashed, hence DOM query)
2920
const html = await fixture.readFile('/index.html');
3021
const $ = cheerio.load(html);
31-
bundledCSS = await getCssContent($, fixture);
22+
const bundledCSSHREF = $('link[rel=stylesheet][href^=/_astro/]').attr('href');
23+
bundledCSS = (await fixture.readFile(bundledCSSHREF.replace(/^\/?/, '/')))
24+
.replace(/\s/g, '')
25+
.replace('/n', '');
3226
},
3327
{ timeout: 45000 },
3428
);

packages/astro/test/remote-css.test.js

+2-10
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,6 @@ import { before, describe, it } from 'node:test';
33
import * as cheerio from 'cheerio';
44
import { loadFixture } from './test-utils.js';
55

6-
async function getCssContent($, fixture) {
7-
const contents = await Promise.all(
8-
$('link[rel=stylesheet][href^=/_astro/]').map((_, el) =>
9-
fixture.readFile(el.attribs.href.replace(/^\/?/, '/')),
10-
),
11-
);
12-
return contents.join('').replace(/\s/g, '').replace('/n', '');
13-
}
14-
156
describe('Remote CSS', () => {
167
let fixture;
178

@@ -28,7 +19,8 @@ describe('Remote CSS', () => {
2819
const html = await fixture.readFile('/index.html');
2920
const $ = cheerio.load(html);
3021

31-
const css = await getCssContent($, fixture);
22+
const relPath = $('link').attr('href');
23+
const css = await fixture.readFile(relPath);
3224

3325
assert.match(css, /https:\/\/unpkg.com\/open-props/);
3426
assert.match(css, /body/);

packages/integrations/tailwind/test/fixtures/basic/astro.config.js

+1-4
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,5 @@ export default defineConfig({
88
configFile: fileURLToPath(new URL('./tailwind.config.js', import.meta.url)),
99
nesting: true
1010
}),
11-
],
12-
build: {
13-
inlineStylesheets: 'never'
14-
}
11+
]
1512
});

0 commit comments

Comments
 (0)