From fcefe8b7d11c839b3de9e924e45d4c3bbe754a8a Mon Sep 17 00:00:00 2001
From: smallstonesk <575913857@qq.com>
Date: Sun, 10 Jan 2021 20:47:50 +0800
Subject: [PATCH] feat: supports compiling code for running online
---
example/mock/db.json | 24 ++--
packages/compile-code/src/compileForOnline.ts | 107 ++++++++++++++++++
.../compile-code/src/compileForProject.ts | 34 ++++++
packages/compile-code/src/index.ts | 37 +-----
.../compile-code/src/template/runOnline.ts | 31 +++++
.../flowChart/contextMenu/menuConfig/node.tsx | 7 +-
.../mods/flowChart/registerServerStorage.ts | 1 -
.../src/mods/header/export/exportModal.tsx | 4 +-
.../mods/settingBar/mods/testCase/index.tsx | 11 +-
packages/core/src/utils/flowChartUtils.ts | 8 ++
packages/core/src/utils/index.ts | 7 ++
11 files changed, 221 insertions(+), 50 deletions(-)
create mode 100644 packages/compile-code/src/compileForOnline.ts
create mode 100644 packages/compile-code/src/compileForProject.ts
create mode 100644 packages/compile-code/src/template/runOnline.ts
diff --git a/example/mock/db.json b/example/mock/db.json
index 2b7eb5e..551cad4 100644
--- a/example/mock/db.json
+++ b/example/mock/db.json
@@ -447,20 +447,24 @@
{
"position": {
"x": 56,
- "y": 370
+ "y": 380
},
"size": {
- "width": 80,
- "height": 80
+ "width": 100,
+ "height": 40
},
- "shape": "imove-start",
+ "attrs": {
+ "label": {
+ "text": "在线运行测试"
+ }
+ },
+ "shape": "imove-behavior",
"data": {
- "label": "开始",
+ "label": "在线运行测试",
"configSchema": "{\n \n}",
"configData": {},
- "trigger": "start",
- "dependencies": "{\n \n}",
- "code": "export default async function(ctx) {\n \n}"
+ "dependencies": "{\n \"lodash.get\": \"4.4.2\"\n}",
+ "code": "import fpget from 'lodash.get';\n\nexport default async function(ctx) {\n const obj = {a: {b: 'hello imove~'}};\n console.log(fpget(obj, 'a.b'));\n}"
},
"ports": {
"groups": {
@@ -532,8 +536,8 @@
}
]
},
- "id": "170e1bef-3acf-4172-8a03-cd9fd6e6fc81",
- "zIndex": 8
+ "id": "1d174369-929b-4bd5-8841-f271399c4a4f",
+ "zIndex": 9
}
]
}
\ No newline at end of file
diff --git a/packages/compile-code/src/compileForOnline.ts b/packages/compile-code/src/compileForOnline.ts
new file mode 100644
index 0000000..5af5984
--- /dev/null
+++ b/packages/compile-code/src/compileForOnline.ts
@@ -0,0 +1,107 @@
+import {Cell} from '@antv/x6';
+import tpl from './template/runOnline';
+import simplifyDSL from './simplifyDSL';
+
+interface DSL {
+ cells: Cell.Properties[];
+}
+
+/**
+ * Solution
+ *
+ * 1. find the source node form dsl, and if it is not imove-start,
+ * then insert a vitural imove-start at first.
+ *
+ * 2. transform node funciton, follows should be noted:
+ * - import statement should be replaced with import('packge/from/network')
+ * - export statement should be replace with return function
+ * - each node function should be wrapped within a new function to avoid duplicate declaration global variable
+ *
+ * 3. assemble Logic, Context, simplyfied dsl and nodeFns map into one file
+ *
+ */
+
+const INSERT_DSL_COMMENT = '// define dsl here';
+const INSERT_NODE_FNS_COMMENT = '// define nodeFns here';
+const importRegex = /import\s+([\s\S]*?)\s+from\s+(?:('[@\.\/\-\w]+')|("[@\.\/\-\w]+"))\s*;?/mg;
+const virtualSourceNode = {
+ id: 'virtual-imove-start',
+ shape: 'imove-start',
+ data: {
+ trigger: 'virtual-imove-start',
+ configData: {},
+ code: 'export default async function(ctx) {\n \n}'
+ }
+};
+
+const findStartNode = (dsl: DSL): Cell.Properties => {
+
+ const nodes = dsl.cells.filter(cell => cell.shape !== 'edge');
+ const edges = dsl.cells.filter(cell => cell.shape === 'edge');
+
+ if(nodes.length === 0) {
+ throw new Error('Compile failed, no node is selected');
+ }
+
+ let foundEdge = null;
+ let startNode = nodes[0];
+ while(foundEdge = edges.find(edge => edge.target.cell === startNode.id)) {
+ const newSourceId = foundEdge.source.cell;
+ startNode = nodes.find(node => node.id === newSourceId) as Cell.Properties;
+ }
+
+ if(startNode.shape !== 'imove-start') {
+ dsl.cells.push(
+ virtualSourceNode,
+ {
+ shape: "edge",
+ source: {
+ cell: 'virtual-imove-start',
+ },
+ target: {
+ cell: startNode.id
+ }
+ }
+ );
+ startNode = virtualSourceNode;
+ }
+
+ return startNode;
+};
+
+const compileSimplifiedDSL = (dsl: DSL): string => {
+ const simplyfiedDSL = JSON.stringify(simplifyDSL(dsl), null, 2);
+ return `const dsl = ${simplyfiedDSL};`;
+};
+
+const compileNodeFn = (node: Cell.Properties): string => {
+ const {data: {label, code}} = node;
+ const newCode = code.replace(importRegex, (match: string, p1: string, p2: string, p3: string) => {
+ const pkgName = (p2 || p3).replace(/('|")/g, '');
+ return `const ${p1} = (await import('https://jspm.dev/${pkgName}')).default;`;
+ }).replace(/export\s+default/, 'return');
+
+ return `await (async function() {
+ ${newCode}
+ }())`;
+};
+
+const compileNodeFnsMap = (dsl: DSL): string => {
+ const nodes = dsl.cells.filter(cell => cell.shape !== 'edge');
+ const kvs = nodes.map(node => {
+ const {id} = node;
+ return `'${id}': ${compileNodeFn(node)}`;
+ });
+
+ return `const nodeFns = {\n ${kvs.join(',\n ')}\n}`;
+};
+
+const compile = (dsl: DSL): string => {
+ const startNode = findStartNode(dsl);
+ return tpl
+ .replace(INSERT_DSL_COMMENT, compileSimplifiedDSL(dsl))
+ .replace(INSERT_NODE_FNS_COMMENT, compileNodeFnsMap(dsl))
+ .replace('$TRIGGER$', startNode.data.trigger);
+};
+
+export default compile;
diff --git a/packages/compile-code/src/compileForProject.ts b/packages/compile-code/src/compileForProject.ts
new file mode 100644
index 0000000..2d48d3a
--- /dev/null
+++ b/packages/compile-code/src/compileForProject.ts
@@ -0,0 +1,34 @@
+import {Cell} from '@antv/x6';
+import addPlugins from './addPlugins';
+import simplifyDSL from './simplifyDSL';
+import extractNodeFns from './extractNodeFns';
+import logicTpl from './template/logic';
+import indexTpl from './template/index';
+import contextTpl from './template/context';
+
+interface DSL {
+ cells: Cell.Properties[];
+}
+
+interface IOutput {
+ 'nodeFns': {
+ [fileName: string]: string
+ };
+ 'context.js': string;
+ 'dsl.json': string;
+ 'index.js': string;
+ 'logic.js': string;
+}
+
+const compile = (dsl: DSL, plugins = []): IOutput => {
+ const output: IOutput = {
+ 'nodeFns': extractNodeFns(dsl),
+ "context.js": contextTpl,
+ 'dsl.json': JSON.stringify(simplifyDSL(dsl), null, 2),
+ 'index.js': addPlugins(indexTpl, plugins),
+ 'logic.js': logicTpl
+ };
+ return output;
+};
+
+export default compile;
diff --git a/packages/compile-code/src/index.ts b/packages/compile-code/src/index.ts
index 2d48d3a..70d3ddb 100644
--- a/packages/compile-code/src/index.ts
+++ b/packages/compile-code/src/index.ts
@@ -1,34 +1,7 @@
-import {Cell} from '@antv/x6';
-import addPlugins from './addPlugins';
-import simplifyDSL from './simplifyDSL';
-import extractNodeFns from './extractNodeFns';
-import logicTpl from './template/logic';
-import indexTpl from './template/index';
-import contextTpl from './template/context';
+import compileForOnline from './compileForOnline';
+import compileForProject from './compileForProject';
-interface DSL {
- cells: Cell.Properties[];
-}
-
-interface IOutput {
- 'nodeFns': {
- [fileName: string]: string
- };
- 'context.js': string;
- 'dsl.json': string;
- 'index.js': string;
- 'logic.js': string;
-}
-
-const compile = (dsl: DSL, plugins = []): IOutput => {
- const output: IOutput = {
- 'nodeFns': extractNodeFns(dsl),
- "context.js": contextTpl,
- 'dsl.json': JSON.stringify(simplifyDSL(dsl), null, 2),
- 'index.js': addPlugins(indexTpl, plugins),
- 'logic.js': logicTpl
- };
- return output;
+export {
+ compileForOnline,
+ compileForProject
};
-
-export default compile;
diff --git a/packages/compile-code/src/template/runOnline.ts b/packages/compile-code/src/template/runOnline.ts
new file mode 100644
index 0000000..5f3a425
--- /dev/null
+++ b/packages/compile-code/src/template/runOnline.ts
@@ -0,0 +1,31 @@
+import logic from './logic';
+import context from './context';
+
+export default `
+(async function run() {
+ // Context
+ ${context.replace(/export\s+default/, '')}
+
+ // Logic
+ ${logic
+ .split('\n')
+ .filter(line => !line.match(/import nodeFns/) && !line.match(/import Context/))
+ .join('\n')
+ .replace(/export\s+default/, '')
+ .replace(
+ `import EventEmitter from 'eventemitter3';`,
+ `const EventEmitter = (await import('https://jspm.dev/eventemitter3')).default;`
+ )
+ }
+
+ // DSL
+ // define dsl here
+
+ // nodeFns map
+ // define nodeFns here
+
+ // instantiation and invoke
+ const logic = new Logic({ dsl });
+ logic.invoke('$TRIGGER$');
+})();
+`;
diff --git a/packages/core/src/mods/flowChart/contextMenu/menuConfig/node.tsx b/packages/core/src/mods/flowChart/contextMenu/menuConfig/node.tsx
index 53ad236..7c8a18c 100644
--- a/packages/core/src/mods/flowChart/contextMenu/menuConfig/node.tsx
+++ b/packages/core/src/mods/flowChart/contextMenu/menuConfig/node.tsx
@@ -7,9 +7,10 @@ import {
FormOutlined,
DeleteOutlined,
} from '@ant-design/icons';
+
+import { Graph } from '@antv/x6';
import XIcon from '../../../../components/xIcon';
import shortcuts from '../../../../common/shortcuts';
-import { Graph } from '@antv/x6';
import { getSelectedNodes } from '../../../../utils/flowChartUtils';
const nodeMenuConfig = [
@@ -38,14 +39,14 @@ const nodeMenuConfig = [
key: 'bringToTop',
title: '置于顶层',
icon: ,
- handler:shortcuts.bringToTop.handler
+ handler: shortcuts.bringToTop.handler
},
{
key: 'bringToBack',
title: '置于底层',
icon: ,
showDividerBehind: true,
- handler:shortcuts.bringToBack.handler
+ handler: shortcuts.bringToBack.handler
},
{
key: 'editCode',
diff --git a/packages/core/src/mods/flowChart/registerServerStorage.ts b/packages/core/src/mods/flowChart/registerServerStorage.ts
index 4c7390b..2e9816b 100644
--- a/packages/core/src/mods/flowChart/registerServerStorage.ts
+++ b/packages/core/src/mods/flowChart/registerServerStorage.ts
@@ -80,7 +80,6 @@ export const registerServerStorage = (flowChart: Graph) => {
const events = nodeActionEventMap[actionType];
events.forEach((event) => {
flowChart.on(event, (args: any) => {
- console.log('node event:', event, 'args:', args);
save(flowChart, 'node', actionType as ActionType, args.node.toJSON());
});
});
diff --git a/packages/core/src/mods/header/export/exportModal.tsx b/packages/core/src/mods/header/export/exportModal.tsx
index 3d77149..b1c2e8e 100644
--- a/packages/core/src/mods/header/export/exportModal.tsx
+++ b/packages/core/src/mods/header/export/exportModal.tsx
@@ -6,7 +6,7 @@ import styles from './index.module.less';
import JSZip from 'jszip';
import { Modal } from 'antd';
import { DataUri, Graph } from '@antv/x6';
-import compileCode from '@imove/compile-code';
+import { compileForProject } from '@imove/compile-code';
interface IExportModalProps {
flowChart: Graph;
@@ -25,7 +25,7 @@ const ExportModal: React.FC = (props) => {
const onExportCode = () => {
const zip = new JSZip();
const dsl = flowChart.toJSON();
- const output = compileCode(dsl);
+ const output = compileForProject(dsl);
Helper.recursiveZip(zip, output);
zip.generateAsync({ type: 'blob' }).then((blob) => {
DataUri.downloadBlob(blob, 'logic.zip');
diff --git a/packages/core/src/mods/settingBar/mods/testCase/index.tsx b/packages/core/src/mods/settingBar/mods/testCase/index.tsx
index 5cf3e11..ed69f17 100644
--- a/packages/core/src/mods/settingBar/mods/testCase/index.tsx
+++ b/packages/core/src/mods/settingBar/mods/testCase/index.tsx
@@ -3,9 +3,14 @@ import React, {
useEffect,
useCallback
} from 'react';
-import { Graph } from '@antv/x6';
+
import { Modal } from 'antd';
+import { Graph } from '@antv/x6';
+import { executeScript } from '../../../../utils';
import CodeRun from '../../../../components/codeRun';
+import { compileForOnline } from '@imove/compile-code';
+import { toSelectedCellsJSON } from '../../../../utils/flowChartUtils';
+
interface IProps {
flowChart: Graph;
selectedCell: any;
@@ -25,7 +30,9 @@ const TestCase: React.FC = (props) => {
const showModal = useCallback(() => setVisible(true), []);
const closeModal = useCallback(() => setVisible(false), []);
const runCode = useCallback(() => {
- // TODO: 运行节点代码
+ // TODO: 展示运行结果
+ const compiledCode = compileForOnline(toSelectedCellsJSON(flowChart));
+ executeScript(compiledCode);
}, []);
return (
diff --git a/packages/core/src/utils/flowChartUtils.ts b/packages/core/src/utils/flowChartUtils.ts
index 90a628c..0a67488 100644
--- a/packages/core/src/utils/flowChartUtils.ts
+++ b/packages/core/src/utils/flowChartUtils.ts
@@ -19,3 +19,11 @@ export const getSelectedNodes = (flowChart: Graph): Cell[] => {
export const getSelectedEdges = (flowChart: Graph): Cell[] => {
return flowChart.getSelectedCells().filter((cell: Cell) => cell.isEdge());
};
+
+export const toSelectedCellsJSON = (flowChart: Graph): {cells: Cell.Properties[]} => {
+ const json = flowChart.toJSON();
+ const selectedCells = flowChart.getSelectedCells();
+ return {
+ cells: json.cells.filter(cell => selectedCells.find(o => o.id === cell.id))
+ };
+}
\ No newline at end of file
diff --git a/packages/core/src/utils/index.ts b/packages/core/src/utils/index.ts
index 964ae60..c134e24 100644
--- a/packages/core/src/utils/index.ts
+++ b/packages/core/src/utils/index.ts
@@ -36,3 +36,10 @@ const parseConfig = {
export const parseQuery = (): { [key: string]: any } => {
return parse(location.search, parseConfig);
};
+
+export const executeScript = (code: string, type: string = 'module') => {
+ const script = document.createElement('script');
+ script.type = type;
+ script.text = code;
+ document.body.appendChild(script);
+};