Skip to content

Commit

Permalink
fix: update node2value
Browse files Browse the repository at this point in the history
  • Loading branch information
wwsun committed May 15, 2024
1 parent 80ccd77 commit 4a1703c
Show file tree
Hide file tree
Showing 6 changed files with 100 additions and 60 deletions.
24 changes: 24 additions & 0 deletions apps/playground/src/helpers/mock-files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,13 +86,19 @@ export function registerComponentPrototype(proto) {

const routesCode = `
import Index from "./pages/list";
import Detail from "./pages/detail";
const routes = [
{
path: '/',
exact: true,
component: Index
},
{
path: '/detail',
exact: true,
component: Detail
},
];
export default routes;
Expand Down Expand Up @@ -247,6 +253,23 @@ class App extends React.Component {
export default definePage(App);
`;

export const emptyPageCode = `
import React from "react";
import { definePage } from "@music163/tango-boot";
import {
Page,
Section,
} from "@music163/antd";
function App() {
return (<Page title="Detail Page">
<Section></Section>
</Page>)
}
export default definePage(App);
`;

const componentsButtonCode = `
import React from 'react';
import { registerComponentPrototype } from '../utils';
Expand Down Expand Up @@ -386,6 +409,7 @@ export const sampleFiles = [
{ filename: '/src/style.css', code: cssCode },
{ filename: '/src/index.js', code: entryCode },
{ filename: '/src/pages/list.js', code: viewHomeCode },
{ filename: '/src/pages/detail.js', code: emptyPageCode },
{ filename: '/src/components/button.js', code: componentsButtonCode },
{ filename: '/src/components/input.js', code: componentsInputCode },
{ filename: '/src/components/index.js', code: componentsEntryCode },
Expand Down
41 changes: 39 additions & 2 deletions apps/playground/src/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Box } from 'coral-system';
import { Button, Space } from 'antd';
import { Button, Form, Input, Modal, Space } from 'antd';
import {
Designer,
DesignerPanel,
Expand All @@ -15,15 +15,18 @@ import {
} from '@music163/tango-designer';
import { createEngine, Workspace } from '@music163/tango-core';
import prototypes from '../helpers/prototypes';
import { Logo, ProjectDetail, bootHelperVariables, sampleFiles } from '../helpers';
import { Logo, ProjectDetail, bootHelperVariables, emptyPageCode, sampleFiles } from '../helpers';
import {
ApiOutlined,
AppstoreAddOutlined,
BuildOutlined,
ClusterOutlined,
FunctionOutlined,
PlusOutlined,
createFromIconfontCN,
} from '@ant-design/icons';
import { Action } from '@music163/tango-ui';
import { useState } from 'react';

// 1. 实例化工作区
const workspace = new Workspace({
Expand Down Expand Up @@ -87,6 +90,8 @@ const menuData = {
* 5. 平台初始化,访问 https://local.netease.com:6006/
*/
export default function App() {
const [showNewPageModal, setShowNewPageModal] = useState(false);
const [form] = Form.useForm();
return (
<Designer
theme={themeLight}
Expand All @@ -104,6 +109,14 @@ export default function App() {
<Box px="l">
<Toolbar>
<Toolbar.Item key="routeSwitch" placement="left" />
<Toolbar.Item key="addPage" placement="left">
<Action
tooltip="添加页面"
shape="outline"
icon={<PlusOutlined />}
onClick={() => setShowNewPageModal(true)}
/>
</Toolbar.Item>
<Toolbar.Item key="history" placement="left" />
<Toolbar.Item key="preview" placement="left" />
<Toolbar.Item key="modeSwitch" placement="right" />
Expand All @@ -115,6 +128,30 @@ export default function App() {
</Space>
</Toolbar.Item>
</Toolbar>
<Modal
title="添加新页面"
open={showNewPageModal}
onCancel={() => setShowNewPageModal(false)}
footer={null}
>
<Form
form={form}
onFinish={(values) => {
workspace.addViewFile(values.name, emptyPageCode);
setShowNewPageModal(false);
}}
layout="vertical"
>
<Form.Item label="文件名" name="name" required rules={[{ required: true }]}>
<Input placeholder="请输入文件名" />
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit">
提交
</Button>
</Form.Item>
</Form>
</Modal>
</Box>
}
>
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/helpers/assert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ const templatePattern = /^{(.+)}$/s;
/**
* 判断给定字符串是否被表达式容器`{expCode}`包裹
* @param code
* @deprecated 新版改为 {{code}} 作为容器
* @deprecated 新版改为 {{code}} 作为容器,使用 isWrappedCode 代替
*/
export function isWrappedByExpressionContainer(code: string, isStrict = true) {
if (isStrict && isValidExpressionCode(code)) {
Expand Down
83 changes: 29 additions & 54 deletions packages/core/src/helpers/ast/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,12 +154,12 @@ export function node2code(node: t.Node) {
}

/**
* 将 t.Node 生成为 js 值(不适用于jsx value node)
* 将 t.Node 生成为 js 值
* @param node ast node
* @param hasExpressionWrapper 是否包裹表达式
* @param isWrapCode 是否包裹代码,例如 code -> {{code}}
* @returns a plain javascript value
*/
export function node2value(node: t.Node, hasExpressionWrapper = true): any {
export function node2value(node: t.Node, isWrapCode = true): any {
let ret;
switch (node.type) {
case 'StringLiteral':
Expand All @@ -185,26 +185,36 @@ export function node2value(node: t.Node, hasExpressionWrapper = true): any {
case 'JSXElement': // {{<Box>hello</Box>}}
case 'JSXFragment': // {{<><Box /></>}}
ret = expression2code(node);
if (hasExpressionWrapper) {
if (isWrapCode) {
ret = wrapCode(ret);
}
break;
case 'ObjectExpression': {
// FIXME: object, array 统一按照 code 进行处理,不解析为对象
ret = node.properties.reduce((prev, propertyNode) => {
if (propertyNode.type === 'ObjectProperty') {
const key = keyNode2value(propertyNode.key);
const value = node2value(propertyNode.value, hasExpressionWrapper);
// key 可能是字符串,也可能是数字
prev[key] = value;
const isSimpleObject = node.properties.every(
(propertyNode) => propertyNode.type === 'ObjectProperty',
);
if (isSimpleObject) {
// simple object: { key1, key2, key3 }
ret = node.properties.reduce((prev, propertyNode) => {
if (propertyNode.type === 'ObjectProperty') {
const key = keyNode2value(propertyNode.key);
const value = node2value(propertyNode.value, isWrapCode);
prev[key] = value; // key 可能是字符串,也可能是数字
}
return prev;
}, {});
} else {
// mixed object, object property maybe SpreadElement or ObjectMethod, e.g. { key1, fn() {}, ...obj1 }
ret = expression2code(node);
if (wrapCode) {
ret = wrapCode(ret);
}
// FIXME: property is a SpreadElement
return prev;
}, {});
}
break;
}
case 'ArrayExpression': {
ret = node.elements.map((elementNode) => node2value(elementNode, hasExpressionWrapper));
// FIXME: 有可能会解析失败
ret = node.elements.map((elementNode) => node2value(elementNode, isWrapCode));
break;
}
default:
Expand All @@ -215,7 +225,6 @@ export function node2value(node: t.Node, hasExpressionWrapper = true): any {
}

/**
* TODO: 是不是要和 node2value 的逻辑合并
* jsx prop value 节点转为 js value
*/
export function jsxAttributeValueNode2value(node: t.Node): any {
Expand All @@ -234,48 +243,14 @@ export function jsxAttributeValueNode2value(node: t.Node): any {
// <Foo bar={[]}>
ret = jsxAttributeValueNode2value(node.expression);
break;
case 'StringLiteral':
case 'NumericLiteral':
case 'BooleanLiteral': {
ret = node.value;
break;
}
case 'NullLiteral':
ret = null;
break;
case 'ObjectExpression': {
const isSimpleObject = node.properties.every(
(propertyNode) => propertyNode.type === 'ObjectProperty',
);
if (isSimpleObject) {
// simple object: { key1, key2, key3 }
ret = node2value(node);
} else {
// mixed object: { key1, ...obj1 }
ret = expression2code(node);
ret = wrapCode(ret);
}
break;
}
case 'ArrayExpression': // [{ key }]
case 'Identifier': // tango
case 'MemberExpression': // this.props.data
case 'OptionalMemberExpression': // a?.b
case 'UnaryExpression': // !false
case 'ArrowFunctionExpression': // () => {}
case 'TemplateLiteral': // `hello ${text}`
case 'ConditionalExpression': // a ? 'foo' : 'bar'
case 'LogicalExpression': // a || b
case 'BinaryExpression': // a + b
case 'TaggedTemplateExpression': // css``
case 'CallExpression': // [1,2,3].map(fn)
case 'JSXElement': // <Box>hello</Box>
case 'JSXFragment': // <><Box /></>
case 'ArrayExpression': {
// 数组统一处理为 code
ret = expression2code(node);
ret = wrapCode(ret);
break;
}
default: {
logger.error('unknown ast node:', node);
ret = node2value(node);
break;
}
}
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/helpers/code-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export const value2expressionCode = value2code;
/**
* 代码字符串转为 js value
* TODO: 暂时还没有使用,需要测试充分
* FIXME: expect(code2value('{ foo: "foo", ...{ bar: "bar"} }')).toEqual({ foo: 'foo', bar: 'bar' });
*/
export function code2value(code: string) {
if (isWrappedCode(code)) {
Expand Down
9 changes: 6 additions & 3 deletions packages/core/tests/helpers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,18 @@ describe('helpers', () => {

it('parse jsxElement attributes', () => {
const node = code2expression(
"<XColumn dataIndex='col' enumMap={{ 1: '已解决', 2: '未解决' }} />",
"<Foo id={tango.user.id} num={1} str='col' enumMap={{ 1: '已解决', 2: '未解决' }} list={[{ key: 1 }, { key: 2 }]} />",
);
const attributes = getJSXElementAttributes(node as JSXElement);
expect(attributes).toEqual({
dataIndex: 'col',
id: '{{tango.user.id}}',
num: 1,
str: 'col',
enumMap: {
1: '已解决',
2: '未解决',
},
list: '{{[{ key: 1 }, { key: 2 }]}}',
});
});

Expand Down Expand Up @@ -225,6 +228,6 @@ describe('code helper', () => {
expect(code2value('{{false}}')).toEqual(false);
expect(code2value('{{"foo"}}')).toBe('foo');
// TODO: 这种情况需要考虑下
expect(code2value('{ foo: "foo", ...{ bar: "bar"} }')).toBe('foo');
// expect(code2value('{ foo: "foo", ...{ bar: "bar"} }')).toBe('foo');
});
});

0 comments on commit 4a1703c

Please sign in to comment.