Browse Source

feat: supports compiling code for running online

master
smallstonesk 4 years ago
parent
commit
fcefe8b7d1
  1. 24
      example/mock/db.json
  2. 107
      packages/compile-code/src/compileForOnline.ts
  3. 34
      packages/compile-code/src/compileForProject.ts
  4. 37
      packages/compile-code/src/index.ts
  5. 31
      packages/compile-code/src/template/runOnline.ts
  6. 7
      packages/core/src/mods/flowChart/contextMenu/menuConfig/node.tsx
  7. 1
      packages/core/src/mods/flowChart/registerServerStorage.ts
  8. 4
      packages/core/src/mods/header/export/exportModal.tsx
  9. 11
      packages/core/src/mods/settingBar/mods/testCase/index.tsx
  10. 8
      packages/core/src/utils/flowChartUtils.ts
  11. 7
      packages/core/src/utils/index.ts

24
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
}
]
}

107
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;

34
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;

37
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;

31
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$');
})();
`;

7
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: <XIcon type={'icon-bring-to-top'} />,
handler:shortcuts.bringToTop.handler
handler: shortcuts.bringToTop.handler
},
{
key: 'bringToBack',
title: '置于底层',
icon: <XIcon type={'icon-bring-to-bottom'} />,
showDividerBehind: true,
handler:shortcuts.bringToBack.handler
handler: shortcuts.bringToBack.handler
},
{
key: 'editCode',

1
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());
});
});

4
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<IExportModalProps> = (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');

11
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<IProps> = (props) => {
const showModal = useCallback(() => setVisible(true), []);
const closeModal = useCallback(() => setVisible(false), []);
const runCode = useCallback(() => {
// TODO: 运行节点代码
// TODO: 展示运行结果
const compiledCode = compileForOnline(toSelectedCellsJSON(flowChart));
executeScript(compiledCode);
}, []);
return (

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

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

Loading…
Cancel
Save