|
|
|
'use strict';
|
|
|
|
|
|
|
|
var uglifyjs = require('uglify-js');
|
|
|
|
var async = require('async');
|
|
|
|
var fs = require('fs');
|
|
|
|
var childProcess = require('child_process');
|
|
|
|
var os = require('os');
|
|
|
|
var winston = require('winston');
|
|
|
|
var less = require('less');
|
|
|
|
var postcss = require('postcss');
|
|
|
|
var autoprefixer = require('autoprefixer');
|
|
|
|
var clean = require('postcss-clean');
|
|
|
|
|
|
|
|
var file = require('../file');
|
|
|
|
|
|
|
|
var Minifier = module.exports;
|
|
|
|
|
|
|
|
function setupDebugging() {
|
|
|
|
/**
|
|
|
|
* Check if the parent process is running with the debug option --debug (or --debug-brk)
|
|
|
|
*/
|
|
|
|
var forkProcessParams = {};
|
|
|
|
if (global.v8debug || parseInt(process.execArgv.indexOf('--debug'), 10) !== -1) {
|
|
|
|
/**
|
|
|
|
* use the line below if you want to debug minifier.js script too (or even --debug-brk option, but
|
|
|
|
* you'll have to setup your debugger and connect to the forked process)
|
|
|
|
*/
|
|
|
|
// forkProcessParams = { execArgv: ['--debug=' + (global.process.debugPort + 1), '--nolazy'] };
|
|
|
|
|
|
|
|
/**
|
|
|
|
* otherwise, just clean up --debug/--debug-brk options which are set up by default from the parent one
|
|
|
|
*/
|
|
|
|
forkProcessParams = {
|
|
|
|
execArgv: [],
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
return forkProcessParams;
|
|
|
|
}
|
|
|
|
|
|
|
|
var children = [];
|
|
|
|
|
|
|
|
Minifier.maxThreads = os.cpus().length - 1;
|
|
|
|
|
|
|
|
winston.verbose('[minifier] utilizing a maximum of ' + Minifier.maxThreads + ' additional threads');
|
|
|
|
|
|
|
|
Minifier.killAll = function () {
|
|
|
|
children.forEach(function (child) {
|
|
|
|
child.kill('SIGTERM');
|
|
|
|
});
|
|
|
|
|
|
|
|
children = [];
|
|
|
|
};
|
|
|
|
|
|
|
|
function removeChild(proc) {
|
|
|
|
children = children.filter(function (child) {
|
|
|
|
return child !== proc;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function forkAction(action, callback) {
|
|
|
|
var forkProcessParams = setupDebugging();
|
|
|
|
var proc = childProcess.fork(__filename, [], Object.assign({}, forkProcessParams, {
|
|
|
|
cwd: __dirname,
|
|
|
|
env: {
|
|
|
|
minifier_child: true,
|
|
|
|
},
|
|
|
|
}));
|
|
|
|
|
|
|
|
children.push(proc);
|
|
|
|
|
|
|
|
proc.on('message', function (message) {
|
|
|
|
proc.kill();
|
|
|
|
removeChild(proc);
|
|
|
|
|
|
|
|
if (message.type === 'error') {
|
|
|
|
return callback(message.err);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (message.type === 'end') {
|
|
|
|
callback(null, message.result);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
proc.on('error', function (err) {
|
|
|
|
proc.kill();
|
|
|
|
removeChild(proc);
|
|
|
|
callback(err);
|
|
|
|
});
|
|
|
|
|
|
|
|
proc.send({
|
|
|
|
type: 'action',
|
|
|
|
action: action,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
var actions = {};
|
|
|
|
|
|
|
|
if (process.env.minifier_child) {
|
|
|
|
process.on('message', function (message) {
|
|
|
|
if (message.type === 'action') {
|
|
|
|
var action = message.action;
|
|
|
|
if (typeof actions[action.act] !== 'function') {
|
|
|
|
process.send({
|
|
|
|
type: 'error',
|
|
|
|
message: 'Unknown action',
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
actions[action.act](action, function (err, result) {
|
|
|
|
if (err) {
|
|
|
|
process.send({
|
|
|
|
type: 'error',
|
|
|
|
err: err,
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
process.send({
|
|
|
|
type: 'end',
|
|
|
|
result: result,
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function executeAction(action, fork, callback) {
|
|
|
|
if (fork && children.length < Minifier.maxThreads) {
|
|
|
|
forkAction(action, callback);
|
|
|
|
} else {
|
|
|
|
if (typeof actions[action.act] !== 'function') {
|
|
|
|
return callback(Error('Unknown action'));
|
|
|
|
}
|
|
|
|
actions[action.act](action, callback);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function concat(data, callback) {
|
|
|
|
if (data.files && data.files.length) {
|
|
|
|
async.mapLimit(data.files, 1000, fs.readFile, function (err, files) {
|
|
|
|
if (err) {
|
|
|
|
return callback(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
var output = files.join(os.EOL + ';');
|
|
|
|
callback(null, { code: output });
|
|
|
|
});
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
callback();
|
|
|
|
}
|
|
|
|
actions.concat = concat;
|
|
|
|
|
|
|
|
function minifyJS(data, callback) {
|
|
|
|
var minified;
|
|
|
|
|
|
|
|
if (data.fromSource) {
|
|
|
|
var sources = data.source;
|
|
|
|
var multiple = Array.isArray(sources);
|
|
|
|
if (!multiple) {
|
|
|
|
sources = [sources];
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
minified = sources.map(function (source) {
|
|
|
|
return uglifyjs.minify(source, {
|
|
|
|
// outSourceMap: data.filename + '.map',
|
|
|
|
compress: data.compress,
|
|
|
|
fromString: true,
|
|
|
|
output: {
|
|
|
|
// suppress uglify line length warnings
|
|
|
|
max_line_len: 400000,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
});
|
|
|
|
} catch (e) {
|
|
|
|
return callback(e);
|
|
|
|
}
|
|
|
|
|
|
|
|
return callback(null, multiple ? minified : minified[0]);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (data.files && data.files.length) {
|
|
|
|
async.filter(data.files, file.exists, function (err, scripts) {
|
|
|
|
if (err) {
|
|
|
|
return callback(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
minified = uglifyjs.minify(scripts, {
|
|
|
|
// outSourceMap: data.filename + '.map',
|
|
|
|
compress: data.compress,
|
|
|
|
fromString: false,
|
|
|
|
});
|
|
|
|
} catch (e) {
|
|
|
|
return callback(e);
|
|
|
|
}
|
|
|
|
|
|
|
|
callback(null, minified);
|
|
|
|
});
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
callback();
|
|
|
|
}
|
|
|
|
actions.minifyJS = minifyJS;
|
|
|
|
|
|
|
|
Minifier.js = {};
|
|
|
|
Minifier.js.bundle = function (scripts, minify, fork, callback) {
|
|
|
|
executeAction({
|
|
|
|
act: minify ? 'minifyJS' : 'concat',
|
|
|
|
files: scripts,
|
|
|
|
compress: false,
|
|
|
|
}, fork, callback);
|
|
|
|
};
|
|
|
|
|
|
|
|
Minifier.js.minify = function (source, fork, callback) {
|
|
|
|
executeAction({
|
|
|
|
act: 'minifyJS',
|
|
|
|
fromSource: true,
|
|
|
|
source: source,
|
|
|
|
}, fork, callback);
|
|
|
|
};
|
|
|
|
|
|
|
|
function buildCSS(data, callback) {
|
|
|
|
less.render(data.source, {
|
|
|
|
paths: data.paths,
|
|
|
|
}, function (err, lessOutput) {
|
|
|
|
if (err) {
|
|
|
|
return callback(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
postcss(data.minify ? [
|
|
|
|
autoprefixer,
|
|
|
|
clean({
|
|
|
|
processImportFrom: ['local'],
|
|
|
|
}),
|
|
|
|
] : [autoprefixer]).process(lessOutput.css).then(function (result) {
|
|
|
|
callback(null, { code: result.css });
|
|
|
|
}, function (err) {
|
|
|
|
callback(err);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
actions.buildCSS = buildCSS;
|
|
|
|
|
|
|
|
Minifier.css = {};
|
|
|
|
Minifier.css.bundle = function (source, paths, minify, fork, callback) {
|
|
|
|
executeAction({
|
|
|
|
act: 'buildCSS',
|
|
|
|
source: source,
|
|
|
|
paths: paths,
|
|
|
|
minify: minify,
|
|
|
|
}, fork, callback);
|
|
|
|
};
|