Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support code id #111

Merged
merged 11 commits into from
Mar 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions apps/playground/.umirc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,14 @@ export default defineConfig({
antd: 'antd',
},
headScripts: [
'https://cdn.jsdelivr.net/npm/[email protected]/umd/react.development.js',
'https://cdn.jsdelivr.net/npm/[email protected]/umd/react-dom.development.js',
'https://cdn.jsdelivr.net/npm/[email protected]/umd/react-is.production.min.js',
'https://cdn.jsdelivr.net/npm/moment/min/moment-with-locales.js',
'https://cdn.jsdelivr.net/npm/[email protected]/dist/styled-components.js',
'https://cdn.jsdelivr.net/npm/[email protected]/dist/antd-with-locales.min.js',
'https://cdn.jsdelivr.net/npm/[email protected]/standalone.js',
'https://cdn.jsdelivr.net/npm/[email protected]/parser-babel.js',
'https://unpkg.com/[email protected]/umd/react.development.js',
'https://unpkg.com/[email protected]/umd/react-dom.development.js',
'https://unpkg.com/[email protected]/umd/react-is.production.min.js',
'https://unpkg.com/moment/min/moment-with-locales.js',
'https://unpkg.com/[email protected]/dist/styled-components.js',
'https://unpkg.com/[email protected]/dist/antd-with-locales.min.js',
'https://unpkg.com/[email protected]/standalone.js',
'https://unpkg.com/[email protected]/parser-babel.js',
],
https: {
key: path.resolve(__dirname, 'local.netease.com-key.pem'),
Expand Down
53 changes: 25 additions & 28 deletions apps/playground/src/helpers/mock-files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ const packageJson = {
name: 'demo',
private: true,
dependencies: {
'@music163/antd': '0.1.6',
'@music163/tango-boot': '0.1.3',
'@music163/antd': '0.2.1',
'@music163/tango-boot': '0.2.5',
react: '17.0.2',
'react-dom': '17.0.2',
'prop-types': '15.7.2',
Expand All @@ -12,64 +12,60 @@ const packageJson = {
};

const tangoConfigJson = {
designerConfig: {
autoGenerateComponentId: true,
},
packages: {
react: {
version: '17.0.2',
library: 'React',
type: 'dependency',
resources: ['https://cdn.jsdelivr.net/npm/react@{{version}}/umd/react.development.js'],
resources: ['https://unpkg.com/react@{{version}}/umd/react.development.js'],
},
'react-dom': {
version: '17.0.2',
library: 'ReactDOM',
type: 'dependency',
resources: [
'https://cdn.jsdelivr.net/npm/react-dom@{{version}}/umd/react-dom.development.js',
],
resources: ['https://unpkg.com/react-dom@{{version}}/umd/react-dom.development.js'],
},
'react-is': {
version: '16.13.1',
library: 'ReactIs',
type: 'dependency',
resources: [
'https://cdn.jsdelivr.net/npm/react-is@{{version}}/umd/react-is.production.min.js',
],
resources: ['https://unpkg.com/react-is@{{version}}/umd/react-is.production.min.js'],
},
'styled-components': {
version: '5.3.5',
library: 'styled',
type: 'dependency',
resources: [
'https://cdn.jsdelivr.net/npm/styled-components@{{version}}/dist/styled-components.min.js',
],
resources: ['https://unpkg.com/styled-components@{{version}}/dist/styled-components.min.js'],
},
moment: {
version: '2.29.4',
library: 'moment',
type: 'dependency',
resources: ['https://cdn.jsdelivr.net/npm/moment@{{version}}/moment.js'],
resources: ['https://unpkg.com/moment@{{version}}/moment.js'],
},
'@music163/tango-boot': {
version: '0.2.1',
description: '云音乐低代码运行时框架',
version: '0.2.5',
library: 'TangoBoot',
type: 'baseDependency',
resources: ['https://cdn.jsdelivr.net/npm/@music163/tango-boot@{{version}}/dist/boot.js'],
resources: ['https://unpkg.com/@music163/tango-boot@{{version}}/dist/boot.js'],
// resources: ['http://localhost:9001/boot.js'],
description: '云音乐低代码运行时框架',
},
'@music163/antd': {
version: '0.1.7',
description: '云音乐低代码中后台应用基础物料',
version: '0.2.1',
library: 'TangoAntd',
type: 'baseDependency',
resources: [
'https://cdn.jsdelivr.net/npm/@music163/antd@{{version}}/dist/index.js',
'https://cdn.jsdelivr.net/npm/[email protected]/dist/antd.css',
'https://unpkg.com/@music163/antd@{{version}}/dist/index.js',
'https://unpkg.com/[email protected]/dist/antd.css',
],
description: '云音乐低代码中后台应用基础物料',
designerResources: [
'https://cdn.jsdelivr.net/npm/@music163/antd@{{version}}/dist/designer.js',
// 'http://localhost:9002/designer.js',
'https://cdn.jsdelivr.net/npm/[email protected]/dist/antd.css',
'https://unpkg.com/@music163/antd@{{version}}/dist/designer.js',
'https://unpkg.com/[email protected]/dist/antd.css',
],
},
},
Expand Down Expand Up @@ -161,13 +157,14 @@ class App extends React.Component {
render() {
return (
<Page title={tango.stores.app.title}>
<Section title="Section Title">
<Section tid="section1" title="Section Title">
your input: <Input tid="input1" defaultValue="hello" />
copy input: <Input value={tango.page.input1?.value} />
</Section>
<Section>
<Space>
<Section tid="section2">
<Space tid="space1">
<LocalButton />
<Button>button</Button>
<Input />
<Button tid="button1">button</Button>
</Space>
</Section>
</Page>
Expand Down
8 changes: 7 additions & 1 deletion apps/storybook/src/setting-form.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,13 @@ export function Basic() {

return (
<Box display="flex">
<SettingForm model={model} prototype={prototype} />
<SettingForm
model={model}
prototype={prototype}
showIdentifier={{
identifierKey: 'tid',
}}
/>
<Box position="relative">
<Card title="表单状态预览" style={{ position: 'sticky', top: 0 }}>
<FormValuePreview model={model} />
Expand Down
5 changes: 5 additions & 0 deletions packages/core/src/helpers/ast/parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
isValidObjectString,
getVariableContent,
isPlainObject,
Dict,
} from '@music163/tango-helpers';
import { isWrappedByExpressionContainer } from '../assert';

Expand Down Expand Up @@ -255,6 +256,10 @@ export function makeJSXAttribute(name: string, value: any) {
return t.jsxAttribute(t.jsxIdentifier(name), value2jsxAttributeValueNode(value));
}

export function makeJSXAttributes(props: Dict) {
return Object.keys(props).map((key) => makeJSXAttribute(key, props[key]));
}

/**
* 给定具体的 value 代码,生成 JSXAttribute
* @param name 属性名
Expand Down
100 changes: 65 additions & 35 deletions packages/core/src/helpers/ast/traverse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
makeJSXAttribute,
code2expression,
object2node,
makeJSXAttributes,
} from './parse';
import { getFullPath, isValidComponentName } from '../string';
import { isDefineService, isDefineStore, isTangoVariable } from '../assert';
Expand Down Expand Up @@ -1110,51 +1111,75 @@ function getJSXElementName(node: t.JSXElement) {
return keyNode2value(node.openingElement.name) as string;
}

type JSXAttributeFilterType = (attrName: string | number, attrNode: t.JSXAttribute) => boolean;

function createJSXElementAttributesFilter(filter: JSXAttributeFilterType) {
return (node: t.JSXElement) => {
const attributes = node.openingElement.attributes.filter((attrNode) => {
if (t.isJSXAttribute(attrNode)) {
const attrName = keyNode2value(attrNode.name);
return filter(attrName, attrNode);
}
return true;
});
node.openingElement.attributes = attributes;
return node;
};
}

/**
* JSXElement 中移除追踪属性
* 清楚 JSXElement 的追踪属性
* @param node
* @returns
*/
function clearJSXElementTrackingData(node: t.JSXElement) {
const attributes = node.openingElement.attributes.filter((attrNode) => {
if (t.isJSXAttribute(attrNode)) {
const attrName = keyNode2value(attrNode.name);
if (attrName === SLOT.dnd) {
return false;
}
}
return true;
});
node.openingElement.attributes = attributes;
return node;
}
const removeTrackingAttributes = createJSXElementAttributesFilter((attrName) => {
return attrName !== SLOT.dnd;
});

/**
* JSXElement 中移除追踪属性
* @param node
* 从文件中移除所有 JSXElement 的追踪属性
* @param ast
* @returns
*/
function removeTrackingDataFromNodeAst(node: t.JSXElement) {
traverseExpressionNode(node, {
function clearTrackingData(ast: t.File) {
traverse(ast, {
JSXElement(path) {
clearJSXElementTrackingData(path.node);
path.node = removeTrackingAttributes(path.node);
},
});
return node;
return ast;
}

/**
* 从视图文件的 ast 中移除追踪代码
* @param ast
* @warning TODO: 有 bug ,注释会重复生成,参考 https://github.com/babel/babel/issues/14549
* 从 JSXElement 中移除追踪属性
* @param node
* @returns
*/
function removeTrackingDataFromViewAst(ast: t.File) {
traverse(ast, {
function clearJSXElementTrackingData(node: t.JSXElement, overrideProps?: Dict) {
traverseExpressionNode(node, {
JSXElement(path) {
clearJSXElementTrackingData(path.node);
const newNode = createJSXElementAttributesFilter((attrName) => {
if (attrName === SLOT.dnd) {
return false;
}
if (overrideProps && attrName in overrideProps) {
return false;
}
return true;
})(path.node);

if (overrideProps) {
const overrideAttributes = makeJSXAttributes(overrideProps);
newNode.openingElement.attributes = [
...overrideAttributes,
...path.node.openingElement.attributes,
];
}

path.node = newNode;
},
});
return ast;
return node;
}

/**
Expand Down Expand Up @@ -1198,9 +1223,9 @@ export function removeUnusedImportSpecifiers(ast: t.File) {
* @param node
* @returns
*/
export function cloneJSXElementWithoutTrackingData(node: t.JSXElement) {
export function cloneJSXElement(node: t.JSXElement, overrideProps?: Dict) {
let cloned = t.cloneNode(node, true, true);
cloned = removeTrackingDataFromNodeAst(cloned);
cloned = clearJSXElementTrackingData(cloned, overrideProps);
return cloned;
}

Expand All @@ -1209,7 +1234,7 @@ export function traverseViewFile(ast: t.File, idGenerator: IdGenerator) {
const importedModules: Dict<IImportDeclarationPayload | IImportDeclarationPayload[]> = {};
const nodes: Array<ITangoViewNodeData<t.JSXElement>> = [];
const cloneAst = t.cloneNode(ast, true, true);
const cleanAst = removeTrackingDataFromViewAst(cloneAst);
const cleanAst = clearTrackingData(cloneAst);
const variables: string[] = []; // 使用的 tango 变量

traverse(ast, {
Expand Down Expand Up @@ -1279,19 +1304,20 @@ export function traverseViewFile(ast: t.File, idGenerator: IdGenerator) {
const attributes = getJSXElementAttributes(path.node);

// 获取组件的追踪属性
const trackId = attributes[SLOT.dnd];
const trackDnd = attributes[SLOT.dnd];
// 用户代码中的 id 标记
const codeId = attributes.tid;

let { component, id } = parseDndId(trackId);
let { component, id } = parseDndId(trackDnd);
component = component || getJSXElementName(path.node);
idGenerator.setItem(component);

if (!isValidComponentName(component)) {
return;
}

// 如果没有 ID,生成组件的追踪 ID
if (!trackId) {
id = idGenerator.generateId(component);
if (!trackDnd) {
id = idGenerator.generateId(component, codeId).fullId;
}

// 在组件属性中添加追踪标记
Expand All @@ -1301,16 +1327,20 @@ export function traverseViewFile(ast: t.File, idGenerator: IdGenerator) {

// parentId 用于追溯上下游关系
let parentId;
let parentCodeId;
const parentNode = path.findParent((p) => p.isJSXElement());

if (t.isJSXElement(parentNode?.node)) {
const parentAttributes = getJSXElementAttributes(parentNode.node);
parentId = parentAttributes[SLOT.dnd];
parentCodeId = parentAttributes.tid;
}

nodes.push({
id,
codeId,
parentId,
parentCodeId,
component,
rawNode: path.node,
});
Expand Down
22 changes: 15 additions & 7 deletions packages/core/src/helpers/id-generator.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { camelCase } from '@music163/tango-helpers';

type IdGeneratorOptionsType = { prefix?: string };
/**
* ID 生成器
Expand Down Expand Up @@ -28,22 +30,28 @@ export class IdGenerator {
}
this.map.set(component, record);
} else {
this.map.set(component, []);
const value = id ? [id] : [];
this.map.set(component, value);
}
}

/**
* 获取组件 ID
* @param component
* @param component 组件名, 如 Button, DatePicker
* @param codeId 用户自定义的 ID
* @returns
*/
generateId(component: string) {
generateId(component: string, codeId?: string) {
// FIXME: 使用 size 这里可能存在冲突的风险
const size = this.map.get(component)?.length + 1 || 1;
let id = `${component}:${size}`;
const id = codeId || `${camelCase(component)}${size}`;
this.setItem(component, id);

let fullId = `${component}:${id}`;
if (this.prefix) {
id = `${this.prefix}:${id}`;
fullId = `${this.prefix}:${fullId}`;
}
this.setItem(component, id);
return id;

return { id, fullId };
}
}
Loading
Loading