Skip to content

Commit

Permalink
Use import order to sort CSS in prod (#4724)
Browse files Browse the repository at this point in the history
* Use import order to sort CSS in prod

* Adding a changeset

* Pass through the parent id

* Update remaining test
  • Loading branch information
matthewp committed Sep 12, 2022
1 parent 56e225b commit 6efafa4
Show file tree
Hide file tree
Showing 9 changed files with 92 additions and 20 deletions.
5 changes: 5 additions & 0 deletions .changeset/many-tables-brush.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'astro': patch
---

Use import order to sort CSS in the build
12 changes: 7 additions & 5 deletions packages/astro/src/core/build/graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

Expand All @@ -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;
Expand Down
20 changes: 16 additions & 4 deletions packages/astro/src/core/build/internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
if(orderA === -1 && orderB >= 0) {
return 1;
} else if(orderB === -1 && orderA >= 0) {
return -1;
} else if (depthB === -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);
Expand Down
2 changes: 1 addition & 1 deletion packages/astro/src/core/build/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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>;
Expand Down
21 changes: 14 additions & 7 deletions packages/astro/src/core/build/vite-plugin-css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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 });
}
}
};
Expand Down Expand Up @@ -161,20 +168,20 @@ 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 });
}
}
}
}

// 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' &&
Expand All @@ -184,7 +191,7 @@ export function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin[]
internals,
pageInfo.id
)) {
appendCSSToPage(pageData, meta, -1);
appendCSSToPage(pageData, meta, -1, order);
}
}
}
Expand All @@ -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 });
}
}
}
Expand Down
21 changes: 18 additions & 3 deletions packages/astro/test/css-order.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
}
});
});
});
14 changes: 14 additions & 0 deletions packages/astro/test/fixtures/css-order/astro.config.mjs
Original file line number Diff line number Diff line change
@@ -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';`);
}
}
}
]
});
14 changes: 14 additions & 0 deletions packages/astro/test/fixtures/css-order/src/pages/three.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<html>
<head>
<title>Testing</title>
<style>
body {
margin: 2px;
color: blueviolet;
}
</style>
</head>
<body>
<h1>Testing</h1>
</body>
</html>
3 changes: 3 additions & 0 deletions packages/astro/test/fixtures/css-order/src/styles/base.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
body {
background: green;
}

0 comments on commit 6efafa4

Please sign in to comment.