@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 +109 -64
- package/dist/es/index.js +110 -65
- package/package.json +1 -1
- package/src/constants.ts +2 -0
- package/src/module.ts +34 -5
- package/src/type.ts +12 -0
- package/src/worker-pool.ts +64 -55
- package/src/worker.ts +23 -14
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
|
-
|
|
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') {
|
|
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
|
-
|
|
42
|
-
|
|
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 =
|
|
69
|
+
this.workers = [];
|
|
70
|
+
this.freeWorkers = [];
|
|
52
71
|
this.maxInstances = options.maxWorkers || os.cpus().length;
|
|
53
72
|
this.filePath = options.filePath;
|
|
54
|
-
this.on(
|
|
73
|
+
this.on(freeWorker, () => {
|
|
55
74
|
if (this.tasks.length > 0) {
|
|
56
|
-
this.
|
|
75
|
+
const { context, cb } = this.tasks.shift();
|
|
76
|
+
this.runTask(context, cb);
|
|
57
77
|
}
|
|
58
78
|
});
|
|
59
79
|
}
|
|
60
|
-
|
|
61
|
-
this.
|
|
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
|
-
|
|
83
|
+
addAsync(context) {
|
|
71
84
|
return new Promise((resolve, reject) => {
|
|
72
|
-
this.
|
|
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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
98
|
+
close() {
|
|
99
|
+
for (let i = 0; i < this.workers.length; i++) {
|
|
100
|
+
const worker = this.workers[i];
|
|
101
|
+
worker.terminate();
|
|
88
102
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
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
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|
|
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
|
-
|
|
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') {
|
|
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
|
-
|
|
38
|
-
|
|
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 =
|
|
65
|
+
this.workers = [];
|
|
66
|
+
this.freeWorkers = [];
|
|
48
67
|
this.maxInstances = options.maxWorkers || cpus().length;
|
|
49
68
|
this.filePath = options.filePath;
|
|
50
|
-
this.on(
|
|
69
|
+
this.on(freeWorker, () => {
|
|
51
70
|
if (this.tasks.length > 0) {
|
|
52
|
-
this.
|
|
71
|
+
const { context, cb } = this.tasks.shift();
|
|
72
|
+
this.runTask(context, cb);
|
|
53
73
|
}
|
|
54
74
|
});
|
|
55
75
|
}
|
|
56
|
-
|
|
57
|
-
this.
|
|
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
|
-
|
|
79
|
+
addAsync(context) {
|
|
67
80
|
return new Promise((resolve, reject) => {
|
|
68
|
-
this.
|
|
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
|
-
|
|
82
|
-
|
|
83
|
-
|
|
94
|
+
close() {
|
|
95
|
+
for (let i = 0; i < this.workers.length; i++) {
|
|
96
|
+
const worker = this.workers[i];
|
|
97
|
+
worker.terminate();
|
|
84
98
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
-
|
|
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
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
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
|
};
|
|
@@ -31,7 +39,11 @@ export default function terser(input: Options = {}) {
|
|
|
31
39
|
}
|
|
32
40
|
|
|
33
41
|
try {
|
|
34
|
-
const {
|
|
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 {
|
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,26 +21,36 @@ 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
|
+
}
|
|
35
36
|
|
|
36
|
-
const
|
|
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 ||
|
|
42
|
+
code: result.code || data.code,
|
|
40
43
|
nameCache: options.nameCache
|
|
41
44
|
};
|
|
42
45
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
}
|