Last active
March 5, 2021 21:18
-
-
Save Danetag/232a01f25b94bea3617b3f0679617892 to your computer and use it in GitHub Desktop.
Using critical.js with cluster for maxi perf
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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