227 lines
7.5 KiB
JavaScript
227 lines
7.5 KiB
JavaScript
import { isMainThread, parentPort, workerData, Worker } from 'worker_threads';
|
|
import { isObject, hasOwnProperty, merge } from 'smob';
|
|
import { minify } from 'terser';
|
|
import { fileURLToPath } from 'url';
|
|
import { AsyncResource } from 'async_hooks';
|
|
import { cpus } from 'os';
|
|
import { EventEmitter } from 'events';
|
|
import serializeJavascript from 'serialize-javascript';
|
|
|
|
const taskInfo = Symbol('taskInfo');
|
|
const freeWorker = Symbol('freeWorker');
|
|
const workerPoolWorkerFlag = 'WorkerPoolWorker';
|
|
|
|
/**
|
|
* Duck typing worker context.
|
|
*
|
|
* @param input
|
|
*/
|
|
function isWorkerContextSerialized(input) {
|
|
return (isObject(input) &&
|
|
hasOwnProperty(input, 'code') &&
|
|
typeof input.code === 'string' &&
|
|
hasOwnProperty(input, 'options') &&
|
|
typeof input.options === 'string');
|
|
}
|
|
function runWorker() {
|
|
if (isMainThread || !parentPort || workerData !== workerPoolWorkerFlag) {
|
|
return;
|
|
}
|
|
// eslint-disable-next-line no-eval
|
|
const eval2 = eval;
|
|
parentPort.on('message', async (data) => {
|
|
if (!isWorkerContextSerialized(data)) {
|
|
return;
|
|
}
|
|
const options = eval2(`(${data.options})`);
|
|
const result = await minify(data.code, options);
|
|
const output = {
|
|
code: result.code || data.code,
|
|
nameCache: options.nameCache
|
|
};
|
|
if (typeof result.map === 'string') {
|
|
output.sourceMap = JSON.parse(result.map);
|
|
}
|
|
if (isObject(result.map)) {
|
|
output.sourceMap = result.map;
|
|
}
|
|
parentPort === null || parentPort === void 0 ? void 0 : parentPort.postMessage(output);
|
|
});
|
|
}
|
|
|
|
class WorkerPoolTaskInfo extends AsyncResource {
|
|
constructor(callback) {
|
|
super('WorkerPoolTaskInfo');
|
|
this.callback = callback;
|
|
}
|
|
done(err, result) {
|
|
this.runInAsyncScope(this.callback, null, err, result);
|
|
this.emitDestroy();
|
|
}
|
|
}
|
|
class WorkerPool extends EventEmitter {
|
|
constructor(options) {
|
|
super();
|
|
this.tasks = [];
|
|
this.workers = [];
|
|
this.freeWorkers = [];
|
|
this.maxInstances = options.maxWorkers || cpus().length;
|
|
this.filePath = options.filePath;
|
|
this.on(freeWorker, () => {
|
|
if (this.tasks.length > 0) {
|
|
const { context, cb } = this.tasks.shift();
|
|
this.runTask(context, cb);
|
|
}
|
|
});
|
|
}
|
|
get numWorkers() {
|
|
return this.workers.length;
|
|
}
|
|
addAsync(context) {
|
|
return new Promise((resolve, reject) => {
|
|
this.runTask(context, (err, output) => {
|
|
if (err) {
|
|
reject(err);
|
|
return;
|
|
}
|
|
if (!output) {
|
|
reject(new Error('The output is empty'));
|
|
return;
|
|
}
|
|
resolve(output);
|
|
});
|
|
});
|
|
}
|
|
close() {
|
|
for (let i = 0; i < this.workers.length; i++) {
|
|
const worker = this.workers[i];
|
|
worker.terminate();
|
|
}
|
|
}
|
|
addNewWorker() {
|
|
const worker = new Worker(this.filePath, {
|
|
workerData: workerPoolWorkerFlag
|
|
});
|
|
worker.on('message', (result) => {
|
|
var _a;
|
|
(_a = worker[taskInfo]) === null || _a === void 0 ? void 0 : _a.done(null, result);
|
|
worker[taskInfo] = null;
|
|
this.freeWorkers.push(worker);
|
|
this.emit(freeWorker);
|
|
});
|
|
worker.on('error', (err) => {
|
|
if (worker[taskInfo]) {
|
|
worker[taskInfo].done(err, null);
|
|
}
|
|
else {
|
|
this.emit('error', err);
|
|
}
|
|
this.workers.splice(this.workers.indexOf(worker), 1);
|
|
this.addNewWorker();
|
|
});
|
|
this.workers.push(worker);
|
|
this.freeWorkers.push(worker);
|
|
this.emit(freeWorker);
|
|
}
|
|
runTask(context, cb) {
|
|
if (this.freeWorkers.length === 0) {
|
|
this.tasks.push({ context, cb });
|
|
if (this.numWorkers < this.maxInstances) {
|
|
this.addNewWorker();
|
|
}
|
|
return;
|
|
}
|
|
const worker = this.freeWorkers.pop();
|
|
if (worker) {
|
|
worker[taskInfo] = new WorkerPoolTaskInfo(cb);
|
|
worker.postMessage({
|
|
code: context.code,
|
|
options: serializeJavascript(context.options)
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
function terser(input = {}) {
|
|
const { maxWorkers, ...options } = input;
|
|
let workerPool;
|
|
let numOfChunks = 0;
|
|
let numOfWorkersUsed = 0;
|
|
return {
|
|
name: 'terser',
|
|
async renderChunk(code, chunk, outputOptions) {
|
|
if (!workerPool) {
|
|
workerPool = new WorkerPool({
|
|
filePath: fileURLToPath(import.meta.url),
|
|
maxWorkers
|
|
});
|
|
}
|
|
numOfChunks += 1;
|
|
const defaultOptions = {
|
|
sourceMap: outputOptions.sourcemap === true || typeof outputOptions.sourcemap === 'string'
|
|
};
|
|
if (outputOptions.format === 'es') {
|
|
defaultOptions.module = true;
|
|
}
|
|
if (outputOptions.format === 'cjs') {
|
|
defaultOptions.toplevel = true;
|
|
}
|
|
try {
|
|
const { code: result, nameCache, sourceMap } = await workerPool.addAsync({
|
|
code,
|
|
options: merge({}, options || {}, defaultOptions)
|
|
});
|
|
if (options.nameCache && nameCache) {
|
|
let vars = {
|
|
props: {}
|
|
};
|
|
if (hasOwnProperty(options.nameCache, 'vars') && isObject(options.nameCache.vars)) {
|
|
vars = merge({}, options.nameCache.vars || {}, vars);
|
|
}
|
|
if (hasOwnProperty(nameCache, 'vars') && isObject(nameCache.vars)) {
|
|
vars = merge({}, nameCache.vars, vars);
|
|
}
|
|
// eslint-disable-next-line no-param-reassign
|
|
options.nameCache.vars = vars;
|
|
let props = {};
|
|
if (hasOwnProperty(options.nameCache, 'props') && isObject(options.nameCache.props)) {
|
|
// eslint-disable-next-line prefer-destructuring
|
|
props = options.nameCache.props;
|
|
}
|
|
if (hasOwnProperty(nameCache, 'props') && isObject(nameCache.props)) {
|
|
props = merge({}, nameCache.props, props);
|
|
}
|
|
// eslint-disable-next-line no-param-reassign
|
|
options.nameCache.props = props;
|
|
}
|
|
if ((!!defaultOptions.sourceMap || !!options.sourceMap) && isObject(sourceMap)) {
|
|
return {
|
|
code: result,
|
|
map: sourceMap
|
|
};
|
|
}
|
|
return result;
|
|
}
|
|
catch (e) {
|
|
return Promise.reject(e);
|
|
}
|
|
finally {
|
|
numOfChunks -= 1;
|
|
if (numOfChunks === 0) {
|
|
numOfWorkersUsed = workerPool.numWorkers;
|
|
workerPool.close();
|
|
workerPool = null;
|
|
}
|
|
}
|
|
},
|
|
get numOfWorkersUsed() {
|
|
return numOfWorkersUsed;
|
|
}
|
|
};
|
|
}
|
|
|
|
runWorker();
|
|
|
|
export { terser as default };
|
|
//# sourceMappingURL=index.js.map
|