performance: Use worker threads to minify JS/CSS files (#3823)

This commit is contained in:
Chocobozzz 2020-06-04 15:00:50 +02:00 committed by GitHub
parent 02af7d0c2d
commit c854cced65
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 175 additions and 82 deletions

View file

@ -23,19 +23,23 @@ var ERR = require("async-stacktrace");
var settings = require('./Settings');
var async = require('async');
var fs = require('fs');
var StringDecoder = require('string_decoder').StringDecoder;
var CleanCSS = require('clean-css');
var uglifyJS = require("uglify-js");
var path = require('path');
var plugins = require("ep_etherpad-lite/static/js/pluginfw/plugins");
var RequireKernel = require('etherpad-require-kernel');
var urlutil = require('url');
var mime = require('mime-types')
var Threads = require('threads')
var log4js = require('log4js');
var logger = log4js.getLogger("Minify");
var ROOT_DIR = path.normalize(__dirname + "/../../static/");
var TAR_PATH = path.join(__dirname, 'tar.json');
var tar = JSON.parse(fs.readFileSync(TAR_PATH, 'utf8'));
var threadsPool = Threads.Pool(function () {
return Threads.spawn(new Threads.Worker("./MinifyWorker"))
}, 2)
var LIBRARY_WHITELIST = [
'async'
@ -178,7 +182,7 @@ function minify(req, res)
}
}
var contentType = mime.lookup(filename);
var contentType = mime.lookup(filename);
statFile(filename, function (error, date, exists) {
if (date) {
@ -362,20 +366,36 @@ function getFileCompressed(filename, contentType, callback) {
if (error || !content || !settings.minify) {
callback(error, content);
} else if (contentType == 'text/javascript') {
try {
content = compressJS(content);
if (content.error) {
console.error(`Error compressing JS (${filename}) using UglifyJS`, content.error);
callback('compressionError', content.error);
} else {
content = content.code.toString(); // Convert content obj code to string
threadsPool.queue(async ({ compressJS }) => {
try {
logger.info('Compress JS file %s.', filename)
content = content.toString();
const compressResult = await compressJS(content);
if (compressResult.error) {
console.error(`Error compressing JS (${filename}) using UglifyJS`, compressResult.error);
} else {
content = compressResult.code.toString(); // Convert content obj code to string
}
} catch (error) {
console.error(`getFile() returned an error in getFileCompressed(${filename}, ${contentType}): ${error}`);
}
} catch (error) {
console.error(`getFile() returned an error in getFileCompressed(${filename}, ${contentType}): ${error}`);
}
callback(null, content);
callback(null, content);
})
} else if (contentType == 'text/css') {
compressCSS(filename, content, callback);
threadsPool.queue(async ({ compressCSS }) => {
try {
logger.info('Compress CSS file %s.', filename)
content = await compressCSS(filename, ROOT_DIR);
} catch (error) {
console.error(`CleanCSS.minify() returned an error on ${filename}: ${error}`);
}
callback(null, content);
})
} else {
callback(null, content);
}
@ -392,65 +412,6 @@ function getFile(filename, callback) {
}
}
function compressJS(content)
{
const contentAsString = content.toString();
const codeObj = uglifyJS.minify(contentAsString);
return codeObj;
}
function compressCSS(filename, content, callback)
{
try {
const absPath = path.join(ROOT_DIR, filename);
/*
* Changes done to migrate CleanCSS 3.x -> 4.x:
*
* 1. Rework the rebase logic, because the API was simplified (but we have
* less control now). See:
* https://github.com/jakubpawlowicz/clean-css/blob/08f3a74925524d30bbe7ac450979de0a8a9e54b2/README.md#important-40-breaking-changes
*
* EXAMPLE:
* The URLs contained in a CSS file (including all the stylesheets
* imported by it) residing on disk at:
* /home/muxator/etherpad/src/static/css/pad.css
*
* Will be rewritten rebasing them to:
* /home/muxator/etherpad/src/static/css
*
* 2. CleanCSS.minify() can either receive a string containing the CSS, or
* an array of strings. In that case each array element is interpreted as
* an absolute local path from which the CSS file is read.
*
* In version 4.x, CleanCSS API was simplified, eliminating the
* relativeTo parameter, and thus we cannot use our already loaded
* "content" argument, but we have to wrap the absolute path to the CSS
* in an array and ask the library to read it by itself.
*/
const basePath = path.dirname(absPath);
new CleanCSS({
rebase: true,
rebaseTo: basePath,
}).minify([absPath], function (errors, minified) {
if (errors) {
// on error, just yield the un-minified original, but write a log message
console.error(`CleanCSS.minify() returned an error on ${filename} (${absPath}): ${errors}`);
callback(null, content);
} else {
callback(null, minified.styles);
}
});
} catch (error) {
// on error, just yield the un-minified original, but write a log message
console.error(`Unexpected error minifying ${filename} (${absPath}): ${error}`);
callback(null, content);
}
}
exports.minify = minify;
exports.requestURI = requestURI;

View file

@ -0,0 +1,67 @@
/**
* Worker thread to minify JS & CSS files out of the main NodeJS thread
*/
var CleanCSS = require('clean-css');
var uglifyJS = require("uglify-js");
var path = require('path');
var Threads = require('threads')
function compressJS(content)
{
return uglifyJS.minify(content);
}
function compressCSS(filename, ROOT_DIR)
{
return new Promise((res, rej) => {
try {
const absPath = path.join(ROOT_DIR, filename);
/*
* Changes done to migrate CleanCSS 3.x -> 4.x:
*
* 1. Rework the rebase logic, because the API was simplified (but we have
* less control now). See:
* https://github.com/jakubpawlowicz/clean-css/blob/08f3a74925524d30bbe7ac450979de0a8a9e54b2/README.md#important-40-breaking-changes
*
* EXAMPLE:
* The URLs contained in a CSS file (including all the stylesheets
* imported by it) residing on disk at:
* /home/muxator/etherpad/src/static/css/pad.css
*
* Will be rewritten rebasing them to:
* /home/muxator/etherpad/src/static/css
*
* 2. CleanCSS.minify() can either receive a string containing the CSS, or
* an array of strings. In that case each array element is interpreted as
* an absolute local path from which the CSS file is read.
*
* In version 4.x, CleanCSS API was simplified, eliminating the
* relativeTo parameter, and thus we cannot use our already loaded
* "content" argument, but we have to wrap the absolute path to the CSS
* in an array and ask the library to read it by itself.
*/
const basePath = path.dirname(absPath);
new CleanCSS({
rebase: true,
rebaseTo: basePath,
}).minify([absPath], function (errors, minified) {
if (errors) return rej(errors)
return res(minified.styles)
});
} catch (error) {
// on error, just yield the un-minified original, but write a log message
console.error(`Unexpected error minifying ${filename} (${absPath}): ${error}`);
callback(null, content);
}
})
}
Threads.expose({
compressJS,
compressCSS
})