orbit
orbit copied to clipboard
astro-purgecss does not load css in collections
Describe the bug
When updating astro-purgecss from version 4.5.0 to 4.6.0, pages from astro collections don't load their layout's css files. Looking at the network activity, the css files on these collection pages have a different filename (probably a hash) than the rest of the website, and because that file doesn't exist, these page load as unstyled.
I have a small repository which reproduces the issue: https://github.com/tmuntaner/astro-purgecss-issue-reproduce
To reproduce the issue, run astro build and then astro preview. Go to the blog page and select an article.
To show that it works before the update, change astro-purgecss to version 4.5.0.
Notes:
- This issue doesn't happen in the dev environment. I only see it when I build the website with
astro build.
To Reproduce Steps to reproduce the behavior:
- Create a website with a layout file and css.
- Create other pages. These should be styled.
- Create an astro collection. These pages should be unstyled.
Expected behavior The collection pages should be styled.
Screenshots
Network Activity
styled content:
unstyled content:
hey @tmuntaner thanks for reporting
I'll try to look into this soon. Feel free to open a PR if you already know the solution and want it released quickly.
I've got the same issue. Quick research shows that the .css files are being renamed with a different hash, but their references in html remain with old names. This happens on linux only because of that:
// Skip re-hashing a file if it's not generated by Astro ex: assets/styles/light.css
if (!file.includes('_astro/')) {
await writeFile(file, css);
return [file, file];
}
This clause is true for Windows, because it has backslashes; as a result .css files are NOT renamed, and everything works fine. On linux this is not true, so files are renamed, but the references in html remains old.
Btw, '_astro' outdir can be set in astro.config.mjs: https://docs.astro.build/en/reference/configuration-reference/#buildassets so better not hardcode it like that.
The best solution here is to DISABLE file renaming completely. Another solution is to control renaming via the config setting.
Okay, I understand now why .css files are not being updated with new .css names. I had build.format = "directory" set in my astro.config.mjs which is important for my project. This creates folders for pathnames with index.html inside. So, instead of "dist/page1.html" it creates "dist/page1/index.html" This case is not recognized by this plugin, hence we have an error.
This code is totally incorrect:
headline('Generating purged html pages...');
const pages = routes
.filter((route) => {
if (
route.pathname === undefined ||
route.distURL === undefined ||
route.type !== 'page'
) {
return false;
}
return true;
})
.map((route) => cleanPath(route.distURL as URL))
With simple routes when .astro page components are named like page1.astro, about.astro etc, this will work. However, this will fail with complex routing: blog-[id].astro or [...slug].page where one page component may correspond to multiple routes.
You need to iterate through 'pages' parameter, not 'routes' and explore 'pathname' field:
"astro:build:done": ({ dir, routes, pages, logger }) => {
console.dir(pages.map((e: any) => e.pathname), { depth: null });
}
And then take care because each pathname may be a folder or html file.
@dmitrysmagin if we disable file renaming will endup with css files generated with wrong hashes. that was a reported issue before check: https://github.com/codiume/orbit/issues/450.
Okay, I understand now why .css files are not being updated with new .css names. I had build.format = "directory" set in my astro.config.mjs which is important for my project. This creates folders for pathnames with index.html inside. So, instead of "dist/page1.html" it creates "dist/page1/index.html" This case is not recognized by this plugin, hence we have an error.
This code is totally incorrect:
headline('Generating purged html pages...'); const pages = routes .filter((route) => { if ( route.pathname === undefined || route.distURL === undefined || route.type !== 'page' ) { return false; } return true; }) .map((route) => cleanPath(route.distURL as URL))With simple routes when .astro page components are named like page1.astro, about.astro etc, this will work. However, this will fail with complex routing: blog-[id].astro or [...slug].page where one page component may correspond to multiple routes.
You need to iterate through 'pages' parameter, not 'routes' and explore 'pathname' field:
"astro:build:done": ({ dir, routes, pages, logger }) => { console.dir(pages.map((e: any) => e.pathname), { depth: null }); }And then take care because each pathname may be a folder or html file.
hmm. I think I need to give this a closer look. thanks for looking into this. :pray:
@dmitrysmagin if we disable file renaming will endup with css files generated with wrong hashes. that was a reported issue before check: #450.
Ideally it would be better to have an option setting whether to rename files or not. For my project I've cut your plugin like that:
function Plugin(options: PurgeCSSOptions = {}): AstroIntegration {
return {
name: 'astro-purgecss-simple',
hooks: {
'astro:build:done': async ({ dir, logger }) => {
logger.info('Generating purged css files...');
const outDir = cleanPath(dir);
const purged = await new PurgeCSS().purge({
css: [`${outDir}/**/*.css`],
defaultExtractor: (content) => content.match(/[\w-/:]+(?<!:)/g) || [],
...options,
content: [
`${outDir}/**/*.html`,
`${outDir}/**/*.js`,
...(options.content || [])
]
});
await Promise.all(
purged
.filter(({ file }) => file?.endsWith('.css'))
.map(async ({ css, file }) => {
await writeFile(file as string, css);
logger.info(file as string);
})
);
}
}
};
}
This is enough to do what it needs to do without renaming any file.
In my other plugin i do this to get the full list of htmls generated by Astro (not the most beautiful code, but it works):
hooks: {
"astro:build:done": ({ dir, pages, logger }) => {
let slugs = pages
.filter((e: any) => typeof e.pathname == "string")
.map((e: any) => e.pathname.endsWith("/") ? e.pathname.slice(0, -1) : e.pathname);
let outDir = fileURLToPath(dir);
for (const slug of slugs) {
let htmlpath = path.join(outDir, slug);
htmlpath = (htmlpath.endsWith("/") || htmlpath.endsWith("\\")) ? htmlpath.slice(0, -1) : htmlpath;
if (fs.existsSync(htmlpath) && fs.statSync(htmlpath).isDirectory()) {
htmlpath = path.join(htmlpath, "index.html");
} else {
htmlpath += ".html";
}
if (fs.existsSync(htmlpath)) {
const originalHTML = fs.readFileSync(htmlpath, "utf-8");
// Do smth useful with html
}
}
}
}
What you have shared is what the plugin used to be. prior to version 4.5.0, but since this plugin is updating CSS files and updating the content without updating the hash it was problematic.
thanks for the shared snippets I will make sure to check and fix this asap.
thanks to @dmitrysmagin this issue has been resolved, you can try with:
pnpm add https://pkg.pr.new/codiume/orbit/astro-purgecss@ae450b4
sorry for taking to much time to resolve this :pray: .