Comparing version 0.2.3 to 0.2.4
@@ -44,6 +44,7 @@ var wk = function (c, msg, transfer, cb) { | ||
var rand = function () { return Math.ceil(Math.random() * 1073741823); }; | ||
var iwInternalRE = /^__iw(.*)__$/; | ||
var getAllPropertyKeys = function (o) { | ||
var keys = Object.getOwnPropertyNames(o); | ||
var keys = Object.getOwnPropertyNames(o).filter(function (name) { return !iwInternalRE.test(name); }); | ||
if (Object.getOwnPropertySymbols) { | ||
keys = keys.concat(Object.getOwnPropertySymbols(o)); | ||
keys = keys.concat(Object.getOwnPropertySymbols(o).filter(function (sym) { return !iwInternalRE.test(Symbol.keyFor(sym)); })); | ||
} | ||
@@ -54,3 +55,3 @@ return keys; | ||
var toReplace = '/*r*/null'; | ||
var toReplaceRE = /\/\*r\*\/null/g; | ||
var toReplaceRE = /\/\*r\*\/null/; | ||
var renderCtx = function (ctx, ab, m) { | ||
@@ -234,24 +235,11 @@ var out = 'self'; | ||
else | ||
out += "Object.create(" + encoder["function"](ctr, ab, m, g, s, ctx.concat('constructor'), dat) + ".prototype)"; | ||
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})()'; | ||
} | ||
}; | ||
/** | ||
* Creates a context for a worker execution environment | ||
* @param depList The dependencies in the worker environment | ||
* @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. | ||
*/ | ||
export function createContext(depList) { | ||
var depListStr = depList.toString(); | ||
var depNames = depListStr | ||
.slice(depListStr.indexOf('[') + 1, depListStr.lastIndexOf(']')) | ||
.replace(/\s/g, '') | ||
.split(','); | ||
var depValues = depList(); | ||
var ccRaw = function (depNames, depValues, env) { | ||
var out = ''; | ||
var dat = []; | ||
var ab = []; | ||
var symMap = {}; | ||
var gbnKey = typeof Symbol == 'undefined' ? "__iwgbn" + rand() + "__" : Symbol(); | ||
var gbnKey = env.r; | ||
var getGBN = function (obj) { | ||
@@ -271,3 +259,3 @@ var gbn = obj[gbnKey]; | ||
var key = depNames[i], value = depValues[i]; | ||
var v = encoder[typeof value](value, ab, symMap, getGBN, setGBN, [key], dat); | ||
var v = encoder[typeof value](value, ab, env.s, getGBN, setGBN, [key], dat); | ||
var parts = key | ||
@@ -284,11 +272,57 @@ .replace(/\\/, '') | ||
} | ||
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. | ||
*/ | ||
export 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); | ||
} | ||
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) { | ||
a.push(v.buffer); | ||
var buf = v.buffer; | ||
if (!buf[tfKey]) { | ||
buf[tfKey] = 1; | ||
a.push(buf); | ||
} | ||
} | ||
else if (wk.t.indexOf(ctr) != -1) { | ||
else if (wk.t.indexOf(ctr) != -1 && !v[tfKey]) { | ||
v[tfKey] = 1; | ||
a.push(v); | ||
@@ -299,2 +333,8 @@ } | ||
}; | ||
/** | ||
* 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. | ||
*/ | ||
export var dataPlaceholder = toReplaceRE; | ||
var globalEnv = typeof globalThis == 'undefined' | ||
@@ -325,4 +365,5 @@ ? typeof window == 'undefined' | ||
*/ | ||
export function workerize(fn, deps, replaceTransfer) { | ||
var _a = createContext(deps), str = _a[0], exec = _a[1], tfl = _a[2]; | ||
export function workerize(fn, deps, serializeArgs, replaceTransfer) { | ||
var env = {}; | ||
var _a = createContext(deps, env), str = _a[0], exec = _a[1], tfl = _a[2]; | ||
var currentCb; | ||
@@ -345,12 +386,6 @@ var runCount = 0; | ||
} | ||
if (!replaceTransfer) { | ||
var tfKey = typeof Symbol == 'undefined' ? "__iwtf" + rand() + "__" : Symbol(); | ||
for (var i = 0; i < transfer.length; ++i) { | ||
var buf = transfer[i]; | ||
if (buf[tfKey]) | ||
transfer.splice(i--, 1); | ||
buf[tfKey] = 1; | ||
} | ||
} | ||
var worker = wk(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){try{_p(v.apply(self,e.data))}catch(e){_e(e)}}}", msg, transfer, function (err, res) { | ||
var fixArgs = serializeArgs | ||
? 'var b=e.data[1];eval(e.data[0]);d=self.__iwargs__;' | ||
: ''; | ||
var worker = wk(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; | ||
@@ -395,3 +430,16 @@ var rtErr = res && | ||
}; | ||
worker.postMessage(args, findTransferables(args)); | ||
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)); | ||
}; | ||
@@ -398,0 +446,0 @@ wfn.close = function () { |
@@ -0,11 +1,27 @@ | ||
declare type SymbolMap = Record<symbol, number>; | ||
export declare type Context = [string, Array<string | [string, unknown]>, unknown[]]; | ||
export declare type DepList = () => unknown[]; | ||
export declare type Environment = { | ||
s: SymbolMap; | ||
r: symbol | string; | ||
}; | ||
/** | ||
* 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. | ||
*/ | ||
export declare function createContext(depList: DepList): Context; | ||
export declare function createContext(depList: DepList | DepList[], env?: Partial<Environment>): Context; | ||
/** | ||
* 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. | ||
*/ | ||
export declare const dataPlaceholder: RegExp; | ||
/** | ||
* A workerized function (from arguments and return type) | ||
@@ -36,2 +52,3 @@ */ | ||
*/ | ||
export declare function workerize<TA extends unknown[], TR>(fn: (...args: TA) => TR, deps: DepList, replaceTransfer?: unknown[] | boolean): Workerized<TA, TR>; | ||
export declare function workerize<TA extends unknown[], TR>(fn: (...args: TA) => TR, deps: DepList | DepList[], serializeArgs?: boolean, replaceTransfer?: unknown[] | boolean): Workerized<TA, TR>; | ||
export {}; |
{ | ||
"name": "isoworker", | ||
"version": "0.2.3", | ||
"version": "0.2.4", | ||
"description": "Isomorphic workerization with dependencies", | ||
@@ -5,0 +5,0 @@ "main": "./lib/index.cjs", |
@@ -172,3 +172,3 @@ # isoworker | ||
console.log(err); // null | ||
console.log(result); // Uint8Array(1073741824) [ ... ] | ||
console.log(result); // Uint8Array(268435456) [ ... ] | ||
}); | ||
@@ -185,4 +185,38 @@ getRandomBuffer(2 ** 31, (err, result) => { | ||
Although `isoworker` can handle most dependencies, including objects of user-created classes, certain native classes and objects will not work. Of course, the basic ones (primitives, dates, objects, arrays, sets, maps, etc.) work well, but more advanced types such as `MediaRecorder` and `Audio` cannot be used as dependencies. You'll need to send over information that you used to construct them (for `Audio`, the URL) via function parameters. | ||
One important issue to note is that `isoworker` does NOT serialize the data passed into and out of your functions by default, meaning that custom classes and objects cannot be used as parameters or return values. If you want to use complex arguments, you can use `true` as the third parameter to the `workerize` function. | ||
```js | ||
class CustomClass { | ||
static Y = 10; | ||
x = 1; | ||
constructor(y = 2) { | ||
this.y = y; | ||
} | ||
getX() { | ||
return this.x; | ||
} | ||
} | ||
// The dependency list can be [CustomClass] or [CustomClass.Y] | ||
// With [CustomClass.Y], the worker may initialize more quickly. | ||
const diffStaticYWithXY = workerize(obj => { | ||
return CustomClass.Y - obj.getX() * obj.y; | ||
}, () => [CustomClass.Y], true); // <-- third argument is true | ||
// Now, custom classes work in arguments | ||
diffStaticYWithXY(new CustomClass(), (err, res) => { | ||
console.log(res); // 8 | ||
}); | ||
const cc2 = new CustomClass(3); | ||
cc2.x = 4; | ||
diffStaticYWithXY(cc2, (err, res) => { | ||
console.log(res); // -2 | ||
}); | ||
``` | ||
This isn't the default behavior because it's expensive performance-wise and because dynamic evaluation can break on sites with a tight Content Security Protocol. In addition, note that the return value may never be something that cannot be structured-cloned (besides `Promise`, which is automatically resolved). | ||
Although `isoworker` can handle most dependencies, including objects of user-created classes, certain native classes and objects will not work. Of course, the basic ones (primitives, dates, objects, arrays, sets, maps, etc.) work well, but more advanced types such as `MediaRecorder` and `Audio` cannot be used as dependencies. You'll need to send over information that you used to construct them (for `Audio`, the URL) via function parameters. Additionally, any custom class using a private field (denoted by a `#` prefix, e.g. `#someKey`) will not have access to the private field post-workerization because finding the value for that field at runtime is not possible. | ||
Another point to note is that much of the package is based off of elaborate (but fallible) pseudo-parsers for stringified functions. In other words, if you try to break things, you can. However, as long as you don't do something like this: | ||
@@ -189,0 +223,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
104816
2275
234