| 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 |