@rollup/plugin-terser 0.2.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cjs/index.js CHANGED
@@ -2,11 +2,11 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
- var process = require('process');
6
5
  var worker_threads = require('worker_threads');
7
6
  var smob = require('smob');
8
7
  var terser$1 = require('terser');
9
8
  var url = require('url');
9
+ var async_hooks = require('async_hooks');
10
10
  var os = require('os');
11
11
  var events = require('events');
12
12
  var serializeJavascript = require('serialize-javascript');
@@ -23,53 +23,66 @@ function isWorkerContextSerialized(input) {
23
23
  smob.hasOwnProperty(input, 'options') &&
24
24
  typeof input.options === 'string');
25
25
  }
26
- async function runWorker() {
27
- if (worker_threads.isMainThread || !worker_threads.parentPort || !isWorkerContextSerialized(worker_threads.workerData)) {
26
+ function runWorker() {
27
+ if (worker_threads.isMainThread || !worker_threads.parentPort) {
28
28
  return;
29
29
  }
30
- try {
31
- // eslint-disable-next-line no-eval
32
- const eval2 = eval;
33
- const options = eval2(`(${worker_threads.workerData.options})`);
34
- const result = await terser$1.minify(worker_threads.workerData.code, options);
30
+ // eslint-disable-next-line no-eval
31
+ const eval2 = eval;
32
+ worker_threads.parentPort.on('message', async (data) => {
33
+ if (!isWorkerContextSerialized(data)) {
34
+ return;
35
+ }
36
+ const options = eval2(`(${data.options})`);
37
+ const result = await terser$1.minify(data.code, options);
35
38
  const output = {
36
- code: result.code || worker_threads.workerData.code,
39
+ code: result.code || data.code,
37
40
  nameCache: options.nameCache
38
41
  };
39
- worker_threads.parentPort.postMessage(output);
42
+ if (typeof result.map === 'string') {
43
+ output.sourceMap = JSON.parse(result.map);
44
+ }
45
+ if (smob.isObject(result.map)) {
46
+ output.sourceMap = result.map;
47
+ }
48
+ worker_threads.parentPort === null || worker_threads.parentPort === void 0 ? void 0 : worker_threads.parentPort.postMessage(output);
49
+ });
50
+ }
51
+
52
+ const taskInfo = Symbol('taskInfo');
53
+ const freeWorker = Symbol('freeWorker');
54
+
55
+ class WorkerPoolTaskInfo extends async_hooks.AsyncResource {
56
+ constructor(callback) {
57
+ super('WorkerPoolTaskInfo');
58
+ this.callback = callback;
40
59
  }
41
- catch (e) {
42
- process.exit(1);
60
+ done(err, result) {
61
+ this.runInAsyncScope(this.callback, null, err, result);
62
+ this.emitDestroy();
43
63
  }
44
64
  }
45
-
46
- const symbol = Symbol.for('FreeWoker');
47
65
  class WorkerPool extends events.EventEmitter {
48
66
  constructor(options) {
49
67
  super();
50
68
  this.tasks = [];
51
- this.workers = 0;
69
+ this.workers = [];
70
+ this.freeWorkers = [];
52
71
  this.maxInstances = options.maxWorkers || os.cpus().length;
53
72
  this.filePath = options.filePath;
54
- this.on(symbol, () => {
73
+ this.on(freeWorker, () => {
55
74
  if (this.tasks.length > 0) {
56
- this.run();
75
+ const { context, cb } = this.tasks.shift();
76
+ this.runTask(context, cb);
57
77
  }
58
78
  });
59
79
  }
60
- add(context, cb) {
61
- this.tasks.push({
62
- context,
63
- cb
64
- });
65
- if (this.workers >= this.maxInstances) {
66
- return;
67
- }
68
- this.run();
80
+ get numWorkers() {
81
+ return this.workers.length;
69
82
  }
70
- async addAsync(context) {
83
+ addAsync(context) {
71
84
  return new Promise((resolve, reject) => {
72
- this.add(context, (err, output) => {
85
+ this.runTask(context, (err, output) => {
73
86
  if (err) {
74
87
  reject(err);
75
88
  return;
@@ -82,54 +95,69 @@ class WorkerPool extends events.EventEmitter {
82
95
  });
83
96
  });
84
97
  }
85
- run() {
86
- if (this.tasks.length === 0) {
87
- return;
98
+ close() {
99
+ for (let i = 0; i < this.workers.length; i++) {
100
+ const worker = this.workers[i];
101
+ worker.terminate();
88
102
  }
89
- const task = this.tasks.shift();
90
- if (typeof task === 'undefined') {
91
- return;
92
- }
93
- this.workers += 1;
94
- let called = false;
95
- const callCallback = (err, output) => {
96
- if (called) {
97
- return;
98
- }
99
- called = true;
100
- this.workers -= 1;
101
- task.cb(err, output);
102
- this.emit(symbol);
103
- };
104
- const worker = new worker_threads.Worker(this.filePath, {
105
- workerData: {
106
- code: task.context.code,
107
- options: serializeJavascript(task.context.options)
108
- }
109
- });
110
- worker.on('message', (data) => {
111
- callCallback(null, data);
103
+ }
104
+ addNewWorker() {
105
+ const worker = new worker_threads.Worker(this.filePath);
106
+ worker.on('message', (result) => {
107
+ var _a;
108
+ (_a = worker[taskInfo]) === null || _a === void 0 ? void 0 : _a.done(null, result);
109
+ worker[taskInfo] = null;
110
+ this.freeWorkers.push(worker);
111
+ this.emit(freeWorker);
112
112
  });
113
113
  worker.on('error', (err) => {
114
- callCallback(err);
115
- });
116
- worker.on('exit', (code) => {
117
- if (code !== 0) {
118
- callCallback(new Error(`Minify worker stopped with exit code ${code}`));
114
+ if (worker[taskInfo]) {
115
+ worker[taskInfo].done(err, null);
119
116
  }
117
+ else {
118
+ this.emit('error', err);
119
+ }
120
+ this.workers.splice(this.workers.indexOf(worker), 1);
121
+ this.addNewWorker();
120
122
  });
123
+ this.workers.push(worker);
124
+ this.freeWorkers.push(worker);
125
+ this.emit(freeWorker);
126
+ }
127
+ runTask(context, cb) {
128
+ if (this.freeWorkers.length === 0) {
129
+ this.tasks.push({ context, cb });
130
+ if (this.numWorkers < this.maxInstances) {
131
+ this.addNewWorker();
132
+ }
133
+ return;
134
+ }
135
+ const worker = this.freeWorkers.pop();
136
+ if (worker) {
137
+ worker[taskInfo] = new WorkerPoolTaskInfo(cb);
138
+ worker.postMessage({
139
+ code: context.code,
140
+ options: serializeJavascript(context.options)
141
+ });
142
+ }
121
143
  }
122
144
  }
123
145
 
124
146
  function terser(input = {}) {
125
147
  const { maxWorkers, ...options } = input;
126
- const workerPool = new WorkerPool({
127
- filePath: url.fileURLToPath((typeof document === 'undefined' ? new (require('u' + 'rl').URL)('file:' + __filename).href : (document.currentScript && document.currentScript.src || new URL('index.js', document.baseURI).href))),
128
- maxWorkers
129
- });
148
+ let workerPool;
149
+ let numOfChunks = 0;
150
+ let numOfWorkersUsed = 0;
130
151
  return {
131
152
  name: 'terser',
132
153
  async renderChunk(code, chunk, outputOptions) {
154
+ if (!workerPool) {
155
+ workerPool = new WorkerPool({
156
+ filePath: url.fileURLToPath((typeof document === 'undefined' ? new (require('u' + 'rl').URL)('file:' + __filename).href : (document.currentScript && document.currentScript.src || new URL('index.js', document.baseURI).href))),
157
+ maxWorkers
158
+ });
159
+ }
160
+ numOfChunks += 1;
133
161
  const defaultOptions = {
134
162
  sourceMap: outputOptions.sourcemap === true || typeof outputOptions.sourcemap === 'string'
135
163
  };
@@ -140,7 +168,7 @@ function terser(input = {}) {
140
168
  defaultOptions.toplevel = true;
141
169
  }
142
170
  try {
143
- const { code: result, nameCache } = await workerPool.addAsync({
171
+ const { code: result, nameCache, sourceMap } = await workerPool.addAsync({
144
172
  code,
145
173
  options: smob.merge({}, options || {}, defaultOptions)
146
174
  });
@@ -167,11 +195,28 @@ function terser(input = {}) {
167
195
  // eslint-disable-next-line no-param-reassign
168
196
  options.nameCache.props = props;
169
197
  }
198
+ if ((!!defaultOptions.sourceMap || !!options.sourceMap) && smob.isObject(sourceMap)) {
199
+ return {
200
+ code: result,
201
+ map: sourceMap
202
+ };
203
+ }
170
204
  return result;
171
205
  }
172
206
  catch (e) {
173
207
  return Promise.reject(e);
174
208
  }
209
+ finally {
210
+ numOfChunks -= 1;
211
+ if (numOfChunks === 0) {
212
+ numOfWorkersUsed = workerPool.numWorkers;
213
+ workerPool.close();
214
+ workerPool = null;
215
+ }
216
+ }
217
+ },
218
+ get numOfWorkersUsed() {
219
+ return numOfWorkersUsed;
175
220
  }
176
221
  };
177
222
  }
package/dist/es/index.js CHANGED
@@ -1,8 +1,8 @@
1
- import process from 'process';
2
- import { isMainThread, parentPort, workerData, Worker } from 'worker_threads';
1
+ import { isMainThread, parentPort, Worker } from 'worker_threads';
3
2
  import { isObject, hasOwnProperty, merge } from 'smob';
4
3
  import { minify } from 'terser';
5
4
  import { fileURLToPath } from 'url';
5
+ import { AsyncResource } from 'async_hooks';
6
6
  import { cpus } from 'os';
7
7
  import { EventEmitter } from 'events';
8
8
  import serializeJavascript from 'serialize-javascript';
@@ -19,53 +19,66 @@ function isWorkerContextSerialized(input) {
19
19
  hasOwnProperty(input, 'options') &&
20
20
  typeof input.options === 'string');
21
21
  }
22
- async function runWorker() {
23
- if (isMainThread || !parentPort || !isWorkerContextSerialized(workerData)) {
22
+ function runWorker() {
23
+ if (isMainThread || !parentPort) {
24
24
  return;
25
25
  }
26
- try {
27
- // eslint-disable-next-line no-eval
28
- const eval2 = eval;
29
- const options = eval2(`(${workerData.options})`);
30
- const result = await minify(workerData.code, options);
26
+ // eslint-disable-next-line no-eval
27
+ const eval2 = eval;
28
+ parentPort.on('message', async (data) => {
29
+ if (!isWorkerContextSerialized(data)) {
30
+ return;
31
+ }
32
+ const options = eval2(`(${data.options})`);
33
+ const result = await minify(data.code, options);
31
34
  const output = {
32
- code: result.code || workerData.code,
35
+ code: result.code || data.code,
33
36
  nameCache: options.nameCache
34
37
  };
35
- parentPort.postMessage(output);
38
+ if (typeof result.map === 'string') {
39
+ output.sourceMap = JSON.parse(result.map);
40
+ }
41
+ if (isObject(result.map)) {
42
+ output.sourceMap = result.map;
43
+ }
44
+ parentPort === null || parentPort === void 0 ? void 0 : parentPort.postMessage(output);
45
+ });
46
+ }
47
+
48
+ const taskInfo = Symbol('taskInfo');
49
+ const freeWorker = Symbol('freeWorker');
50
+
51
+ class WorkerPoolTaskInfo extends AsyncResource {
52
+ constructor(callback) {
53
+ super('WorkerPoolTaskInfo');
54
+ this.callback = callback;
36
55
  }
37
- catch (e) {
38
- process.exit(1);
56
+ done(err, result) {
57
+ this.runInAsyncScope(this.callback, null, err, result);
58
+ this.emitDestroy();
39
59
  }
40
60
  }
41
-
42
- const symbol = Symbol.for('FreeWoker');
43
61
  class WorkerPool extends EventEmitter {
44
62
  constructor(options) {
45
63
  super();
46
64
  this.tasks = [];
47
- this.workers = 0;
65
+ this.workers = [];
66
+ this.freeWorkers = [];
48
67
  this.maxInstances = options.maxWorkers || cpus().length;
49
68
  this.filePath = options.filePath;
50
- this.on(symbol, () => {
69
+ this.on(freeWorker, () => {
51
70
  if (this.tasks.length > 0) {
52
- this.run();
71
+ const { context, cb } = this.tasks.shift();
72
+ this.runTask(context, cb);
53
73
  }
54
74
  });
55
75
  }
56
- add(context, cb) {
57
- this.tasks.push({
58
- context,
59
- cb
60
- });
61
- if (this.workers >= this.maxInstances) {
62
- return;
63
- }
64
- this.run();
76
+ get numWorkers() {
77
+ return this.workers.length;
65
78
  }
66
- async addAsync(context) {
79
+ addAsync(context) {
67
80
  return new Promise((resolve, reject) => {
68
- this.add(context, (err, output) => {
81
+ this.runTask(context, (err, output) => {
69
82
  if (err) {
70
83
  reject(err);
71
84
  return;
@@ -78,54 +91,69 @@ class WorkerPool extends EventEmitter {
78
91
  });
79
92
  });
80
93
  }
81
- run() {
82
- if (this.tasks.length === 0) {
83
- return;
94
+ close() {
95
+ for (let i = 0; i < this.workers.length; i++) {
96
+ const worker = this.workers[i];
97
+ worker.terminate();
84
98
  }
85
- const task = this.tasks.shift();
86
- if (typeof task === 'undefined') {
87
- return;
88
- }
89
- this.workers += 1;
90
- let called = false;
91
- const callCallback = (err, output) => {
92
- if (called) {
93
- return;
94
- }
95
- called = true;
96
- this.workers -= 1;
97
- task.cb(err, output);
98
- this.emit(symbol);
99
- };
100
- const worker = new Worker(this.filePath, {
101
- workerData: {
102
- code: task.context.code,
103
- options: serializeJavascript(task.context.options)
104
- }
105
- });
106
- worker.on('message', (data) => {
107
- callCallback(null, data);
99
+ }
100
+ addNewWorker() {
101
+ const worker = new Worker(this.filePath);
102
+ worker.on('message', (result) => {
103
+ var _a;
104
+ (_a = worker[taskInfo]) === null || _a === void 0 ? void 0 : _a.done(null, result);
105
+ worker[taskInfo] = null;
106
+ this.freeWorkers.push(worker);
107
+ this.emit(freeWorker);
108
108
  });
109
109
  worker.on('error', (err) => {
110
- callCallback(err);
111
- });
112
- worker.on('exit', (code) => {
113
- if (code !== 0) {
114
- callCallback(new Error(`Minify worker stopped with exit code ${code}`));
110
+ if (worker[taskInfo]) {
111
+ worker[taskInfo].done(err, null);
115
112
  }
113
+ else {
114
+ this.emit('error', err);
115
+ }
116
+ this.workers.splice(this.workers.indexOf(worker), 1);
117
+ this.addNewWorker();
116
118
  });
119
+ this.workers.push(worker);
120
+ this.freeWorkers.push(worker);
121
+ this.emit(freeWorker);
122
+ }
123
+ runTask(context, cb) {
124
+ if (this.freeWorkers.length === 0) {
125
+ this.tasks.push({ context, cb });
126
+ if (this.numWorkers < this.maxInstances) {
127
+ this.addNewWorker();
128
+ }
129
+ return;
130
+ }
131
+ const worker = this.freeWorkers.pop();
132
+ if (worker) {
133
+ worker[taskInfo] = new WorkerPoolTaskInfo(cb);
134
+ worker.postMessage({
135
+ code: context.code,
136
+ options: serializeJavascript(context.options)
137
+ });
138
+ }
117
139
  }
118
140
  }
119
141
 
120
142
  function terser(input = {}) {
121
143
  const { maxWorkers, ...options } = input;
122
- const workerPool = new WorkerPool({
123
- filePath: fileURLToPath(import.meta.url),
124
- maxWorkers
125
- });
144
+ let workerPool;
145
+ let numOfChunks = 0;
146
+ let numOfWorkersUsed = 0;
126
147
  return {
127
148
  name: 'terser',
128
149
  async renderChunk(code, chunk, outputOptions) {
150
+ if (!workerPool) {
151
+ workerPool = new WorkerPool({
152
+ filePath: fileURLToPath(import.meta.url),
153
+ maxWorkers
154
+ });
155
+ }
156
+ numOfChunks += 1;
129
157
  const defaultOptions = {
130
158
  sourceMap: outputOptions.sourcemap === true || typeof outputOptions.sourcemap === 'string'
131
159
  };
@@ -136,7 +164,7 @@ function terser(input = {}) {
136
164
  defaultOptions.toplevel = true;
137
165
  }
138
166
  try {
139
- const { code: result, nameCache } = await workerPool.addAsync({
167
+ const { code: result, nameCache, sourceMap } = await workerPool.addAsync({
140
168
  code,
141
169
  options: merge({}, options || {}, defaultOptions)
142
170
  });
@@ -163,11 +191,28 @@ function terser(input = {}) {
163
191
  // eslint-disable-next-line no-param-reassign
164
192
  options.nameCache.props = props;
165
193
  }
194
+ if ((!!defaultOptions.sourceMap || !!options.sourceMap) && isObject(sourceMap)) {
195
+ return {
196
+ code: result,
197
+ map: sourceMap
198
+ };
199
+ }
166
200
  return result;
167
201
  }
168
202
  catch (e) {
169
203
  return Promise.reject(e);
170
204
  }
205
+ finally {
206
+ numOfChunks -= 1;
207
+ if (numOfChunks === 0) {
208
+ numOfWorkersUsed = workerPool.numWorkers;
209
+ workerPool.close();
210
+ workerPool = null;
211
+ }
212
+ }
213
+ },
214
+ get numOfWorkersUsed() {
215
+ return numOfWorkersUsed;
171
216
  }
172
217
  };
173
218
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rollup/plugin-terser",
3
- "version": "0.2.1",
3
+ "version": "0.4.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -0,0 +1,2 @@
1
+ export const taskInfo = Symbol('taskInfo');
2
+ export const freeWorker = Symbol('freeWorker');
package/src/module.ts CHANGED
@@ -9,15 +9,23 @@ import { WorkerPool } from './worker-pool';
9
9
  export default function terser(input: Options = {}) {
10
10
  const { maxWorkers, ...options } = input;
11
11
 
12
- const workerPool = new WorkerPool({
13
- filePath: fileURLToPath(import.meta.url),
14
- maxWorkers
15
- });
12
+ let workerPool: WorkerPool | null | undefined;
13
+ let numOfChunks = 0;
14
+ let numOfWorkersUsed = 0;
16
15
 
17
16
  return {
18
17
  name: 'terser',
19
18
 
20
19
  async renderChunk(code: string, chunk: RenderedChunk, outputOptions: NormalizedOutputOptions) {
20
+ if (!workerPool) {
21
+ workerPool = new WorkerPool({
22
+ filePath: fileURLToPath(import.meta.url),
23
+ maxWorkers
24
+ });
25
+ }
26
+
27
+ numOfChunks += 1;
28
+
21
29
  const defaultOptions: Options = {
22
30
  sourceMap: outputOptions.sourcemap === true || typeof outputOptions.sourcemap === 'string'
23
31
  };
@@ -31,7 +39,11 @@ export default function terser(input: Options = {}) {
31
39
  }
32
40
 
33
41
  try {
34
- const { code: result, nameCache } = await workerPool.addAsync({
42
+ const {
43
+ code: result,
44
+ nameCache,
45
+ sourceMap
46
+ } = await workerPool.addAsync({
35
47
  code,
36
48
  options: merge({}, options || {}, defaultOptions)
37
49
  });
@@ -67,10 +79,27 @@ export default function terser(input: Options = {}) {
67
79
  options.nameCache.props = props;
68
80
  }
69
81
 
82
+ if ((!!defaultOptions.sourceMap || !!options.sourceMap) && isObject(sourceMap)) {
83
+ return {
84
+ code: result,
85
+ map: sourceMap
86
+ };
87
+ }
70
88
  return result;
71
89
  } catch (e) {
72
90
  return Promise.reject(e);
91
+ } finally {
92
+ numOfChunks -= 1;
93
+ if (numOfChunks === 0) {
94
+ numOfWorkersUsed = workerPool.numWorkers;
95
+ workerPool.close();
96
+ workerPool = null;
97
+ }
73
98
  }
99
+ },
100
+
101
+ get numOfWorkersUsed() {
102
+ return numOfWorkersUsed;
74
103
  }
75
104
  };
76
105
  }
package/src/type.ts CHANGED
@@ -1,5 +1,10 @@
1
+ import type { AsyncResource } from 'async_hooks';
2
+ import type { Worker } from 'worker_threads';
3
+
1
4
  import type { MinifyOptions } from 'terser';
2
5
 
6
+ import type { taskInfo } from './constants';
7
+
3
8
  export interface Options extends MinifyOptions {
4
9
  nameCache?: Record<string, any>;
5
10
  maxWorkers?: number;
@@ -12,6 +17,12 @@ export interface WorkerContext {
12
17
 
13
18
  export type WorkerCallback = (err: Error | null, output?: WorkerOutput) => void;
14
19
 
20
+ interface WorkerPoolTaskInfo extends AsyncResource {
21
+ done(err: Error | null, result: any): void;
22
+ }
23
+
24
+ export type WorkerWithTaskInfo = Worker & { [taskInfo]?: WorkerPoolTaskInfo | null };
25
+
15
26
  export interface WorkerContextSerialized {
16
27
  code: string;
17
28
  options: string;
@@ -20,6 +31,7 @@ export interface WorkerContextSerialized {
20
31
  export interface WorkerOutput {
21
32
  code: string;
22
33
  nameCache?: Options['nameCache'];
34
+ sourceMap?: Record<string, any>;
23
35
  }
24
36
 
25
37
  export interface WorkerPoolOptions {
@@ -1,18 +1,31 @@
1
+ import { AsyncResource } from 'async_hooks';
1
2
  import { Worker } from 'worker_threads';
2
3
  import { cpus } from 'os';
3
4
  import { EventEmitter } from 'events';
4
5
 
5
6
  import serializeJavascript from 'serialize-javascript';
6
7
 
8
+ import { freeWorker, taskInfo } from './constants';
9
+
7
10
  import type {
8
11
  WorkerCallback,
9
12
  WorkerContext,
10
13
  WorkerOutput,
11
14
  WorkerPoolOptions,
12
- WorkerPoolTask
15
+ WorkerPoolTask,
16
+ WorkerWithTaskInfo
13
17
  } from './type';
14
18
 
15
- const symbol = Symbol.for('FreeWoker');
19
+ class WorkerPoolTaskInfo extends AsyncResource {
20
+ constructor(private callback: WorkerCallback) {
21
+ super('WorkerPoolTaskInfo');
22
+ }
23
+
24
+ done(err: Error | null, result: any) {
25
+ this.runInAsyncScope(this.callback, null, err, result);
26
+ this.emitDestroy();
27
+ }
28
+ }
16
29
 
17
30
  export class WorkerPool extends EventEmitter {
18
31
  protected maxInstances: number;
@@ -21,7 +34,8 @@ export class WorkerPool extends EventEmitter {
21
34
 
22
35
  protected tasks: WorkerPoolTask[] = [];
23
36
 
24
- protected workers = 0;
37
+ protected workers: WorkerWithTaskInfo[] = [];
38
+ protected freeWorkers: WorkerWithTaskInfo[] = [];
25
39
 
26
40
  constructor(options: WorkerPoolOptions) {
27
41
  super();
@@ -29,29 +43,21 @@ export class WorkerPool extends EventEmitter {
29
43
  this.maxInstances = options.maxWorkers || cpus().length;
30
44
  this.filePath = options.filePath;
31
45
 
32
- this.on(symbol, () => {
46
+ this.on(freeWorker, () => {
33
47
  if (this.tasks.length > 0) {
34
- this.run();
48
+ const { context, cb } = this.tasks.shift()!;
49
+ this.runTask(context, cb);
35
50
  }
36
51
  });
37
52
  }
38
53
 
39
- add(context: WorkerContext, cb: WorkerCallback) {
40
- this.tasks.push({
41
- context,
42
- cb
43
- });
44
-
45
- if (this.workers >= this.maxInstances) {
46
- return;
47
- }
48
-
49
- this.run();
54
+ get numWorkers(): number {
55
+ return this.workers.length;
50
56
  }
51
57
 
52
- async addAsync(context: WorkerContext): Promise<WorkerOutput> {
58
+ addAsync(context: WorkerContext): Promise<WorkerOutput> {
53
59
  return new Promise((resolve, reject) => {
54
- this.add(context, (err, output) => {
60
+ this.runTask(context, (err, output) => {
55
61
  if (err) {
56
62
  reject(err);
57
63
  return;
@@ -67,51 +73,54 @@ export class WorkerPool extends EventEmitter {
67
73
  });
68
74
  }
69
75
 
70
- private run() {
71
- if (this.tasks.length === 0) {
72
- return;
73
- }
74
-
75
- const task = this.tasks.shift();
76
-
77
- if (typeof task === 'undefined') {
78
- return;
76
+ close() {
77
+ for (let i = 0; i < this.workers.length; i++) {
78
+ const worker = this.workers[i];
79
+ worker.terminate();
79
80
  }
81
+ }
80
82
 
81
- this.workers += 1;
82
-
83
- let called = false;
84
- const callCallback = (err: Error | null, output?: WorkerOutput) => {
85
- if (called) {
86
- return;
87
- }
88
- called = true;
89
-
90
- this.workers -= 1;
91
-
92
- task.cb(err, output);
93
- this.emit(symbol);
94
- };
95
-
96
- const worker = new Worker(this.filePath, {
97
- workerData: {
98
- code: task.context.code,
99
- options: serializeJavascript(task.context.options)
100
- }
101
- });
83
+ private addNewWorker() {
84
+ const worker: WorkerWithTaskInfo = new Worker(this.filePath);
102
85
 
103
- worker.on('message', (data) => {
104
- callCallback(null, data);
86
+ worker.on('message', (result) => {
87
+ worker[taskInfo]?.done(null, result);
88
+ worker[taskInfo] = null;
89
+ this.freeWorkers.push(worker);
90
+ this.emit(freeWorker);
105
91
  });
106
92
 
107
93
  worker.on('error', (err) => {
108
- callCallback(err);
94
+ if (worker[taskInfo]) {
95
+ worker[taskInfo].done(err, null);
96
+ } else {
97
+ this.emit('error', err);
98
+ }
99
+ this.workers.splice(this.workers.indexOf(worker), 1);
100
+ this.addNewWorker();
109
101
  });
110
102
 
111
- worker.on('exit', (code) => {
112
- if (code !== 0) {
113
- callCallback(new Error(`Minify worker stopped with exit code ${code}`));
103
+ this.workers.push(worker);
104
+ this.freeWorkers.push(worker);
105
+ this.emit(freeWorker);
106
+ }
107
+
108
+ private runTask(context: WorkerContext, cb: WorkerCallback) {
109
+ if (this.freeWorkers.length === 0) {
110
+ this.tasks.push({ context, cb });
111
+ if (this.numWorkers < this.maxInstances) {
112
+ this.addNewWorker();
114
113
  }
115
- });
114
+ return;
115
+ }
116
+
117
+ const worker = this.freeWorkers.pop();
118
+ if (worker) {
119
+ worker[taskInfo] = new WorkerPoolTaskInfo(cb);
120
+ worker.postMessage({
121
+ code: context.code,
122
+ options: serializeJavascript(context.options)
123
+ });
124
+ }
116
125
  }
117
126
  }
package/src/worker.ts CHANGED
@@ -1,5 +1,4 @@
1
- import process from 'process';
2
- import { isMainThread, parentPort, workerData } from 'worker_threads';
1
+ import { isMainThread, parentPort } from 'worker_threads';
3
2
 
4
3
  import { hasOwnProperty, isObject } from 'smob';
5
4
 
@@ -22,26 +21,36 @@ function isWorkerContextSerialized(input: unknown): input is WorkerContextSerial
22
21
  );
23
22
  }
24
23
 
25
- export async function runWorker() {
26
- if (isMainThread || !parentPort || !isWorkerContextSerialized(workerData)) {
24
+ export function runWorker() {
25
+ if (isMainThread || !parentPort) {
27
26
  return;
28
27
  }
29
28
 
30
- try {
31
- // eslint-disable-next-line no-eval
32
- const eval2 = eval;
29
+ // eslint-disable-next-line no-eval
30
+ const eval2 = eval;
33
31
 
34
- const options = eval2(`(${workerData.options})`);
32
+ parentPort.on('message', async (data: WorkerContextSerialized) => {
33
+ if (!isWorkerContextSerialized(data)) {
34
+ return;
35
+ }
35
36
 
36
- const result = await minify(workerData.code, options);
37
+ const options = eval2(`(${data.options})`);
38
+
39
+ const result = await minify(data.code, options);
37
40
 
38
41
  const output: WorkerOutput = {
39
- code: result.code || workerData.code,
42
+ code: result.code || data.code,
40
43
  nameCache: options.nameCache
41
44
  };
42
45
 
43
- parentPort.postMessage(output);
44
- } catch (e) {
45
- process.exit(1);
46
- }
46
+ if (typeof result.map === 'string') {
47
+ output.sourceMap = JSON.parse(result.map);
48
+ }
49
+
50
+ if (isObject(result.map)) {
51
+ output.sourceMap = result.map;
52
+ }
53
+
54
+ parentPort?.postMessage(output);
55
+ });
47
56
  }