Use import order to sort CSS in prod (#4724)

* Use import order to sort CSS in prod

* Adding a changeset

* Pass through the parent id

* Update remaining test
This commit is contained in:
Matthew Phillips 2022-09-12 16:35:39 -04:00 committed by GitHub
parent 56e225b41a
commit 6efafa4b0e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 93 additions and 21 deletions

View file

@ -0,0 +1,5 @@
---
'astro': patch
---
Use import order to sort CSS in the build

View file

@ -7,19 +7,21 @@ export function* walkParentInfos(
id: string,
ctx: { getModuleInfo: GetModuleInfo },
depth = 0,
seen = new Set<string>()
): Generator<[ModuleInfo, number], void, unknown> {
seen = new Set<string>(),
childId = '',
): Generator<[ModuleInfo, number, number], void, unknown> {
seen.add(id);
const info = ctx.getModuleInfo(id);
if (info) {
yield [info, depth];
let order = childId ? info.importedIds.indexOf(childId) : 0;
yield [info, depth, order];
}
const importers = (info?.importers || []).concat(info?.dynamicImporters || []);
for (const imp of importers) {
if (seen.has(imp)) {
continue;
}
yield* walkParentInfos(imp, ctx, ++depth, seen);
yield* walkParentInfos(imp, ctx, ++depth, seen, id);
}
}
@ -34,7 +36,7 @@ export function moduleIsTopLevelPage(info: ModuleInfo): boolean {
export function* getTopLevelPages(
id: string,
ctx: { getModuleInfo: GetModuleInfo }
): Generator<[ModuleInfo, number], void, unknown> {
): Generator<[ModuleInfo, number, number], void, unknown> {
for (const res of walkParentInfos(id, ctx)) {
if (moduleIsTopLevelPage(res[0])) {
yield res;

View file

@ -191,14 +191,26 @@ export function sortedCSS(pageData: PageBuildData) {
return Array.from(pageData.css)
.sort((a, b) => {
let depthA = a[1].depth,
depthB = b[1].depth;
depthB = b[1].depth,
orderA = a[1].order,
orderB = b[1].order;
if (depthA === -1) {
return -1;
} else if (depthB === -1) {
if(orderA === -1 && orderB >= 0) {
return 1;
} else if(orderB === -1 && orderA >= 0) {
return -1;
} else if(orderA > orderB) {
return 1;
} else if(orderA < orderB) {
return -1;
} else {
return depthA > depthB ? -1 : 1;
if (depthA === -1) {
return -1;
} else if (depthB === -1) {
return 1;
} else {
return depthA > depthB ? -1 : 1;
}
}
})
.map(([id]) => id);

View file

@ -18,7 +18,7 @@ export interface PageBuildData {
component: ComponentPath;
route: RouteData;
moduleSpecifier: string;
css: Map<string, { depth: number }>;
css: Map<string, { depth: number; order: number; }>;
hoistedScript: { type: 'inline' | 'external'; value: string } | undefined;
}
export type AllPagesData = Record<ComponentPath, PageBuildData>;

View file

@ -110,7 +110,7 @@ export function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin[]
importedCss: Set<string>;
};
const appendCSSToPage = (pageData: PageBuildData, meta: ViteMetadata, depth: number) => {
const appendCSSToPage = (pageData: PageBuildData, meta: ViteMetadata, depth: number, order: number) => {
for (const importedCssImport of meta.importedCss) {
// CSS is prioritized based on depth. Shared CSS has a higher depth due to being imported by multiple pages.
// Depth info is used when sorting the links on the page.
@ -120,8 +120,15 @@ export function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin[]
if (depth < cssInfo.depth) {
cssInfo.depth = depth;
}
// Update the order, preferring the lowest order we have.
if(cssInfo.order === -1) {
cssInfo.order = order;
} else if(order < cssInfo.order && order > -1) {
cssInfo.order = order;
}
} else {
pageData?.css.set(importedCssImport, { depth });
pageData?.css.set(importedCssImport, { depth, order });
}
}
};
@ -161,7 +168,7 @@ export function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin[]
for (const id of Object.keys(c.modules)) {
for (const pageData of getParentClientOnlys(id, this)) {
for (const importedCssImport of meta.importedCss) {
pageData.css.set(importedCssImport, { depth: -1 });
pageData.css.set(importedCssImport, { depth: -1, order: -1 });
}
}
}
@ -169,12 +176,12 @@ 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.keys(c.modules)) {
for (const [pageInfo, depth] of walkParentInfos(id, this)) {
for (const [pageInfo, depth, order] of walkParentInfos(id, this)) {
if (moduleIsTopLevelPage(pageInfo)) {
const pageViteID = pageInfo.id;
const pageData = getPageDataByViteID(internals, pageViteID);
if (pageData) {
appendCSSToPage(pageData, meta, depth);
appendCSSToPage(pageData, meta, depth, order);
}
} else if (
options.target === 'client' &&
@ -184,7 +191,7 @@ export function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin[]
internals,
pageInfo.id
)) {
appendCSSToPage(pageData, meta, -1);
appendCSSToPage(pageData, meta, -1, order);
}
}
}
@ -211,7 +218,7 @@ export function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin[]
);
if (cssChunk) {
for (const pageData of eachPageData(internals)) {
pageData.css.set(cssChunk.fileName, { depth: -1 });
pageData.css.set(cssChunk.fileName, { depth: -1, order: -1 });
}
}
}

View file

@ -86,10 +86,25 @@ describe('CSS production ordering', () => {
getLinks(html).map((href) => getLinkContent(fixture, href))
);
expect(content).to.have.a.lengthOf(2, 'there are 2 stylesheets');
const [, last] = content;
expect(content).to.have.a.lengthOf(3, 'there are 3 stylesheets');
const [,found] = content;
expect(last.css).to.match(/#00f/);
expect(found.css).to.match(/#00f/);
});
it('CSS injected by injectScript comes first because of import order', async () => {
let oneHtml = await fixture.readFile('/one/index.html');
let twoHtml = await fixture.readFile('/two/index.html');
let threeHtml = await fixture.readFile('/three/index.html');
for(const html of [oneHtml, twoHtml, threeHtml]) {
const content = await Promise.all(
getLinks(html).map((href) => getLinkContent(fixture, href))
);
const [first] = content;
expect(first.css).to.include('green', 'Came from the injected script');
}
});
});
});

View file

@ -0,0 +1,14 @@
import { defineConfig } from 'astro/config';
export default defineConfig({
integrations: [
{
name: 'test-integration',
hooks: {
'astro:config:setup'({ injectScript }) {
injectScript('page-ssr', `import '/src/styles/base.css';`);
}
}
}
]
});

View file

@ -0,0 +1,14 @@
<html>
<head>
<title>Testing</title>
<style>
body {
margin: 2px;
color: blueviolet;
}
</style>
</head>
<body>
<h1>Testing</h1>
</body>
</html>

View file

@ -0,0 +1,3 @@
body {
background: green;
}