@rollup/plugin-terser 0.3.0 → 0.4.1
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 +96 -63
- package/dist/es/index.js +97 -64
- package/package.json +1 -1
- package/src/constants.ts +2 -0
- package/src/module.ts +23 -4
- package/src/type.ts +11 -0
- package/src/worker-pool.ts +64 -55
- package/src/worker.ts +15 -14
- package/types/index.d.ts +6 -1
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,17 +23,20 @@ function isWorkerContextSerialized(input) {
|
|
|
23
23
|
smob.hasOwnProperty(input, 'options') &&
|
|
24
24
|
typeof input.options === 'string');
|
|
25
25
|
}
|
|
26
|
-
|
|
27
|
-
if (worker_threads.isMainThread || !worker_threads.parentPort
|
|
26
|
+
function runWorker() {
|
|
27
|
+
if (worker_threads.isMainThread || !worker_threads.parentPort) {
|
|
28
28
|
return;
|
|
29
29
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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 ||
|
|
39
|
+
code: result.code || data.code,
|
|
37
40
|
nameCache: options.nameCache
|
|
38
41
|
};
|
|
39
42
|
if (typeof result.map === 'string') {
|
|
@@ -42,40 +45,44 @@ async function runWorker() {
|
|
|
42
45
|
if (smob.isObject(result.map)) {
|
|
43
46
|
output.sourceMap = result.map;
|
|
44
47
|
}
|
|
45
|
-
worker_threads.parentPort.postMessage(output);
|
|
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;
|
|
46
59
|
}
|
|
47
|
-
|
|
48
|
-
|
|
60
|
+
done(err, result) {
|
|
61
|
+
this.runInAsyncScope(this.callback, null, err, result);
|
|
62
|
+
this.emitDestroy();
|
|
49
63
|
}
|
|
50
64
|
}
|
|
51
|
-
|
|
52
|
-
const symbol = Symbol.for('FreeWoker');
|
|
53
65
|
class WorkerPool extends events.EventEmitter {
|
|
54
66
|
constructor(options) {
|
|
55
67
|
super();
|
|
56
68
|
this.tasks = [];
|
|
57
|
-
this.workers =
|
|
69
|
+
this.workers = [];
|
|
70
|
+
this.freeWorkers = [];
|
|
58
71
|
this.maxInstances = options.maxWorkers || os.cpus().length;
|
|
59
72
|
this.filePath = options.filePath;
|
|
60
|
-
this.on(
|
|
73
|
+
this.on(freeWorker, () => {
|
|
61
74
|
if (this.tasks.length > 0) {
|
|
62
|
-
this.
|
|
75
|
+
const { context, cb } = this.tasks.shift();
|
|
76
|
+
this.runTask(context, cb);
|
|
63
77
|
}
|
|
64
78
|
});
|
|
65
79
|
}
|
|
66
|
-
|
|
67
|
-
this.
|
|
68
|
-
context,
|
|
69
|
-
cb
|
|
70
|
-
});
|
|
71
|
-
if (this.workers >= this.maxInstances) {
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
this.run();
|
|
80
|
+
get numWorkers() {
|
|
81
|
+
return this.workers.length;
|
|
75
82
|
}
|
|
76
|
-
|
|
83
|
+
addAsync(context) {
|
|
77
84
|
return new Promise((resolve, reject) => {
|
|
78
|
-
this.
|
|
85
|
+
this.runTask(context, (err, output) => {
|
|
79
86
|
if (err) {
|
|
80
87
|
reject(err);
|
|
81
88
|
return;
|
|
@@ -88,54 +95,69 @@ class WorkerPool extends events.EventEmitter {
|
|
|
88
95
|
});
|
|
89
96
|
});
|
|
90
97
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
const task = this.tasks.shift();
|
|
96
|
-
if (typeof task === 'undefined') {
|
|
97
|
-
return;
|
|
98
|
+
close() {
|
|
99
|
+
for (let i = 0; i < this.workers.length; i++) {
|
|
100
|
+
const worker = this.workers[i];
|
|
101
|
+
worker.terminate();
|
|
98
102
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
this.
|
|
107
|
-
|
|
108
|
-
this.emit(symbol);
|
|
109
|
-
};
|
|
110
|
-
const worker = new worker_threads.Worker(this.filePath, {
|
|
111
|
-
workerData: {
|
|
112
|
-
code: task.context.code,
|
|
113
|
-
options: serializeJavascript(task.context.options)
|
|
114
|
-
}
|
|
115
|
-
});
|
|
116
|
-
worker.on('message', (data) => {
|
|
117
|
-
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);
|
|
118
112
|
});
|
|
119
113
|
worker.on('error', (err) => {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
114
|
+
if (worker[taskInfo]) {
|
|
115
|
+
worker[taskInfo].done(err, null);
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
this.emit('error', err);
|
|
125
119
|
}
|
|
120
|
+
this.workers.splice(this.workers.indexOf(worker), 1);
|
|
121
|
+
this.addNewWorker();
|
|
126
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
|
+
}
|
|
127
143
|
}
|
|
128
144
|
}
|
|
129
145
|
|
|
130
146
|
function terser(input = {}) {
|
|
131
147
|
const { maxWorkers, ...options } = input;
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
});
|
|
148
|
+
let workerPool;
|
|
149
|
+
let numOfChunks = 0;
|
|
150
|
+
let numOfWorkersUsed = 0;
|
|
136
151
|
return {
|
|
137
152
|
name: 'terser',
|
|
138
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;
|
|
139
161
|
const defaultOptions = {
|
|
140
162
|
sourceMap: outputOptions.sourcemap === true || typeof outputOptions.sourcemap === 'string'
|
|
141
163
|
};
|
|
@@ -184,6 +206,17 @@ function terser(input = {}) {
|
|
|
184
206
|
catch (e) {
|
|
185
207
|
return Promise.reject(e);
|
|
186
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;
|
|
187
220
|
}
|
|
188
221
|
};
|
|
189
222
|
}
|
package/dist/es/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import
|
|
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,17 +19,20 @@ function isWorkerContextSerialized(input) {
|
|
|
19
19
|
hasOwnProperty(input, 'options') &&
|
|
20
20
|
typeof input.options === 'string');
|
|
21
21
|
}
|
|
22
|
-
|
|
23
|
-
if (isMainThread || !parentPort
|
|
22
|
+
function runWorker() {
|
|
23
|
+
if (isMainThread || !parentPort) {
|
|
24
24
|
return;
|
|
25
25
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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 ||
|
|
35
|
+
code: result.code || data.code,
|
|
33
36
|
nameCache: options.nameCache
|
|
34
37
|
};
|
|
35
38
|
if (typeof result.map === 'string') {
|
|
@@ -38,40 +41,44 @@ async function runWorker() {
|
|
|
38
41
|
if (isObject(result.map)) {
|
|
39
42
|
output.sourceMap = result.map;
|
|
40
43
|
}
|
|
41
|
-
parentPort.postMessage(output);
|
|
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;
|
|
42
55
|
}
|
|
43
|
-
|
|
44
|
-
|
|
56
|
+
done(err, result) {
|
|
57
|
+
this.runInAsyncScope(this.callback, null, err, result);
|
|
58
|
+
this.emitDestroy();
|
|
45
59
|
}
|
|
46
60
|
}
|
|
47
|
-
|
|
48
|
-
const symbol = Symbol.for('FreeWoker');
|
|
49
61
|
class WorkerPool extends EventEmitter {
|
|
50
62
|
constructor(options) {
|
|
51
63
|
super();
|
|
52
64
|
this.tasks = [];
|
|
53
|
-
this.workers =
|
|
65
|
+
this.workers = [];
|
|
66
|
+
this.freeWorkers = [];
|
|
54
67
|
this.maxInstances = options.maxWorkers || cpus().length;
|
|
55
68
|
this.filePath = options.filePath;
|
|
56
|
-
this.on(
|
|
69
|
+
this.on(freeWorker, () => {
|
|
57
70
|
if (this.tasks.length > 0) {
|
|
58
|
-
this.
|
|
71
|
+
const { context, cb } = this.tasks.shift();
|
|
72
|
+
this.runTask(context, cb);
|
|
59
73
|
}
|
|
60
74
|
});
|
|
61
75
|
}
|
|
62
|
-
|
|
63
|
-
this.
|
|
64
|
-
context,
|
|
65
|
-
cb
|
|
66
|
-
});
|
|
67
|
-
if (this.workers >= this.maxInstances) {
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
this.run();
|
|
76
|
+
get numWorkers() {
|
|
77
|
+
return this.workers.length;
|
|
71
78
|
}
|
|
72
|
-
|
|
79
|
+
addAsync(context) {
|
|
73
80
|
return new Promise((resolve, reject) => {
|
|
74
|
-
this.
|
|
81
|
+
this.runTask(context, (err, output) => {
|
|
75
82
|
if (err) {
|
|
76
83
|
reject(err);
|
|
77
84
|
return;
|
|
@@ -84,54 +91,69 @@ class WorkerPool extends EventEmitter {
|
|
|
84
91
|
});
|
|
85
92
|
});
|
|
86
93
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
const task = this.tasks.shift();
|
|
92
|
-
if (typeof task === 'undefined') {
|
|
93
|
-
return;
|
|
94
|
+
close() {
|
|
95
|
+
for (let i = 0; i < this.workers.length; i++) {
|
|
96
|
+
const worker = this.workers[i];
|
|
97
|
+
worker.terminate();
|
|
94
98
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
this.
|
|
103
|
-
|
|
104
|
-
this.emit(symbol);
|
|
105
|
-
};
|
|
106
|
-
const worker = new Worker(this.filePath, {
|
|
107
|
-
workerData: {
|
|
108
|
-
code: task.context.code,
|
|
109
|
-
options: serializeJavascript(task.context.options)
|
|
110
|
-
}
|
|
111
|
-
});
|
|
112
|
-
worker.on('message', (data) => {
|
|
113
|
-
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);
|
|
114
108
|
});
|
|
115
109
|
worker.on('error', (err) => {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
110
|
+
if (worker[taskInfo]) {
|
|
111
|
+
worker[taskInfo].done(err, null);
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
this.emit('error', err);
|
|
121
115
|
}
|
|
116
|
+
this.workers.splice(this.workers.indexOf(worker), 1);
|
|
117
|
+
this.addNewWorker();
|
|
122
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
|
+
}
|
|
123
139
|
}
|
|
124
140
|
}
|
|
125
141
|
|
|
126
142
|
function terser(input = {}) {
|
|
127
143
|
const { maxWorkers, ...options } = input;
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
});
|
|
144
|
+
let workerPool;
|
|
145
|
+
let numOfChunks = 0;
|
|
146
|
+
let numOfWorkersUsed = 0;
|
|
132
147
|
return {
|
|
133
148
|
name: 'terser',
|
|
134
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;
|
|
135
157
|
const defaultOptions = {
|
|
136
158
|
sourceMap: outputOptions.sourcemap === true || typeof outputOptions.sourcemap === 'string'
|
|
137
159
|
};
|
|
@@ -180,6 +202,17 @@ function terser(input = {}) {
|
|
|
180
202
|
catch (e) {
|
|
181
203
|
return Promise.reject(e);
|
|
182
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;
|
|
183
216
|
}
|
|
184
217
|
};
|
|
185
218
|
}
|
package/package.json
CHANGED
package/src/constants.ts
ADDED
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
};
|
|
@@ -80,7 +88,18 @@ export default function terser(input: Options = {}) {
|
|
|
80
88
|
return result;
|
|
81
89
|
} catch (e) {
|
|
82
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
|
+
}
|
|
83
98
|
}
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
get numOfWorkersUsed() {
|
|
102
|
+
return numOfWorkersUsed;
|
|
84
103
|
}
|
|
85
104
|
};
|
|
86
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;
|
package/src/worker-pool.ts
CHANGED
|
@@ -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
|
-
|
|
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 =
|
|
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(
|
|
46
|
+
this.on(freeWorker, () => {
|
|
33
47
|
if (this.tasks.length > 0) {
|
|
34
|
-
this.
|
|
48
|
+
const { context, cb } = this.tasks.shift()!;
|
|
49
|
+
this.runTask(context, cb);
|
|
35
50
|
}
|
|
36
51
|
});
|
|
37
52
|
}
|
|
38
53
|
|
|
39
|
-
|
|
40
|
-
this.
|
|
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
|
-
|
|
58
|
+
addAsync(context: WorkerContext): Promise<WorkerOutput> {
|
|
53
59
|
return new Promise((resolve, reject) => {
|
|
54
|
-
this.
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
|
|
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', (
|
|
104
|
-
|
|
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
|
-
|
|
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
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
|
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,21 +21,25 @@ function isWorkerContextSerialized(input: unknown): input is WorkerContextSerial
|
|
|
22
21
|
);
|
|
23
22
|
}
|
|
24
23
|
|
|
25
|
-
export
|
|
26
|
-
if (isMainThread || !parentPort
|
|
24
|
+
export function runWorker() {
|
|
25
|
+
if (isMainThread || !parentPort) {
|
|
27
26
|
return;
|
|
28
27
|
}
|
|
29
28
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const eval2 = eval;
|
|
29
|
+
// eslint-disable-next-line no-eval
|
|
30
|
+
const eval2 = eval;
|
|
33
31
|
|
|
34
|
-
|
|
32
|
+
parentPort.on('message', async (data: WorkerContextSerialized) => {
|
|
33
|
+
if (!isWorkerContextSerialized(data)) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const options = eval2(`(${data.options})`);
|
|
35
38
|
|
|
36
|
-
const result = await minify(
|
|
39
|
+
const result = await minify(data.code, options);
|
|
37
40
|
|
|
38
41
|
const output: WorkerOutput = {
|
|
39
|
-
code: result.code ||
|
|
42
|
+
code: result.code || data.code,
|
|
40
43
|
nameCache: options.nameCache
|
|
41
44
|
};
|
|
42
45
|
|
|
@@ -48,8 +51,6 @@ export async function runWorker() {
|
|
|
48
51
|
output.sourceMap = result.map;
|
|
49
52
|
}
|
|
50
53
|
|
|
51
|
-
parentPort
|
|
52
|
-
}
|
|
53
|
-
process.exit(1);
|
|
54
|
-
}
|
|
54
|
+
parentPort?.postMessage(output);
|
|
55
|
+
});
|
|
55
56
|
}
|
package/types/index.d.ts
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import type { Plugin } from 'rollup';
|
|
2
2
|
import type { MinifyOptions } from 'terser';
|
|
3
3
|
|
|
4
|
+
export interface Options extends MinifyOptions {
|
|
5
|
+
nameCache?: Record<string, any>;
|
|
6
|
+
maxWorkers?: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
4
9
|
/**
|
|
5
10
|
* A Rollup plugin to generate a minified output bundle.
|
|
6
11
|
*
|
|
7
12
|
* @param options - Plugin options.
|
|
8
13
|
* @returns Plugin instance.
|
|
9
14
|
*/
|
|
10
|
-
export default function terser(options?:
|
|
15
|
+
export default function terser(options?: Options): Plugin;
|