Merge pull request #616 from lowdefy/fix-remove-js-wasm

Remove deprecated _js wasm
This commit is contained in:
Sam 2021-06-07 10:08:04 +02:00 committed by GitHub
commit cd967c46b3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 17 additions and 685 deletions

10
.pnp.cjs generated
View File

@ -4973,7 +4973,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
["jest", "npm:26.6.3"],
["js-yaml", "npm:4.1.0"],
["mingo", "npm:4.1.2"],
["quickjs-emscripten", "npm:0.11.0"],
["uuid", "npm:8.3.2"]
],
"linkType": "SOFT",
@ -22219,15 +22218,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
"linkType": "HARD",
}]
]],
["quickjs-emscripten", [
["npm:0.11.0", {
"packageLocation": "./.yarn/cache/quickjs-emscripten-npm-0.11.0-4f09eb00c3-a5cf45a1fd.zip/node_modules/quickjs-emscripten/",
"packageDependencies": [
["quickjs-emscripten", "npm:0.11.0"]
],
"linkType": "HARD",
}]
]],
["raf", [
["npm:3.4.1", {
"packageLocation": "./.yarn/cache/raf-npm-3.4.1-c25d48d76e-567b0160be.zip/node_modules/raf/",

View File

@ -36,20 +36,6 @@ _ref:
- A list of arguments can be passed to the JavaScript function which will be spread as function parameters.
- The returned value of the custom JavaScript function will be the operator result.
-----------
> DEPRECATION WARNING: The QuickJS JavaScript operators are depreciated and will be removed in the next version. Instead native browser functions can now be loaded at build time by registering the operator function with `window.lowdefy.registerJsOperator(name: string, operatorFunction: function)`. See the [Custom Code](/custom-code) section for details.
For depreciated `evaluate`, and `function`, the following applies to the JavaScript function definitions:
- The `code` operator argument requires a function definition.
- Function arguments can be used inside the function, and are passed via the `args` operator argument as a array.
- A primitive or JSON result will be returned, so the function result must be JSON serializable.
- Only a limited subset of the browser JavaScript APIs are available, in particular the JavaScript inside the VM will not have network access.
- Dependencies and file imports are not supported.
- The function should be a pure function with no side effects. The function should be stateless, and should always return the same result if given the same input.
> The JavaScript function can be passed to the `code` argument during build using the `_ref.eval` method. See the examples below and the [`_ref`](/_ref) operator for more details.
methods:
- name: '{{ method_name }}'
types: |
@ -201,232 +187,3 @@ _ref:
label:
rotate: 0
````
- name: evaluate [DEPRECATED]
types: |
```
({
code: string,
args?: any[]
}): any
```
description: |
The `_js.evaluate` method evaluates JavaScript, takes an array of `args` as input for parameters, and returns the evaluated result.
A JavaScript function string must be provided to the `code` argument.
arguments: |
###### code
The JavaScript function as a string to evaluate.
###### args
An array of input arguments to pass to the JavaScript function in the order in which the function arguments are defined.
examples: |
###### JavaScript evaluate a inline function:
```yaml
_js.evaluate:
code: |
function makePrimes(to) {
return [...Array(to-1).keys()].map(i=>i+2).filter(n =>
[...Array(n-2).keys()].map(i=>i+2).reduce((acc,x)=> acc && n % x !== 0, true)
}
args:
- 50
```
Returns:
```
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47]
```
###### JavaScript evaluate loaded from a JavaScript file:
Operator definition:
```yaml
_js.evaluate:
code:
_ref:
eval: myMath/makePrimes.js
args:
- 50
```
JavaScript loaded from a file using the `_ref.eval` operator method:
```js
// myMath/makePrimes.js
function makePrimes(to) {
return [...Array(to-1).keys()].map(i=>i+2).filter(n =>
[...Array(n-2).keys()].map(i=>i+2).reduce((acc,x)=> acc && n % x !== 0, true)
}
module.exports = makePrimes;
```
Returns:
```
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47]
```
- name: function [DEPRECATED]
types: |
```
({
code: string,
}): any
```
description: |
The `_js.function` method returns a JavaScript function. The JavaScript function definition is be provided as a string to the `code` operator argument.
arguments: |
###### code
The JavaScript function as a string to evaluate.
examples: |
###### _js.function to create a label.formatter function for an EChart block:
```yaml
id: chart
type: EChart
properties:
height: 600
option:
series:
- radius:
- '15%'
- '80%'
type: 'sunburst'
sort: null
emphasis:
focus: 'ancestor'
data:
- value: 8,
children:
- value: 4,
children:
- value: 2
- value: 1
- value: 1
- value: 0.5
- value: 2
- value: 4
children:
- children:
- value: 2
- value: 4
children:
- children:
- value: 2
- value: 3
children:
- children:
- value: 1
label:
color: '#000'
textBorderColor: '#fff'
textBorderWidth: 2
formatter:
_js.function:
code: |
function (param) {
var depth = param.treePathInfo.length;
if (depth === 2) {
return 'radial';
}
else if (depth === 3) {
return 'tangential';
}
else if (depth === 4) {
return '0';
}
}
levels:
- {}
- itemStyle:
color: '#CD4949'
label:
rotate: 'radial'
- itemStyle:
color: '#F47251'
label:
rotate: 'tangential'
- itemStyle:
color: '#FFC75F'
label:
rotate: 0
```
###### _js.function to create a label.formatter function for an EChart block loaded using the `_ref.eval` operator:
The chart config:
```yaml
id: chart
type: EChart
properties:
height: 600
option:
series:
- radius:
- '15%'
- '80%'
type: 'sunburst'
sort: null
emphasis:
focus: 'ancestor'
data:
- value: 8,
children:
- value: 4,
children:
- value: 2
- value: 1
- value: 1
- value: 0.5
- value: 2
- value: 4
children:
- children:
- value: 2
- value: 4
children:
- children:
- value: 2
- value: 3
children:
- children:
- value: 1
label:
color: '#000'
textBorderColor: '#fff'
textBorderWidth: 2
formatter:
_js.function:
code:
_ref:
eval: 'foo/fooFormatter.js'
levels:
- {}
- itemStyle:
color: '#CD4949'
label:
rotate: 'radial'
- itemStyle:
color: '#F47251'
label:
rotate: 'tangential'
- itemStyle:
color: '#FFC75F'
label:
rotate: 0
```
JavaScript loaded from a file using the `_ref.eval` operator method:
```js
// foo/fooFormatter.js
function fooFormatter(param) {
var depth = param.treePathInfo.length;
if (depth === 2) {
return 'radial';
}
else if (depth === 3) {
return 'tangential';
}
else if (depth === 4) {
return '0';
}
}
module.exports = fooFormatter;
```

View File

@ -42,7 +42,6 @@
"deep-diff": "1.0.2",
"js-yaml": "4.1.0",
"mingo": "4.1.2",
"quickjs-emscripten": "0.11.0",
"uuid": "8.3.2"
},
"devDependencies": {

View File

@ -16,126 +16,14 @@
import { type } from '@lowdefy/helpers';
// ! ---------------
// ! DEPRECATED
// ! ---------------
import { serializer } from '@lowdefy/helpers';
import { getQuickJS, shouldInterruptAfterDeadline } from 'quickjs-emscripten';
let QuickJsVm;
function createFunction({ params, location, methodName }) {
if (!params.code || !type.isString(params.code)) {
throw new Error(
`Operator Error: _js.${methodName} "code" argument should be a string at ${location}.`
);
}
const fn = (...args) => {
const jsFnString = `
var logs = [];
function log(...item) {
logs.push(item);
}
var console = {
log,
};
function dateReviver(key, value) {
if (typeof value === 'object' && value !== null && value.hasOwnProperty('_date')) {
var date = new Date(value._date);
if (date instanceof Date && !Number.isNaN(date.getTime())) {
return date;
}
}
return value;
}
function dateSerialize(key, value) {
if (typeof value === 'object' && value !== null) {
Object.keys(value).forEach((k) => {
if (value[k] instanceof Date && !Number.isNaN(value[k].getTime())) {
value[k] = { "_date": value[k].valueOf() };
}
})
}
return value;
}
var args = JSON.parse(decodeURIComponent("${encodeURIComponent(
serializer.serializeToString(args)
)}"), dateReviver);
var fnResult = (${params.code})(...(args || []));
var result = encodeURIComponent(JSON.stringify([fnResult, logs], dateSerialize));
`;
const codeHandle = QuickJsVm.unwrapResult(
QuickJsVm.evalCode(jsFnString, {
shouldInterrupt: shouldInterruptAfterDeadline(Date.now() + 1000),
memoryLimitBytes: 1024 * 1024,
})
);
const resultHandle = QuickJsVm.getProp(QuickJsVm.global, 'result');
codeHandle.dispose();
const result = serializer.deserializeFromString(
decodeURIComponent(QuickJsVm.getString(resultHandle))
);
result[1].forEach((item) => {
console.log(...item);
});
return result[0];
};
return fn;
}
function evaluate({ params, location, methodName }) {
if (!type.isArray(params.args) && !type.isNone(params.args)) {
throw new Error(
`Operator Error: _js.evaluate "args" argument should be an array, null or undefined at ${location}.`
);
}
const fn = createFunction({ params, location, methodName });
return fn(...(params.args || []));
}
const methods = { evaluate, function: createFunction };
function _DEPRECATED_js({ params, location, methodName }) {
if (!QuickJsVm) {
throw new Error(
`Operator Error: _js is not initialized. Received: ${JSON.stringify(params)} at ${location}.`
);
}
return methods[methodName]({ params, location, methodName });
}
async function init() {
const QuickJs = await getQuickJS();
QuickJsVm = QuickJs.createVm();
}
async function clear() {
QuickJsVm = null;
}
_js.init = init;
_js.clear = clear;
// ! ---------------
function _js({ context, params, location, methodName }) {
// ! DEPRECATED methods
if (!type.isFunction(context.lowdefy.imports.jsOperators[methodName]) && !methods[methodName]) {
if (!type.isFunction(context.lowdefy.imports.jsOperators[methodName])) {
throw new Error(`Operator Error: _js.${methodName} is not a function.`);
}
if (context.lowdefy.imports.jsOperators[methodName]) {
if (!type.isNone(params) && !type.isArray(params)) {
throw new Error(`Operator Error: _js.${methodName} takes an array as input at ${location}.`);
}
return context.lowdefy.imports.jsOperators[methodName](...(params || []));
if (!type.isNone(params) && !type.isArray(params)) {
throw new Error(`Operator Error: _js.${methodName} takes an array as input at ${location}.`);
}
// ! DEPRECATED ---------------
console.warn(
'WARNING: _js.evaluate and _js.function will has been deprecated and will be removed in the next version. Please see: https://docs.lowdefy.com/_js for more details.'
);
if (!type.isObject(params)) {
throw new Error(`Operator Error: _js.${methodName} takes an object as input at ${location}.`);
}
return _DEPRECATED_js({ params, location, methodName });
// ! ---------------
return context.lowdefy.imports.jsOperators[methodName](...(params || []));
}
export default _js;

View File

@ -499,19 +499,18 @@ describe('parse operators', () => {
expect(errors).toEqual([]);
});
test('parse _js operator', async () => {
test.only('parse _js operator retuning a function', async () => {
const test_fn = () => (a, b) => a + b;
const mockFn = jest.fn().mockImplementation(test_fn);
context.lowdefy.imports.jsOperators.test_fn = mockFn;
const input = {
'_js.function': {
code: `function (a, b){
return a + b;
}`,
},
'_js.test_fn': [],
};
const parser = new WebParser({ context, contexts });
await parser.init();
const { output, errors } = parser.parse({ input, location: 'locationId' });
expect(output).toBeInstanceOf(Function);
expect(output(1, 2)).toEqual(3);
expect(output(4, 2)).toEqual(6);
expect(errors).toEqual([]);
});

View File

@ -18,9 +18,6 @@ import _js from '../../src/web/js';
import { context } from '../testContext';
const location = 'location';
beforeAll(async () => {
await _js.init();
});
test('_js.test_fn and params to return a value', () => {
const params = [12, 14];
@ -59,302 +56,12 @@ test('_js.test_fn params not an array', () => {
);
});
// ! --------------
// ! DEPRECATED
// ! --------------
test('_js with code and args specified', () => {
const params = {
code: `function (one, two) {
return one + two;
}`,
args: [12, 14],
};
const fn = _js({ context, location, params, methodName: 'function' });
expect(fn).toBeInstanceOf(Function);
expect(fn(1, 2)).toEqual(3);
expect(_js({ context, location, params, methodName: 'evaluate' })).toEqual(26);
expect(_js({ context, location, params: { code: params.code }, methodName: 'evaluate' })).toEqual(
null
test('_js.not_a_function', () => {
const params = 10;
const test_fn = (a, b) => a + b;
const mockFn = jest.fn().mockImplementation(test_fn);
context.lowdefy.imports.jsOperators.test_fn = mockFn;
expect(() => _js({ context, location, params, methodName: 'not_a_function' })).toThrow(
new Error('Operator Error: _js.not_a_function is not a function.')
);
});
test('_js with code and args specified to return json object', () => {
const params = {
code: `function (one, two) {
return { a: one, b: two, c: [1,2,3, one, two, "three"]};
}`,
args: [12, 14],
};
const fn = _js({ context, location, params, methodName: 'function' });
expect(fn).toBeInstanceOf(Function);
expect(fn(1, 2)).toEqual({ a: 1, b: 2, c: [1, 2, 3, 1, 2, 'three'] });
expect(_js({ context, location, params, methodName: 'evaluate' })).toEqual({
a: 12,
b: 14,
c: [1, 2, 3, 12, 14, 'three'],
});
expect(_js({ context, location, params: { code: params.code }, methodName: 'evaluate' })).toEqual(
{
c: [1, 2, 3, null, null, 'three'],
}
);
});
test('_js with code and args specified to return json array', () => {
const params = {
code: `function (one, two) {
return [1,2,3, one, two, "three"];
}`,
args: [12, 14],
};
const fn = _js({ context, location, params, methodName: 'function' });
expect(fn).toBeInstanceOf(Function);
expect(fn(1, 2)).toEqual([1, 2, 3, 1, 2, 'three']);
expect(_js({ context, location, params, methodName: 'evaluate' })).toEqual([
1,
2,
3,
12,
14,
'three',
]);
expect(_js({ context, location, params: { code: params.code }, methodName: 'evaluate' })).toEqual(
[1, 2, 3, null, null, 'three']
);
});
test('_js with open "\'" in result', () => {
const str = `<div style=\"test: one;\">one's result</div>`;
const params = {
code: `function chars(input) {
return [{x: input, b: 1}];
}`,
args: [str],
};
const fn = _js({ context, location, params, methodName: 'function' });
expect(fn).toBeInstanceOf(Function);
expect(fn(str)).toEqual([{ x: str, b: 1 }]);
expect(_js({ context, location, params, methodName: 'evaluate' })).toEqual([{ x: str, b: 1 }]);
});
test('_js with date in input and result', () => {
const params = {
code: `function duration(from, to) {
return {
from,
to,
fromIsDate: from instanceof Date,
toIsDate: from instanceof Date,
duration: to - from,
};
}`,
args: [new Date(0), new Date(10)],
};
const fn = _js({ context, location, params, methodName: 'function' });
expect(fn).toBeInstanceOf(Function);
expect(_js({ context, location, params, methodName: 'evaluate' })).toEqual({
from: new Date(0),
to: new Date(10),
duration: 10,
fromIsDate: true,
toIsDate: true,
});
expect(fn(new Date(0), new Date(10))).toEqual({
from: new Date(0),
to: new Date(10),
duration: 10,
fromIsDate: true,
toIsDate: true,
});
});
test('_js with undefined result returns null', () => {
const params = {
code: `function add(one, two) {
return;
}`,
args: [12, 14],
};
const fn = _js({ context, location, params, methodName: 'function' });
expect(fn).toBeInstanceOf(Function);
expect(fn()).toEqual(null);
expect(_js({ context, location, params, methodName: 'evaluate' })).toEqual(null);
expect(_js({ context, location, params: { code: params.code }, methodName: 'evaluate' })).toEqual(
null
);
});
test('_js with console.log', () => {
const logger = console.log;
console.log = jest.fn();
const params = {
code: `function logTest(one, two) {
console.log(one)
console.log(two)
console.log(one, two, null, 123, 'abc', true, false);
return;
}`,
args: [12, new Date(1)],
};
const fn = _js({ context, location, params, methodName: 'function' });
expect(fn).toBeInstanceOf(Function);
_js({ context, location, params, methodName: 'evaluate' });
expect(console.log.mock.calls).toEqual([
[12],
[new Date(1)],
[12, new Date(1), null, 123, 'abc', true, false],
]);
console.log = logger;
});
test('_js with code with args that needs escaped characters', () => {
const params = {
code: `function (obj) {
return "<div>" + obj.html + "</html>";
}`,
args: [
{
html: '<a href="https://lowdefy.com">Lowdefy Website</a>',
},
],
};
expect(_js({ context, location, params, methodName: 'evaluate' })).toEqual(
'<div><a href="https://lowdefy.com">Lowdefy Website</a></html>'
);
const fn = _js({ context, location, params, methodName: 'function' });
expect(fn).toBeInstanceOf(Function);
expect(
fn({
html: '<a href="https://lowdefy.com">Lowdefy Website</a>',
})
).toEqual('<div><a href="https://lowdefy.com">Lowdefy Website</a></html>');
});
test('_js with code and no "function" specified to throw', () => {
const params = {
code: `
var value = "world";
var result = 'a new ' + 'vm for Hello ' + value;
`,
};
expect(() =>
_js({ context, location, params, methodName: 'evaluate' })
).toThrowErrorMatchingInlineSnapshot(`"unexpected token in expression: 'var'"`);
expect(() => {
const fn = _js({ context, location, params, methodName: 'function' });
fn();
}).toThrowErrorMatchingInlineSnapshot(`"unexpected token in expression: 'var'"`);
});
test('_js invalid js code', () => {
const params = {
code: 'Hello',
};
expect(() =>
_js({ context, location, params, methodName: 'evaluate' })
).toThrowErrorMatchingInlineSnapshot(`"'Hello' is not defined"`);
const fn = _js({ context, location, params, methodName: 'function' });
expect(() => fn()).toThrowErrorMatchingInlineSnapshot(`"'Hello' is not defined"`);
});
test('_js not a function', () => {
const params = {
code: '"Hello"',
};
expect(() =>
_js({ context, location, params, methodName: 'evaluate' })
).toThrowErrorMatchingInlineSnapshot(`"not a function"`);
const fn = _js({ context, location, params, methodName: 'function' });
expect(() => fn()).toThrowErrorMatchingInlineSnapshot(`"not a function"`);
});
test('_js params not a object', () => {
const params = [];
expect(() =>
_js({ context, location, params, methodName: 'function' })
).toThrowErrorMatchingInlineSnapshot(
`"Operator Error: _js.function takes an object as input at location."`
);
expect(() =>
_js({ context, location, params, methodName: 'evaluate' })
).toThrowErrorMatchingInlineSnapshot(
`"Operator Error: _js.evaluate takes an object as input at location."`
);
});
test('_js.invalid methodName', () => {
const params = {
code: `function () {
return args[0] + args[1]
}`,
};
expect(() =>
_js({ context, location, params, methodName: 'invalid' })
).toThrowErrorMatchingInlineSnapshot(`"Operator Error: _js.invalid is not a function."`);
});
test('_js invalid js code', () => {
const params = {
code: 1,
};
expect(() =>
_js({ context, location, params, methodName: 'function' })
).toThrowErrorMatchingInlineSnapshot(
`"Operator Error: _js.function \\"code\\" argument should be a string at location."`
);
expect(() =>
_js({ context, location, params, methodName: 'evaluate' })
).toThrowErrorMatchingInlineSnapshot(
`"Operator Error: _js.evaluate \\"code\\" argument should be a string at location."`
);
});
test('_js no body or file', () => {
const params = {};
expect(() =>
_js({ context, location, params, methodName: 'function' })
).toThrowErrorMatchingInlineSnapshot(
`"Operator Error: _js.function \\"code\\" argument should be a string at location."`
);
expect(() =>
_js({ context, location, params, methodName: 'evaluate' })
).toThrowErrorMatchingInlineSnapshot(
`"Operator Error: _js.evaluate \\"code\\" argument should be a string at location."`
);
});
test('_js.evaluate, args not an array', () => {
const params = {
args: 1,
body: `function () {
return args[0] + args[1]
}`,
};
expect(() =>
_js({ context, location, params, methodName: 'evaluate' })
).toThrowErrorMatchingInlineSnapshot(
`"Operator Error: _js.evaluate \\"args\\" argument should be an array, null or undefined at location."`
);
});
test('_js with undefined vm', () => {
const params = {
code: `{
return args[0] + args[1]
}`,
};
_js.clear();
expect(() =>
_js({ context, location, params, methodName: 'function' })
).toThrowErrorMatchingInlineSnapshot(
`"Operator Error: _js is not initialized. Received: {\\"code\\":\\"{\\\\n return args[0] + args[1]\\\\n }\\"} at location."`
);
expect(() =>
_js({ context, location, params, methodName: 'evaluate' })
).toThrowErrorMatchingInlineSnapshot(
`"Operator Error: _js is not initialized. Received: {\\"code\\":\\"{\\\\n return args[0] + args[1]\\\\n }\\"} at location."`
);
});
// ! --------------

View File

@ -3422,7 +3422,6 @@ __metadata:
jest: 26.6.3
js-yaml: 4.1.0
mingo: 4.1.2
quickjs-emscripten: 0.11.0
uuid: 8.3.2
languageName: unknown
linkType: soft
@ -16738,13 +16737,6 @@ fsevents@^1.2.7:
languageName: node
linkType: hard
"quickjs-emscripten@npm:0.11.0":
version: 0.11.0
resolution: "quickjs-emscripten@npm:0.11.0"
checksum: a5cf45a1fdf570decbfd7f224f8cd5b36d838a669232d27cdc9f6ec5c617af1301a36c2c1e3fc4228bed1b65a347256ea0cb62774a00f1ff4d1edd172f101952
languageName: node
linkType: hard
"raf@npm:^3.4.0, raf@npm:^3.4.1":
version: 3.4.1
resolution: "raf@npm:3.4.1"