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:
parent
56e225b41a
commit
6efafa4b0e
9 changed files with 93 additions and 21 deletions
5
.changeset/many-tables-brush.md
Normal file
5
.changeset/many-tables-brush.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
'astro': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Use import order to sort CSS in the build
|
|
@ -7,19 +7,21 @@ export function* walkParentInfos(
|
||||||
id: string,
|
id: string,
|
||||||
ctx: { getModuleInfo: GetModuleInfo },
|
ctx: { getModuleInfo: GetModuleInfo },
|
||||||
depth = 0,
|
depth = 0,
|
||||||
seen = new Set<string>()
|
seen = new Set<string>(),
|
||||||
): Generator<[ModuleInfo, number], void, unknown> {
|
childId = '',
|
||||||
|
): Generator<[ModuleInfo, number, number], void, unknown> {
|
||||||
seen.add(id);
|
seen.add(id);
|
||||||
const info = ctx.getModuleInfo(id);
|
const info = ctx.getModuleInfo(id);
|
||||||
if (info) {
|
if (info) {
|
||||||
yield [info, depth];
|
let order = childId ? info.importedIds.indexOf(childId) : 0;
|
||||||
|
yield [info, depth, order];
|
||||||
}
|
}
|
||||||
const importers = (info?.importers || []).concat(info?.dynamicImporters || []);
|
const importers = (info?.importers || []).concat(info?.dynamicImporters || []);
|
||||||
for (const imp of importers) {
|
for (const imp of importers) {
|
||||||
if (seen.has(imp)) {
|
if (seen.has(imp)) {
|
||||||
continue;
|
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(
|
export function* getTopLevelPages(
|
||||||
id: string,
|
id: string,
|
||||||
ctx: { getModuleInfo: GetModuleInfo }
|
ctx: { getModuleInfo: GetModuleInfo }
|
||||||
): Generator<[ModuleInfo, number], void, unknown> {
|
): Generator<[ModuleInfo, number, number], void, unknown> {
|
||||||
for (const res of walkParentInfos(id, ctx)) {
|
for (const res of walkParentInfos(id, ctx)) {
|
||||||
if (moduleIsTopLevelPage(res[0])) {
|
if (moduleIsTopLevelPage(res[0])) {
|
||||||
yield res;
|
yield res;
|
||||||
|
|
|
@ -191,14 +191,26 @@ export function sortedCSS(pageData: PageBuildData) {
|
||||||
return Array.from(pageData.css)
|
return Array.from(pageData.css)
|
||||||
.sort((a, b) => {
|
.sort((a, b) => {
|
||||||
let depthA = a[1].depth,
|
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 (depthB === -1) {
|
|
||||||
return 1;
|
return 1;
|
||||||
|
} else if(orderB === -1 && orderA >= 0) {
|
||||||
|
return -1;
|
||||||
|
} else if(orderA > orderB) {
|
||||||
|
return 1;
|
||||||
|
} else if(orderA < orderB) {
|
||||||
|
return -1;
|
||||||
} else {
|
} 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);
|
.map(([id]) => id);
|
||||||
|
|
|
@ -18,7 +18,7 @@ export interface PageBuildData {
|
||||||
component: ComponentPath;
|
component: ComponentPath;
|
||||||
route: RouteData;
|
route: RouteData;
|
||||||
moduleSpecifier: string;
|
moduleSpecifier: string;
|
||||||
css: Map<string, { depth: number }>;
|
css: Map<string, { depth: number; order: number; }>;
|
||||||
hoistedScript: { type: 'inline' | 'external'; value: string } | undefined;
|
hoistedScript: { type: 'inline' | 'external'; value: string } | undefined;
|
||||||
}
|
}
|
||||||
export type AllPagesData = Record<ComponentPath, PageBuildData>;
|
export type AllPagesData = Record<ComponentPath, PageBuildData>;
|
||||||
|
|
|
@ -110,7 +110,7 @@ export function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin[]
|
||||||
importedCss: Set<string>;
|
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) {
|
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.
|
// 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.
|
// 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) {
|
if (depth < cssInfo.depth) {
|
||||||
cssInfo.depth = 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 {
|
} 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 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, 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 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 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)) {
|
if (moduleIsTopLevelPage(pageInfo)) {
|
||||||
const pageViteID = pageInfo.id;
|
const pageViteID = pageInfo.id;
|
||||||
const pageData = getPageDataByViteID(internals, pageViteID);
|
const pageData = getPageDataByViteID(internals, pageViteID);
|
||||||
if (pageData) {
|
if (pageData) {
|
||||||
appendCSSToPage(pageData, meta, depth);
|
appendCSSToPage(pageData, meta, depth, order);
|
||||||
}
|
}
|
||||||
} else if (
|
} else if (
|
||||||
options.target === 'client' &&
|
options.target === 'client' &&
|
||||||
|
@ -184,7 +191,7 @@ export function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin[]
|
||||||
internals,
|
internals,
|
||||||
pageInfo.id
|
pageInfo.id
|
||||||
)) {
|
)) {
|
||||||
appendCSSToPage(pageData, meta, -1);
|
appendCSSToPage(pageData, meta, -1, order);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -211,7 +218,7 @@ export function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin[]
|
||||||
);
|
);
|
||||||
if (cssChunk) {
|
if (cssChunk) {
|
||||||
for (const pageData of eachPageData(internals)) {
|
for (const pageData of eachPageData(internals)) {
|
||||||
pageData.css.set(cssChunk.fileName, { depth: -1 });
|
pageData.css.set(cssChunk.fileName, { depth: -1, order: -1 });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -86,10 +86,25 @@ describe('CSS production ordering', () => {
|
||||||
getLinks(html).map((href) => getLinkContent(fixture, href))
|
getLinks(html).map((href) => getLinkContent(fixture, href))
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(content).to.have.a.lengthOf(2, 'there are 2 stylesheets');
|
expect(content).to.have.a.lengthOf(3, 'there are 3 stylesheets');
|
||||||
const [, last] = content;
|
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
packages/astro/test/fixtures/css-order/astro.config.mjs
vendored
Normal file
14
packages/astro/test/fixtures/css-order/astro.config.mjs
vendored
Normal 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';`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
14
packages/astro/test/fixtures/css-order/src/pages/three.astro
vendored
Normal file
14
packages/astro/test/fixtures/css-order/src/pages/three.astro
vendored
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Testing</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
margin: 2px;
|
||||||
|
color: blueviolet;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Testing</h1>
|
||||||
|
</body>
|
||||||
|
</html>
|
3
packages/astro/test/fixtures/css-order/src/styles/base.css
vendored
Normal file
3
packages/astro/test/fixtures/css-order/src/styles/base.css
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
body {
|
||||||
|
background: green;
|
||||||
|
}
|
Loading…
Reference in a new issue