UNPKG

threads

Version:

Web workers & worker threads as simple as a function call

252 lines (251 loc) 10.5 kB
"use strict"; /// <reference lib="dom" /> // tslint:disable function-constructor no-eval no-duplicate-super max-classes-per-file var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.isWorkerRuntime = exports.getWorkerImplementation = exports.defaultPoolSize = void 0; const callsites_1 = __importDefault(require("callsites")); const events_1 = require("events"); const os_1 = require("os"); const path = __importStar(require("path")); const url_1 = require("url"); let tsNodeAvailable; exports.defaultPoolSize = os_1.cpus().length; function detectTsNode() { if (typeof __non_webpack_require__ === "function") { // Webpack build: => No ts-node required or possible return false; } if (tsNodeAvailable) { return tsNodeAvailable; } try { eval("require").resolve("ts-node"); tsNodeAvailable = true; } catch (error) { if (error && error.code === "MODULE_NOT_FOUND") { tsNodeAvailable = false; } else { // Re-throw throw error; } } return tsNodeAvailable; } function createTsNodeModule(scriptPath) { const content = ` require("ts-node/register/transpile-only"); require(${JSON.stringify(scriptPath)}); `; return content; } function rebaseScriptPath(scriptPath, ignoreRegex) { const parentCallSite = callsites_1.default().find((callsite) => { const filename = callsite.getFileName(); return Boolean(filename && !filename.match(ignoreRegex) && !filename.match(/[\/\\]master[\/\\]implementation/) && !filename.match(/^internal\/process/)); }); const rawCallerPath = parentCallSite ? parentCallSite.getFileName() : null; let callerPath = rawCallerPath ? rawCallerPath : null; if (callerPath && callerPath.startsWith('file:')) { callerPath = url_1.fileURLToPath(callerPath); } const rebasedScriptPath = callerPath ? path.join(path.dirname(callerPath), scriptPath) : scriptPath; return rebasedScriptPath; } function resolveScriptPath(scriptPath, baseURL) { const makeRelative = (filePath) => { // eval() hack is also webpack-related return path.isAbsolute(filePath) ? filePath : path.join(baseURL || eval("__dirname"), filePath); }; const workerFilePath = typeof __non_webpack_require__ === "function" ? __non_webpack_require__.resolve(makeRelative(scriptPath)) : eval("require").resolve(makeRelative(rebaseScriptPath(scriptPath, /[\/\\]worker_threads[\/\\]/))); return workerFilePath; } function initWorkerThreadsWorker() { // Webpack hack const NativeWorker = typeof __non_webpack_require__ === "function" ? __non_webpack_require__("worker_threads").Worker : eval("require")("worker_threads").Worker; let allWorkers = []; class Worker extends NativeWorker { constructor(scriptPath, options) { const resolvedScriptPath = options && options.fromSource ? null : resolveScriptPath(scriptPath, (options || {})._baseURL); if (!resolvedScriptPath) { // `options.fromSource` is true const sourceCode = scriptPath; super(sourceCode, Object.assign(Object.assign({}, options), { eval: true })); } else if (resolvedScriptPath.match(/\.tsx?$/i) && detectTsNode()) { super(createTsNodeModule(resolvedScriptPath), Object.assign(Object.assign({}, options), { eval: true })); } else if (resolvedScriptPath.match(/\.asar[\/\\]/)) { // See <https://github.com/andywer/threads-plugin/issues/17> super(resolvedScriptPath.replace(/\.asar([\/\\])/, ".asar.unpacked$1"), options); } else { super(resolvedScriptPath, options); } this.mappedEventListeners = new WeakMap(); allWorkers.push(this); } addEventListener(eventName, rawListener) { const listener = (message) => { rawListener({ data: message }); }; this.mappedEventListeners.set(rawListener, listener); this.on(eventName, listener); } removeEventListener(eventName, rawListener) { const listener = this.mappedEventListeners.get(rawListener) || rawListener; this.off(eventName, listener); } } const terminateWorkersAndMaster = () => { // we should terminate all workers and then gracefully shutdown self process Promise.all(allWorkers.map(worker => worker.terminate())).then(() => process.exit(0), () => process.exit(1)); allWorkers = []; }; // Take care to not leave orphaned processes behind. See #147. process.on("SIGINT", () => terminateWorkersAndMaster()); process.on("SIGTERM", () => terminateWorkersAndMaster()); class BlobWorker extends Worker { constructor(blob, options) { super(Buffer.from(blob).toString("utf-8"), Object.assign(Object.assign({}, options), { fromSource: true })); } static fromText(source, options) { return new Worker(source, Object.assign(Object.assign({}, options), { fromSource: true })); } } return { blob: BlobWorker, default: Worker }; } function initTinyWorker() { const TinyWorker = require("tiny-worker"); let allWorkers = []; class Worker extends TinyWorker { constructor(scriptPath, options) { // Need to apply a work-around for Windows or it will choke upon the absolute path // (`Error [ERR_INVALID_PROTOCOL]: Protocol 'c:' not supported`) const resolvedScriptPath = options && options.fromSource ? null : process.platform === "win32" ? `file:///${resolveScriptPath(scriptPath).replace(/\\/g, "/")}` : resolveScriptPath(scriptPath); if (!resolvedScriptPath) { // `options.fromSource` is true const sourceCode = scriptPath; super(new Function(sourceCode), [], { esm: true }); } else if (resolvedScriptPath.match(/\.tsx?$/i) && detectTsNode()) { super(new Function(createTsNodeModule(resolveScriptPath(scriptPath))), [], { esm: true }); } else if (resolvedScriptPath.match(/\.asar[\/\\]/)) { // See <https://github.com/andywer/threads-plugin/issues/17> super(resolvedScriptPath.replace(/\.asar([\/\\])/, ".asar.unpacked$1"), [], { esm: true }); } else { super(resolvedScriptPath, [], { esm: true }); } allWorkers.push(this); this.emitter = new events_1.EventEmitter(); this.onerror = (error) => this.emitter.emit("error", error); this.onmessage = (message) => this.emitter.emit("message", message); } addEventListener(eventName, listener) { this.emitter.addListener(eventName, listener); } removeEventListener(eventName, listener) { this.emitter.removeListener(eventName, listener); } terminate() { allWorkers = allWorkers.filter(worker => worker !== this); return super.terminate(); } } const terminateWorkersAndMaster = () => { // we should terminate all workers and then gracefully shutdown self process Promise.all(allWorkers.map(worker => worker.terminate())).then(() => process.exit(0), () => process.exit(1)); allWorkers = []; }; // Take care to not leave orphaned processes behind // See <https://github.com/avoidwork/tiny-worker#faq> process.on("SIGINT", () => terminateWorkersAndMaster()); process.on("SIGTERM", () => terminateWorkersAndMaster()); class BlobWorker extends Worker { constructor(blob, options) { super(Buffer.from(blob).toString("utf-8"), Object.assign(Object.assign({}, options), { fromSource: true })); } static fromText(source, options) { return new Worker(source, Object.assign(Object.assign({}, options), { fromSource: true })); } } return { blob: BlobWorker, default: Worker }; } let implementation; let isTinyWorker; function selectWorkerImplementation() { try { isTinyWorker = false; return initWorkerThreadsWorker(); } catch (error) { // tslint:disable-next-line no-console console.debug("Node worker_threads not available. Trying to fall back to tiny-worker polyfill..."); isTinyWorker = true; return initTinyWorker(); } } function getWorkerImplementation() { if (!implementation) { implementation = selectWorkerImplementation(); } return implementation; } exports.getWorkerImplementation = getWorkerImplementation; function isWorkerRuntime() { if (isTinyWorker) { return typeof self !== "undefined" && self.postMessage ? true : false; } else { // Webpack hack const isMainThread = typeof __non_webpack_require__ === "function" ? __non_webpack_require__("worker_threads").isMainThread : eval("require")("worker_threads").isMainThread; return !isMainThread; } } exports.isWorkerRuntime = isWorkerRuntime;