@rollup/plugin-terser 0.1.0 → 0.2.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/README.md +29 -8
- package/dist/cjs/index.js +156 -2
- package/dist/es/index.js +156 -2
- package/package.json +4 -1
- package/src/index.ts +5 -21
- package/src/module.ts +72 -0
- package/src/type.ts +33 -0
- package/src/worker-pool.ts +117 -0
- package/src/worker.ts +47 -0
- package/types/index.d.ts +2 -2
package/README.md
CHANGED
|
@@ -9,11 +9,11 @@
|
|
|
9
9
|
|
|
10
10
|
# @rollup/plugin-terser
|
|
11
11
|
|
|
12
|
-
🍣 A Rollup plugin to generate a minified
|
|
12
|
+
🍣 A Rollup plugin to generate a minified bundle with terser.
|
|
13
13
|
|
|
14
14
|
## Requirements
|
|
15
15
|
|
|
16
|
-
This plugin requires an [LTS](https://github.com/nodejs/Release) Node version (v14.0.0+) and Rollup
|
|
16
|
+
This plugin requires an [LTS](https://github.com/nodejs/Release) Node version (v14.0.0+) and Rollup v2.0+.
|
|
17
17
|
|
|
18
18
|
## Install
|
|
19
19
|
|
|
@@ -27,7 +27,7 @@ npm install @rollup/plugin-terser --save-dev
|
|
|
27
27
|
|
|
28
28
|
Create a `rollup.config.js` [configuration file](https://www.rollupjs.org/guide/en/#configuration-files) and import the plugin:
|
|
29
29
|
|
|
30
|
-
```
|
|
30
|
+
```typescript
|
|
31
31
|
import terser from '@rollup/plugin-terser';
|
|
32
32
|
|
|
33
33
|
export default {
|
|
@@ -47,13 +47,34 @@ Then call `rollup` either via the [CLI](https://www.rollupjs.org/guide/en/#comma
|
|
|
47
47
|
The plugin accepts a terser [Options](https://github.com/terser/terser#minify-options) object as input parameter,
|
|
48
48
|
to modify the default behaviour.
|
|
49
49
|
|
|
50
|
+
In addition to the `terser` options, it is also possible to provide the following options:
|
|
51
|
+
|
|
52
|
+
### `maxWorkers`
|
|
53
|
+
|
|
54
|
+
Type: `Number`<br>
|
|
55
|
+
Default: `undefined`
|
|
56
|
+
|
|
57
|
+
Instructs the plugin to use a specific amount of cpu threads.
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
import terser from '@rollup/plugin-terser';
|
|
61
|
+
|
|
62
|
+
export default {
|
|
63
|
+
input: 'src/index.js',
|
|
64
|
+
output: {
|
|
65
|
+
dir: 'output',
|
|
66
|
+
format: 'cjs'
|
|
67
|
+
},
|
|
68
|
+
plugins: [
|
|
69
|
+
terser({
|
|
70
|
+
maxWorkers: 4
|
|
71
|
+
})
|
|
72
|
+
]
|
|
73
|
+
};
|
|
74
|
+
```
|
|
75
|
+
|
|
50
76
|
## Meta
|
|
51
77
|
|
|
52
78
|
[CONTRIBUTING](/.github/CONTRIBUTING.md)
|
|
53
79
|
|
|
54
80
|
[LICENSE (MIT)](/LICENSE)
|
|
55
|
-
|
|
56
|
-
## Credits
|
|
57
|
-
|
|
58
|
-
This package was originally developed by [https://github.com/TrySound](TrySound) but is not
|
|
59
|
-
maintained anymore.
|
package/dist/cjs/index.js
CHANGED
|
@@ -2,9 +2,129 @@
|
|
|
2
2
|
|
|
3
3
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
4
|
|
|
5
|
+
var process = require('process');
|
|
6
|
+
var worker_threads = require('worker_threads');
|
|
7
|
+
var smob = require('smob');
|
|
5
8
|
var terser$1 = require('terser');
|
|
9
|
+
var os = require('os');
|
|
10
|
+
var events = require('events');
|
|
11
|
+
var serializeJavascript = require('serialize-javascript');
|
|
6
12
|
|
|
7
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Duck typing worker context.
|
|
15
|
+
*
|
|
16
|
+
* @param input
|
|
17
|
+
*/
|
|
18
|
+
function isWorkerContextSerialized(input) {
|
|
19
|
+
return (smob.isObject(input) &&
|
|
20
|
+
smob.hasOwnProperty(input, 'code') &&
|
|
21
|
+
typeof input.code === 'string' &&
|
|
22
|
+
smob.hasOwnProperty(input, 'options') &&
|
|
23
|
+
typeof input.options === 'string');
|
|
24
|
+
}
|
|
25
|
+
async function runWorker() {
|
|
26
|
+
if (worker_threads.isMainThread || !worker_threads.parentPort || !isWorkerContextSerialized(worker_threads.workerData)) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
// eslint-disable-next-line no-eval
|
|
31
|
+
const eval2 = eval;
|
|
32
|
+
const options = eval2(`(${worker_threads.workerData.options})`);
|
|
33
|
+
const result = await terser$1.minify(worker_threads.workerData.code, options);
|
|
34
|
+
const output = {
|
|
35
|
+
code: result.code || worker_threads.workerData.code,
|
|
36
|
+
nameCache: options.nameCache
|
|
37
|
+
};
|
|
38
|
+
worker_threads.parentPort.postMessage(output);
|
|
39
|
+
}
|
|
40
|
+
catch (e) {
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const symbol = Symbol.for('FreeWoker');
|
|
46
|
+
class WorkerPool extends events.EventEmitter {
|
|
47
|
+
constructor(options) {
|
|
48
|
+
super();
|
|
49
|
+
this.tasks = [];
|
|
50
|
+
this.workers = 0;
|
|
51
|
+
this.maxInstances = options.maxWorkers || os.cpus().length;
|
|
52
|
+
this.filePath = options.filePath;
|
|
53
|
+
this.on(symbol, () => {
|
|
54
|
+
if (this.tasks.length > 0) {
|
|
55
|
+
this.run();
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
add(context, cb) {
|
|
60
|
+
this.tasks.push({
|
|
61
|
+
context,
|
|
62
|
+
cb
|
|
63
|
+
});
|
|
64
|
+
if (this.workers >= this.maxInstances) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
this.run();
|
|
68
|
+
}
|
|
69
|
+
async addAsync(context) {
|
|
70
|
+
return new Promise((resolve, reject) => {
|
|
71
|
+
this.add(context, (err, output) => {
|
|
72
|
+
if (err) {
|
|
73
|
+
reject(err);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (!output) {
|
|
77
|
+
reject(new Error('The output is empty'));
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
resolve(output);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
run() {
|
|
85
|
+
if (this.tasks.length === 0) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const task = this.tasks.shift();
|
|
89
|
+
if (typeof task === 'undefined') {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
this.workers += 1;
|
|
93
|
+
let called = false;
|
|
94
|
+
const callCallback = (err, output) => {
|
|
95
|
+
if (called) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
called = true;
|
|
99
|
+
this.workers -= 1;
|
|
100
|
+
task.cb(err, output);
|
|
101
|
+
this.emit(symbol);
|
|
102
|
+
};
|
|
103
|
+
const worker = new worker_threads.Worker(this.filePath, {
|
|
104
|
+
workerData: {
|
|
105
|
+
code: task.context.code,
|
|
106
|
+
options: serializeJavascript(task.context.options)
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
worker.on('message', (data) => {
|
|
110
|
+
callCallback(null, data);
|
|
111
|
+
});
|
|
112
|
+
worker.on('error', (err) => {
|
|
113
|
+
callCallback(err);
|
|
114
|
+
});
|
|
115
|
+
worker.on('exit', (code) => {
|
|
116
|
+
if (code !== 0) {
|
|
117
|
+
callCallback(new Error(`Minify worker stopped with exit code ${code}`));
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function terser(options = {}) {
|
|
124
|
+
const workerPool = new WorkerPool({
|
|
125
|
+
filePath: __filename,
|
|
126
|
+
maxWorkers: options.maxWorkers
|
|
127
|
+
});
|
|
8
128
|
return {
|
|
9
129
|
name: 'terser',
|
|
10
130
|
async renderChunk(code, chunk, outputOptions) {
|
|
@@ -17,11 +137,45 @@ function terser(options) {
|
|
|
17
137
|
if (outputOptions.format === 'cjs') {
|
|
18
138
|
defaultOptions.toplevel = true;
|
|
19
139
|
}
|
|
20
|
-
|
|
140
|
+
try {
|
|
141
|
+
const { code: result, nameCache } = await workerPool.addAsync({
|
|
142
|
+
code,
|
|
143
|
+
options: smob.merge({}, options || {}, defaultOptions)
|
|
144
|
+
});
|
|
145
|
+
if (options.nameCache && nameCache) {
|
|
146
|
+
let vars = {
|
|
147
|
+
props: {}
|
|
148
|
+
};
|
|
149
|
+
if (smob.hasOwnProperty(options.nameCache, 'vars') && smob.isObject(options.nameCache.vars)) {
|
|
150
|
+
vars = smob.merge({}, options.nameCache.vars || {}, vars);
|
|
151
|
+
}
|
|
152
|
+
if (smob.hasOwnProperty(nameCache, 'vars') && smob.isObject(nameCache.vars)) {
|
|
153
|
+
vars = smob.merge({}, nameCache.vars, vars);
|
|
154
|
+
}
|
|
155
|
+
// eslint-disable-next-line no-param-reassign
|
|
156
|
+
options.nameCache.vars = vars;
|
|
157
|
+
let props = {};
|
|
158
|
+
if (smob.hasOwnProperty(options.nameCache, 'props') && smob.isObject(options.nameCache.props)) {
|
|
159
|
+
// eslint-disable-next-line prefer-destructuring
|
|
160
|
+
props = options.nameCache.props;
|
|
161
|
+
}
|
|
162
|
+
if (smob.hasOwnProperty(nameCache, 'props') && smob.isObject(nameCache.props)) {
|
|
163
|
+
props = smob.merge({}, nameCache.props, props);
|
|
164
|
+
}
|
|
165
|
+
// eslint-disable-next-line no-param-reassign
|
|
166
|
+
options.nameCache.props = props;
|
|
167
|
+
}
|
|
168
|
+
return result;
|
|
169
|
+
}
|
|
170
|
+
catch (e) {
|
|
171
|
+
return Promise.reject(e);
|
|
172
|
+
}
|
|
21
173
|
}
|
|
22
174
|
};
|
|
23
175
|
}
|
|
24
176
|
|
|
177
|
+
runWorker();
|
|
178
|
+
|
|
25
179
|
exports.default = terser;
|
|
26
180
|
module.exports = Object.assign(exports.default, exports);
|
|
27
181
|
//# sourceMappingURL=index.js.map
|
package/dist/es/index.js
CHANGED
|
@@ -1,6 +1,126 @@
|
|
|
1
|
+
import process from 'process';
|
|
2
|
+
import { isMainThread, parentPort, workerData, Worker } from 'worker_threads';
|
|
3
|
+
import { isObject, hasOwnProperty, merge } from 'smob';
|
|
1
4
|
import { minify } from 'terser';
|
|
5
|
+
import { cpus } from 'os';
|
|
6
|
+
import { EventEmitter } from 'events';
|
|
7
|
+
import serializeJavascript from 'serialize-javascript';
|
|
2
8
|
|
|
3
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Duck typing worker context.
|
|
11
|
+
*
|
|
12
|
+
* @param input
|
|
13
|
+
*/
|
|
14
|
+
function isWorkerContextSerialized(input) {
|
|
15
|
+
return (isObject(input) &&
|
|
16
|
+
hasOwnProperty(input, 'code') &&
|
|
17
|
+
typeof input.code === 'string' &&
|
|
18
|
+
hasOwnProperty(input, 'options') &&
|
|
19
|
+
typeof input.options === 'string');
|
|
20
|
+
}
|
|
21
|
+
async function runWorker() {
|
|
22
|
+
if (isMainThread || !parentPort || !isWorkerContextSerialized(workerData)) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
// eslint-disable-next-line no-eval
|
|
27
|
+
const eval2 = eval;
|
|
28
|
+
const options = eval2(`(${workerData.options})`);
|
|
29
|
+
const result = await minify(workerData.code, options);
|
|
30
|
+
const output = {
|
|
31
|
+
code: result.code || workerData.code,
|
|
32
|
+
nameCache: options.nameCache
|
|
33
|
+
};
|
|
34
|
+
parentPort.postMessage(output);
|
|
35
|
+
}
|
|
36
|
+
catch (e) {
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const symbol = Symbol.for('FreeWoker');
|
|
42
|
+
class WorkerPool extends EventEmitter {
|
|
43
|
+
constructor(options) {
|
|
44
|
+
super();
|
|
45
|
+
this.tasks = [];
|
|
46
|
+
this.workers = 0;
|
|
47
|
+
this.maxInstances = options.maxWorkers || cpus().length;
|
|
48
|
+
this.filePath = options.filePath;
|
|
49
|
+
this.on(symbol, () => {
|
|
50
|
+
if (this.tasks.length > 0) {
|
|
51
|
+
this.run();
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
add(context, cb) {
|
|
56
|
+
this.tasks.push({
|
|
57
|
+
context,
|
|
58
|
+
cb
|
|
59
|
+
});
|
|
60
|
+
if (this.workers >= this.maxInstances) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
this.run();
|
|
64
|
+
}
|
|
65
|
+
async addAsync(context) {
|
|
66
|
+
return new Promise((resolve, reject) => {
|
|
67
|
+
this.add(context, (err, output) => {
|
|
68
|
+
if (err) {
|
|
69
|
+
reject(err);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if (!output) {
|
|
73
|
+
reject(new Error('The output is empty'));
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
resolve(output);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
run() {
|
|
81
|
+
if (this.tasks.length === 0) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const task = this.tasks.shift();
|
|
85
|
+
if (typeof task === 'undefined') {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
this.workers += 1;
|
|
89
|
+
let called = false;
|
|
90
|
+
const callCallback = (err, output) => {
|
|
91
|
+
if (called) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
called = true;
|
|
95
|
+
this.workers -= 1;
|
|
96
|
+
task.cb(err, output);
|
|
97
|
+
this.emit(symbol);
|
|
98
|
+
};
|
|
99
|
+
const worker = new Worker(this.filePath, {
|
|
100
|
+
workerData: {
|
|
101
|
+
code: task.context.code,
|
|
102
|
+
options: serializeJavascript(task.context.options)
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
worker.on('message', (data) => {
|
|
106
|
+
callCallback(null, data);
|
|
107
|
+
});
|
|
108
|
+
worker.on('error', (err) => {
|
|
109
|
+
callCallback(err);
|
|
110
|
+
});
|
|
111
|
+
worker.on('exit', (code) => {
|
|
112
|
+
if (code !== 0) {
|
|
113
|
+
callCallback(new Error(`Minify worker stopped with exit code ${code}`));
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function terser(options = {}) {
|
|
120
|
+
const workerPool = new WorkerPool({
|
|
121
|
+
filePath: __filename,
|
|
122
|
+
maxWorkers: options.maxWorkers
|
|
123
|
+
});
|
|
4
124
|
return {
|
|
5
125
|
name: 'terser',
|
|
6
126
|
async renderChunk(code, chunk, outputOptions) {
|
|
@@ -13,10 +133,44 @@ function terser(options) {
|
|
|
13
133
|
if (outputOptions.format === 'cjs') {
|
|
14
134
|
defaultOptions.toplevel = true;
|
|
15
135
|
}
|
|
16
|
-
|
|
136
|
+
try {
|
|
137
|
+
const { code: result, nameCache } = await workerPool.addAsync({
|
|
138
|
+
code,
|
|
139
|
+
options: merge({}, options || {}, defaultOptions)
|
|
140
|
+
});
|
|
141
|
+
if (options.nameCache && nameCache) {
|
|
142
|
+
let vars = {
|
|
143
|
+
props: {}
|
|
144
|
+
};
|
|
145
|
+
if (hasOwnProperty(options.nameCache, 'vars') && isObject(options.nameCache.vars)) {
|
|
146
|
+
vars = merge({}, options.nameCache.vars || {}, vars);
|
|
147
|
+
}
|
|
148
|
+
if (hasOwnProperty(nameCache, 'vars') && isObject(nameCache.vars)) {
|
|
149
|
+
vars = merge({}, nameCache.vars, vars);
|
|
150
|
+
}
|
|
151
|
+
// eslint-disable-next-line no-param-reassign
|
|
152
|
+
options.nameCache.vars = vars;
|
|
153
|
+
let props = {};
|
|
154
|
+
if (hasOwnProperty(options.nameCache, 'props') && isObject(options.nameCache.props)) {
|
|
155
|
+
// eslint-disable-next-line prefer-destructuring
|
|
156
|
+
props = options.nameCache.props;
|
|
157
|
+
}
|
|
158
|
+
if (hasOwnProperty(nameCache, 'props') && isObject(nameCache.props)) {
|
|
159
|
+
props = merge({}, nameCache.props, props);
|
|
160
|
+
}
|
|
161
|
+
// eslint-disable-next-line no-param-reassign
|
|
162
|
+
options.nameCache.props = props;
|
|
163
|
+
}
|
|
164
|
+
return result;
|
|
165
|
+
}
|
|
166
|
+
catch (e) {
|
|
167
|
+
return Promise.reject(e);
|
|
168
|
+
}
|
|
17
169
|
}
|
|
18
170
|
};
|
|
19
171
|
}
|
|
20
172
|
|
|
173
|
+
runWorker();
|
|
174
|
+
|
|
21
175
|
export { terser as default };
|
|
22
176
|
//# sourceMappingURL=index.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rollup/plugin-terser",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -61,9 +61,12 @@
|
|
|
61
61
|
}
|
|
62
62
|
},
|
|
63
63
|
"dependencies": {
|
|
64
|
+
"serialize-javascript": "^6.0.0",
|
|
65
|
+
"smob": "^0.0.6",
|
|
64
66
|
"terser": "^5.15.1"
|
|
65
67
|
},
|
|
66
68
|
"devDependencies": {
|
|
69
|
+
"@types/serialize-javascript": "^5.0.2",
|
|
67
70
|
"rollup": "^3.0.0-7",
|
|
68
71
|
"typescript": "^4.8.3"
|
|
69
72
|
},
|
package/src/index.ts
CHANGED
|
@@ -1,24 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
1
|
+
import { runWorker } from './worker';
|
|
2
|
+
import terser from './module';
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
return {
|
|
6
|
-
name: 'terser',
|
|
4
|
+
runWorker();
|
|
7
5
|
|
|
8
|
-
|
|
9
|
-
const defaultOptions: MinifyOptions = {
|
|
10
|
-
sourceMap: outputOptions.sourcemap === true || typeof outputOptions.sourcemap === 'string'
|
|
11
|
-
};
|
|
6
|
+
export * from './type';
|
|
12
7
|
|
|
13
|
-
|
|
14
|
-
defaultOptions.module = true;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
if (outputOptions.format === 'cjs') {
|
|
18
|
-
defaultOptions.toplevel = true;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
return minify(code, { ...defaultOptions, ...(options || {}) });
|
|
22
|
-
}
|
|
23
|
-
};
|
|
24
|
-
}
|
|
8
|
+
export default terser;
|
package/src/module.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { NormalizedOutputOptions, RenderedChunk } from 'rollup';
|
|
2
|
+
import { hasOwnProperty, isObject, merge } from 'smob';
|
|
3
|
+
|
|
4
|
+
import type { Options } from './type';
|
|
5
|
+
import { WorkerPool } from './worker-pool';
|
|
6
|
+
|
|
7
|
+
export default function terser(options: Options = {}) {
|
|
8
|
+
const workerPool = new WorkerPool({
|
|
9
|
+
filePath: __filename,
|
|
10
|
+
maxWorkers: options.maxWorkers
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
return {
|
|
14
|
+
name: 'terser',
|
|
15
|
+
|
|
16
|
+
async renderChunk(code: string, chunk: RenderedChunk, outputOptions: NormalizedOutputOptions) {
|
|
17
|
+
const defaultOptions: Options = {
|
|
18
|
+
sourceMap: outputOptions.sourcemap === true || typeof outputOptions.sourcemap === 'string'
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
if (outputOptions.format === 'es') {
|
|
22
|
+
defaultOptions.module = true;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (outputOptions.format === 'cjs') {
|
|
26
|
+
defaultOptions.toplevel = true;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
const { code: result, nameCache } = await workerPool.addAsync({
|
|
31
|
+
code,
|
|
32
|
+
options: merge({}, options || {}, defaultOptions)
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
if (options.nameCache && nameCache) {
|
|
36
|
+
let vars: Record<string, any> = {
|
|
37
|
+
props: {}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
if (hasOwnProperty(options.nameCache, 'vars') && isObject(options.nameCache.vars)) {
|
|
41
|
+
vars = merge({}, options.nameCache.vars || {}, vars);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (hasOwnProperty(nameCache, 'vars') && isObject(nameCache.vars)) {
|
|
45
|
+
vars = merge({}, nameCache.vars, vars);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// eslint-disable-next-line no-param-reassign
|
|
49
|
+
options.nameCache.vars = vars;
|
|
50
|
+
|
|
51
|
+
let props: Record<string, any> = {};
|
|
52
|
+
|
|
53
|
+
if (hasOwnProperty(options.nameCache, 'props') && isObject(options.nameCache.props)) {
|
|
54
|
+
// eslint-disable-next-line prefer-destructuring
|
|
55
|
+
props = options.nameCache.props;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (hasOwnProperty(nameCache, 'props') && isObject(nameCache.props)) {
|
|
59
|
+
props = merge({}, nameCache.props, props);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// eslint-disable-next-line no-param-reassign
|
|
63
|
+
options.nameCache.props = props;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return result;
|
|
67
|
+
} catch (e) {
|
|
68
|
+
return Promise.reject(e);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
}
|
package/src/type.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { MinifyOptions } from 'terser';
|
|
2
|
+
|
|
3
|
+
export interface Options extends MinifyOptions {
|
|
4
|
+
nameCache?: Record<string, any>;
|
|
5
|
+
maxWorkers?: number;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface WorkerContext {
|
|
9
|
+
code: string;
|
|
10
|
+
options: Options;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type WorkerCallback = (err: Error | null, output?: WorkerOutput) => void;
|
|
14
|
+
|
|
15
|
+
export interface WorkerContextSerialized {
|
|
16
|
+
code: string;
|
|
17
|
+
options: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface WorkerOutput {
|
|
21
|
+
code: string;
|
|
22
|
+
nameCache?: Options['nameCache'];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface WorkerPoolOptions {
|
|
26
|
+
filePath: string;
|
|
27
|
+
maxWorkers?: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface WorkerPoolTask {
|
|
31
|
+
context: WorkerContext;
|
|
32
|
+
cb: WorkerCallback;
|
|
33
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { Worker } from 'worker_threads';
|
|
2
|
+
import { cpus } from 'os';
|
|
3
|
+
import { EventEmitter } from 'events';
|
|
4
|
+
|
|
5
|
+
import serializeJavascript from 'serialize-javascript';
|
|
6
|
+
|
|
7
|
+
import type {
|
|
8
|
+
WorkerCallback,
|
|
9
|
+
WorkerContext,
|
|
10
|
+
WorkerOutput,
|
|
11
|
+
WorkerPoolOptions,
|
|
12
|
+
WorkerPoolTask
|
|
13
|
+
} from './type';
|
|
14
|
+
|
|
15
|
+
const symbol = Symbol.for('FreeWoker');
|
|
16
|
+
|
|
17
|
+
export class WorkerPool extends EventEmitter {
|
|
18
|
+
protected maxInstances: number;
|
|
19
|
+
|
|
20
|
+
protected filePath: string;
|
|
21
|
+
|
|
22
|
+
protected tasks: WorkerPoolTask[] = [];
|
|
23
|
+
|
|
24
|
+
protected workers = 0;
|
|
25
|
+
|
|
26
|
+
constructor(options: WorkerPoolOptions) {
|
|
27
|
+
super();
|
|
28
|
+
|
|
29
|
+
this.maxInstances = options.maxWorkers || cpus().length;
|
|
30
|
+
this.filePath = options.filePath;
|
|
31
|
+
|
|
32
|
+
this.on(symbol, () => {
|
|
33
|
+
if (this.tasks.length > 0) {
|
|
34
|
+
this.run();
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
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();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async addAsync(context: WorkerContext): Promise<WorkerOutput> {
|
|
53
|
+
return new Promise((resolve, reject) => {
|
|
54
|
+
this.add(context, (err, output) => {
|
|
55
|
+
if (err) {
|
|
56
|
+
reject(err);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (!output) {
|
|
61
|
+
reject(new Error('The output is empty'));
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
resolve(output);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
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;
|
|
79
|
+
}
|
|
80
|
+
|
|
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
|
+
});
|
|
102
|
+
|
|
103
|
+
worker.on('message', (data) => {
|
|
104
|
+
callCallback(null, data);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
worker.on('error', (err) => {
|
|
108
|
+
callCallback(err);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
worker.on('exit', (code) => {
|
|
112
|
+
if (code !== 0) {
|
|
113
|
+
callCallback(new Error(`Minify worker stopped with exit code ${code}`));
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
package/src/worker.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import process from 'process';
|
|
2
|
+
import { isMainThread, parentPort, workerData } from 'worker_threads';
|
|
3
|
+
|
|
4
|
+
import { hasOwnProperty, isObject } from 'smob';
|
|
5
|
+
|
|
6
|
+
import { minify } from 'terser';
|
|
7
|
+
|
|
8
|
+
import type { WorkerContextSerialized, WorkerOutput } from './type';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Duck typing worker context.
|
|
12
|
+
*
|
|
13
|
+
* @param input
|
|
14
|
+
*/
|
|
15
|
+
function isWorkerContextSerialized(input: unknown): input is WorkerContextSerialized {
|
|
16
|
+
return (
|
|
17
|
+
isObject(input) &&
|
|
18
|
+
hasOwnProperty(input, 'code') &&
|
|
19
|
+
typeof input.code === 'string' &&
|
|
20
|
+
hasOwnProperty(input, 'options') &&
|
|
21
|
+
typeof input.options === 'string'
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function runWorker() {
|
|
26
|
+
if (isMainThread || !parentPort || !isWorkerContextSerialized(workerData)) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
// eslint-disable-next-line no-eval
|
|
32
|
+
const eval2 = eval;
|
|
33
|
+
|
|
34
|
+
const options = eval2(`(${workerData.options})`);
|
|
35
|
+
|
|
36
|
+
const result = await minify(workerData.code, options);
|
|
37
|
+
|
|
38
|
+
const output: WorkerOutput = {
|
|
39
|
+
code: result.code || workerData.code,
|
|
40
|
+
nameCache: options.nameCache
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
parentPort.postMessage(output);
|
|
44
|
+
} catch (e) {
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
}
|
package/types/index.d.ts
CHANGED