mirror of
https://github.com/react/react-native-devtools-frontend.git
synced 2026-09-29 05:34:21 +08:00
Since we have the major version jump, we needed to update quite
a few things to make this work:
1. rollup4 ships with native binaries. We can't check them into
our node_modules for all platforms. Luckily they also ship a
a wasm version. This requires us to adjust the 'binary' path
to `node_modules/@rollup/wasm-node/dist/bin/rollup` instead.
2. rollup4 is more strict when it comes to ES modules vs
CommonJS. We adjust this by renaming the rollup config files.
3. Some plugins got deprecated so we had to replace them with
the newer versions (source maps and terser).
Bug: None
Change-Id: If13d590c8185db6ad85b3992d0a869111909dc8c
Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/5999255
Commit-Queue: Simon Zünd <szuend@chromium.org>
Reviewed-by: Alex Rudenko <alexrudenko@chromium.org>
227 lines
7.5 KiB
JavaScript
227 lines
7.5 KiB
JavaScript
import { isMainThread, parentPort, workerData, Worker } from 'worker_threads';
|
|
import { isObject, hasOwnProperty, merge } from 'smob';
|
|
import { minify } from 'terser';
|
|
import { fileURLToPath } from 'url';
|
|
import { AsyncResource } from 'async_hooks';
|
|
import { cpus } from 'os';
|
|
import { EventEmitter } from 'events';
|
|
import serializeJavascript from 'serialize-javascript';
|
|
|
|
const taskInfo = Symbol('taskInfo');
|
|
const freeWorker = Symbol('freeWorker');
|
|
const workerPoolWorkerFlag = 'WorkerPoolWorker';
|
|
|
|
/**
|
|
* Duck typing worker context.
|
|
*
|
|
* @param input
|
|
*/
|
|
function isWorkerContextSerialized(input) {
|
|
return (isObject(input) &&
|
|
hasOwnProperty(input, 'code') &&
|
|
typeof input.code === 'string' &&
|
|
hasOwnProperty(input, 'options') &&
|
|
typeof input.options === 'string');
|
|
}
|
|
function runWorker() {
|
|
if (isMainThread || !parentPort || workerData !== workerPoolWorkerFlag) {
|
|
return;
|
|
}
|
|
// eslint-disable-next-line no-eval
|
|
const eval2 = eval;
|
|
parentPort.on('message', async (data) => {
|
|
if (!isWorkerContextSerialized(data)) {
|
|
return;
|
|
}
|
|
const options = eval2(`(${data.options})`);
|
|
const result = await minify(data.code, options);
|
|
const output = {
|
|
code: result.code || data.code,
|
|
nameCache: options.nameCache
|
|
};
|
|
if (typeof result.map === 'string') {
|
|
output.sourceMap = JSON.parse(result.map);
|
|
}
|
|
if (isObject(result.map)) {
|
|
output.sourceMap = result.map;
|
|
}
|
|
parentPort === null || parentPort === void 0 ? void 0 : parentPort.postMessage(output);
|
|
});
|
|
}
|
|
|
|
class WorkerPoolTaskInfo extends AsyncResource {
|
|
constructor(callback) {
|
|
super('WorkerPoolTaskInfo');
|
|
this.callback = callback;
|
|
}
|
|
done(err, result) {
|
|
this.runInAsyncScope(this.callback, null, err, result);
|
|
this.emitDestroy();
|
|
}
|
|
}
|
|
class WorkerPool extends EventEmitter {
|
|
constructor(options) {
|
|
super();
|
|
this.tasks = [];
|
|
this.workers = [];
|
|
this.freeWorkers = [];
|
|
this.maxInstances = options.maxWorkers || cpus().length;
|
|
this.filePath = options.filePath;
|
|
this.on(freeWorker, () => {
|
|
if (this.tasks.length > 0) {
|
|
const { context, cb } = this.tasks.shift();
|
|
this.runTask(context, cb);
|
|
}
|
|
});
|
|
}
|
|
get numWorkers() {
|
|
return this.workers.length;
|
|
}
|
|
addAsync(context) {
|
|
return new Promise((resolve, reject) => {
|
|
this.runTask(context, (err, output) => {
|
|
if (err) {
|
|
reject(err);
|
|
return;
|
|
}
|
|
if (!output) {
|
|
reject(new Error('The output is empty'));
|
|
return;
|
|
}
|
|
resolve(output);
|
|
});
|
|
});
|
|
}
|
|
close() {
|
|
for (let i = 0; i < this.workers.length; i++) {
|
|
const worker = this.workers[i];
|
|
worker.terminate();
|
|
}
|
|
}
|
|
addNewWorker() {
|
|
const worker = new Worker(this.filePath, {
|
|
workerData: workerPoolWorkerFlag
|
|
});
|
|
worker.on('message', (result) => {
|
|
var _a;
|
|
(_a = worker[taskInfo]) === null || _a === void 0 ? void 0 : _a.done(null, result);
|
|
worker[taskInfo] = null;
|
|
this.freeWorkers.push(worker);
|
|
this.emit(freeWorker);
|
|
});
|
|
worker.on('error', (err) => {
|
|
if (worker[taskInfo]) {
|
|
worker[taskInfo].done(err, null);
|
|
}
|
|
else {
|
|
this.emit('error', err);
|
|
}
|
|
this.workers.splice(this.workers.indexOf(worker), 1);
|
|
this.addNewWorker();
|
|
});
|
|
this.workers.push(worker);
|
|
this.freeWorkers.push(worker);
|
|
this.emit(freeWorker);
|
|
}
|
|
runTask(context, cb) {
|
|
if (this.freeWorkers.length === 0) {
|
|
this.tasks.push({ context, cb });
|
|
if (this.numWorkers < this.maxInstances) {
|
|
this.addNewWorker();
|
|
}
|
|
return;
|
|
}
|
|
const worker = this.freeWorkers.pop();
|
|
if (worker) {
|
|
worker[taskInfo] = new WorkerPoolTaskInfo(cb);
|
|
worker.postMessage({
|
|
code: context.code,
|
|
options: serializeJavascript(context.options)
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
function terser(input = {}) {
|
|
const { maxWorkers, ...options } = input;
|
|
let workerPool;
|
|
let numOfChunks = 0;
|
|
let numOfWorkersUsed = 0;
|
|
return {
|
|
name: 'terser',
|
|
async renderChunk(code, chunk, outputOptions) {
|
|
if (!workerPool) {
|
|
workerPool = new WorkerPool({
|
|
filePath: fileURLToPath(import.meta.url),
|
|
maxWorkers
|
|
});
|
|
}
|
|
numOfChunks += 1;
|
|
const defaultOptions = {
|
|
sourceMap: outputOptions.sourcemap === true || typeof outputOptions.sourcemap === 'string'
|
|
};
|
|
if (outputOptions.format === 'es') {
|
|
defaultOptions.module = true;
|
|
}
|
|
if (outputOptions.format === 'cjs') {
|
|
defaultOptions.toplevel = true;
|
|
}
|
|
try {
|
|
const { code: result, nameCache, sourceMap } = await workerPool.addAsync({
|
|
code,
|
|
options: merge({}, options || {}, defaultOptions)
|
|
});
|
|
if (options.nameCache && nameCache) {
|
|
let vars = {
|
|
props: {}
|
|
};
|
|
if (hasOwnProperty(options.nameCache, 'vars') && isObject(options.nameCache.vars)) {
|
|
vars = merge({}, options.nameCache.vars || {}, vars);
|
|
}
|
|
if (hasOwnProperty(nameCache, 'vars') && isObject(nameCache.vars)) {
|
|
vars = merge({}, nameCache.vars, vars);
|
|
}
|
|
// eslint-disable-next-line no-param-reassign
|
|
options.nameCache.vars = vars;
|
|
let props = {};
|
|
if (hasOwnProperty(options.nameCache, 'props') && isObject(options.nameCache.props)) {
|
|
// eslint-disable-next-line prefer-destructuring
|
|
props = options.nameCache.props;
|
|
}
|
|
if (hasOwnProperty(nameCache, 'props') && isObject(nameCache.props)) {
|
|
props = merge({}, nameCache.props, props);
|
|
}
|
|
// eslint-disable-next-line no-param-reassign
|
|
options.nameCache.props = props;
|
|
}
|
|
if ((!!defaultOptions.sourceMap || !!options.sourceMap) && isObject(sourceMap)) {
|
|
return {
|
|
code: result,
|
|
map: sourceMap
|
|
};
|
|
}
|
|
return result;
|
|
}
|
|
catch (e) {
|
|
return Promise.reject(e);
|
|
}
|
|
finally {
|
|
numOfChunks -= 1;
|
|
if (numOfChunks === 0) {
|
|
numOfWorkersUsed = workerPool.numWorkers;
|
|
workerPool.close();
|
|
workerPool = null;
|
|
}
|
|
}
|
|
},
|
|
get numOfWorkersUsed() {
|
|
return numOfWorkersUsed;
|
|
}
|
|
};
|
|
}
|
|
|
|
runWorker();
|
|
|
|
export { terser as default };
|
|
//# sourceMappingURL=index.js.map
|