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); +};