orbit icon indicating copy to clipboard operation
orbit copied to clipboard

astro-purgecss does not load css in collections

Open tmuntaner opened this issue 1 year ago • 7 comments

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:

  1. Create a website with a layout file and css.
  2. Create other pages. These should be styled.
  3. Create an astro collection. These pages should be unstyled.

Expected behavior The collection pages should be styled.

Screenshots

Network Activity

image

styled content:

image

unstyled content:

image

tmuntaner avatar Sep 21 '24 11:09 tmuntaner

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.

mhdcodes avatar Sep 22 '24 09:09 mhdcodes

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.

dmitrysmagin avatar Sep 23 '24 21:09 dmitrysmagin

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 avatar Sep 24 '24 06:09 dmitrysmagin

@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.

mhdcodes avatar Sep 26 '24 23:09 mhdcodes

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:

mhdcodes avatar Sep 26 '24 23:09 mhdcodes

@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
                    }
                }
            }
        }

dmitrysmagin avatar Sep 27 '24 18:09 dmitrysmagin

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.

mhdcodes avatar Sep 28 '24 15:09 mhdcodes

thanks to @dmitrysmagin this issue has been resolved, you can try with:

pnpm add https://pkg.pr.new/codiume/orbit/astro-purgecss@ae450b4

image

sorry for taking to much time to resolve this :pray: .

mhdcodes avatar Nov 07 '24 14:11 mhdcodes