mirror of
https://github.com/NetEase/tango.git
synced 2026-08-31 09:30:28 +08:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 14e17ebbed | |||
| 4a06eb20cb | |||
| 089a482f75 | |||
| afb94d6a30 | |||
| b9403cfec6 | |||
| 6cf673e7f6 | |||
| 653386dcc0 | |||
| 208e275bb1 |
@@ -17,37 +17,43 @@ const tangoConfigJson = {
|
||||
version: '17.0.2',
|
||||
library: 'React',
|
||||
type: 'dependency',
|
||||
resources: ['https://unpkg.com/react@{{version}}/umd/react.development.js'],
|
||||
resources: ['https://cdn.jsdelivr.net/npm/react@{{version}}/umd/react.development.js'],
|
||||
},
|
||||
'react-dom': {
|
||||
version: '17.0.2',
|
||||
library: 'ReactDOM',
|
||||
type: 'dependency',
|
||||
resources: ['https://unpkg.com/react-dom@{{version}}/umd/react-dom.development.js'],
|
||||
resources: [
|
||||
'https://cdn.jsdelivr.net/npm/react-dom@{{version}}/umd/react-dom.development.js',
|
||||
],
|
||||
},
|
||||
'react-is': {
|
||||
version: '16.13.1',
|
||||
library: 'ReactIs',
|
||||
type: 'dependency',
|
||||
resources: ['https://unpkg.com/react-is@{{version}}/umd/react-is.production.min.js'],
|
||||
resources: [
|
||||
'https://cdn.jsdelivr.net/npm/react-is@{{version}}/umd/react-is.production.min.js',
|
||||
],
|
||||
},
|
||||
'styled-components': {
|
||||
version: '5.3.5',
|
||||
library: 'styled',
|
||||
type: 'dependency',
|
||||
resources: ['https://unpkg.com/styled-components@{{version}}/dist/styled-components.min.js'],
|
||||
resources: [
|
||||
'https://cdn.jsdelivr.net/npm/styled-components@{{version}}/dist/styled-components.min.js',
|
||||
],
|
||||
},
|
||||
moment: {
|
||||
version: '2.29.4',
|
||||
library: 'moment',
|
||||
type: 'dependency',
|
||||
resources: ['https://unpkg.com/moment@{{version}}/moment.js'],
|
||||
resources: ['https://cdn.jsdelivr.net/npm/moment@{{version}}/moment.js'],
|
||||
},
|
||||
'@music163/tango-boot': {
|
||||
version: '0.2.1',
|
||||
library: 'TangoBoot',
|
||||
type: 'baseDependency',
|
||||
resources: ['https://unpkg.com/@music163/tango-boot@{{version}}/dist/boot.js'],
|
||||
resources: ['https://cdn.jsdelivr.net/npm/@music163/tango-boot@{{version}}/dist/boot.js'],
|
||||
// resources: ['http://localhost:9001/boot.js'],
|
||||
description: '云音乐低代码运行时框架',
|
||||
},
|
||||
@@ -56,14 +62,14 @@ const tangoConfigJson = {
|
||||
library: 'TangoAntd',
|
||||
type: 'baseDependency',
|
||||
resources: [
|
||||
'https://unpkg.com/@music163/antd@{{version}}/dist/index.js',
|
||||
'https://unpkg.com/antd@4.24.13/dist/antd.css',
|
||||
'https://cdn.jsdelivr.net/npm/@music163/antd@{{version}}/dist/index.js',
|
||||
'https://cdn.jsdelivr.net/npm/antd@4.24.13/dist/antd.css',
|
||||
],
|
||||
description: '云音乐低代码中后台应用基础物料',
|
||||
designerResources: [
|
||||
'https://unpkg.com/@music163/antd@{{version}}/dist/designer.js',
|
||||
'https://cdn.jsdelivr.net/npm/@music163/antd@{{version}}/dist/designer.js',
|
||||
// 'http://localhost:9002/designer.js',
|
||||
'https://unpkg.com/antd@4.24.13/dist/antd.css',
|
||||
'https://cdn.jsdelivr.net/npm/antd@4.24.13/dist/antd.css',
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
@@ -8,14 +8,14 @@
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script src="https://unpkg.com/react@16.14.0/umd/react.development.js"></script>
|
||||
<script src="https://unpkg.com/react-dom@16.14.0/umd/react-dom.development.js"></script>
|
||||
<script src="https://unpkg.com/react-is@16.13.1/umd/react-is.production.min.js"></script>
|
||||
<script src="https://unpkg.com/moment/min/moment-with-locales.js"></script>
|
||||
<script src="https://unpkg.com/styled-components@5.3.11/dist/styled-components.js"></script>
|
||||
<script src="https://unpkg.com/@babel/standalone@7.15.6/babel.js"></script>
|
||||
<script src="https://unpkg.com/prettier@2.6.0/standalone.js"></script>
|
||||
<script src="https://unpkg.com/prettier@2.6.0/parser-babel.js"></script>
|
||||
<script src="https://unpkg.com/antd@4.24.13/dist/antd-with-locales.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/react@16.14.0/umd/react.development.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/react-dom@16.14.0/umd/react-dom.development.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/react-is@16.13.1/umd/react-is.production.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/moment/min/moment-with-locales.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/styled-components@5.3.11/dist/styled-components.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/@babel/standalone@7.15.6/babel.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/prettier@2.6.0/standalone.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/prettier@2.6.0/parser-babel.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/antd@4.24.13/dist/antd-with-locales.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -29,9 +29,6 @@ import {
|
||||
const workspace = new Workspace({
|
||||
entry: '/src/index.js',
|
||||
files: sampleFiles,
|
||||
onFilesChange: (filenames) => {
|
||||
console.log('onFilesChange', filenames, workspace.files);
|
||||
},
|
||||
});
|
||||
|
||||
// 2. 引擎初始化
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
import React from 'react';
|
||||
import { Box } from 'coral-system';
|
||||
import { CodeSandbox } from '@music163/tango-sandbox';
|
||||
import { JsonView } from '@music163/tango-ui';
|
||||
|
||||
export default {
|
||||
title: 'CodeSandbox',
|
||||
};
|
||||
|
||||
const entryFilename = '/index.js';
|
||||
|
||||
const packageJson = {
|
||||
name: 'demo',
|
||||
private: true,
|
||||
main: entryFilename,
|
||||
dependencies: {
|
||||
react: '^17.0.1',
|
||||
'react-dom': '^17.0.1',
|
||||
},
|
||||
};
|
||||
|
||||
const entryFile = `
|
||||
import React from 'react';
|
||||
import ReactDOM from "react-dom";
|
||||
import App from './src';
|
||||
|
||||
const rootElement = document.getElementById("root");
|
||||
|
||||
ReactDOM.render(
|
||||
<App />,
|
||||
rootElement
|
||||
);
|
||||
`;
|
||||
|
||||
const srcAppFile = `
|
||||
import React from 'react';
|
||||
|
||||
export default function App() {
|
||||
return <div style={{ background: 'yellow', color: 'red' }}>hello world</div>;
|
||||
}
|
||||
`;
|
||||
|
||||
const files = {
|
||||
'/package.json': {
|
||||
code: JSON.stringify(packageJson),
|
||||
},
|
||||
[entryFilename]: {
|
||||
code: entryFile,
|
||||
},
|
||||
'/src/index.js': { code: srcAppFile },
|
||||
};
|
||||
|
||||
export function Basic() {
|
||||
return (
|
||||
<Box display="flex" height="100vh">
|
||||
<CodeSandbox
|
||||
template="create-react-app"
|
||||
bundlerURL="https://codesandbox.fn.netease.com"
|
||||
files={files}
|
||||
entry={entryFilename}
|
||||
style={{
|
||||
flex: 1,
|
||||
}}
|
||||
/>
|
||||
<Box width="30%">
|
||||
<JsonView src={files} />
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
+1
-1
@@ -7,7 +7,7 @@
|
||||
],
|
||||
"scripts": {
|
||||
"start": "yarn workspace playground start",
|
||||
"start:story": "yarn workspace storybook storybook",
|
||||
"start:docs": "yarn workspace storybook storybook",
|
||||
"start:site": "yarn workspace website start",
|
||||
"deploy:site": "yarn workspace website deploy",
|
||||
"build": "lerna run build",
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [0.2.5](https://github.com/netease/tango/compare/@music163/tango-context@0.2.4...@music163/tango-context@0.2.5) (2023-11-16)
|
||||
|
||||
**Note:** Version bump only for package @music163/tango-context
|
||||
|
||||
## [0.2.4](https://github.com/netease/tango/compare/@music163/tango-context@0.2.3...@music163/tango-context@0.2.4) (2023-11-02)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@music163/tango-context",
|
||||
"version": "0.2.4",
|
||||
"version": "0.2.5",
|
||||
"description": "react context for tango-apps",
|
||||
"keywords": [
|
||||
"react",
|
||||
@@ -31,8 +31,8 @@
|
||||
"react": ">= 16.8"
|
||||
},
|
||||
"dependencies": {
|
||||
"@music163/tango-core": "^0.2.3",
|
||||
"@music163/tango-helpers": "^0.1.5",
|
||||
"@music163/tango-core": "^0.2.4",
|
||||
"@music163/tango-helpers": "^0.1.6",
|
||||
"mobx-react-lite": "4.0.2"
|
||||
},
|
||||
"publishConfig": {
|
||||
|
||||
@@ -3,6 +3,15 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [0.2.4](https://github.com/netease/tango/compare/@music163/tango-core@0.2.3...@music163/tango-core@0.2.4) (2023-11-16)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- allow override existing file via addFile ([6cf673e](https://github.com/netease/tango/commit/6cf673e7f6f330613e1d6331256a59900e8cbc68))
|
||||
- export isJSXElementById from traverse ([#54](https://github.com/netease/tango/issues/54)) ([208e275](https://github.com/netease/tango/commit/208e275bb1cd98ed21d816c26179f9ebc11df223))
|
||||
- parse variables from view ([089a482](https://github.com/netease/tango/commit/089a482f750e9d9a7743a6641d6e39989347d318))
|
||||
- update types ([653386d](https://github.com/netease/tango/commit/653386dcc0b064b41915548129952eeabe53019f))
|
||||
|
||||
## [0.2.3](https://github.com/netease/tango/compare/@music163/tango-core@0.2.2...@music163/tango-core@0.2.3) (2023-11-02)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@music163/tango-core",
|
||||
"version": "0.2.3",
|
||||
"version": "0.2.4",
|
||||
"description": "tango core",
|
||||
"author": "wwsun <ww.sun@outlook.com>",
|
||||
"homepage": "",
|
||||
@@ -28,7 +28,7 @@
|
||||
"@babel/parser": "^7.23.0",
|
||||
"@babel/traverse": "^7.23.2",
|
||||
"@babel/types": "^7.23.0",
|
||||
"@music163/tango-helpers": "^0.1.5",
|
||||
"@music163/tango-helpers": "^0.1.6",
|
||||
"@types/babel__generator": "^7.6.6",
|
||||
"@types/babel__traverse": "^7.20.3",
|
||||
"mobx": "6.9.0",
|
||||
|
||||
@@ -20,3 +20,15 @@ const defineStoreHandlerName = 'defineStore';
|
||||
export function isDefineStore(name: string) {
|
||||
return defineStoreHandlerName === name;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否是 tango 的变量引用
|
||||
* @example tango.stores.app.name
|
||||
* @example tango.stores?.app?.name
|
||||
*
|
||||
* @param name
|
||||
* @returns
|
||||
*/
|
||||
export function isTangoVariable(name: string) {
|
||||
return /^tango\??\.(stores|services)\??\./.test(name) && name.split('.').length > 2;
|
||||
}
|
||||
|
||||
@@ -23,15 +23,15 @@ import {
|
||||
object2node,
|
||||
} from './parse';
|
||||
import { isValidComponentName } from '../string';
|
||||
import { isDefineService, isDefineStore } from '../assert';
|
||||
import { isDefineService, isDefineStore, isTangoVariable } from '../assert';
|
||||
import type {
|
||||
IRouteData,
|
||||
IStorePropertyData,
|
||||
IClassPropertyNodeData,
|
||||
ITangoViewNodeData,
|
||||
IImportDeclarationPayload,
|
||||
IServiceFunctionPayload,
|
||||
InsertChildPositionType,
|
||||
IImportSpecifierData,
|
||||
} from '../../types';
|
||||
import { IdGenerator } from '../id-generator';
|
||||
|
||||
@@ -194,7 +194,7 @@ export function queryXFormItemFields(ast: t.File | t.JSXElement) {
|
||||
* @param node
|
||||
* @param id
|
||||
*/
|
||||
function isJSXElementById(node: t.JSXElement, jsxElementNodeId: string) {
|
||||
export function isJSXElementById(node: t.JSXElement, jsxElementNodeId: string) {
|
||||
let isTargetJSXElement = false;
|
||||
visitJSXElementAttributes(node, (name, value) => {
|
||||
if (name === SLOT.dnd && value === jsxElementNodeId) {
|
||||
@@ -448,6 +448,7 @@ function getLastExportDeclarationIndex(node: t.Program) {
|
||||
|
||||
/**
|
||||
* 生成新的 ImportDeclaration
|
||||
* @deprecated 使用 makeImportDeclaration2 代替
|
||||
* @param importedModule
|
||||
* @param importedSourcePath
|
||||
* @returns
|
||||
@@ -467,6 +468,27 @@ export function makeImportDeclaration(importedModule: IImportDeclarationPayload)
|
||||
return t.importDeclaration(newSpecifierNodes, t.stringLiteral(importedModule.sourcePath));
|
||||
}
|
||||
|
||||
export function makeImportDeclaration2(source: string, specifiers: IImportSpecifierData[]) {
|
||||
const specifierNodes = specifiers.map((item) => {
|
||||
switch (item.type) {
|
||||
case 'ImportDefaultSpecifier':
|
||||
return t.importDefaultSpecifier(t.identifier(item.localName));
|
||||
case 'ImportSpecifier':
|
||||
return t.importSpecifier(t.identifier(item.importedName), t.identifier(item.localName));
|
||||
case 'ImportNamespaceSpecifier':
|
||||
return t.importNamespaceSpecifier(t.identifier(item.localName));
|
||||
default:
|
||||
return;
|
||||
}
|
||||
});
|
||||
return t.importDeclaration(specifierNodes, t.stringLiteral(source));
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated 使用 parseImportDeclaration 代替
|
||||
* @param node
|
||||
* @returns
|
||||
*/
|
||||
function getImportDeclarationData(node: t.ImportDeclaration): IImportDeclarationPayload {
|
||||
const sourcePath = node2value(node.source);
|
||||
let defaultSpecifier;
|
||||
@@ -487,6 +509,7 @@ function getImportDeclarationData(node: t.ImportDeclaration): IImportDeclaration
|
||||
|
||||
/**
|
||||
* 添加新的导入语句
|
||||
* @deprecated 使用 addImportDeclaration2 代替
|
||||
* @param ast
|
||||
* @param importedModule
|
||||
* @param importedSourcePath
|
||||
@@ -504,8 +527,24 @@ export function addImportDeclaration(ast: t.File, importedModule: IImportDeclara
|
||||
return ast;
|
||||
}
|
||||
|
||||
export function addImportDeclaration2(
|
||||
ast: t.File,
|
||||
source: string,
|
||||
specifiers: IImportSpecifierData[],
|
||||
) {
|
||||
traverse(ast, {
|
||||
Program(path) {
|
||||
const lastIndex = getLastImportDeclarationIndex(path.node);
|
||||
const newImportDeclaration = makeImportDeclaration2(source, specifiers);
|
||||
path.node.body.splice(lastIndex, 0, newImportDeclaration);
|
||||
path.stop();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新已有的导入语句
|
||||
* @deprecated 使用 updateImportDeclaration2 代替
|
||||
* @param ast
|
||||
* @param importedModule
|
||||
* @param importedSourcePath
|
||||
@@ -525,6 +564,24 @@ export function updateImportDeclaration(ast: t.File, importedModule: IImportDecl
|
||||
return ast;
|
||||
}
|
||||
|
||||
export function updateImportDeclaration2(
|
||||
ast: t.File,
|
||||
source: string,
|
||||
specifiers: IImportSpecifierData[],
|
||||
) {
|
||||
traverse(ast, {
|
||||
ImportDeclaration(path) {
|
||||
const currentSourcePath = node2value(path.node.source);
|
||||
if (currentSourcePath === source) {
|
||||
const newImportDeclaration = makeImportDeclaration2(source, specifiers);
|
||||
path.replaceWith(newImportDeclaration);
|
||||
path.stop(); // 只修改匹配到的第一条
|
||||
}
|
||||
},
|
||||
});
|
||||
return ast;
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增类属性
|
||||
* @param ast
|
||||
@@ -986,7 +1043,12 @@ export function traverseServiceFile(ast: t.File) {
|
||||
const baseConfig: any = {
|
||||
encryptFetch: false,
|
||||
};
|
||||
const imports: Record<string, IImportSpecifierData[]> = {};
|
||||
traverse(ast, {
|
||||
ImportDeclaration(path) {
|
||||
const { source, specifiers } = parseImportDeclaration(path.node);
|
||||
imports[source] = specifiers;
|
||||
},
|
||||
CallExpression(path) {
|
||||
const calleeName = keyNode2value(path.node.callee) as string;
|
||||
if (isDefineService(calleeName)) {
|
||||
@@ -1003,6 +1065,7 @@ export function traverseServiceFile(ast: t.File) {
|
||||
},
|
||||
});
|
||||
return {
|
||||
imports,
|
||||
services,
|
||||
baseConfig,
|
||||
};
|
||||
@@ -1107,6 +1170,7 @@ export function cloneJSXElementWithoutTrackingData(node: t.JSXElement) {
|
||||
}
|
||||
|
||||
export function traverseViewFile(ast: t.File, idGenerator: IdGenerator) {
|
||||
const imports: Record<string, IImportSpecifierData[]> = {};
|
||||
const importedModules: Dict<IImportDeclarationPayload | IImportDeclarationPayload[]> = {};
|
||||
const nodes: Array<ITangoViewNodeData<t.JSXElement>> = [];
|
||||
const cloneAst = t.cloneNode(ast, true, true);
|
||||
@@ -1115,12 +1179,38 @@ export function traverseViewFile(ast: t.File, idGenerator: IdGenerator) {
|
||||
|
||||
traverse(ast, {
|
||||
ImportDeclaration(path) {
|
||||
const declarationData = getImportDeclarationData(path.node);
|
||||
const exist = importedModules[declarationData.sourcePath];
|
||||
const { source, specifiers } = parseImportDeclaration(path.node);
|
||||
imports[source] = specifiers;
|
||||
|
||||
// FIXME: 下面的逻辑兼容旧的逻辑,后续需要移除掉
|
||||
const declarationData = specifiers.reduce(
|
||||
(prev, cur) => {
|
||||
switch (cur.type) {
|
||||
case 'ImportDefaultSpecifier':
|
||||
prev.defaultSpecifier = cur.localName;
|
||||
break;
|
||||
case 'ImportSpecifier':
|
||||
prev.specifiers.push(cur.localName);
|
||||
break;
|
||||
case 'ImportNamespaceSpecifier':
|
||||
prev.specifiers.push(cur.localName);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return prev;
|
||||
},
|
||||
{
|
||||
defaultSpecifier: undefined,
|
||||
specifiers: [],
|
||||
sourcePath: source,
|
||||
},
|
||||
);
|
||||
const exist = importedModules[source];
|
||||
if (!exist) {
|
||||
importedModules[declarationData.sourcePath] = declarationData;
|
||||
importedModules[source] = declarationData;
|
||||
} else {
|
||||
importedModules[declarationData.sourcePath] = Array.isArray(exist)
|
||||
importedModules[source] = Array.isArray(exist)
|
||||
? exist.concat([declarationData])
|
||||
: [exist, declarationData];
|
||||
}
|
||||
@@ -1128,11 +1218,14 @@ export function traverseViewFile(ast: t.File, idGenerator: IdGenerator) {
|
||||
|
||||
MemberExpression(path) {
|
||||
const variable = node2value(path.node, false);
|
||||
if (
|
||||
/^tango\.(stores|services)\./.test(variable) &&
|
||||
variable.split('.').length === 3 &&
|
||||
!variables.includes(variable)
|
||||
) {
|
||||
if (isTangoVariable(variable) && !variables.includes(variable)) {
|
||||
variables.push(variable);
|
||||
}
|
||||
},
|
||||
|
||||
OptionalMemberExpression(path) {
|
||||
const variable = node2value(path.node, false);
|
||||
if (isTangoVariable(variable) && !variables.includes(variable)) {
|
||||
variables.push(variable);
|
||||
}
|
||||
},
|
||||
@@ -1183,7 +1276,50 @@ export function traverseViewFile(ast: t.File, idGenerator: IdGenerator) {
|
||||
ast,
|
||||
cleanAst,
|
||||
nodes,
|
||||
imports,
|
||||
importedModules,
|
||||
variables,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析导入语句
|
||||
*/
|
||||
function parseImportDeclaration(node: t.ImportDeclaration) {
|
||||
const source = node2value(node.source) as string;
|
||||
const specifiers: IImportSpecifierData[] = [];
|
||||
node.specifiers.forEach((specifierNode) => {
|
||||
const data: IImportSpecifierData = {
|
||||
localName: keyNode2value(specifierNode.local) as string,
|
||||
type: specifierNode.type,
|
||||
};
|
||||
if (specifierNode.type === 'ImportSpecifier') {
|
||||
data.importedName = keyNode2value(specifierNode.imported) as string;
|
||||
}
|
||||
specifiers.push(data);
|
||||
});
|
||||
|
||||
return {
|
||||
source,
|
||||
specifiers,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 基本的文件解析过程
|
||||
* @param ast
|
||||
*/
|
||||
export function traverseFile(ast: t.File) {
|
||||
const imports: Record<string, IImportSpecifierData[]> = {};
|
||||
|
||||
traverse(ast, {
|
||||
ImportDeclaration(path) {
|
||||
const { source, specifiers } = parseImportDeclaration(path.node);
|
||||
imports[source] = specifiers;
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
imports,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Dict } from '@music163/tango-helpers';
|
||||
import { IImportDeclarationPayload, IImportSpecifierData } from '../types';
|
||||
import { IImportDeclarationPayload, IImportSpecifierSourceData } from '../types';
|
||||
|
||||
/**
|
||||
* 导入列表解析为导入声明对象
|
||||
@@ -7,7 +7,10 @@ import { IImportDeclarationPayload, IImportSpecifierData } from '../types';
|
||||
* @param nameMap
|
||||
* @returns
|
||||
*/
|
||||
export function namesToImportDeclarations(names: string[], nameMap: Dict<IImportSpecifierData>) {
|
||||
export function namesToImportDeclarations(
|
||||
names: string[],
|
||||
nameMap: Dict<IImportSpecifierSourceData>,
|
||||
) {
|
||||
const map = {};
|
||||
names.forEach((name) => {
|
||||
const mod = nameMap[name];
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
export * from './models';
|
||||
export * from './factory';
|
||||
export * from './helpers';
|
||||
export * from './types';
|
||||
export * from './types';
|
||||
|
||||
export { default as generator } from '@babel/generator';
|
||||
export { default as traverse } from '@babel/types';
|
||||
export * from '@babel/parser';
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
ITangoConfigPackages,
|
||||
IPageConfigData,
|
||||
IServiceFunctionPayload,
|
||||
IImportSpecifierData,
|
||||
IImportSpecifierSourceData,
|
||||
} from '../types';
|
||||
import { TangoFile, TangoJsonFile } from './file';
|
||||
import { TangoRouteModule } from './route-module';
|
||||
@@ -25,7 +25,7 @@ export interface IViewFile {
|
||||
/**
|
||||
* 通过导入组件名查找组件来自的包
|
||||
*/
|
||||
importMap?: Dict<IImportSpecifierData>;
|
||||
importMap?: Dict<IImportSpecifierSourceData>;
|
||||
|
||||
listImportSources?: () => string[];
|
||||
listModals?: () => Array<{ label: string; value: string }>;
|
||||
@@ -148,8 +148,8 @@ export interface IWorkspace {
|
||||
storeModules?: Record<string, TangoStoreModule>;
|
||||
serviceModules?: Record<string, TangoServiceModule>;
|
||||
|
||||
refresh: (names: string[]) => void;
|
||||
ready: () => void;
|
||||
refresh: (names: string[]) => void;
|
||||
|
||||
setActiveRoute: (path: string) => void;
|
||||
setActiveFile: (filename: string) => void;
|
||||
@@ -159,22 +159,25 @@ export interface IWorkspace {
|
||||
|
||||
onFilesChange?: (filenames: string[]) => void;
|
||||
|
||||
getFile: (filename: string) => TangoFile;
|
||||
listFiles: () => Record<string, string>;
|
||||
addFile: (filename: string, code: string, fileType?: FileType) => void;
|
||||
// ----------------- 文件操作 -----------------
|
||||
addFiles: (files: IFileConfig[]) => void;
|
||||
updateFile: (filename: string, code: string, shouldFormatCode?: boolean) => void;
|
||||
addFile: (filename: string, code: string, fileType?: FileType) => void;
|
||||
|
||||
addServiceFile: (serviceName: string, code: string) => void;
|
||||
addStoreFile: (storeName: string, code: string) => void;
|
||||
addViewFile: (viewName: string, code: string) => void;
|
||||
|
||||
removeFile: (filename: string) => void;
|
||||
|
||||
renameFile: (oldFilename: string, newFilename: string) => void;
|
||||
renameFolder: (oldFoldername: string, newFoldername: string) => void;
|
||||
|
||||
/**
|
||||
* 查询节点
|
||||
* @param id 节点 ID
|
||||
* @param module 节点所在的模块名
|
||||
* @returns 返回节点对象
|
||||
*/
|
||||
getNode: (id: string, module?: string) => IViewNode;
|
||||
updateFile: (filename: string, code: string, shouldFormatCode?: boolean) => void;
|
||||
|
||||
listFiles: () => Record<string, string>;
|
||||
getFile: (filename: string) => TangoFile;
|
||||
|
||||
// ----------------- 节点操作 -----------------
|
||||
|
||||
removeSelectedNode: () => void;
|
||||
cloneSelectedNode: () => void;
|
||||
@@ -195,33 +198,47 @@ export interface IWorkspace {
|
||||
relatedImports?: string[],
|
||||
) => void;
|
||||
|
||||
getServiceFunction?: (serviceKey: string) => object;
|
||||
listServiceFunctions?: () => Record<string, object>;
|
||||
removeServiceFunction?: (serviceName: string, modName?: string) => void;
|
||||
addServiceFunction?: (
|
||||
/**
|
||||
* 查询节点
|
||||
* @param id 节点 ID
|
||||
* @param module 节点所在的模块名
|
||||
* @returns 返回节点对象
|
||||
*/
|
||||
getNode: (id: string, module?: string) => IViewNode;
|
||||
|
||||
// ----------------- 服务函数文件操作 -----------------
|
||||
|
||||
getServiceFunction: (serviceKey: string) => object;
|
||||
listServiceFunctions: () => Record<string, object>;
|
||||
removeServiceFunction: (serviceName: string, modName?: string) => void;
|
||||
addServiceFunction: (
|
||||
payload: IServiceFunctionPayload | IServiceFunctionPayload[],
|
||||
modName?: string,
|
||||
) => void;
|
||||
updateServiceFunction?: (payload: IServiceFunctionPayload, modName?: string) => void;
|
||||
updateServiceBaseConfig?: (IServiceFunctionPayload: object, modName?: string) => void;
|
||||
updateServiceFunction: (payload: IServiceFunctionPayload, modName?: string) => void;
|
||||
updateServiceBaseConfig: (IServiceFunctionPayload: object, modName?: string) => void;
|
||||
|
||||
addStoreModule?: (storeName: string, code: string) => void;
|
||||
removeStoreModule?: (storeName: string) => void;
|
||||
// ----------------- 状态管理文件操作 -----------------
|
||||
addStoreState?: (storeName: string, stateName: string, initValue: string) => void;
|
||||
removeStoreState?: (storeName: string, stateName: string) => void;
|
||||
removeStoreModule?: (storeName: string) => void;
|
||||
|
||||
updateModuleCodeByVariablePath?: (variablePath: string, code: string) => void;
|
||||
// ----------------- 视图文件操作 -----------------
|
||||
|
||||
// TODO: 是否需要,和 addFile 有啥区别 ???
|
||||
addViewPage: (name: string, code: string) => void;
|
||||
removeViewModule: (routePath: string) => void;
|
||||
copyViewPage: (sourceRoutePath: string, targetPageData: IPageConfigData) => void;
|
||||
|
||||
// ----------------- 路由文件操作 -----------------
|
||||
|
||||
updateRoute: (sourceRoutePath: string, targetPageData: IPageConfigData) => void;
|
||||
|
||||
listDependencies?: () => any;
|
||||
// ----------------- 依赖包操作 -----------------
|
||||
|
||||
updateDependency?: (
|
||||
addDependency: (data: any) => void;
|
||||
listDependencies: () => any;
|
||||
getDependency: (pkgName: string) => object;
|
||||
|
||||
updateDependency: (
|
||||
name: string,
|
||||
version: string,
|
||||
options?: {
|
||||
@@ -230,7 +247,7 @@ export interface IWorkspace {
|
||||
},
|
||||
) => void;
|
||||
|
||||
removeDependency?: (name: string) => void;
|
||||
removeDependency: (name: string) => void;
|
||||
|
||||
addBizComp?: (
|
||||
name: string,
|
||||
@@ -243,6 +260,12 @@ export interface IWorkspace {
|
||||
|
||||
removeBizComp?: (name: string) => void;
|
||||
|
||||
// ----------------- 其他操作 -----------------
|
||||
|
||||
updateModuleCodeByVariablePath?: (variablePath: string, code: string) => void;
|
||||
|
||||
// ----------------- getter -----------------
|
||||
|
||||
get activeViewModule(): IViewFile;
|
||||
get pages(): any[];
|
||||
get bizComps(): string[];
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
import * as t from '@babel/types';
|
||||
import { action, computed, makeObservable, observable } from 'mobx';
|
||||
import { isNil } from '@music163/tango-helpers';
|
||||
import { code2ast, ast2code, formatCode } from '../helpers';
|
||||
import {
|
||||
code2ast,
|
||||
ast2code,
|
||||
formatCode,
|
||||
traverseFile,
|
||||
addImportDeclaration2,
|
||||
updateImportDeclaration2,
|
||||
} from '../helpers';
|
||||
import { TangoFile } from './file';
|
||||
import { IFileConfig } from '../types';
|
||||
import { IFileConfig, IImportSpecifierData, ImportDeclarationDataType } from '../types';
|
||||
import { IWorkspace } from './interfaces';
|
||||
|
||||
/**
|
||||
@@ -14,6 +21,11 @@ import { IWorkspace } from './interfaces';
|
||||
export class TangoModule extends TangoFile {
|
||||
ast: t.File;
|
||||
|
||||
/**
|
||||
* 导入语句
|
||||
*/
|
||||
imports: ImportDeclarationDataType;
|
||||
|
||||
constructor(workspace: IWorkspace, props: IFileConfig, isSyncCode = true) {
|
||||
super(workspace, props, isSyncCode);
|
||||
}
|
||||
@@ -39,6 +51,16 @@ export class TangoModule extends TangoFile {
|
||||
}
|
||||
}
|
||||
|
||||
addImportDeclaration(source: string, specifiers: IImportSpecifierData[]) {
|
||||
addImportDeclaration2(this.ast, source, specifiers);
|
||||
return this;
|
||||
}
|
||||
|
||||
updateImportDeclaration(source: string, specifiers: IImportSpecifierData[]) {
|
||||
updateImportDeclaration2(this.ast, source, specifiers);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 基于最新的 ast 进行源码同步
|
||||
*/
|
||||
@@ -69,7 +91,10 @@ export class TangoModule extends TangoFile {
|
||||
this.ast = code2ast(code);
|
||||
}
|
||||
|
||||
_analysisAst() {}
|
||||
_analysisAst() {
|
||||
const { imports } = traverseFile(this.ast);
|
||||
this.imports = imports;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -58,7 +58,8 @@ export class TangoServiceModule extends TangoModule {
|
||||
}
|
||||
|
||||
_analysisAst() {
|
||||
const { services, baseConfig } = traverseServiceFile(this.ast);
|
||||
const { imports, services, baseConfig } = traverseServiceFile(this.ast);
|
||||
this.imports = imports;
|
||||
this._serviceFunctions = services;
|
||||
this._baseConfig = baseConfig;
|
||||
if (baseConfig.namespace) {
|
||||
|
||||
@@ -24,7 +24,8 @@ import {
|
||||
ITangoViewNodeData,
|
||||
IImportDeclarationPayload,
|
||||
InsertChildPositionType,
|
||||
IImportSpecifierData,
|
||||
IImportSpecifierSourceData,
|
||||
ImportDeclarationDataType,
|
||||
} from '../types';
|
||||
import { IViewFile, IWorkspace } from './interfaces';
|
||||
import { TangoModule } from './module';
|
||||
@@ -38,7 +39,7 @@ export class TangoViewModule extends TangoModule implements IViewFile {
|
||||
/**
|
||||
* 通过导入组件名查找组件来自的包
|
||||
*/
|
||||
importMap: Dict<IImportSpecifierData>;
|
||||
importMap: Dict<IImportSpecifierSourceData>;
|
||||
|
||||
/**
|
||||
* 视图中依赖的 tango 变量,仅 stores 和 services
|
||||
@@ -51,6 +52,7 @@ export class TangoViewModule extends TangoModule implements IViewFile {
|
||||
private _nodes: Map<string, TangoNode>;
|
||||
/**
|
||||
* 导入的模块
|
||||
* @deprecated
|
||||
*/
|
||||
private _importedModules: Dict<IImportDeclarationPayload | IImportDeclarationPayload[]>;
|
||||
|
||||
@@ -98,6 +100,7 @@ export class TangoViewModule extends TangoModule implements IViewFile {
|
||||
ast: newAst,
|
||||
cleanAst,
|
||||
nodes,
|
||||
imports,
|
||||
importedModules,
|
||||
variables,
|
||||
} = traverseViewFile(this.ast, this._idGenerator);
|
||||
@@ -107,7 +110,8 @@ export class TangoViewModule extends TangoModule implements IViewFile {
|
||||
this._cleanCode = ast2code(cleanAst);
|
||||
|
||||
this._importedModules = importedModules;
|
||||
this.importMap = this.buildImportMap(importedModules);
|
||||
this.imports = imports;
|
||||
this.importMap = this.buildImportMap(imports);
|
||||
this.variables = variables;
|
||||
|
||||
this._nodes.clear();
|
||||
@@ -127,7 +131,7 @@ export class TangoViewModule extends TangoModule implements IViewFile {
|
||||
* 依赖列表
|
||||
*/
|
||||
listImportSources() {
|
||||
return Object.keys(this._importedModules);
|
||||
return Object.keys(this.imports);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -169,6 +173,8 @@ export class TangoViewModule extends TangoModule implements IViewFile {
|
||||
|
||||
/**
|
||||
* 基于组件的 prototype 信息更新导入信息
|
||||
* TODO: 和 Module.addImportDeclaration 中的保持一致
|
||||
* @deprecated
|
||||
* @param prototype
|
||||
* @param shouldUpdateCode
|
||||
*/
|
||||
@@ -183,6 +189,8 @@ export class TangoViewModule extends TangoModule implements IViewFile {
|
||||
|
||||
/**
|
||||
* 更新导入的变量(新版)
|
||||
* TODO: 和 Module.updateImportDeclaration 中的保持一致
|
||||
* @deprecated
|
||||
*/
|
||||
updateImportSpecifiers(importDeclaration: IImportDeclarationPayload) {
|
||||
const mods = this._importedModules[importDeclaration.sourcePath];
|
||||
@@ -338,24 +346,15 @@ export class TangoViewModule extends TangoModule implements IViewFile {
|
||||
return this;
|
||||
}
|
||||
|
||||
private buildImportMap(
|
||||
importedModules: Dict<IImportDeclarationPayload | IImportDeclarationPayload[]>,
|
||||
) {
|
||||
const map: Dict<IImportSpecifierData> = {};
|
||||
Object.keys(importedModules).forEach((modName) => {
|
||||
const mod = importedModules[modName];
|
||||
(Array.isArray(mod) ? mod : [mod]).forEach((item) => {
|
||||
if (item.defaultSpecifier) {
|
||||
map[item.defaultSpecifier] = {
|
||||
source: modName,
|
||||
isDefault: true,
|
||||
};
|
||||
}
|
||||
if (item.specifiers.length) {
|
||||
item.specifiers.forEach((spe) => {
|
||||
map[spe] = { source: modName };
|
||||
});
|
||||
}
|
||||
private buildImportMap(importedModules: ImportDeclarationDataType) {
|
||||
const map: Dict<IImportSpecifierSourceData> = {};
|
||||
Object.keys(importedModules).forEach((source) => {
|
||||
const specifiers = importedModules[source];
|
||||
specifiers?.forEach((specifier) => {
|
||||
map[specifier.localName] = {
|
||||
source,
|
||||
isDefault: specifier.type === 'ImportDefaultSpecifier',
|
||||
};
|
||||
});
|
||||
});
|
||||
return map;
|
||||
|
||||
@@ -312,7 +312,7 @@ export class Workspace extends EventTarget implements IWorkspace {
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加一组文件到工作区
|
||||
* 添加一组文件到工作区,如果文件同名,后面的文件会覆盖前面的文件
|
||||
* @param files
|
||||
*/
|
||||
addFiles(files: IFileConfig[] = []) {
|
||||
@@ -328,81 +328,99 @@ export class Workspace extends EventTarget implements IWorkspace {
|
||||
* @param fileType 模块类型
|
||||
*/
|
||||
addFile(filename: string, code: string, fileType?: FileType) {
|
||||
if (!this.files.has(filename)) {
|
||||
const moduleType = fileType || inferFileType(filename);
|
||||
const props = {
|
||||
filename,
|
||||
code,
|
||||
type: moduleType,
|
||||
};
|
||||
const moduleType = fileType || inferFileType(filename);
|
||||
const props = {
|
||||
filename,
|
||||
code,
|
||||
type: moduleType,
|
||||
};
|
||||
|
||||
let module;
|
||||
switch (moduleType) {
|
||||
case FileType.StoreEntryModule:
|
||||
module = new TangoStoreEntryModule(this, props);
|
||||
this.storeEntryModule = module;
|
||||
break;
|
||||
case FileType.RouteModule: {
|
||||
module = new TangoRouteModule(this, props);
|
||||
this.routeModule = module;
|
||||
// check if activeRoute exists
|
||||
const route = module.routes.find((item) => item.path === this.activeRoute);
|
||||
if (!route) {
|
||||
this.setActiveRoute(module.routes[0]?.path);
|
||||
}
|
||||
break;
|
||||
let module;
|
||||
switch (moduleType) {
|
||||
case FileType.StoreEntryModule:
|
||||
module = new TangoStoreEntryModule(this, props);
|
||||
this.storeEntryModule = module;
|
||||
break;
|
||||
case FileType.RouteModule: {
|
||||
module = new TangoRouteModule(this, props);
|
||||
this.routeModule = module;
|
||||
// check if activeRoute exists
|
||||
const route = module.routes.find((item) => item.path === this.activeRoute);
|
||||
if (!route) {
|
||||
this.setActiveRoute(module.routes[0]?.path);
|
||||
}
|
||||
case FileType.JsxViewModule:
|
||||
module = new TangoViewModule(this, props);
|
||||
break;
|
||||
case FileType.ServiceModule:
|
||||
module = new TangoServiceModule(this, props);
|
||||
this.serviceModules[module.name] = module;
|
||||
break;
|
||||
case FileType.StoreModule:
|
||||
module = new TangoStoreModule(this, props);
|
||||
this.storeModules[module.name] = module;
|
||||
break;
|
||||
case FileType.BlockEntryModule: {
|
||||
const blockName = getBlockNameByFilename(props.filename);
|
||||
const prototype: ComponentPrototypeType = {
|
||||
name: blockName,
|
||||
exportType: 'defaultExport',
|
||||
package: props.filename,
|
||||
type: 'block',
|
||||
};
|
||||
this.localBlocks[blockName] = props.filename;
|
||||
this.componentPrototypes.set(blockName, prototype);
|
||||
module = new TangoViewModule(this, props);
|
||||
break;
|
||||
}
|
||||
case FileType.Module:
|
||||
module = new TangoJsModule(this, props);
|
||||
break;
|
||||
case FileType.Less:
|
||||
module = new TangoLessFile(this, props);
|
||||
break;
|
||||
case FileType.PackageJson:
|
||||
module = new TangoJsonFile(this, props);
|
||||
this.packageJson = module;
|
||||
break;
|
||||
case FileType.TangoConfigJson:
|
||||
module = new TangoJsonFile(this, props);
|
||||
this.tangoConfigJson = module;
|
||||
break;
|
||||
case FileType.AppJson:
|
||||
module = new TangoJsonFile(this, props);
|
||||
this.appJson = module;
|
||||
break;
|
||||
case FileType.Json:
|
||||
module = new TangoJsonFile(this, props);
|
||||
break;
|
||||
default:
|
||||
module = new TangoFile(this, props);
|
||||
break;
|
||||
}
|
||||
|
||||
this.files.set(filename, module);
|
||||
case FileType.JsxViewModule:
|
||||
module = new TangoViewModule(this, props);
|
||||
break;
|
||||
case FileType.ServiceModule:
|
||||
module = new TangoServiceModule(this, props);
|
||||
this.serviceModules[module.name] = module;
|
||||
break;
|
||||
case FileType.StoreModule:
|
||||
module = new TangoStoreModule(this, props);
|
||||
this.storeModules[module.name] = module;
|
||||
break;
|
||||
case FileType.BlockEntryModule: {
|
||||
const blockName = getBlockNameByFilename(props.filename);
|
||||
const prototype: ComponentPrototypeType = {
|
||||
name: blockName,
|
||||
exportType: 'defaultExport',
|
||||
package: props.filename,
|
||||
type: 'block',
|
||||
};
|
||||
this.localBlocks[blockName] = props.filename;
|
||||
this.componentPrototypes.set(blockName, prototype);
|
||||
module = new TangoViewModule(this, props);
|
||||
break;
|
||||
}
|
||||
case FileType.Module:
|
||||
module = new TangoJsModule(this, props);
|
||||
break;
|
||||
case FileType.Less:
|
||||
module = new TangoLessFile(this, props);
|
||||
break;
|
||||
case FileType.PackageJson:
|
||||
module = new TangoJsonFile(this, props);
|
||||
this.packageJson = module;
|
||||
break;
|
||||
case FileType.TangoConfigJson:
|
||||
module = new TangoJsonFile(this, props);
|
||||
this.tangoConfigJson = module;
|
||||
break;
|
||||
case FileType.AppJson:
|
||||
module = new TangoJsonFile(this, props);
|
||||
this.appJson = module;
|
||||
break;
|
||||
case FileType.Json:
|
||||
module = new TangoJsonFile(this, props);
|
||||
break;
|
||||
default:
|
||||
module = new TangoFile(this, props);
|
||||
}
|
||||
|
||||
this.files.set(filename, module);
|
||||
}
|
||||
|
||||
addServiceFile(serviceName: string, code: string) {
|
||||
const filename = `/src/services/${serviceName}.js`;
|
||||
this.addFile(filename, code, FileType.ServiceModule);
|
||||
const indexServiceModule = this.serviceModules.index;
|
||||
indexServiceModule?.addImportDeclaration(`./${serviceName}`, []).update();
|
||||
}
|
||||
|
||||
addStoreFile(storeName: string, code: string) {
|
||||
const filename = `/src/stores/${storeName}.js`;
|
||||
this.addFile(filename, code);
|
||||
if (!this.storeEntryModule) {
|
||||
this.addFile('/src/stores/index.js', '');
|
||||
}
|
||||
this.storeEntryModule.addStore(storeName).update();
|
||||
}
|
||||
|
||||
addViewFile(viewName: string, code: string) {
|
||||
// TODO: implement it
|
||||
}
|
||||
|
||||
updateFile(filename: string, code: string, shouldFormatCode = false) {
|
||||
@@ -604,16 +622,10 @@ export class Workspace extends EventTarget implements IWorkspace {
|
||||
|
||||
/**
|
||||
* 添加新的模型文件
|
||||
* @param name 模型名
|
||||
* @param code 模型代码
|
||||
* @deprecated 使用 addStoreFile 代替
|
||||
*/
|
||||
addStoreModule(name: string, code: string) {
|
||||
const filename = `/src/stores/${name}.js`;
|
||||
this.addFile(filename, code);
|
||||
if (!this.storeEntryModule) {
|
||||
this.addFile('/src/stores/index.js', '');
|
||||
}
|
||||
this.storeEntryModule.addStore(name).update();
|
||||
this.addStoreFile(name, code);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -730,14 +742,29 @@ export class Workspace extends EventTarget implements IWorkspace {
|
||||
this.serviceModules[moduleName]?.updateBaseConfig(config).update();
|
||||
}
|
||||
|
||||
addDependency(data: any) {
|
||||
// TODO: implement it to replace addBizComp & addServiceComp
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 package.json 中的依赖列表
|
||||
* @returns
|
||||
* TODO: fix this logic to merge dependencies from package.json and tango.config.json
|
||||
*/
|
||||
listDependencies() {
|
||||
return this.packageJson?.getValue('dependencies');
|
||||
}
|
||||
|
||||
getDependency(pkgName: string) {
|
||||
const packages = this.tangoConfigJson?.getValue('packages');
|
||||
const dependencies = this.packageJson?.getValue('dependencies'); // 兼容老版本
|
||||
const detail = {
|
||||
version: dependencies?.[pkgName],
|
||||
...(packages?.[pkgName] || {}),
|
||||
};
|
||||
return detail;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新依赖,没有就添加
|
||||
* @param name
|
||||
|
||||
@@ -73,15 +73,40 @@ export interface ITangoViewNodeData<T = JSXElement> {
|
||||
}
|
||||
|
||||
/**
|
||||
* 导入变量的解析数据
|
||||
* 导入变量的来源
|
||||
*/
|
||||
export interface IImportSpecifierData {
|
||||
export interface IImportSpecifierSourceData {
|
||||
source: string;
|
||||
isDefault?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析的导入变量数据
|
||||
*/
|
||||
export interface IImportSpecifierData {
|
||||
/**
|
||||
* 导入名
|
||||
*/
|
||||
importedName?: string;
|
||||
/**
|
||||
* 本地名
|
||||
*/
|
||||
localName: string;
|
||||
/**
|
||||
* 类型
|
||||
*/
|
||||
type: 'ImportDefaultSpecifier' | 'ImportSpecifier' | 'ImportNamespaceSpecifier';
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析的导入语句数据
|
||||
* FIXME: 需要考虑到有两行重复的语句的情况
|
||||
*/
|
||||
export type ImportDeclarationDataType = Record<string, IImportSpecifierData[]>;
|
||||
|
||||
/**
|
||||
* 模块导入的参数类型
|
||||
* @deprecated 使用 IImportSpecifierData 代替
|
||||
*/
|
||||
export interface IImportDeclarationPayload {
|
||||
defaultSpecifier?: string;
|
||||
@@ -89,12 +114,6 @@ export interface IImportDeclarationPayload {
|
||||
sourcePath: string;
|
||||
}
|
||||
|
||||
export interface IClassPropertyNodeData {
|
||||
reference: string;
|
||||
propertyName: string;
|
||||
propertyBody: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 服务函数参数类型
|
||||
*/
|
||||
|
||||
@@ -126,7 +126,6 @@ describe('helpers', () => {
|
||||
});
|
||||
|
||||
describe('string helpers', () => {
|
||||
|
||||
it('value2code: empty array', () => {
|
||||
expect(value2code([])).toEqual('[]');
|
||||
});
|
||||
@@ -151,9 +150,9 @@ describe('string helpers', () => {
|
||||
it('namesToImportDeclarations', () => {
|
||||
expect(
|
||||
namesToImportDeclarations(['Button', 'Box', 'React'], {
|
||||
Button: { package: '@music163/antd' },
|
||||
Box: { package: '@music163/antd' },
|
||||
React: { package: 'react', isDefault: true },
|
||||
Button: { source: '@music163/antd' },
|
||||
Box: { source: '@music163/antd' },
|
||||
React: { source: 'react', isDefault: true },
|
||||
}),
|
||||
).toEqual([
|
||||
{ sourcePath: '@music163/antd', specifiers: ['Button', 'Box'] },
|
||||
|
||||
@@ -3,6 +3,12 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [0.5.3](https://github.com/netease/tango/compare/@music163/tango-designer@0.5.2...@music163/tango-designer@0.5.3) (2023-11-16)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- allow override existing file via addFile ([6cf673e](https://github.com/netease/tango/commit/6cf673e7f6f330613e1d6331256a59900e8cbc68))
|
||||
|
||||
## [0.5.2](https://github.com/netease/tango/compare/@music163/tango-designer@0.5.1...@music163/tango-designer@0.5.2) (2023-11-02)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@music163/tango-designer",
|
||||
"version": "0.5.2",
|
||||
"version": "0.5.3",
|
||||
"description": "lowcode designer",
|
||||
"keywords": [
|
||||
"react"
|
||||
@@ -33,12 +33,12 @@
|
||||
"dependencies": {
|
||||
"@ant-design/icons": "^4.8.0",
|
||||
"@music163/request": "^0.1.2",
|
||||
"@music163/tango-context": "^0.2.4",
|
||||
"@music163/tango-core": "^0.2.3",
|
||||
"@music163/tango-helpers": "^0.1.5",
|
||||
"@music163/tango-sandbox": "^0.1.8",
|
||||
"@music163/tango-setting-form": "^0.2.7",
|
||||
"@music163/tango-ui": "^0.1.8",
|
||||
"@music163/tango-context": "^0.2.5",
|
||||
"@music163/tango-core": "^0.2.4",
|
||||
"@music163/tango-helpers": "^0.1.6",
|
||||
"@music163/tango-sandbox": "^0.1.9",
|
||||
"@music163/tango-setting-form": "^0.2.8",
|
||||
"@music163/tango-ui": "^0.1.9",
|
||||
"antd": "^4.24.2",
|
||||
"cash-dom": "^8.1.2",
|
||||
"classnames": "^2.3.2",
|
||||
|
||||
@@ -82,6 +82,7 @@ export function ExpressionSetter(props: ExpressionSetterProps) {
|
||||
onChange(undefined);
|
||||
return;
|
||||
}
|
||||
|
||||
if (getVariableContent(code) === value2expressionCode(valueProp)) {
|
||||
return;
|
||||
}
|
||||
@@ -189,15 +190,12 @@ export function ExpressionModal({
|
||||
<Modal
|
||||
closable={false}
|
||||
destroyOnClose
|
||||
width="70%"
|
||||
width="60%"
|
||||
open={visible}
|
||||
onCancel={onCancel}
|
||||
onOk={() => {
|
||||
onOk(exp);
|
||||
}}
|
||||
okButtonProps={{
|
||||
disabled: !!error,
|
||||
}}
|
||||
bodyStyle={{
|
||||
padding: 0,
|
||||
}}
|
||||
|
||||
@@ -275,7 +275,7 @@ function ServiceDetailForm({
|
||||
{...formProps}
|
||||
>
|
||||
<Form.Item
|
||||
label="所属模块"
|
||||
label="命名空间"
|
||||
name="moduleName"
|
||||
rules={[
|
||||
{
|
||||
|
||||
@@ -3,11 +3,14 @@ import cx from 'classnames';
|
||||
import { Box, HTMLCoralProps, css } from 'coral-system';
|
||||
import { Resizable, ResizeHandle } from 'react-resizable';
|
||||
|
||||
export interface ResizableBoxProps extends HTMLCoralProps<'div'> {
|
||||
export interface ResizableBoxProps {
|
||||
width?: number;
|
||||
height?: number;
|
||||
resizeHandlePosition?: 'left' | 'right' | 'top' | 'bottom';
|
||||
axis?: 'x' | 'y';
|
||||
children?: React.ReactNode;
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
||||
const resizeHandleStyle = (axis: ResizableBoxProps['axis'], barStyle: HTMLCoralProps<'div'>) => css`
|
||||
|
||||
@@ -75,6 +75,8 @@ export interface SidebarPanelItemProps
|
||||
}
|
||||
|
||||
function BaseSidebarPanel({ panelWidth: defaultPanelWidth = 280, footer, children }: SidebarProps) {
|
||||
const designer = useDesigner();
|
||||
|
||||
const items = useMemo(() => {
|
||||
const ret: Record<string, SidebarPanelItemProps> = {};
|
||||
React.Children.forEach(children, (child) => {
|
||||
@@ -90,20 +92,6 @@ function BaseSidebarPanel({ panelWidth: defaultPanelWidth = 280, footer, childre
|
||||
return ret;
|
||||
}, [children]);
|
||||
|
||||
const designer = useDesigner();
|
||||
const panel = items[designer.activeSidebarPanel];
|
||||
const floatPanelStyle: any = panel?.isFloat
|
||||
? {
|
||||
position: 'absolute',
|
||||
left: '50px',
|
||||
top: 0,
|
||||
zIndex: 1000,
|
||||
height: '100%',
|
||||
boxShadow: 'var(--tango-shadows-lowRight)',
|
||||
}
|
||||
: {};
|
||||
const panelWidth = typeof panel?.width === 'number' ? panel?.width : defaultPanelWidth;
|
||||
|
||||
return (
|
||||
<Box display="flex" flexShrink={0} css={sidebarStyle} className="SidebarPanel">
|
||||
<Box className="SidebarPanelBar" display="flex" flexDirection="column" position="relative">
|
||||
@@ -142,19 +130,41 @@ function BaseSidebarPanel({ panelWidth: defaultPanelWidth = 280, footer, childre
|
||||
{footer}
|
||||
</Box>
|
||||
</Box>
|
||||
{panel ? (
|
||||
<ResizableBox key={panel.label} width={panelWidth} style={floatPanelStyle}>
|
||||
<SidebarPanelExpandedContent
|
||||
isFloat={panel.isFloat}
|
||||
closeable={panel.isFloat}
|
||||
onClose={() => {
|
||||
designer.closeSidebarPanel();
|
||||
{Object.keys(items).map((key) => {
|
||||
const panel = items[key];
|
||||
const floatPanelStyle: any = panel?.isFloat
|
||||
? {
|
||||
position: 'absolute',
|
||||
left: '50px',
|
||||
top: 0,
|
||||
zIndex: 1000,
|
||||
height: '100%',
|
||||
boxShadow: 'var(--tango-shadows-lowRight)',
|
||||
}
|
||||
: {};
|
||||
const panelWidth = typeof panel?.width === 'number' ? panel?.width : defaultPanelWidth;
|
||||
|
||||
return (
|
||||
<ResizableBox
|
||||
key={panel.label}
|
||||
width={panelWidth}
|
||||
style={{
|
||||
display: designer.activeSidebarPanel === key ? 'block' : 'none',
|
||||
...floatPanelStyle,
|
||||
}}
|
||||
>
|
||||
{panel.children}
|
||||
</SidebarPanelExpandedContent>
|
||||
</ResizableBox>
|
||||
) : null}
|
||||
<SidebarPanelExpandedContent
|
||||
isFloat={panel.isFloat}
|
||||
closeable={panel.isFloat}
|
||||
onClose={() => {
|
||||
designer.closeSidebarPanel();
|
||||
}}
|
||||
>
|
||||
{panel.children}
|
||||
</SidebarPanelExpandedContent>
|
||||
</ResizableBox>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ export const VariablePanel = observer(
|
||||
existNames={storeNames}
|
||||
onCancel={off}
|
||||
onSubmit={(values) => {
|
||||
workspace.addStoreModule(values.name, newStoreTemplate);
|
||||
workspace.addStoreFile(values.name, newStoreTemplate);
|
||||
off();
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import React from 'react';
|
||||
import { Group } from 'coral-system';
|
||||
import { Modal } from 'antd';
|
||||
import { ToggleButton } from '@music163/tango-ui';
|
||||
import { observer, useDesigner } from '@music163/tango-context';
|
||||
import { observer, useDesigner, useWorkspace } from '@music163/tango-context';
|
||||
|
||||
export const ModeSwitchTool = observer(() => {
|
||||
const workspace = useWorkspace();
|
||||
const designer = useDesigner();
|
||||
return (
|
||||
<Group attached>
|
||||
@@ -13,7 +15,7 @@ export const ModeSwitchTool = observer(() => {
|
||||
onClick={() => {
|
||||
designer.setActiveView('design');
|
||||
}}
|
||||
tip="设计视图"
|
||||
tooltip="设计视图"
|
||||
>
|
||||
设计
|
||||
</ToggleButton>
|
||||
@@ -23,8 +25,16 @@ export const ModeSwitchTool = observer(() => {
|
||||
onClick={() => {
|
||||
designer.setActiveView('code'); // 切换到源码视图
|
||||
designer.setActiveSidebarPanel(''); // 关闭左侧面板
|
||||
if (workspace.activeFile !== workspace.activeViewFile) {
|
||||
Modal.confirm({
|
||||
title: '当前打开的文件与视图不匹配,是否切换到当前视图对应的文件?',
|
||||
onOk: () => {
|
||||
workspace.setActiveFile(workspace.activeViewFile);
|
||||
},
|
||||
});
|
||||
}
|
||||
}}
|
||||
tip="源码视图"
|
||||
tooltip="源码视图"
|
||||
>
|
||||
源码
|
||||
</ToggleButton>
|
||||
|
||||
@@ -3,6 +3,13 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [0.1.6](https://github.com/netease/tango/compare/@music163/tango-helpers@0.1.5...@music163/tango-helpers@0.1.6) (2023-11-16)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- isVariableString ([b9403cf](https://github.com/netease/tango/commit/b9403cfec668b2717a68e92a6f837d5a88096c7c))
|
||||
- parse variables from view ([089a482](https://github.com/netease/tango/commit/089a482f750e9d9a7743a6641d6e39989347d318))
|
||||
|
||||
## [0.1.5](https://github.com/netease/tango/compare/@music163/tango-helpers@0.1.4...@music163/tango-helpers@0.1.5) (2023-11-02)
|
||||
|
||||
**Note:** Version bump only for package @music163/tango-helpers
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@music163/tango-helpers",
|
||||
"version": "0.1.5",
|
||||
"version": "0.1.6",
|
||||
"description": "Shared types, helpers, and hooks of tango-apps",
|
||||
"keywords": [
|
||||
"shared",
|
||||
|
||||
@@ -238,17 +238,26 @@ export function getVariableContent(str: string) {
|
||||
return str;
|
||||
}
|
||||
|
||||
// 提供给代码执行环境的全局变量
|
||||
const patchCode = `
|
||||
var tango = {
|
||||
stores: {},
|
||||
services: {},
|
||||
config: {},
|
||||
refs: {},
|
||||
};
|
||||
`;
|
||||
|
||||
/**
|
||||
* 将代码放到函数体中进行执行
|
||||
* @param code
|
||||
* @returns 函数执行的结果
|
||||
*/
|
||||
export function executeCodeAsFunctionBody(code: string) {
|
||||
export function runCode(code: string) {
|
||||
let ret;
|
||||
try {
|
||||
// eslint-disable-next-line no-new-func
|
||||
ret = new Function(code);
|
||||
ret = ret();
|
||||
ret = new Function(`${patchCode}\n return ${code}`)();
|
||||
} catch (err) {
|
||||
// ignore error
|
||||
}
|
||||
@@ -261,14 +270,14 @@ const objectWrapperPattern = /^[{\[].*[}\]]$/s;
|
||||
/**
|
||||
* 将代码片段转成 js 对象
|
||||
* @param code 代码文本
|
||||
* @param isStrict 是否为严格模式
|
||||
* @param isStrict 是否为严格模式(是否废弃)
|
||||
* @returns
|
||||
*/
|
||||
export function code2object(code: string, isStrict = true) {
|
||||
// 非严格模式直接执行
|
||||
// 严格模式下需检测 code 是一个对象
|
||||
if (!isStrict || (isStrict && objectWrapperPattern.test(code))) {
|
||||
const ret = executeCodeAsFunctionBody(`return ${code}`);
|
||||
const ret = runCode(code);
|
||||
return typeof ret === 'object' ? ret : undefined;
|
||||
}
|
||||
return code;
|
||||
|
||||
@@ -42,9 +42,9 @@ describe('string', () => {
|
||||
expect(isVariableString('{value}')).toBeTruthy();
|
||||
expect(isVariableString('{"hello"}')).toBeTruthy();
|
||||
expect(isVariableString('{ foo: "bar" }')).toBeFalsy();
|
||||
expect(isVariableString('{{ foo: "bar" }}')).toBeTruthy();
|
||||
expect(isVariableString('{() => {}}')).toBeTruthy();
|
||||
expect(isVariableString('{() => { console.log(); }}')).toBeTruthy();
|
||||
expect(isVariableString('{ type: tango.stores?.homePage?.tabKey }')).toBeFalsy();
|
||||
// expect(isVariableString('{ type: tango.stores.homePage.tabKey }')).toBeFalsy(); // FIXME: fix this case
|
||||
expect(isVariableString('{ foo: "bar" }')).toBeFalsy();
|
||||
});
|
||||
|
||||
it('getVariableContent', () => {
|
||||
|
||||
@@ -3,6 +3,12 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [0.1.9](https://github.com/netease/tango/compare/@music163/tango-sandbox@0.1.8...@music163/tango-sandbox@0.1.9) (2023-11-16)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- fix sandbox props ([afb94d6](https://github.com/netease/tango/commit/afb94d6a304f217df24a5d3d986dec1670cd29a9))
|
||||
|
||||
## [0.1.8](https://github.com/netease/tango/compare/@music163/tango-sandbox@0.1.7...@music163/tango-sandbox@0.1.8) (2023-11-02)
|
||||
|
||||
**Note:** Version bump only for package @music163/tango-sandbox
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@music163/tango-sandbox",
|
||||
"version": "0.1.8",
|
||||
"version": "0.1.9",
|
||||
"description": "sandbox of tango apps",
|
||||
"author": "wwsun <ww.sun@outlook.com>",
|
||||
"homepage": "",
|
||||
@@ -29,8 +29,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@ant-design/icons": "^4.8.0",
|
||||
"@music163/tango-core": "^0.2.3",
|
||||
"@music163/tango-helpers": "^0.1.5",
|
||||
"@music163/tango-core": "^0.2.4",
|
||||
"@music163/tango-helpers": "^0.1.6",
|
||||
"crypto-js": "^4.1.1",
|
||||
"lodash.isequal": "4.5.0",
|
||||
"react-frame-component": "^5.2.4"
|
||||
|
||||
@@ -21,6 +21,7 @@ import { IFiles, CodeSandboxState, CodeSandboxProps, UnsubscribeFunction } from
|
||||
// };
|
||||
export class CodeSandbox extends React.Component<CodeSandboxProps, CodeSandboxState> {
|
||||
static defaultProps = {
|
||||
template: 'create-react-app',
|
||||
skipEval: false,
|
||||
eventHandlers: {},
|
||||
externalResources: [] as string[],
|
||||
@@ -181,9 +182,10 @@ export class CodeSandbox extends React.Component<CodeSandboxProps, CodeSandboxSt
|
||||
};
|
||||
|
||||
render() {
|
||||
const { style } = this.props;
|
||||
const { loading, status, iframeId } = this.state;
|
||||
return (
|
||||
<div className="CodeSandbox" style={{ position: 'relative', height: '100%' }}>
|
||||
<div className="CodeSandbox" style={{ position: 'relative', height: '100%', ...style }}>
|
||||
{loading ? <Loading status={status} /> : null}
|
||||
<iframe
|
||||
ref={this.setupFrame}
|
||||
|
||||
@@ -504,6 +504,7 @@ export interface CodeSandboxProps {
|
||||
isFile: (path: string) => Promise<boolean>;
|
||||
readFile: (path: string) => Promise<string>;
|
||||
};
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
||||
export interface ViteSandboxProps {
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [0.2.8](https://github.com/netease/tango/compare/@music163/tango-setting-form@0.2.7...@music163/tango-setting-form@0.2.8) (2023-11-16)
|
||||
|
||||
**Note:** Version bump only for package @music163/tango-setting-form
|
||||
|
||||
## [0.2.7](https://github.com/netease/tango/compare/@music163/tango-setting-form@0.2.6...@music163/tango-setting-form@0.2.7) (2023-11-02)
|
||||
|
||||
**Note:** Version bump only for package @music163/tango-setting-form
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@music163/tango-setting-form",
|
||||
"version": "0.2.7",
|
||||
"version": "0.2.8",
|
||||
"description": "setting form of tango-apps",
|
||||
"author": "wwsun <ww.sun@outlook.com>",
|
||||
"homepage": "",
|
||||
@@ -28,8 +28,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@ant-design/icons": "^4.8.0",
|
||||
"@music163/tango-helpers": "^0.1.5",
|
||||
"@music163/tango-ui": "^0.1.8",
|
||||
"@music163/tango-helpers": "^0.1.6",
|
||||
"@music163/tango-ui": "^0.1.9",
|
||||
"antd": "^4.24.2",
|
||||
"coral-system": "^1.0.5",
|
||||
"mobx": "6.9.0",
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [0.1.9](https://github.com/netease/tango/compare/@music163/tango-ui@0.1.8...@music163/tango-ui@0.1.9) (2023-11-16)
|
||||
|
||||
**Note:** Version bump only for package @music163/tango-ui
|
||||
|
||||
## [0.1.8](https://github.com/netease/tango/compare/@music163/tango-ui@0.1.7...@music163/tango-ui@0.1.8) (2023-11-02)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@music163/tango-ui",
|
||||
"version": "0.1.8",
|
||||
"version": "0.1.9",
|
||||
"description": "ui widgets of tango",
|
||||
"keywords": [
|
||||
"react",
|
||||
@@ -38,7 +38,7 @@
|
||||
"@codemirror/lang-javascript": "^6.2.1",
|
||||
"@codemirror/lint": "^6.4.2",
|
||||
"@codemirror/search": "^6.5.4",
|
||||
"@music163/tango-helpers": "^0.1.5",
|
||||
"@music163/tango-helpers": "^0.1.6",
|
||||
"@uiw/react-codemirror": "^4.21.20",
|
||||
"antd": "^4.24.2",
|
||||
"antd-easy-form": "^1.1.3",
|
||||
|
||||
Reference in New Issue
Block a user