import { fileURLToPath } from 'url'; import type { NormalizedOutputOptions, RenderedChunk } from 'rollup'; import { hasOwnProperty, isObject, merge } from 'smob'; import type { Options } from './type'; import { WorkerPool } from './worker-pool'; export default function terser(input: Options = {}) { const { maxWorkers, ...options } = input; let workerPool: WorkerPool | null | undefined; let numOfChunks = 0; let numOfWorkersUsed = 0; // Chained minify tasks; used only when a shared nameCache is provided, // since terser requires sequential minification for consistent mangling. let nameCacheChain: Promise = Promise.resolve(); return { name: 'terser', async renderChunk(code: string, chunk: RenderedChunk, outputOptions: NormalizedOutputOptions) { if (!workerPool) { workerPool = new WorkerPool({ filePath: fileURLToPath(import.meta.url), maxWorkers }); } const pool = workerPool; numOfChunks += 1; const defaultOptions: Options = { sourceMap: outputOptions.sourcemap === true || typeof outputOptions.sourcemap === 'string' }; if (outputOptions.format === 'es') { defaultOptions.module = true; } if (outputOptions.format === 'cjs') { defaultOptions.toplevel = true; } const runTask = async () => { const { code: result, nameCache, sourceMap } = await pool.addAsync({ code, options: merge({}, options || {}, defaultOptions) }); if (options.nameCache && nameCache) { let vars: Record = { 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: Record = {}; 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; } return { result, sourceMap }; }; try { let output; if (options.nameCache) { // The options snapshot and its serialization must only happen once // the previous chunk's nameCache has been merged back. const task = nameCacheChain.then(runTask); nameCacheChain = task.catch(() => {}); output = await task; } else { output = await runTask(); } if ((!!defaultOptions.sourceMap || !!options.sourceMap) && isObject(output.sourceMap)) { return { code: output.result, map: output.sourceMap }; } return output.result; } catch (e) { return Promise.reject(e); } finally { numOfChunks -= 1; if (numOfChunks === 0) { numOfWorkersUsed = workerPool.numWorkers; workerPool.close(); workerPool = null; } } }, get numOfWorkersUsed() { return numOfWorkersUsed; } }; }