Include CSS when child component uses Astro.glob on page (#4156)

* Include CSS when child component uses Astro.glob on page

* windows compat

* Make work on windows

* Remove unnecessary concat
This commit is contained in:
Matthew Phillips 2022-08-05 09:52:12 -04:00 committed by GitHub
parent c7efcf57e0
commit 82a1063cc2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 132 additions and 54 deletions

View file

@ -0,0 +1,5 @@
---
'astro': patch
---
Add CSS to page when child component uses Astro.glob

View file

@ -26,11 +26,10 @@ export function* walkParentInfos(
export function* getTopLevelPages(
id: string,
ctx: { getModuleInfo: GetModuleInfo }
): Generator<string, void, unknown> {
): Generator<ModuleInfo, void, unknown> {
for (const info of walkParentInfos(id, ctx)) {
const importers = (info?.importers || []).concat(info?.dynamicImporters || []);
if (importers.length <= 2 && importers[0] === resolvedPagesVirtualModuleId) {
yield info.id;
if (info?.importers[0] === resolvedPagesVirtualModuleId) {
yield info;
}
}
}

View file

@ -7,7 +7,7 @@ import { BuildInternals, createBuildInternals } from '../../core/build/internal.
import { prependForwardSlash } from '../../core/path.js';
import { emptyDir, isModeServerWithNoAdapter, removeDir } from '../../core/util.js';
import { runHookBuildSetup } from '../../integrations/index.js';
import { rollupPluginAstroBuildCSS } from '../../vite-plugin-build-css/index.js';
import { rollupPluginAstroBuildCSS } from './vite-plugin-css.js';
import { PAGE_SCRIPT_ID } from '../../vite-plugin-scripts/index.js';
import type { ViteConfigWithSSR } from '../create-vite';
import { info } from '../logger/core.js';
@ -151,6 +151,7 @@ async function ssrBuild(opts: StaticBuildOptions, internals: BuildInternals, inp
vitePluginInternals(input, internals),
vitePluginPages(opts, internals),
rollupPluginAstroBuildCSS({
buildOptions: opts,
internals,
target: 'server',
astroConfig,
@ -233,6 +234,7 @@ async function clientBuild(
vitePluginInternals(input, internals),
vitePluginHoistedScripts(astroConfig, internals),
rollupPluginAstroBuildCSS({
buildOptions: opts,
internals,
target: 'client',
astroConfig,

View file

@ -22,7 +22,8 @@ export function vitePluginAnalyzer(internals: BuildInternals): VitePlugin {
}
if (hoistedScripts.size) {
for (const pageId of getTopLevelPages(from, this)) {
for (const pageInfo of getTopLevelPages(from, this)) {
const pageId = pageInfo.id;
for (const hid of hoistedScripts) {
if (pageScripts.has(pageId)) {
pageScripts.get(pageId)?.add(hid);
@ -97,8 +98,8 @@ export function vitePluginAnalyzer(internals: BuildInternals): VitePlugin {
clientOnlys.push(cid);
}
for (const pageId of getTopLevelPages(id, this)) {
const pageData = getPageDataByViteID(internals, pageId);
for (const pageInfo of getTopLevelPages(id, this)) {
const pageData = getPageDataByViteID(internals, pageInfo.id);
if (!pageData) continue;
trackClientOnlyPageDatas(internals, pageData, clientOnlys);

View file

@ -1,29 +1,58 @@
import type { GetModuleInfo, OutputChunk } from 'rollup';
import { BuildInternals } from '../core/build/internal';
import type { PageBuildData } from '../core/build/types';
import type { BuildInternals } from './internal';
import type { PageBuildData, StaticBuildOptions } from './types';
import type { AstroConfig } from '../../@types/astro';
import crypto from 'crypto';
import esbuild from 'esbuild';
import npath from 'path';
import { Plugin as VitePlugin } from 'vite';
import { AstroConfig } from '../@types/astro';
import { getTopLevelPages, walkParentInfos } from '../core/build/graph.js';
import { getPageDataByViteID, getPageDatasByClientOnlyID } from '../core/build/internal.js';
import { isCSSRequest } from '../core/render/util.js';
import { getTopLevelPages, walkParentInfos } from './graph.js';
import { getPageDataByViteID, getPageDatasByClientOnlyID } from './internal.js';
import { relativeToSrcDir } from '../util.js';
import { isCSSRequest } from '../render/util.js';
interface PluginOptions {
internals: BuildInternals;
buildOptions: StaticBuildOptions;
target: 'client' | 'server';
astroConfig: AstroConfig;
}
export function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin[] {
const { internals } = options;
// Arbitrary magic number, can change.
const MAX_NAME_LENGTH = 70;
function createHashOfPageParents(id: string, ctx: { getModuleInfo: GetModuleInfo }): string {
export function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin[] {
const { internals, buildOptions } = options;
const { astroConfig } = buildOptions;
// Turn a page location into a name to be used for the CSS file.
function nameifyPage(id: string) {
let rel = relativeToSrcDir(astroConfig, id);
// Remove pages, ex. blog/posts/something.astro
if(rel.startsWith('pages/')) {
rel = rel.slice(6);
}
// Remove extension, ex. blog/posts/something
const ext = npath.extname(rel);
const noext = rel.slice(0, rel.length - ext.length);
// Replace slashes with dashes, ex. blog-posts-something
const named = noext.replace(/\//g, '-');
return named;
}
function createNameForParentPages(id: string, ctx: { getModuleInfo: GetModuleInfo }): string {
const parents = Array.from(getTopLevelPages(id, ctx)).sort();
const hash = crypto.createHash('sha256');
const proposedName = parents.map(page => nameifyPage(page.id)).join('-');
// We don't want absurdedly long chunk names, so if this is too long create a hashed version instead.
if(proposedName.length <= MAX_NAME_LENGTH) {
return proposedName;
}
const hash = crypto.createHash('sha256');
for (const page of parents) {
hash.update(page, 'utf-8');
hash.update(page.id, 'utf-8');
}
return hash.digest('hex').slice(0, 8);
}
@ -37,12 +66,9 @@ export function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin[]
}
}
const CSS_PLUGIN_NAME = '@astrojs/rollup-plugin-build-css';
const CSS_MINIFY_PLUGIN_NAME = '@astrojs/rollup-plugin-build-css-minify';
return [
{
name: CSS_PLUGIN_NAME,
name: 'astro:rollup-plugin-build-css',
outputOptions(outputOptions) {
const manualChunks = outputOptions.manualChunks || Function.prototype;
@ -62,7 +88,7 @@ export function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin[]
// For CSS, create a hash of all of the pages that use it.
// This causes CSS to be built into shared chunks when used by multiple pages.
if (isCSSRequest(id)) {
return createHashOfPageParents(id, args[0]);
return createNameForParentPages(id, args[0]);
}
};
},
@ -96,7 +122,8 @@ export function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin[]
// 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 pageViteID of getTopLevelPages(id, this)) {
for (const pageInfo of getTopLevelPages(id, this)) {
const pageViteID = pageInfo.id;
const pageData = getPageDataByViteID(internals, pageViteID);
for (const importedCssImport of meta.importedCss) {
pageData?.css.add(importedCssImport);
@ -110,7 +137,7 @@ export function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin[]
},
},
{
name: CSS_MINIFY_PLUGIN_NAME,
name: 'astro:rollup-plugin-build-css-minify',
enforce: 'post',
async generateBundle(_outputOptions, bundle) {
// Minify CSS in each bundle ourselves, since server builds are not minified

View file

@ -186,6 +186,16 @@ export function isModeServerWithNoAdapter(config: AstroConfig): boolean {
return config.output === 'server' && !config._ctx.adapter;
}
export function relativeToSrcDir(config: AstroConfig, idOrUrl: URL | string) {
let id: string;
if(typeof idOrUrl !== 'string') {
id = unwrapId(viteID(idOrUrl));
} else {
id = idOrUrl;
}
return id.slice(slash(fileURLToPath(config.srcDir)).length);
}
export function emoji(char: string, fallback: string) {
return process.platform !== 'win32' ? char : fallback;
}

View file

@ -1,28 +0,0 @@
import type { LoadHook, ResolveIdHook } from 'rollup';
import type { Plugin as VitePlugin, ResolvedConfig } from 'vite';
export function getVitePluginByName(viteConfig: ResolvedConfig, pluginName: string): VitePlugin {
const plugin = viteConfig.plugins.find(({ name }) => name === pluginName);
if (!plugin) throw new Error(`${pluginName} plugin couldnt be found`);
return plugin;
}
export function getViteResolvePlugin(viteConfig: ResolvedConfig): VitePlugin {
return getVitePluginByName(viteConfig, 'vite:resolve');
}
export function getViteLoadFallbackPlugin(viteConfig: ResolvedConfig): VitePlugin {
return getVitePluginByName(viteConfig, 'vite:load-fallback');
}
export function getViteResolve(viteConfig: ResolvedConfig): ResolveIdHook {
const plugin = getViteResolvePlugin(viteConfig);
if (!plugin.resolveId) throw new Error(`vite:resolve has no resolveId() hook`);
return plugin.resolveId.bind(null as any) as any;
}
export function getViteLoad(viteConfig: ResolvedConfig): LoadHook {
const plugin = getViteLoadFallbackPlugin(viteConfig);
if (!plugin.load) throw new Error(`vite:load-fallback has no load() hook`);
return plugin.load.bind(null as any) as any;
}

View file

@ -0,0 +1,12 @@
---
// The side-effect of this happening is needed for the test.
await Astro.glob("../pages/**/*.astro");
---
<style>
.box {
height: 50px;
width: 100%;
background: yellow;
}
</style>
<div class="box"></div>

View file

@ -0,0 +1,6 @@
---
// The side-effect of this happening is needed for the test.
const pages = await Astro.glob("../pages/**/*.astro");
---
<div>navbar</div>

View file

@ -0,0 +1,14 @@
---
import Footer from '../components/Footer.astro';
import Navbar from '../components/Navbar.astro';
---
<html>
<head>
<title>Testing</title>
</head>
<body>
<Navbar />
<h1>Testing</h1>
<Footer />
</body>
</html>

View file

@ -0,0 +1,8 @@
<html>
<head>
<title>Testing</title>
</head>
<body>
<h1>Testing</h1>
</body>
</html>

View file

@ -0,0 +1,22 @@
import { expect } from 'chai';
import * as cheerio from 'cheerio';
import { loadFixture } from './test-utils.js';
describe('Astro.glob on pages/ directory', () => {
let fixture;
before(async () => {
fixture = await loadFixture({
root: './fixtures/glob-pages-css/',
});
await fixture.build();
});
it('It includes styles from child components', async () => {
let html = await fixture.readFile('/index.html');
let $ = cheerio.load(html);
expect($('link[rel=stylesheet]')).to.have.a.lengthOf(1);
});
});