UNPKG

isoworker

Version:

Isomorphic workerization with dependencies

448 lines (446 loc) 18 kB
"use strict"; var wk = function (c, msg, transfer, cb) { var w; try { var url = URL.createObjectURL(new Blob([c], { type: 'text/javascript' })); w = new Worker(url); URL.revokeObjectURL(url); } catch (e) { w = new Worker('data:application/javascript;charset=UTF-8,' + encodeURI(c)); } w.postMessage(msg, transfer); w.addEventListener('message', function (ev) { return cb(null, ev.data); }); w.addEventListener('error', function (ev) { return cb(ev.error, null); }); return w; }; wk.t = [ArrayBuffer, MessagePort]; wk.c = [Blob, File, FileList, ImageData]; if (typeof ImageBitmap != 'undefined') wk.t.push(ImageBitmap); if (typeof OffscreenCanvas != 'undefined') wk.t.push(OffscreenCanvas); var node_worker_1 = {}; node_worker_1["default"] = wk; var abvList = [ Int8Array, Uint8Array, Uint8ClampedArray, Int16Array, Uint16Array, Int32Array, Float32Array, Float64Array ]; var setExists = typeof Set != 'undefined'; var mapExists = typeof Map != 'undefined'; if (setExists && !Set.prototype.values) node_worker_1["default"].c.push(Set); if (mapExists && !Map.prototype.entries) node_worker_1["default"].c.push(Map); if (typeof BigInt64Array != 'undefined') abvList.push(BigInt64Array); if (typeof BigUint64Array != 'undefined') abvList.push(BigUint64Array); var rand = function () { return Math.ceil(Math.random() * 1073741823); }; var iwInternalRE = /^__iw(.*)__$/; var getAllPropertyKeys = function (o) { var keys = Object.getOwnPropertyNames(o).filter(function (name) { return !iwInternalRE.test(name); }); if (Object.getOwnPropertySymbols) { keys = keys.concat(Object.getOwnPropertySymbols(o).filter(function (sym) { return !iwInternalRE.test(Symbol.keyFor(sym)); })); } return keys; }; var bannedFunctionKeys = getAllPropertyKeys(Function.prototype).concat('prototype'); var toReplace = '/*r*/null'; var toReplaceRE = /\/\*r\*\/null/; var renderCtx = function (ctx, ab, m) { var out = 'self'; for (var _i = 0, ctx_1 = ctx; _i < ctx_1.length; _i++) { var key = ctx_1[_i]; out += "[" + encoder[typeof key](key, ab, m) + "]"; } return out; }; var vDescriptors = function (v, keys, ab, m, g, s, ctx, dat) { var out = ''; var renderedCtx = renderCtx(ctx, ab, m); for (var _i = 0, keys_1 = keys; _i < keys_1.length; _i++) { var t = keys_1[_i]; var _a = Object.getOwnPropertyDescriptor(v, t), enumerable = _a.enumerable, configurable = _a.configurable, get = _a.get, set = _a.set, writable = _a.writable, value = _a.value; var keyEnc = encoder[typeof t](t, ab, m); var desc = '{', res = void 0; var enc = typeof writable == 'boolean' && encoder[typeof value](value, ab, m, g, s, ctx.concat(t), dat); var replaced = enc == toReplace; var obj = 'v'; if (replaced) { obj = renderedCtx; dat.pop(); } if (enc) { if (writable && configurable && enumerable) res = obj + "[" + keyEnc + "]=" + enc; else desc += "writable:" + writable + ",value:" + enc; } else desc += "get:" + (get ? encoder["function"](get, ab, m, g, s, ctx, dat) : 'void 0') + ",set:" + (set ? encoder["function"](set, ab, m, g, s, ctx, dat) : 'void 0'); if (!res) { desc += ",enumerable:" + enumerable + ",configurable:" + configurable + "}"; res = "Object.defineProperty(" + obj + ", " + encoder[typeof t](t, ab, m) + ", " + desc + ")"; } if (replaced) dat.push([res + ";", value]); else out += ";" + res; } if (Object.isSealed(v)) dat.push("Object.seal(" + renderedCtx + ");"); if (!Object.isExtensible(v)) dat.push("Object.preventExtensions(" + renderedCtx + ");"); if (Object.isFrozen(v)) dat.push("Object.freeze(" + renderedCtx + ");"); return out; }; var encoder = { undefined: function () { return 'void 0'; }, bigint: function (v) { return v + 'n'; }, string: function (v) { return JSON.stringify(v); }, boolean: function (v) { return v + ''; }, number: function (v) { return v + ''; }, symbol: function (v, _, m) { var key = Symbol.keyFor(v); if (key) return "Symbol.for(" + encoder.string(key) + ")"; var gbn = m[v]; if (gbn) return "self[" + gbn + "]"; gbn = m[v] = rand(); return "(self[" + gbn + "]=Symbol(" + encoder.string(v.toString().slice(7, -1)) + "))"; }, "function": function (v, ab, m, g, s, ctx, dat) { var st = v.toString(); var proto = v.prototype; if (st.indexOf('[native code]', 12) != -1) return v.name; if (st[0] != '(' && !proto) { var headMatch = st.match(/^(.+?)(?=\()/g); if (headMatch) st = 'function' + st.slice(headMatch[0].length); else throw new TypeError("failed to find function body in " + st); } var vd = vDescriptors(v, getAllPropertyKeys(v).filter(function (key) { return bannedFunctionKeys.indexOf(key) == -1; }), ab, m, g, s, ctx, dat); var gbn = g(v); if (gbn) return "(function(){var v=" + gbn + vd + ";return v})()"; if (proto) { var superCtr = Object.getPrototypeOf(proto).constructor; // TODO: Avoid duplicating methods for ES6 classes var base = '(function(){'; if (superCtr == Object) st = base + "var v=" + st; else { var superEnc = encoder["function"](superCtr, ab, m, g, s, ctx.concat('prototype'), dat); if (st[0] == 'c') { var superName = st.match(/(?<=^class(.*?)extends(.*?)(\s+))(.+?)(?=(\s*){)/g); if (!superName) throw new TypeError("failed to find superclass in " + st); st = base + "var " + superName[0] + "=" + superEnc + ";var v=" + st; } else st = base + "var v=" + st + ";v.prototype=Object.create(" + superEnc + ")"; } for (var _i = 0, _a = getAllPropertyKeys(proto); _i < _a.length; _i++) { var t = _a[_i]; var val = proto[t]; if (t != 'constructor') { var key = encoder[typeof t](t, ab, m); st += ";v.prototype[" + key + "]=" + encoder[typeof val](val, ab, m, g, s, ctx.concat('prototype', key), dat); } } st += vd + ";return v})()"; } else if (vd.length) st = "(function(){var v=" + st + vd + ";return v})()"; return s(v, st); }, object: function (v, ab, m, g, s, ctx, dat) { if (v == null) return 'null'; var proto = Object.getPrototypeOf(v), ctr = proto.constructor; var cln = 0; if (abvList.indexOf(ctr) != -1) cln = ab.push(v.buffer); else if (node_worker_1["default"].t.indexOf(ctr) != -1) cln = ab.push(v); else if (node_worker_1["default"].c.indexOf(ctr) != -1) cln = 1; if (cln) { dat.push([renderCtx(ctx, ab, m) + "=" + toReplace + ";", v]); return toReplace; } var gbn = g(v); if (gbn) return gbn; var out = '(function(){var v='; var keys = getAllPropertyKeys(v); if (ctr == Object) out += "{}"; else if (ctr == Array) { var arrStr = ''; for (var i = 0; i < v.length; ++i) { if (i in v) { var val = v[i]; arrStr += encoder[typeof val](val, ab, m, g, s, ctx.concat(i), dat); } arrStr += ','; } keys = keys.filter(function (k) { return isNaN(+k) && k != 'length'; }); out += "[" + arrStr.slice(0, -1) + "]"; } else if (setExists && ctr == Set) { var setStr = ''; var getsSets = ''; var it = v.values(); for (var i = 0, v_1 = it.next(); !v_1.done; ++i, v_1 = it.next()) { var dl = dat.length; var sn = "__iwinit" + i + "__"; setStr += encoder[typeof v_1.value](v_1.value, ab, m, g, s, ctx.concat(sn), dat) + ","; if (dat.length != dl) getsSets += ";Object.defineProperty(v,\"" + sn + "\",{get:function(){return i[" + i + "]},set:function(n){v.delete(i[" + i + "]);v.add(i[" + i + "]=n)}})"; } out += "0;var i=[" + setStr.slice(0, -1) + "];v=new Set(i)" + getsSets; } else if (mapExists && ctr == Map) { var mapStr = ''; var getsSets = ''; var it = v.entries(); for (var i = 0, v_2 = it.next(); !v_2.done; ++i, v_2 = it.next()) { var _a = v_2.value, key = _a[0], val = _a[1]; var dl = dat.length; var sn = "__iwinit" + i + "__"; mapStr += "[" + encoder[typeof key](key, ab, m, g, s, ctx.concat(sn, 0), dat) + "," + encoder[typeof val](val, ab, m, g, s, ctx.concat(sn, 1), dat) + "],"; if (dat.length != dl) getsSets += ";Object.defineProperty(v,\"" + sn + "\",{value:{get 0(){return i[" + i + "][0]},set 0(n){v.delete(i[" + i + "][0]);v.set(i[" + i + "][0]=n,i[" + i + "][1])},get 1(){return i[" + i + "][1]},set 1(n){v.set(i[" + i + "][0],i[" + i + "][1]=n)}}})"; } out += "0;var i=[" + mapStr.slice(0, -1) + "];v=new Map(i)" + getsSets; } else if (ctr == Date) out += "new Date(" + v.getTime() + ")"; else out += "Object.create((" + encoder["function"](ctr, ab, m, g, s, ctx.concat('constructor'), dat) + ").prototype)"; return out + vDescriptors(v, keys, ab, m, g, s, ctx, dat) + ';return v})()'; } }; var ccRaw = function (depNames, depValues, env) { var out = ''; var dat = []; var ab = []; var gbnKey = env.r; var getGBN = function (obj) { var gbn = obj[gbnKey]; if (gbn) return "self[" + gbn + "]"; }; var setGBN = function (obj, wrap) { var gbn = rand(); Object.defineProperty(obj, gbnKey, { value: gbn }); return "(self[" + gbn + "]=" + wrap + ")"; }; for (var i = 0; i < depValues.length; ++i) { var key = depNames[i], value = depValues[i]; var v = encoder[typeof value](value, ab, env.s, getGBN, setGBN, [key], dat); var parts = key .replace(/\\/, '') .match(/^(.*?)(?=(\.|\[|$))|\[(.*?)\]|(\.(.*?))(?=(\.|\[|$))/g); var pfx = 'self.' + parts[0]; var chain = pfx; for (var i_1 = 1; i_1 < parts.length; ++i_1) { chain = "(" + chain + "||(" + pfx + "={}))" + parts[i_1]; pfx += parts[i_1]; } out += chain + "=" + v + ";"; } for (var i = 0; i < ab.length; ++i) { var buf = ab[i]; if (buf[gbnKey] == -1) ab.splice(i--, 1); else buf[gbnKey] = -1; } return [out, dat, ab]; }; /** * Creates a context for a worker execution environment * @param depList The dependencies in the worker environment * @param env The environment to use for the worker. If this object was used * before, `isoworker` will use certain optimizations to avoid code * duplication. This is useful for sending a serialized message to * a worker to be dynamically evaluated, in which case you may want * to avoid duplicating existing function declarations. * @returns An environment that can be built to a Worker. Note the fourth * element of the tuple, the global element registry, is currently not useful. */ function createContext(depList, env) { if (env === void 0) { env = {}; } var depNames = []; var depValues = []; if (!env.r) { var base = "__iwgbn" + rand() + "__"; env.r = typeof Symbol == 'undefined' ? base : Symbol["for"](base); } if (!env.s) env.s = {}; for (var _i = 0, _a = depList instanceof Array ? depList : [depList]; _i < _a.length; _i++) { var dl = _a[_i]; var dlStr = dl.toString(); depNames = depNames.concat(dlStr .slice(dlStr.indexOf('[') + 1, dlStr.lastIndexOf(']')) .replace(/\s/g, '') .split(',')); depValues = depValues.concat(dl()); } return ccRaw(depNames, depValues, env); } exports.createContext = createContext; var findTransferables = function (vals) { var tfKeyBase = "__iwtf" + rand() + "__"; var tfKey = typeof Symbol == 'undefined' ? tfKeyBase : Symbol["for"](tfKeyBase); return vals.reduce(function (a, v) { var proto = Object.getPrototypeOf(v), ctr = proto.constructor; if (abvList.indexOf(ctr) != -1) { var buf = v.buffer; if (!buf[tfKey]) { buf[tfKey] = 1; a.push(buf); } } else if (node_worker_1["default"].t.indexOf(ctr) != -1 && !v[tfKey]) { v[tfKey] = 1; a.push(v); } return a; }, []); }; /** * A RegExp that matches the placeholder used in isoworker's initial message code. * Used after loading structured-cloneable data onto the worker thread in order to * finalize the context creation. */ exports.dataPlaceholder = toReplaceRE; var globalEnv = typeof globalThis == 'undefined' ? typeof window == 'undefined' ? typeof self == 'undefined' ? typeof global == 'undefined' ? {} : global : self : window : globalThis; /** * Converts a function with dependencies into a worker * @param fn The function to workerize * @param deps The dependencies to add. This should include sub-dependencies. * For example, if you are workerizing a function that calls * another function, put any dependencies of the other function * here as well. * @param replaceTransfer The list of objects to replace the default transfer * list with. If you provide an array of transferable * items, they will be used; if you provide the value * `true`, `isoworker` will refrain from the default * behavior of automatically transferring everything * it can. * @returns A function that accepts parameters and, as the last argument, a * callback to use when the worker returns. */ function workerize(fn, deps, serializeArgs, replaceTransfer) { var env = {}; var _a = createContext(deps, env), str = _a[0], exec = _a[1], tfl = _a[2]; var currentCb; var runCount = 0; var callCount = 0; var assignStr = ''; var transfer = (replaceTransfer ? replaceTransfer instanceof Array ? replaceTransfer : [] : tfl); var msg = []; for (var _i = 0, exec_1 = exec; _i < exec_1.length; _i++) { var cmd = exec_1[_i]; if (typeof cmd != 'string') { cmd = cmd[0].replace(toReplaceRE, "e.data[" + (msg.push(cmd[1]) - 1) + "]"); } assignStr += cmd; } var fixArgs = serializeArgs ? 'var b=e.data[1];eval(e.data[0]);d=self.__iwargs__;' : ''; var worker = node_worker_1["default"](str + "onmessage=function(e){" + assignStr + "var v=" + fn + ";var _p=function(d){d?typeof d.then=='function'?d.then(_p,_e):postMessage(d,d.__transfer):postMessage(d)};var _e=function(e){!(e instanceof Error)&&(e=new Error(e));postMessage({__iwerr__:{s:e.stack,m:e.message,n:e.name}})};onmessage=function(e){var d=e.data;" + fixArgs + "try{_p(v.apply(self,d))}catch(e){_e(e)}}}", msg, transfer, function (err, res) { ++runCount; var rtErr = res && res.__iwerr__; if (rtErr) { err = new (globalEnv[rtErr.n] || Error)(); err.message = rtErr.m; err.name = rtErr.n; err.stack = rtErr.s; currentCb(err, null); } else if (err) { for (; runCount <= callCount; ++runCount) currentCb(err, res); wfn.close(); } else currentCb(err, res); }); var closed = false; var wfn = function () { var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } var cb = args.pop(); if (typeof cb != 'function') throw new TypeError('no callback provided'); if (closed) { cb(new Error('worker thread closed'), null); return; } var lastCb = currentCb; var startCount = ++callCount; currentCb = function (err, r) { if (runCount == startCount) cb(err, r); else lastCb(err, r); }; if (serializeArgs) { var _a = ccRaw(['__iwargs__'], [args], env), code = _a[0], ext = _a[1], mtfl = _a[2]; var rawMsg = []; for (var _b = 0, ext_1 = ext; _b < ext_1.length; _b++) { var cmd = ext_1[_b]; if (typeof cmd != 'string') { cmd = cmd[0].replace(toReplaceRE, "b[" + (rawMsg.push(cmd[1]) - 1) + "]"); } code += cmd; } worker.postMessage([code, rawMsg], mtfl); } else worker.postMessage(args, findTransferables(args)); }; wfn.close = function () { worker.terminate(); closed = true; }; return wfn; } exports.workerize = workerize;