Skip to content

Commit

Permalink
implement inlining of critical css
Browse files Browse the repository at this point in the history
  • Loading branch information
Blargian committed Feb 11, 2025
1 parent ff8807c commit 988f68c
Show file tree
Hide file tree
Showing 7 changed files with 2,253 additions and 639 deletions.
13 changes: 11 additions & 2 deletions docusaurus.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import math from 'remark-math';
import katex from 'rehype-katex';
import chHeader from './plugins/header.js';
import fixLinks from './src/hooks/fixLinks.js';
import customPostCssPlugin from "./plugins/customPostCss.cjs";

// Helper function to skip over index.md files.
function skipIndex(items) {
Expand Down Expand Up @@ -164,7 +165,6 @@ const config = {
href: 'https://widget.kapa.ai',
rel: 'preconnect',
}

},
{
tagName: 'link',
Expand Down Expand Up @@ -334,7 +334,16 @@ const config = {
},
}
},
chHeader
chHeader,
[
customPostCssPlugin,
{
"outputPath": "build/assets/css/",
"minify": false,
"preserve": false,
"outputDest": "critical.css"
}
]
],
customFields: {
blogSidebarLink: '/docs/knowledgebase',
Expand Down
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@
"@playwright/test": "^1.49.1",
"@types/react": "^19.0.4",
"cheerio": "^1.0.0",
"glob": "^11.0.1",
"p-map": "^7.0.3",
"postcss-critical-css": "^3.0.7",
"postcss-discard": "^2.0.0",
"rsync": "^0.6.1",
"typescript": "^5.7.3"
},
Expand Down
85 changes: 85 additions & 0 deletions plugins/customPostCss.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
Added as an optimization for render blocking CSS which is dragging down site vitals:
- extracts CSS surrounded by @critical (determined by one of the available online analysis tools)
- removes the critical CSS from the styles.css bundle rspack/webpack spits out
- adds the critical CSS as an inline style tag to the <head> element of HTML pages
*/

const fs = require('fs/promises');
const path = require('path');
const glob = require('glob');
const cheerio = require('cheerio');
const { default: pMap } = require('p-map'); // Import the CommonJS version

async function customPostCssPlugin(context, options) {
return {
name: "custom-postcss",
configurePostCss(postCssOptions) {
try {
// adds the postcss-critical-css to current postcss plugin
const postCssCriticalCss = require('postcss-critical-css')(options);
if (!postCssOptions.plugins.includes(postCssCriticalCss)) {
postCssOptions.plugins.push(postCssCriticalCss);
}
// adds the postcss-discard to current postcss plugin
const postCssDiscard = require('postcss-discard')({
rules: [
{
selector: '@critical *' // Targets everything inside @critical
}
]
});
if (!postCssOptions.plugins.includes(postCssDiscard)) {
postCssOptions.plugins.push(postCssDiscard);
}
} catch (error) {
console.error("Error occurred in custom-postcss configuration:", error);
throw error; // Re-throw the error to stop the build
}
return postCssOptions;
},
postBuild: async function({ siteConfig = {}, routesPaths = [], outDir }) {
console.log("Running postBuild hook from the customPostCss plugin");
const criticalCssFile = path.join(outDir, 'assets', 'css', 'critical.css'); // generated in configurePostCss above
try {
const criticalCss = await fs.readFile(criticalCssFile, 'utf8');
const htmlFiles = findHtmlFiles(outDir);

await pMap(
htmlFiles,
async (htmlFile) => {
const htmlPath = path.join(outDir, htmlFile);
try {
let htmlContent = await fs.readFile(htmlPath, 'utf8');
const $ = cheerio.load(htmlContent); // Load HTML with Cheerio
const head = $('head');

if (head.length > 0 &&!head.find('style[critical-css]').length) {
head.append(`<style critical-css="">${criticalCss}</style>`); // Append with Cheerio
htmlContent = $.html(); // Get the updated HTML
await fs.writeFile(htmlPath, htmlContent, 'utf8');
console.log(`Injected critical CSS into ${htmlFile}`);
} else if (head.length === 0) {
console.warn(`Could not find a <head> tag in ${htmlFile}. Critical CSS not injected.`);
} else {
console.log(`Critical CSS already injected into ${htmlFile}. Skipping.`);
}
} catch (err) {
console.error(`Error processing ${htmlFile}:`, err);
}
},
{ concurrency: 8 }
);
} catch (error) {
console.error("Error processing critical CSS:", error);
}
}
};
}

// Helper function to find all HTML files in a directory (recursive)
function findHtmlFiles(outDir) {
return glob.sync('**/*.html', { cwd: outDir }); // Find all .html files recursively
}

module.exports = customPostCssPlugin
Loading

0 comments on commit 988f68c

Please sign in to comment.