Skip to content

Instantly share code, notes, and snippets.

@Danetag
Last active March 5, 2021 21:18
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Danetag/232a01f25b94bea3617b3f0679617892 to your computer and use it in GitHub Desktop.
Save Danetag/232a01f25b94bea3617b3f0679617892 to your computer and use it in GitHub Desktop.
Using critical.js with cluster for maxi perf
const cluster = require('cluster');
const fs = require('fs');
const path = require('path');
const critical = require('critical');
const minify = require('html-minifier').minify;
/*** Step 01 - define pages */
// Manifest is created with Webpack on build
const manifest = JSON.parse(fs.readFileSync('./static/bundles/manifest.json', 'utf8'));
// Get all the possible CSS files here.
// Critical will be in charge to cherry pick only the classes that are rendered within the specified viewport
const aCSS = [];
Object.values(manifest).forEach(path => {
if (path.substr(path.length - 3) === 'css') aCSS.push(`static/bundles/${path}`);
})
// Helper
const fromDir = (startPath,filter,callback) => {
if (!fs.existsSync(startPath)){
console.log("no dir ",startPath);
return;
}
var files=fs.readdirSync(startPath);
for(var i=0;i<files.length;i++){
var filename=path.join(startPath,files[i]);
var stat = fs.lstatSync(filename);
if (stat.isDirectory()){
fromDir(filename,filter,callback); //recurse
}
else if (filter.test(filename)) callback(filename);
};
};
let pages = [];
// Get all pages
fromDir('public/',/\.html$/, filename => {
filename = filename.replace('public/', '');
pages.push(filename)
});
/*** Step 02 - Start processes */
if (cluster.isMaster) {
let cptDone = 0;
const workers = [];
const start = +new Date();
let numCores = require('os').cpus().length;
const steps = Math.floor(pages.length / numCores);
console.log('--- Start Critical CSS');
console.log('Master cluster setting up ' + numCores + ' workers');
// iterate on number of cores need to be utilized by an application
// current example will utilize all of them
for(let i = 0; i < numCores; i++) {
let indexStart = i * steps;
let indexEnd = ((i + 1) * steps) - 1;
if (i + 1 === numCores) indexEnd = pages.length - 1;
// creating workers and pushing reference in an array
// these references can be used to receive messages from workers
workers.push(cluster.fork());
// to receive messages from worker process
workers[i].on('message', function(message) {
console.log(message);
});
// start
workers[i].send({step: 'start', indexStart:indexStart, indexEnd:indexEnd});
}
// process is clustered on a core and process id is assigned
cluster.on('online', function(worker) {
console.log('Worker ' + worker.process.pid + ' is listening');
});
// if any of the worker process dies
cluster.on('exit', function(worker, code, signal) {
console.log('Worker ' + worker.process.pid + ' died with code: ' + code + ', and signal: ' + signal);
cptDone ++;
// Work is done!
if (cptDone === numCores) {
const time = (+new Date() - start) / 1000;
const mn = Math.floor(time / 60);
const s = time - (mn * 60);
console.log('--- End Critical CSS. Finished in', `${mn}mn:${s}s`);
process.exit(0);
}
});
return;
}
// Not master
let cptTask = 0;
process.on('message', function(message) {
if (message.step === 'start') {
start(message.indexStart, message.indexEnd);
}
});
// recursively generates critical css for one url at the time,
// until all urls have been handled
function startNewJob () {
const src = pages.pop() // NOTE: mutates urls array
if (!src) {
// no more new jobs to process (might still be jobs currently in process)
return Promise.resolve()
}
return critical.generate({
inline: true,
base: 'public/',
src: src,
css: aCSS,
minify: true,
width: 1440,
height: 10000,
target: src,
}).then(html => {
cptTask++;
console.log(`CSS critical generated!`, src);
const htmlMinified = minify(html.toString(), {collapseWhitespace: true, removeComments: true});
fs.writeFileSync('public/' + src, htmlMinified);
return startNewJob();
}).catch(err => {
console.log('error', err);
})
}
const start = (indexStart, indexEnd) => {
pages = pages.slice(indexStart, indexEnd);
// how many jobs do we want to handle in paralell?
// Below, 2:
Promise.all([
startNewJob(),
startNewJob(),
])
.then(() => {
console.log(`done with`, cptTask, 'tasks executed');
process.exit(0);
})
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment