isoworker
Version:
Isomorphic workerization with dependencies
448 lines (446 loc) • 18 kB
JavaScript
;
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;