mirror of
https://github.com/alibaba/lowcode-engine.git
synced 2026-04-23 18:28:09 +00:00
feat: first commit - genesis
This commit is contained in:
parent
2ea4ef2735
commit
4f4ac5115d
13
.editorconfig
Normal file
13
.editorconfig
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
end_of_line = lf
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
|
quote_type = single
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
18
.eslintignore
Normal file
18
.eslintignore
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# 忽略目录
|
||||||
|
node_modules
|
||||||
|
test-cases
|
||||||
|
test
|
||||||
|
output
|
||||||
|
build
|
||||||
|
dist
|
||||||
|
demo
|
||||||
|
es
|
||||||
|
lib
|
||||||
|
tests
|
||||||
|
.*
|
||||||
|
~*
|
||||||
|
|
||||||
|
# 忽略文件
|
||||||
|
**/*.min.js
|
||||||
|
**/*-min.js
|
||||||
|
**/*.bundle.js
|
||||||
38
.eslintrc.js
Normal file
38
.eslintrc.js
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
module.exports = {
|
||||||
|
extends: 'eslint-config-ali/typescript/react',
|
||||||
|
rules: {
|
||||||
|
'react/no-multi-comp': 0,
|
||||||
|
'no-unused-expressions': 0,
|
||||||
|
'implicit-arrow-linebreak': 1,
|
||||||
|
'no-nested-ternary': 1,
|
||||||
|
'no-mixed-operators': 1,
|
||||||
|
'@typescript-eslint/ban-types': 1,
|
||||||
|
'no-shadow': 1,
|
||||||
|
'no-prototype-builtins': 1,
|
||||||
|
'no-useless-constructor': 1,
|
||||||
|
'no-empty-function': 1,
|
||||||
|
'@typescript-eslint/member-ordering': 0,
|
||||||
|
'lines-between-class-members': 0,
|
||||||
|
'no-await-in-loop': 0,
|
||||||
|
'no-plusplus': 0,
|
||||||
|
'@typescript-eslint/no-parameter-properties': 0,
|
||||||
|
'@typescript-eslint/no-unused-vars': 1,
|
||||||
|
'no-multi-assign': 1,
|
||||||
|
'no-dupe-class-members': 1,
|
||||||
|
'react/no-deprecated': 1,
|
||||||
|
'no-useless-escape': 1,
|
||||||
|
'brace-style': 1,
|
||||||
|
'@typescript-eslint/no-inferrable-types': 0,
|
||||||
|
'no-proto': 0,
|
||||||
|
'prefer-const': 0,
|
||||||
|
'eol-last': 0,
|
||||||
|
'react/no-find-dom-node': 0,
|
||||||
|
'no-case-declarations': 0,
|
||||||
|
'@typescript-eslint/indent': 0,
|
||||||
|
'import/no-cycle': 0,
|
||||||
|
'@typescript-eslint/no-shadow': 0,
|
||||||
|
"@typescript-eslint/method-signature-style": 0,
|
||||||
|
"@typescript-eslint/consistent-type-assertions": 0,
|
||||||
|
"@typescript-eslint/no-useless-constructor": 0,
|
||||||
|
}
|
||||||
|
};
|
||||||
108
.gitignore
vendored
Normal file
108
.gitignore
vendored
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
# project custom
|
||||||
|
build
|
||||||
|
dist
|
||||||
|
packages/*/lib/
|
||||||
|
packages/*/es/
|
||||||
|
packages/*/dist/
|
||||||
|
packages/*/output/
|
||||||
|
packages/demo/
|
||||||
|
package-lock.json
|
||||||
|
yarn.lock
|
||||||
|
deploy-space/packages
|
||||||
|
deploy-space/.env
|
||||||
|
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.vscode
|
||||||
|
.idea
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Bower dependency directory (https://bower.io/)
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
lib
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# TypeScript v1 declaration files
|
||||||
|
typings/
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variables file
|
||||||
|
.env
|
||||||
|
.env.test
|
||||||
|
|
||||||
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
|
.cache
|
||||||
|
|
||||||
|
# next.js build output
|
||||||
|
.next
|
||||||
|
|
||||||
|
# nuxt.js build output
|
||||||
|
.nuxt
|
||||||
|
|
||||||
|
# vuepress build output
|
||||||
|
.vuepress/dist
|
||||||
|
|
||||||
|
# Serverless directories
|
||||||
|
.serverless/
|
||||||
|
|
||||||
|
# FuseBox cache
|
||||||
|
.fusebox/
|
||||||
|
|
||||||
|
# DynamoDB Local files
|
||||||
|
.dynamodb/
|
||||||
|
|
||||||
|
# mac config files
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# codealike
|
||||||
|
codealike.json
|
||||||
|
.node
|
||||||
7
.prettierrc.js
Normal file
7
.prettierrc.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
module.exports = {
|
||||||
|
printWidth: 100,
|
||||||
|
tabWidth: 2,
|
||||||
|
semi: true,
|
||||||
|
singleQuote: true,
|
||||||
|
trailingComma: 'all',
|
||||||
|
};
|
||||||
9
.stylelintignore
Normal file
9
.stylelintignore
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# 忽略目录
|
||||||
|
node_modules/
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
|
||||||
|
# 忽略文件
|
||||||
|
**/*.min.css
|
||||||
|
**/*-min.css
|
||||||
|
**/*.bundle.css
|
||||||
6
.stylelintrc.js
Normal file
6
.stylelintrc.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
module.exports = {
|
||||||
|
extends: 'stylelint-config-ali',
|
||||||
|
rules: {
|
||||||
|
"selector-max-id": 2
|
||||||
|
}
|
||||||
|
};
|
||||||
2634
CHANGELOG.md
Normal file
2634
CHANGELOG.md
Normal file
File diff suppressed because it is too large
Load Diff
23
CONTRIBUTOR.md
Normal file
23
CONTRIBUTOR.md
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
十分感谢参与贡献过低代码引擎的小伙伴们,下面名单按字母排序:
|
||||||
|
- [albertxiao1994](https://github.com/albertxiao1994)
|
||||||
|
- [alex-mm](https://github.com/alex-mm)
|
||||||
|
- [alvarto](https://github.com/alvarto)
|
||||||
|
- [chenmingjia](https://github.com/chenmingjia)
|
||||||
|
- [Clarence-pan](https://github.com/Clarence-pan)
|
||||||
|
- [hujiulong](https://github.com/hujiulong)
|
||||||
|
- [hzd822](https://github.com/hzd822)
|
||||||
|
- [JackLian](https://github.com/JackLian)
|
||||||
|
- [jayjliang](https://github.com/jayjliang)
|
||||||
|
- [Jeffery-Young](https://github.com/Jeffery-Young)
|
||||||
|
- [jinggk](https://github.com/jinggk)
|
||||||
|
- [junlonghuo](https://github.com/junlonghuo)
|
||||||
|
- [leoyuan](https://github.com/leoyuan)
|
||||||
|
- [liujuping](https://github.com/liujuping)
|
||||||
|
- [lqy978599280](https://github.com/lqy978599280)
|
||||||
|
- [mark-ck](https://github.com/mark-ck)
|
||||||
|
- [mochen666](https://github.com/mochen666)
|
||||||
|
- [tsy77](https://github.com/tsy77)
|
||||||
|
- [Ychangqing](https://github.com/Ychangqing)
|
||||||
|
- [youluna](https://github.com/youluna)
|
||||||
|
|
||||||
|
如果您贡献过低代码引擎,但是没有看到您的名字,为我们的疏忽感到抱歉。欢迎您通过 PR 补充上自己的名字。
|
||||||
136
README.md
Normal file
136
README.md
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
<p align="center">
|
||||||
|
<a href="http://lowcode-engine.cn">
|
||||||
|
<img width="200" src="https://img.alicdn.com/imgextra/i3/O1CN01i8K9cD1d0HU7TjDtv_!!6000000003673-2-tps-500-591.png">
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h1 align="center">LowCodeEngine</h1>
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
|
||||||
|
一套面向扩展设计的企业级低代码技术体系
|
||||||
|
|
||||||
|
[![NPM version][npm-image]][npm-url] [![NPM downloads][download-image]][download-url]
|
||||||
|
|
||||||
|
[![Discussions][discussions-image]][discussions-url] [![][issues-helper-image]][issues-helper-url] [![Issues need help][help-wanted-image]][help-wanted-url]
|
||||||
|
|
||||||
|
[npm-image]: https://img.shields.io/npm/v/@alilc/lowcode-engine.svg?style=flat-square
|
||||||
|
[npm-url]: http://npmjs.org/package/@alilc/lowcode-engine
|
||||||
|
|
||||||
|
[download-image]: https://img.shields.io/npm/dm/@alilc/lowcode-engine.svg?style=flat-square
|
||||||
|
[download-url]: https://npmjs.org/package/@alilc/lowcode-engine
|
||||||
|
[help-wanted-image]: https://flat.badgen.net/github/label-issues/alibaba/lowcode-engine/help%20wanted/open
|
||||||
|
[help-wanted-url]: https://github.com/alibaba/lowcode-engine/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22
|
||||||
|
[discussions-image]: https://img.shields.io/badge/discussions-on%20github-blue?style=flat-square
|
||||||
|
[discussions-url]: https://github.com/alibaba/lowcode-engine/discussions
|
||||||
|
|
||||||
|
[issues-helper-image]: https://img.shields.io/badge/using-issues--helper-orange?style=flat-square
|
||||||
|
[issues-helper-url]: https://github.com/actions-cool/issues-helper
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
[](http://lowcode-engine.cn)
|
||||||
|
|
||||||
|
## ✨ 特性
|
||||||
|
|
||||||
|
- 🌈 提炼自企业级低代码平台的面向扩展开发的内核引擎,奉行最小内核,最强生态的设计理念
|
||||||
|
- 📦 开箱即用的高质量生态元素,包括 物料体系、设置器、插件 等
|
||||||
|
- ⚙️ 完善的工具链,支持 物料体系、设置器、插件 等生态元素的全链路研发周期
|
||||||
|
- 🔌 强大的扩展能力,已支撑近 100 个各种垂直类低代码平台
|
||||||
|
- 🛡 使用 TypeScript 开发,提供完整的类型定义文件
|
||||||
|
|
||||||
|
## 🎯 兼容环境
|
||||||
|
|
||||||
|
- 现代浏览器(Chrome >= 80, Edge >= 80, last 2 safari versions, last 2 firefox versions)
|
||||||
|
|
||||||
|
## 📚 引擎协议
|
||||||
|
|
||||||
|
引擎完整实现了《阿里巴巴中后台前端基础搭建协议规范》和《阿里巴巴中后台前端物料协议规范》,协议栈是低代码领域的物料能否流通的关键部分。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## 🌰 使用示例
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install @alilc/lowcode-engine --save-dev
|
||||||
|
```
|
||||||
|
|
||||||
|
> **TIPS:仅支持 cdn 方式引入,npm 包用于提供 typings 等代码提示能力**
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { init, skeleton } from '@alilc/lowcode-engine';
|
||||||
|
|
||||||
|
skeleton.add({
|
||||||
|
area: 'topArea',
|
||||||
|
type: 'Widget',
|
||||||
|
name: 'logo',
|
||||||
|
content: YourFantaticLogo,
|
||||||
|
contentProps: {
|
||||||
|
logo:
|
||||||
|
'https://img.alicdn.com/tfs/TB1_SocGkT2gK0jSZFkXXcIQFXa-66-66.png',
|
||||||
|
href: '/',
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
align: 'left',
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
init(document.getElementById('lce'));
|
||||||
|
```
|
||||||
|
|
||||||
|
### 工程化配置:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"externals": {
|
||||||
|
"@alilc/lowcode-engine": "var window.AliLowCodeEngine",
|
||||||
|
"@alilc/lowcode-engine-ext": "var window.AliLowCodeEngineExt"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### cdn 可选方式:
|
||||||
|
#### 方式 1:unpkg
|
||||||
|
```html
|
||||||
|
https://unpkg.com/@alilc/lowcode-engine@1.0.0/dist/js/engine-core.js
|
||||||
|
|
||||||
|
https://unpkg.com/@alilc/lowcode-react-simulator-renderer@1.0.0/dist/js/react-simulator-renderer.js
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 方式 2:jsdelivr
|
||||||
|
```html
|
||||||
|
https://cdn.jsdelivr.net/npm/@alilc/lowcode-engine@1.0.0/dist/js/engine-core.js
|
||||||
|
|
||||||
|
https://cdn.jsdelivr.net/npm/@alilc/lowcode-react-simulator-renderer@1.0.0/dist/js/react-simulator-renderer.js
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 方式 3:使用自有 cdn
|
||||||
|
将源码中 packages/engine/dist 和 packages/(react|rax)-simulator-renderer/dist 下的文件传至你的 cdn 提供商
|
||||||
|
|
||||||
|
## 🔗 链接
|
||||||
|
|
||||||
|
- [官网首页 WIP](http://lowcode-engine.cn/)
|
||||||
|
- [官方物料 WIP](http://lowcode-engine.cn/)
|
||||||
|
- [官方设置器(setter)WIP](http://lowcode-engine.cn/)
|
||||||
|
- [官方插件(plugin)WIP](http://lowcode-engine.cn/)
|
||||||
|
- [用户文档 WIP](http://lowcode-engine.cn/)
|
||||||
|
- [贡献指南 WIP](http://lowcode-engine.cn/)
|
||||||
|
- [更新日志](CHANGELOG..md)
|
||||||
|
|
||||||
|
## 💻 本地调试
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ git clone git@github.com:alibaba/lowcode-engine.git
|
||||||
|
$ cd lowcode-engine
|
||||||
|
$ npm install
|
||||||
|
$ npm run setup
|
||||||
|
$ npm start
|
||||||
|
```
|
||||||
|
|
||||||
|
lowcode-engine 启动后,提供了几个 umd 文件,可以结合 [lowcode-demo](https://github.com/alibaba/lowcode-demo) 项目做调试,文件代理规则参考这里。
|
||||||
|
|
||||||
|
## 🤝 参与共建
|
||||||
|
|
||||||
|
请先阅读 [贡献指南 WIP](http://lowcode-engine.cn/docs/react/contributing-cn).
|
||||||
|
|
||||||
|
> 强烈推荐阅读 [《提问的智慧》](https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way)、[《如何向开源社区提问题》](https://github.com/seajs/seajs/issues/545) 和 [《如何有效地报告 Bug》](http://www.chiark.greenend.org.uk/%7Esgtatham/bugs-cn.html)、[《如何向开源项目提交无法解答的问题》](https://zhuanlan.zhihu.com/p/25795393),更好的问题更容易获得帮助。
|
||||||
11
abc.json
Normal file
11
abc.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"name": "lowcode-engine",
|
||||||
|
"assets": {
|
||||||
|
"type": "command",
|
||||||
|
"command": {
|
||||||
|
"cmd": [
|
||||||
|
"./scripts/deploy.sh $BUILD_DEST"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
3
commitlint.config.js
Normal file
3
commitlint.config.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
module.exports = {
|
||||||
|
extends: ['ali'],
|
||||||
|
};
|
||||||
9
deploy-space/lerna.json
Normal file
9
deploy-space/lerna.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"version": "independent",
|
||||||
|
"npmClient": "yarn",
|
||||||
|
"registry": "http://registry.npm.alibaba-inc.com",
|
||||||
|
"useWorkspaces": true,
|
||||||
|
"packages": [
|
||||||
|
"packages/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
16
deploy-space/package.json
Normal file
16
deploy-space/package.json
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"private": true,
|
||||||
|
"workspaces": {
|
||||||
|
"packages": [
|
||||||
|
"packages/*"
|
||||||
|
],
|
||||||
|
"nohoist": [
|
||||||
|
"**/css-modules-typescript-loader",
|
||||||
|
"**/@alife/theme-lowcode-*"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^1.11.1",
|
||||||
|
"typescript": "^3.8.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
79
deploy-space/static/index.html
Normal file
79
deploy-space/static/index.html
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta http-equiv="x-ua-compatible" content="ie=edge,chrome=1" />
|
||||||
|
<meta name="viewport" content="width=device-width" />
|
||||||
|
<title>LowCodeEngine Editor DEMO</title>
|
||||||
|
<link rel="shortcut icon" href="./favicon.png" />
|
||||||
|
<script src="https://g.alicdn.com/code/lib/react/16.9.0/umd/react.development.js"></script>
|
||||||
|
<script src="https://g.alicdn.com/code/lib/react-dom/16.9.0/umd/react-dom.development.js"></script>
|
||||||
|
<script src="https://g.alicdn.com/code/lib/prop-types/15.7.2/prop-types.js"></script>
|
||||||
|
<script> React.PropTypes = PropTypes; </script>
|
||||||
|
<script src="https://g.alicdn.com/platform/c/??react15-polyfill/0.0.1/dist/index.js,lodash/4.6.1/lodash.min.js,immutable/3.7.6/dist/immutable.min.js,natty-storage/2.0.2/dist/natty-storage.min.js,natty-fetch/2.6.0/dist/natty-fetch.pc.min.js,tinymce/4.2.5/tinymce-full.js"></script>
|
||||||
|
<script src="https://g.alicdn.com/mylib/moment/2.24.0/min/moment.min.js"></script>
|
||||||
|
<link rel="stylesheet" href="https://alifd.alicdn.com/npm/@alifd/next/1.11.6/next.min.css" />
|
||||||
|
<script src="https://unpkg.alibaba-inc.com/@alifd/next@1.18.17/dist/next.min.js"></script>
|
||||||
|
<!-- lowcode engine globals -->
|
||||||
|
<link rel="stylesheet" href="./editor-preset-vision.css" />
|
||||||
|
<!-- lowcode engine app -->
|
||||||
|
<link rel="stylesheet" href="./lowcode-editor.css" />
|
||||||
|
<script>
|
||||||
|
window.pageConfig = {
|
||||||
|
env: 'release',
|
||||||
|
locale: 'zh_CN',
|
||||||
|
pageType: 'single',
|
||||||
|
deviceType: 'web',
|
||||||
|
appName: '基础包管理后台',
|
||||||
|
appType: '',
|
||||||
|
templateType: '',
|
||||||
|
pageId: 'FORM-3KYJN7RV-DIOD8LLK1WGQ89S7NHA92-QJVH497K-V',
|
||||||
|
slug: 'test',
|
||||||
|
appMode: 'back',
|
||||||
|
isAppAdmin: 'y',
|
||||||
|
isSuperAdmin: 'n',
|
||||||
|
isBetaDeveloper: 'n',
|
||||||
|
formType: 'display',
|
||||||
|
title: { en_US: '测试', type: 'i18n', zh_CN: '测试' },
|
||||||
|
urlPrefix: 'https://go.alibaba-inc.com',
|
||||||
|
APIUrlPrefix: 'https://mocks.alibaba-inc.com/mock/lowCodeEngine',
|
||||||
|
devVersion: '0.1.0', // 这个是子应用的变更 id
|
||||||
|
subAppType: '0.1.0',
|
||||||
|
appKey: '',
|
||||||
|
RE_VERSION: '7.1.1',
|
||||||
|
appSource: '',
|
||||||
|
isDomainDefault: 'n',
|
||||||
|
useReleaseBundle: 'n',
|
||||||
|
isDomainPkg: 'n',
|
||||||
|
medusaAppName: '',
|
||||||
|
domainCode: 'kS6SyH',
|
||||||
|
aecp: {
|
||||||
|
mdcDomain: '',
|
||||||
|
projectId: '',
|
||||||
|
appCode: '',
|
||||||
|
},
|
||||||
|
designerConfigs: {},
|
||||||
|
navConfig:
|
||||||
|
'{"appName":{"en_US":"基础包管理后台","key":"","type":"i18n","zh_CN":"基础包管理后台"},"bgColor":"white","data":[{"children":[],"hidden":false,"icon":"","inner":true,"navUuid":"FORM-3KYJN7RV-DIOD8LLK1WGQ89S7NHA92-QJVH497K-V","relateUuid":"FORM-3KYJN7RV-DIOD8LLK1WGQ89S7NHA92-QJVH497K-V","slug":"test","targetNew":false,"title":{"en_US":"测试","type":"i18n","zh_CN":"测试"}}],"isFixed":"y","isFold":"y","isFoldHorizontal":"n","languageChangeUrl":{"en_US":"/common/account/changeAccountLanguage.json","type":"i18n","zh_CN":"/common/account/changeAccountLanguage.json"},"layout":"auto","navStyle":"orange","navTheme":"light","openSubMode":false,"showAppTitle":true,"showCrumb":true,"showIcon":false,"showLanguageChange":true,"showNav":true,"showSearch":"n","singletons":{"FORM-3KYJN7RV-DIOD8LLK1WGQ89S7NHA92-QJVH497K-V":{"isFixed":"n","isFold":"n","isFoldHorizontal":"n","showAppTitle":false,"showCrumb":false,"showLanguageChange":false,"showNav":false,"showSearch":"n","singleton":false},"test":{"$ref":"$.singletons.FORM\\-3KYJN7RV\\-DIOD8LLK1WGQ89S7NHA92\\-QJVH497K\\-V"}},"type":"top_fold"}',
|
||||||
|
historyType: 'HASH',
|
||||||
|
isSinglePage: 'n',
|
||||||
|
rhino: 'n',
|
||||||
|
isMiniApp: '',
|
||||||
|
taskId: '',
|
||||||
|
appSchema: 'V5',
|
||||||
|
openSubMode: 'n',
|
||||||
|
};
|
||||||
|
window.g_config = {};
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="lce-container"></div>
|
||||||
|
<!-- lowcode engine globals -->
|
||||||
|
<script src="./editor-preset-vision.js"></script>
|
||||||
|
<script src="https://dev.g.alicdn.com/vision/visualengine-utils/5.0.0/engine-utils.js"></script>
|
||||||
|
<link rel="stylesheet" type="text/css" href="//g.alicdn.com/??platform/common/s/1.1/global/global.css,uxcore/uxcore-kuma/2.2.1/orange.min.css">
|
||||||
|
<!-- lowcode engine app -->
|
||||||
|
<script src="./lowcode-editor.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
83
deploy-space/static/preview.html
Normal file
83
deploy-space/static/preview.html
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta http-equiv="x-ua-compatible" content="ie=edge,chrome=1" />
|
||||||
|
<meta name="viewport" content="width=device-width" />
|
||||||
|
<title>LowCodeEngine Editor DEMO</title>
|
||||||
|
<link rel="shortcut icon" href="./favicon.png" />
|
||||||
|
<script src="https://g.alicdn.com/code/lib/react/16.9.0/umd/react.development.js"></script>
|
||||||
|
<script src="https://g.alicdn.com/code/lib/react-dom/16.9.0/umd/react-dom.development.js"></script>
|
||||||
|
<script src="https://g.alicdn.com/code/lib/prop-types/15.7.2/prop-types.js"></script>
|
||||||
|
<script> React.PropTypes = PropTypes; </script>
|
||||||
|
<script src="https://g.alicdn.com/platform/c/??react15-polyfill/0.0.1/dist/index.js,lodash/4.6.1/lodash.min.js,immutable/3.7.6/dist/immutable.min.js,natty-storage/2.0.2/dist/natty-storage.min.js,natty-fetch/2.6.0/dist/natty-fetch.pc.min.js,tinymce/4.2.5/tinymce-full.js"></script>
|
||||||
|
<script src="https://g.alicdn.com/mylib/moment/2.24.0/min/moment.min.js"></script>
|
||||||
|
<link rel="stylesheet" href="https://alifd.alicdn.com/npm/@alifd/next/1.11.6/next.min.css" />
|
||||||
|
<script src="https://unpkg.alibaba-inc.com/@alifd/next@1.18.17/dist/next.min.js"></script>
|
||||||
|
<!-- lowcode engine globals -->
|
||||||
|
<link rel="stylesheet" href="./core.css" />
|
||||||
|
<!-- lowcode engine globals -->
|
||||||
|
<link rel="stylesheet" href="./vision-preset.css" />
|
||||||
|
<!-- lowcode engine app -->
|
||||||
|
<link rel="stylesheet" href="./lowcode-editor.css" />
|
||||||
|
<script>
|
||||||
|
window.pageConfig = {
|
||||||
|
env: 'release',
|
||||||
|
locale: 'zh_CN',
|
||||||
|
pageType: 'single',
|
||||||
|
deviceType: 'web',
|
||||||
|
appName: '基础包管理后台',
|
||||||
|
appType: '',
|
||||||
|
templateType: '',
|
||||||
|
pageId: 'FORM-3KYJN7RV-DIOD8LLK1WGQ89S7NHA92-QJVH497K-V',
|
||||||
|
slug: 'test',
|
||||||
|
appMode: 'back',
|
||||||
|
isAppAdmin: 'y',
|
||||||
|
isSuperAdmin: 'n',
|
||||||
|
isBetaDeveloper: 'n',
|
||||||
|
formType: 'display',
|
||||||
|
title: { en_US: '测试', type: 'i18n', zh_CN: '测试' },
|
||||||
|
urlPrefix: 'https://go.alibaba-inc.com',
|
||||||
|
APIUrlPrefix: 'https://mocks.alibaba-inc.com/mock/lowCodeEngine',
|
||||||
|
devVersion: '0.1.0', // 这个是子应用的变更 id
|
||||||
|
subAppType: '0.1.0',
|
||||||
|
appKey: '',
|
||||||
|
RE_VERSION: '7.1.1',
|
||||||
|
appSource: '',
|
||||||
|
isDomainDefault: 'n',
|
||||||
|
useReleaseBundle: 'n',
|
||||||
|
isDomainPkg: 'n',
|
||||||
|
medusaAppName: '',
|
||||||
|
domainCode: 'kS6SyH',
|
||||||
|
aecp: {
|
||||||
|
mdcDomain: '',
|
||||||
|
projectId: '',
|
||||||
|
appCode: '',
|
||||||
|
},
|
||||||
|
designerConfigs: {},
|
||||||
|
navConfig:
|
||||||
|
'{"appName":{"en_US":"基础包管理后台","key":"","type":"i18n","zh_CN":"基础包管理后台"},"bgColor":"white","data":[{"children":[],"hidden":false,"icon":"","inner":true,"navUuid":"FORM-3KYJN7RV-DIOD8LLK1WGQ89S7NHA92-QJVH497K-V","relateUuid":"FORM-3KYJN7RV-DIOD8LLK1WGQ89S7NHA92-QJVH497K-V","slug":"test","targetNew":false,"title":{"en_US":"测试","type":"i18n","zh_CN":"测试"}}],"isFixed":"y","isFold":"y","isFoldHorizontal":"n","languageChangeUrl":{"en_US":"/common/account/changeAccountLanguage.json","type":"i18n","zh_CN":"/common/account/changeAccountLanguage.json"},"layout":"auto","navStyle":"orange","navTheme":"light","openSubMode":false,"showAppTitle":true,"showCrumb":true,"showIcon":false,"showLanguageChange":true,"showNav":true,"showSearch":"n","singletons":{"FORM-3KYJN7RV-DIOD8LLK1WGQ89S7NHA92-QJVH497K-V":{"isFixed":"n","isFold":"n","isFoldHorizontal":"n","showAppTitle":false,"showCrumb":false,"showLanguageChange":false,"showNav":false,"showSearch":"n","singleton":false},"test":{"$ref":"$.singletons.FORM\\-3KYJN7RV\\-DIOD8LLK1WGQ89S7NHA92\\-QJVH497K\\-V"}},"type":"top_fold"}',
|
||||||
|
historyType: 'HASH',
|
||||||
|
isSinglePage: 'n',
|
||||||
|
rhino: 'n',
|
||||||
|
isMiniApp: '',
|
||||||
|
taskId: '',
|
||||||
|
appSchema: 'V5',
|
||||||
|
openSubMode: 'n',
|
||||||
|
};
|
||||||
|
window.g_config = {};
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="lce-container"></div>
|
||||||
|
<!-- lowcode engine globals -->
|
||||||
|
<script src="./core.js"></script>
|
||||||
|
<!-- lowcode engine globals -->
|
||||||
|
<script src="./vision-preset.js"></script>
|
||||||
|
<script src="https://dev.g.alicdn.com/vision/visualengine-utils/5.0.0/engine-utils.js"></script>
|
||||||
|
<link rel="stylesheet" type="text/css" href="//g.alicdn.com/??platform/common/s/1.1/global/global.css,uxcore/uxcore-kuma/2.2.1/orange.min.css">
|
||||||
|
<!-- lowcode engine app -->
|
||||||
|
<script src="./lowcode-editor.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
40
deploy-space/tsconfig.json
Normal file
40
deploy-space/tsconfig.json
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"declaration": false,
|
||||||
|
"lib": ["es2015", "dom"],
|
||||||
|
// Target latest version of ECMAScript.
|
||||||
|
"target": "esnext",
|
||||||
|
// Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'.
|
||||||
|
"module": "esnext",
|
||||||
|
// Search under node_modules for non-relative imports.
|
||||||
|
"moduleResolution": "node",
|
||||||
|
// Process & infer types from .js files.
|
||||||
|
"allowJs": true,
|
||||||
|
// Report errors in .js files.
|
||||||
|
"checkJs": false,
|
||||||
|
// Don't emit; allow Babel to transform files.
|
||||||
|
// "noEmit": true,
|
||||||
|
// Enable strictest settings like strictNullChecks & noImplicitAny.
|
||||||
|
"strict": false,
|
||||||
|
// Allow default imports from modules with no default export. This does not affect code emit, just typechecking.
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
// Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'.
|
||||||
|
"esModuleInterop": true,
|
||||||
|
// Specify JSX code generation: 'preserve', 'react-native', or 'react'.
|
||||||
|
"jsx": "preserve",
|
||||||
|
// Import emit helpers (e.g. __extends, __rest, etc..) from tslib
|
||||||
|
"importHelpers": true,
|
||||||
|
// Enables experimental support for ES7 decorators.
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
// Generates corresponding .map file.
|
||||||
|
"sourceMap": false,
|
||||||
|
// Disallow inconsistently-cased references to the same file.
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
// Allow json import
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
// skip type checking of declaration files
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"outDir": "lib"
|
||||||
|
},
|
||||||
|
"exclude": ["**/test", "**/lib", "**/es", "node_modules"]
|
||||||
|
}
|
||||||
37
docs/code-specification.md
Normal file
37
docs/code-specification.md
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
编码规约
|
||||||
|
---
|
||||||
|
|
||||||
|
### 命名
|
||||||
|
|
||||||
|
- 使用 `PascalCase` 为类型命名
|
||||||
|
- 使用 `I` 做为接口名前缀
|
||||||
|
- 使用 `PascalCase` 为枚举值命名
|
||||||
|
- 使用 `camelCase` 为函数命名
|
||||||
|
- 使用 `camelCase` 为属性或本地变量命名
|
||||||
|
- 不要为私有属性名添加 `_` 前缀
|
||||||
|
- 尽可能使用完整的单词拼写命名
|
||||||
|
- 文件夹/文件命名统一使用小写 `get-custom-data.ts`
|
||||||
|
|
||||||
|
### 组件
|
||||||
|
|
||||||
|
- 一个文件对应一个组件或类
|
||||||
|
|
||||||
|
### 类型
|
||||||
|
|
||||||
|
- 不要随意导出类型/函数,除非你要在不同的组件中共享它
|
||||||
|
- 不要在全局命名空间内定义类型/值
|
||||||
|
- 共享的类型应该在 `types.ts` 里定义
|
||||||
|
- 在一个文件里,类型定义应该出现在顶部
|
||||||
|
- interface 和 type 很类似,原则上能用 interface 实现,就用 interface , 如果不能才用 type
|
||||||
|
|
||||||
|
### 注释
|
||||||
|
|
||||||
|
- 为函数,接口,枚举类型和类使用 JSDoc 风格的注释
|
||||||
|
|
||||||
|
### 字符串
|
||||||
|
|
||||||
|
- 使用单引号 `''`
|
||||||
|
|
||||||
|
### 单元测试
|
||||||
|
|
||||||
|
- 单元测试文件根据文件目录结构来放置
|
||||||
7
index.ts
Normal file
7
index.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import renderer from './packages/react-simulator-renderer/src/renderer';
|
||||||
|
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
(window as any).SimulatorRenderer = renderer;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default renderer;
|
||||||
36
lerna.json
Normal file
36
lerna.json
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"lerna": "4.0.0",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"npmClient": "yarn",
|
||||||
|
"useWorkspaces": true,
|
||||||
|
"packages": [
|
||||||
|
"packages/*"
|
||||||
|
],
|
||||||
|
"command": {
|
||||||
|
"bootstrap": {
|
||||||
|
"npmClientArgs": [
|
||||||
|
"--no-package-lock"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"version": {
|
||||||
|
"allowBranch": [
|
||||||
|
"master",
|
||||||
|
"main",
|
||||||
|
"release/*",
|
||||||
|
"daily/*",
|
||||||
|
"refactor/*"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"publish": {
|
||||||
|
"npmClient": "npm",
|
||||||
|
"verifyRegistry": false,
|
||||||
|
"verifyAccess": false,
|
||||||
|
"ignoreChanges": [
|
||||||
|
"**/*.md",
|
||||||
|
"**/test/**"
|
||||||
|
],
|
||||||
|
"message": "chore(release): publish %v",
|
||||||
|
"conventionalCommits": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
113
modules/code-generator/.gitignore
vendored
Normal file
113
modules/code-generator/.gitignore
vendored
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
# project custom
|
||||||
|
build
|
||||||
|
es
|
||||||
|
dist
|
||||||
|
output
|
||||||
|
package-lock.json
|
||||||
|
yarn.lock
|
||||||
|
deploy-space/packages
|
||||||
|
deploy-space/.env
|
||||||
|
/demo/
|
||||||
|
/demo-output*/
|
||||||
|
/generated/
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.vscode
|
||||||
|
.idea
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Bower dependency directory (https://bower.io/)
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
lib
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# TypeScript v1 declaration files
|
||||||
|
typings/
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variables file
|
||||||
|
.env
|
||||||
|
.env.test
|
||||||
|
|
||||||
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
|
.cache
|
||||||
|
|
||||||
|
# next.js build output
|
||||||
|
.next
|
||||||
|
|
||||||
|
# nuxt.js build output
|
||||||
|
.nuxt
|
||||||
|
|
||||||
|
# vuepress build output
|
||||||
|
.vuepress/dist
|
||||||
|
|
||||||
|
# Serverless directories
|
||||||
|
.serverless/
|
||||||
|
|
||||||
|
# FuseBox cache
|
||||||
|
.fusebox/
|
||||||
|
|
||||||
|
# DynamoDB Local files
|
||||||
|
.dynamodb/
|
||||||
|
|
||||||
|
# mac config files
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# codealike
|
||||||
|
codealike.json
|
||||||
|
.node
|
||||||
|
|
||||||
|
# def publish
|
||||||
|
.package
|
||||||
|
|
||||||
|
# types
|
||||||
|
/types
|
||||||
7
modules/code-generator/.prettierrc.js
Normal file
7
modules/code-generator/.prettierrc.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
module.exports = {
|
||||||
|
printWidth: 100,
|
||||||
|
tabWidth: 2,
|
||||||
|
semi: true,
|
||||||
|
singleQuote: true,
|
||||||
|
trailingComma: 'all',
|
||||||
|
};
|
||||||
105
modules/code-generator/CHANGELOG.md
Normal file
105
modules/code-generator/CHANGELOG.md
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
||||||
|
|
||||||
|
## [1.0.0-beta.20](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine-2.x/compare/@alilc/lowcode-code-generator@1.0.0-beta.19...@alilc/lowcode-code-generator@1.0.0-beta.20) (2022-02-14)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 🐛 解决 standalone 入口的类型定义找不到的问题 ([ea735b0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine-2.x/commit/ea735b0422f26f4fdde4989478b308c22bae8f07))
|
||||||
|
|
||||||
|
## [1.0.0-beta.19](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine-2.x/compare/@alilc/lowcode-code-generator@1.0.0-beta.18...@alilc/lowcode-code-generator@1.0.0-beta.19) (2022-02-14)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 🐛 补充自定义出码方案中缺失的示例插件文件 ([b583421](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine-2.x/commit/b5834214482ba73dc33753aed9e1166bca4b7374))
|
||||||
|
* 🐛 解决 init-solution 命令生成的 demo-schema.json 格式非法的问题 ([b090732](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine-2.x/commit/b090732cb750aa60dfe9c7e34dd6709bfd537642))
|
||||||
|
|
||||||
|
## [1.0.0-beta.18](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine-2.x/compare/@alilc/lowcode-code-generator@1.0.0-beta.16...@alilc/lowcode-code-generator@1.0.0-beta.18) (2022-02-14)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* 插件版本号校验 ([b22ae6a](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine-2.x/commit/b22ae6a75065e0c4dfa290c7b8c4ec4c7e661b53))
|
||||||
|
* 插件版本号校验,调整判断条件 ([2cf2182](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine-2.x/commit/2cf218245812825aa729657a9b1ac4b574d75cb5))
|
||||||
|
* 增加 setKey / rerender 几个 API, shell node 单例化 ([e7999fa](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine-2.x/commit/e7999faf78ec22395f218aab22e7accc18ad9b53))
|
||||||
|
* add contributor list ([9368ea4](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine-2.x/commit/9368ea46bd4b1eaae0848d51780d8a0896384875))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 🐛 解决通过命令行初始化自定义出码方案的时候报错的问题 ([ca6daff](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine-2.x/commit/ca6daff89498056b0248e3996edfee75379881ac))
|
||||||
|
* 🐛 解决通过命令行方式使用出码模块的时候报错的问题 ([da3f301](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine-2.x/commit/da3f3011d3ea34f59f0f3d6bd230fcfa436be103))
|
||||||
|
* 处理遗留下来的 todo ([67c20a9](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine-2.x/commit/67c20a993d44d6b2353eec6aab94636c1b2328b6))
|
||||||
|
* 单测问题修复 ([b940fc2](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine-2.x/commit/b940fc215737936267d2a3a202c7269695293060))
|
||||||
|
* 调整 slot 默认关闭逻辑 ([0c0cd84](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine-2.x/commit/0c0cd8452cef94afc3afe025b9ce54a18274f5a4))
|
||||||
|
* cr问题修复 ([a27f5aa](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine-2.x/commit/a27f5aaf73dd884051714a98d85644a9a0b6d69b))
|
||||||
|
|
||||||
|
## [1.0.0-beta.17](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine-2.x/compare/@alilc/lowcode-code-generator@1.0.0-beta.16...@alilc/lowcode-code-generator@1.0.0-beta.17) (2022-02-14)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* 插件版本号校验 ([b22ae6a](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine-2.x/commit/b22ae6a75065e0c4dfa290c7b8c4ec4c7e661b53))
|
||||||
|
* 插件版本号校验,调整判断条件 ([2cf2182](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine-2.x/commit/2cf218245812825aa729657a9b1ac4b574d75cb5))
|
||||||
|
* 增加 setKey / rerender 几个 API, shell node 单例化 ([e7999fa](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine-2.x/commit/e7999faf78ec22395f218aab22e7accc18ad9b53))
|
||||||
|
* add contributor list ([9368ea4](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine-2.x/commit/9368ea46bd4b1eaae0848d51780d8a0896384875))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 🐛 解决通过命令行初始化自定义出码方案的时候报错的问题 ([ca6daff](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine-2.x/commit/ca6daff89498056b0248e3996edfee75379881ac))
|
||||||
|
* 🐛 解决通过命令行方式使用出码模块的时候报错的问题 ([da3f301](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine-2.x/commit/da3f3011d3ea34f59f0f3d6bd230fcfa436be103))
|
||||||
|
* 处理遗留下来的 todo ([67c20a9](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine-2.x/commit/67c20a993d44d6b2353eec6aab94636c1b2328b6))
|
||||||
|
* 单测问题修复 ([b940fc2](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine-2.x/commit/b940fc215737936267d2a3a202c7269695293060))
|
||||||
|
* 调整 slot 默认关闭逻辑 ([0c0cd84](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine-2.x/commit/0c0cd8452cef94afc3afe025b9ce54a18274f5a4))
|
||||||
|
* cr问题修复 ([a27f5aa](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine-2.x/commit/a27f5aaf73dd884051714a98d85644a9a0b6d69b))
|
||||||
|
|
||||||
|
## [1.0.0-beta.16](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine-2.x/compare/@alilc/lowcode-code-generator@1.0.0-beta.15...@alilc/lowcode-code-generator@1.0.0-beta.16) (2022-02-07)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* 🎸 升级 @alilc/lowcode-types ([71f9c6c](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine-2.x/commit/71f9c6c88f55f3e176e5bd40ddf4593a24ad3aea))
|
||||||
|
* 补充 node#isEmpty ([e8229b8](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine-2.x/commit/e8229b89dbdf1b93d529f2534ec9bf1fd25c2566))
|
||||||
|
* 调整插件依赖配置位置 ([655b597](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine-2.x/commit/655b5976ac2a310bb679a06f418a154ccefd9d56))
|
||||||
|
* 增加 setting-prop-entry#getDefaultValue ([3e43cd3](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine-2.x/commit/3e43cd3af9c213b2b7694b6204b509850e786749))
|
||||||
|
* 增设计模式作为是否使用循环判断的条件 & NotFoundComponent 组件新增 __componentName 属性 ([06f86de](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine-2.x/commit/06f86deef7bb88249c54731df9f2deb374443d21))
|
||||||
|
* add preference declaration control to plugin ([2256156](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine-2.x/commit/22561563451dc565a2aea930d0679fe528520513))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 🐛 解决出码模块在 node 环境中使用时 zip publisher 不能正确识别为 node 环境的问题 ([ffa9908](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine-2.x/commit/ffa9908365fbe6fa5230c6f890cebf4fb44a7188))
|
||||||
|
* 🐛 解决通过 npm 安装并测试的时候一堆报错问题 ([7245617](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine-2.x/commit/7245617da2bae55e67e4dff5eeda6c14981fdd4b))
|
||||||
|
* 🐛 修正 icejs 模板中的 abc.json 的 builder ([e63cf7a](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine-2.x/commit/e63cf7affb0bf7f072d965bda3c8dd90cf015e2d))
|
||||||
|
* 根据新的插件 pluginName 修改代码 ([1faed05](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine-2.x/commit/1faed05dab26674b84ab0f065263b9ad9aeade46))
|
||||||
|
* 兼容 arraysetter 场景 ([f462f3d](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine-2.x/commit/f462f3de172d3d05db38234db982c58f5e6321ee))
|
||||||
|
* 修复 getResizingHandlers 回调参数没有转换的问题 ([2e6c51d](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine-2.x/commit/2e6c51d9cb0835791889656b3b9709a9a6cd630d))
|
||||||
|
|
||||||
|
## [1.0.0-beta.15](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine-2.x/compare/@alilc/lowcode-code-generator@1.0.0-beta.14...@alilc/lowcode-code-generator@1.0.0-beta.15) (2022-01-19)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 🐛 修正 standalone-worker 的默认地址 ([f477372](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine-2.x/commit/f477372f8c2432d6300b39b82e73cdfe4cf25f8d))
|
||||||
|
|
||||||
|
## 1.0.0-beta.14 (2022-01-19)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* 补充 dragon 以及部分代码优化 ([595478a](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine-2.x/commit/595478a7cb2d1288450920404bd79bf0b7fa4424))
|
||||||
|
* 补充 setting-prop-entry#getPropValue 值 ([224acbf](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine-2.x/commit/224acbff2c8a949d4cda01f2371d6a7ba0b8df4d))
|
||||||
|
* 补充几个 API 以及调整为 umd 导出 AliLowCodeEngine ([b56debc](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine-2.x/commit/b56debc9b7cb78a5a049291d9079292a32e85d20))
|
||||||
|
* 出码模块支持 standalone 模式 ([f1fb35a](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine-2.x/commit/f1fb35a1c39ebff3d1ddb28262ac556dbd249df3))
|
||||||
|
* 第一版 readme ([db42cb5](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine-2.x/commit/db42cb529a27f58a42cda4f9c33107f4d24abc23))
|
||||||
|
* setting-prop-entry 增加一个 API ([6c1fdc3](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine-2.x/commit/6c1fdc35828cdf0133846454c0a33708dc606d9a))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* 修正命令行的位置 ([eb046c0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine-2.x/commit/eb046c0fd5983105e2d93fbc1c53b6b08935f66f))
|
||||||
|
* less-variables ([15cccd6](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine-2.x/commit/15cccd6892e375c7d83719aed416e3a320bcd931))
|
||||||
39
modules/code-generator/CONTRIBUTING.md
Normal file
39
modules/code-generator/CONTRIBUTING.md
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
# 如何共建
|
||||||
|
|
||||||
|
1. 拉取最新代码,切换到 develop 分支,基于 develop 分支切出一个 feature 或 hotfix 分支
|
||||||
|
2. 安装依赖(`npm`),然后先跑一遍 `npm test` 看看是否所有用例都能通过 (如果网络条件不太好,建议使用 [cnpm - 淘宝提供的中国 NPM 镜像](https://npmmirror.com/))
|
||||||
|
3. 在 tests 目录下编写您的需求/问题的测试用例
|
||||||
|
4. 修改 src 下的一些代码,然后运行 `npm test` 或 `npm start` 启动 jest 进行调测
|
||||||
|
5. 确保所有的测试用例都能通过时,提 MR 给 @牧毅 -- MR 将在 1 个工作日内给您回复意见。
|
||||||
|
|
||||||
|
当然,欢迎提前私聊沟通 @牧毅,或加入 低代码渲染/出码服务金牌用户群 讨论沟通。
|
||||||
|
|
||||||
|
# FAQ
|
||||||
|
|
||||||
|
## 如何查看单测覆盖率?
|
||||||
|
|
||||||
|
执行 `npm test:cov` 命令,这样会自动生成单测覆盖率的报告到 `coverage` 目录下。
|
||||||
|
|
||||||
|
## 如何只执行一个测试用例?
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm test -t 'demo2-utils-name-alias'
|
||||||
|
```
|
||||||
|
|
||||||
|
## 更新特定测试用例的 expected:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm test:update-snapshots -t 'demo2-utils-name-alias'
|
||||||
|
```
|
||||||
|
|
||||||
|
## 如何只执行某个测试用例文件?
|
||||||
|
|
||||||
|
执行 `npx jest 测试用例的文件路径` 即可,如:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npx jest tests/plugins/common/requireUtils.test.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
## 如何调试某个测试用例?
|
||||||
|
|
||||||
|
建议需要打断点的地方通过 VSCode 打上断点,然后打开 VSCode 的 JavaScript Debug Terminal,在其中执行 `npx jest tests/path/to/your/test/file.ts` 或 `npx jest -t your-test-case-title` 来执行你的测试用例 -- 这样执行到打了断点的语句时会自动断住,以便调试。
|
||||||
113
modules/code-generator/README.md
Normal file
113
modules/code-generator/README.md
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
# 出码
|
||||||
|
|
||||||
|
所谓出码,即将低代码编排出的 schema 进行解析并转换成最终可执行的代码的过程。本模块提供有 Icejs 和 Rax 两套框架的出码方案,并提供了强大而灵活的扩展机制。
|
||||||
|
|
||||||
|
## 使用方法
|
||||||
|
|
||||||
|
### 1) 通过命令行快速体验
|
||||||
|
|
||||||
|
欢迎使用命令行工具快速体验:`npx @alilc/lowcode-code-generator -i example-schema.json -o generated -s icejs`
|
||||||
|
|
||||||
|
--其中 example-schema.json 可以从[这里下载](https://unpkg.com/@alilc/lowcode-code-generator@beta/example-schema.json)
|
||||||
|
|
||||||
|
### 2) 通过设计器插件快速体验
|
||||||
|
|
||||||
|
1. 安装依赖: `npm install --save @alilc/lowcode-plugin-code-generator`
|
||||||
|
2. 注册插件:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { plugins } from '@alilc/lowcode-engine';
|
||||||
|
import CodeGenPlugin from '@alilc/lowcode-plugin-code-generator';
|
||||||
|
|
||||||
|
// 在你的初始化函数中:
|
||||||
|
await plugins.register(CodeGenPlugin);
|
||||||
|
|
||||||
|
// 如果您不希望自动加上出码按钮,则可以这样注册
|
||||||
|
await plugins.register(CodeGenPlugin, { disableCodeGenActionBtn: true });
|
||||||
|
```
|
||||||
|
|
||||||
|
然后运行你的低代码编辑器项目即可 -- 在设计器的右上角会出现一个“出码”按钮,点击即可在浏览器中出码并预览。
|
||||||
|
|
||||||
|
### 3)服务端出码接入
|
||||||
|
|
||||||
|
此代码生成器一开始就是为服务端出码设计的,你可以直接这样来在 node.js 环境中使用:
|
||||||
|
|
||||||
|
1. 安装依赖: `npm install --save @alilc/lowcode-code-generator`
|
||||||
|
2. 引入代码生成器:
|
||||||
|
|
||||||
|
```js
|
||||||
|
import CodeGenerator from '@alilc/lowcode-code-generator';
|
||||||
|
```
|
||||||
|
|
||||||
|
3. 创建项目构建器:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const projectBuilder = CodeGenerator.solutions.icejs();
|
||||||
|
```
|
||||||
|
|
||||||
|
4. 生成代码
|
||||||
|
|
||||||
|
```js
|
||||||
|
const project = await projectBuilder.generateProject(
|
||||||
|
schema, // 编排搭建出来的 schema
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
5. 将生成的代码写入到磁盘中(也可以生成一个 zip 包)
|
||||||
|
|
||||||
|
```js
|
||||||
|
// 写入磁盘
|
||||||
|
await CodeGenerator.publishers.disk().publish({
|
||||||
|
project, // 上一步生成的 project
|
||||||
|
outputPath: '/path/to/your/output/dir', // 输出目录
|
||||||
|
projectSlug: 'your-project-slug', // 项目标识
|
||||||
|
});
|
||||||
|
|
||||||
|
// 写入到 zip 包
|
||||||
|
await CodeGenerator.publishers.zip().publish({
|
||||||
|
project, // 上一步生成的 project
|
||||||
|
outputPath: '/path/to/your/output/dir', // 输出目录
|
||||||
|
projectSlug: 'your-project-slug', // 项目标识 -- 对应生成 your-project-slug.zip 文件
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
注:一般来说在服务端出码可以跟 github/gitlab, CI 和 CD 流程等一起串起来使用,通常用于优化性能。
|
||||||
|
|
||||||
|
### 4)浏览器中出码接入
|
||||||
|
|
||||||
|
随着现在电脑性能和浏览器技术的发展,出码其实已经不必非得在服务端做了,借助于 Web Worker 特性,可以在浏览器中进行出码:
|
||||||
|
|
||||||
|
1. 安装依赖: `npm install --save @alilc/lowcode-code-generator`
|
||||||
|
2. 引入代码生成器:
|
||||||
|
|
||||||
|
```js
|
||||||
|
import * as CodeGenerator from '@alilc/lowcode-code-generator/standalone-loader';
|
||||||
|
```
|
||||||
|
|
||||||
|
3. 【可选】提前初始化代码生成器:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// 提前初始化下,这样后面用的时候更快(这个 init 内部会提前准备好创建 worker 的一些资源)
|
||||||
|
await CodeGenerator.init();
|
||||||
|
```
|
||||||
|
|
||||||
|
4. 出码
|
||||||
|
|
||||||
|
```js
|
||||||
|
const result = await CodeGenerator.generateCode({
|
||||||
|
solution: 'icejs', // 出码方案 (目前内置有 icejs 和 rax )
|
||||||
|
schema, // 编排搭建出来的 schema
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(result); // 出码结果(默认是递归结构描述的,可以传 flattenResult: true 以生成扁平结构的结果)
|
||||||
|
```
|
||||||
|
|
||||||
|
注:一般来说在浏览器中出码适合做即时预览功能。
|
||||||
|
|
||||||
|
### 5)自定义出码
|
||||||
|
|
||||||
|
前端框架灵活多变,默认内置的出码方案很难满足所有人的需求,好在此代码生成器支持非常灵活的插件机制 -- 欢迎参考 ./src/plugins/xxx 来编写您自己的出码插件,然后参考 ./src/solutions/xxx 将各种插件组合成一套适合您的业务场景的出码方案。
|
||||||
|
|
||||||
|
## 参与共建
|
||||||
|
|
||||||
|
欢迎参与共建,如何共建请参阅:[./CONTRIBUTING.md](https://code.aone.alibaba-inc.com/ali-lowcode/code-generator/blob/develop/CONTRIBUTING.md)
|
||||||
51
modules/code-generator/bin/lowcode-code-generator.js
Executable file
51
modules/code-generator/bin/lowcode-code-generator.js
Executable file
@ -0,0 +1,51 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
/* eslint-disable no-var */
|
||||||
|
/* eslint-disable prefer-arrow-callback */
|
||||||
|
/* eslint-disable @typescript-eslint/no-require-imports */
|
||||||
|
|
||||||
|
var program = require('commander');
|
||||||
|
|
||||||
|
program
|
||||||
|
.command('generate', { isDefault: true })
|
||||||
|
.description('Generate code from ali lowcode schema')
|
||||||
|
.requiredOption('-s, --solution <solution>', 'specify the solution to use (icejs/rax/recore)')
|
||||||
|
.option('-i, --input <input>', 'specify the input schema file')
|
||||||
|
.option('-o, --output <output>', 'specify the output directory', 'generated')
|
||||||
|
.option('-c, --cwd <cwd>', 'specify the working directory', '.')
|
||||||
|
.option('-q, --quiet', 'be quiet, do not output anything unless get error', false)
|
||||||
|
.option('-v, --verbose', 'be verbose, output more information', false)
|
||||||
|
.arguments('[input-schema] ali lowcode schema JSON file')
|
||||||
|
.action(function doGenerate(inputSchema, command) {
|
||||||
|
var options = command.opts();
|
||||||
|
if (options.cwd) {
|
||||||
|
process.chdir(options.cwd);
|
||||||
|
}
|
||||||
|
|
||||||
|
require('../dist/cli')
|
||||||
|
.run(inputSchema ? [inputSchema] : [], options)
|
||||||
|
.then((retCode) => {
|
||||||
|
process.exit(retCode);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
program
|
||||||
|
.command('init-solution')
|
||||||
|
.option('-c, --cwd <cwd>', 'specify the working directory', '.')
|
||||||
|
.option('-q, --quiet', 'be quiet, do not output anything unless get error', false)
|
||||||
|
.option('-v, --verbose', 'be verbose, output more information', false)
|
||||||
|
.arguments('<your-solution-name>')
|
||||||
|
.action(function initSolution(solutionName, command) {
|
||||||
|
var options = command.opts();
|
||||||
|
if (options.cwd) {
|
||||||
|
process.chdir(options.cwd);
|
||||||
|
}
|
||||||
|
|
||||||
|
require('../dist/cli')
|
||||||
|
.initSolution([solutionName], options)
|
||||||
|
.then((retCode) => {
|
||||||
|
process.exit(retCode);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
program.parse(process.argv);
|
||||||
|
|
||||||
276
modules/code-generator/example-schema.json
Normal file
276
modules/code-generator/example-schema.json
Normal file
@ -0,0 +1,276 @@
|
|||||||
|
{
|
||||||
|
"version": "1.0.0",
|
||||||
|
"componentsMap": [
|
||||||
|
{
|
||||||
|
"componentName": "Button",
|
||||||
|
"package": "@alifd/next",
|
||||||
|
"version": "1.19.18",
|
||||||
|
"destructuring": true,
|
||||||
|
"exportName": "Button"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"componentName": "Button.Group",
|
||||||
|
"package": "@alifd/next",
|
||||||
|
"version": "1.19.18",
|
||||||
|
"destructuring": true,
|
||||||
|
"exportName": "Button",
|
||||||
|
"subName": "Group"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"componentName": "Input",
|
||||||
|
"package": "@alifd/next",
|
||||||
|
"version": "1.19.18",
|
||||||
|
"destructuring": true,
|
||||||
|
"exportName": "Input"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"componentName": "Form",
|
||||||
|
"package": "@alifd/next",
|
||||||
|
"version": "1.19.18",
|
||||||
|
"destructuring": true,
|
||||||
|
"exportName": "Form"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"componentName": "Form.Item",
|
||||||
|
"package": "@alifd/next",
|
||||||
|
"version": "1.19.18",
|
||||||
|
"destructuring": true,
|
||||||
|
"exportName": "Form",
|
||||||
|
"subName": "Item"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"componentName": "NumberPicker",
|
||||||
|
"package": "@alifd/next",
|
||||||
|
"version": "1.19.18",
|
||||||
|
"destructuring": true,
|
||||||
|
"exportName": "NumberPicker"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"componentName": "Select",
|
||||||
|
"package": "@alifd/next",
|
||||||
|
"version": "1.19.18",
|
||||||
|
"destructuring": true,
|
||||||
|
"exportName": "Select"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"componentsTree": [
|
||||||
|
{
|
||||||
|
"componentName": "Page",
|
||||||
|
"id": "node$1",
|
||||||
|
"meta": {
|
||||||
|
"title": "测试",
|
||||||
|
"router": "/"
|
||||||
|
},
|
||||||
|
"props": {
|
||||||
|
"ref": "outterView",
|
||||||
|
"autoLoading": true
|
||||||
|
},
|
||||||
|
"fileName": "test",
|
||||||
|
"state": {
|
||||||
|
"text": "outter"
|
||||||
|
},
|
||||||
|
"lifeCycles": {
|
||||||
|
"componentDidMount": {
|
||||||
|
"type": "JSExpression",
|
||||||
|
"value": "function() { console.log('componentDidMount'); }"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dataSource": {
|
||||||
|
"list": [
|
||||||
|
{
|
||||||
|
"id": "urlParams",
|
||||||
|
"type": "urlParams"
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"id": "user",
|
||||||
|
"type": "fetch",
|
||||||
|
"options": {
|
||||||
|
"method": "GET",
|
||||||
|
"uri": "https://shs.alibaba-inc.com/mock/1458/demo/user",
|
||||||
|
"isSync": true
|
||||||
|
},
|
||||||
|
"dataHandler": {
|
||||||
|
"type": "JSExpression",
|
||||||
|
"value": "function (response) {\nif (!response.data.success){\n throw new Error(response.data.message);\n }\n return response.data.data;\n}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"id": "orders",
|
||||||
|
"type": "fetch",
|
||||||
|
"options": {
|
||||||
|
"method": "GET",
|
||||||
|
"uri": "https://shs.alibaba-inc.com/mock/1458/demo/orders",
|
||||||
|
"isSync": true
|
||||||
|
},
|
||||||
|
"dataHandler": {
|
||||||
|
"type": "JSExpression",
|
||||||
|
"value": "function (response) {\nif (!response.data.success){\n throw new Error(response.data.message);\n }\n return response.data.data.result;\n}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dataHandler": {
|
||||||
|
"type": "JSExpression",
|
||||||
|
"value": "function (dataMap) {\n console.info(\"All datasources loaded:\", dataMap);\n}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"componentName": "Form",
|
||||||
|
"id": "node$2",
|
||||||
|
"props": {
|
||||||
|
"labelCol": {
|
||||||
|
"type": "JSExpression",
|
||||||
|
"value": "this.state.colNum"
|
||||||
|
},
|
||||||
|
"style": {},
|
||||||
|
"ref": "testForm"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"componentName": "Form.Item",
|
||||||
|
"id": "node$3",
|
||||||
|
"props": {
|
||||||
|
"label": "姓名:",
|
||||||
|
"name": "name",
|
||||||
|
"initValue": "李雷"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"componentName": "Input",
|
||||||
|
"id": "node$4",
|
||||||
|
"props": {
|
||||||
|
"placeholder": "请输入",
|
||||||
|
"size": "medium",
|
||||||
|
"style": {
|
||||||
|
"width": 320
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"componentName": "Form.Item",
|
||||||
|
"id": "node$5",
|
||||||
|
"props": {
|
||||||
|
"label": "年龄:",
|
||||||
|
"name": "age",
|
||||||
|
"initValue": "22"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"componentName": "NumberPicker",
|
||||||
|
"id": "node$6",
|
||||||
|
"props": {
|
||||||
|
"size": "medium",
|
||||||
|
"type": "normal"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"componentName": "Form.Item",
|
||||||
|
"id": "node$7",
|
||||||
|
"props": {
|
||||||
|
"label": "职业:",
|
||||||
|
"name": "profession"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"componentName": "Select",
|
||||||
|
"id": "node$8",
|
||||||
|
"props": {
|
||||||
|
"dataSource": [
|
||||||
|
{
|
||||||
|
"label": "教师",
|
||||||
|
"value": "t"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "医生",
|
||||||
|
"value": "d"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "歌手",
|
||||||
|
"value": "s"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"componentName": "Div",
|
||||||
|
"id": "node$9",
|
||||||
|
"props": {
|
||||||
|
"style": {
|
||||||
|
"textAlign": "center"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"componentName": "Button.Group",
|
||||||
|
"id": "node$a",
|
||||||
|
"props": {},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"componentName": "Button",
|
||||||
|
"id": "node$b",
|
||||||
|
"condition": {
|
||||||
|
"type": "JSExpression",
|
||||||
|
"value": "this.index >= 1"
|
||||||
|
},
|
||||||
|
"loop": ["a", "b", "c"],
|
||||||
|
"props": {
|
||||||
|
"type": "primary",
|
||||||
|
"style": {
|
||||||
|
"margin": "0 5px 0 5px"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "JSExpression",
|
||||||
|
"value": "this.item"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"constants": {
|
||||||
|
"ENV": "prod",
|
||||||
|
"DOMAIN": "xxx.alibaba-inc.com"
|
||||||
|
},
|
||||||
|
"css": "body {font-size: 12px;} .table { width: 100px;}",
|
||||||
|
"config": {
|
||||||
|
"sdkVersion": "1.0.3",
|
||||||
|
"historyMode": "hash",
|
||||||
|
"targetRootID": "J_Container",
|
||||||
|
"layout": {
|
||||||
|
"componentName": "BasicLayout",
|
||||||
|
"props": {
|
||||||
|
"logo": "...",
|
||||||
|
"name": "测试网站"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"theme": {
|
||||||
|
"package": "@alife/theme-fusion",
|
||||||
|
"version": "^0.1.0",
|
||||||
|
"primary": "#ff9966"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"meta": {
|
||||||
|
"name": "demo应用",
|
||||||
|
"git_group": "appGroup",
|
||||||
|
"project_name": "app_demo",
|
||||||
|
"description": "这是一个测试应用",
|
||||||
|
"spma": "spa23d",
|
||||||
|
"creator": "Test"
|
||||||
|
}
|
||||||
|
}
|
||||||
276
modules/code-generator/example-schema.json5
Normal file
276
modules/code-generator/example-schema.json5
Normal file
@ -0,0 +1,276 @@
|
|||||||
|
{
|
||||||
|
version: '1.0.0',
|
||||||
|
componentsMap: [
|
||||||
|
{
|
||||||
|
componentName: 'Button',
|
||||||
|
package: '@alifd/next',
|
||||||
|
version: '1.19.18',
|
||||||
|
destructuring: true,
|
||||||
|
exportName: 'Button',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
componentName: 'Button.Group',
|
||||||
|
package: '@alifd/next',
|
||||||
|
version: '1.19.18',
|
||||||
|
destructuring: true,
|
||||||
|
exportName: 'Button',
|
||||||
|
subName: 'Group',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
componentName: 'Input',
|
||||||
|
package: '@alifd/next',
|
||||||
|
version: '1.19.18',
|
||||||
|
destructuring: true,
|
||||||
|
exportName: 'Input',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
componentName: 'Form',
|
||||||
|
package: '@alifd/next',
|
||||||
|
version: '1.19.18',
|
||||||
|
destructuring: true,
|
||||||
|
exportName: 'Form',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
componentName: 'Form.Item',
|
||||||
|
package: '@alifd/next',
|
||||||
|
version: '1.19.18',
|
||||||
|
destructuring: true,
|
||||||
|
exportName: 'Form',
|
||||||
|
subName: 'Item',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
componentName: 'NumberPicker',
|
||||||
|
package: '@alifd/next',
|
||||||
|
version: '1.19.18',
|
||||||
|
destructuring: true,
|
||||||
|
exportName: 'NumberPicker',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
componentName: 'Select',
|
||||||
|
package: '@alifd/next',
|
||||||
|
version: '1.19.18',
|
||||||
|
destructuring: true,
|
||||||
|
exportName: 'Select',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
componentsTree: [
|
||||||
|
{
|
||||||
|
componentName: 'Page',
|
||||||
|
id: 'node$1',
|
||||||
|
meta: {
|
||||||
|
title: '测试',
|
||||||
|
router: '/',
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
ref: 'outterView',
|
||||||
|
autoLoading: true,
|
||||||
|
},
|
||||||
|
fileName: 'test',
|
||||||
|
state: {
|
||||||
|
text: 'outter',
|
||||||
|
},
|
||||||
|
lifeCycles: {
|
||||||
|
componentDidMount: {
|
||||||
|
type: 'JSExpression',
|
||||||
|
value: "function() { console.log('componentDidMount'); }",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dataSource: {
|
||||||
|
list: [
|
||||||
|
{
|
||||||
|
id: 'urlParams',
|
||||||
|
type: 'urlParams',
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
id: 'user',
|
||||||
|
type: 'fetch',
|
||||||
|
options: {
|
||||||
|
method: 'GET',
|
||||||
|
uri: 'https://shs.alibaba-inc.com/mock/1458/demo/user',
|
||||||
|
isSync: true,
|
||||||
|
},
|
||||||
|
dataHandler: {
|
||||||
|
type: 'JSExpression',
|
||||||
|
value: 'function (response) {\nif (!response.data.success){\n throw new Error(response.data.message);\n }\n return response.data.data;\n}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
id: 'orders',
|
||||||
|
type: 'fetch',
|
||||||
|
options: {
|
||||||
|
method: 'GET',
|
||||||
|
uri: 'https://shs.alibaba-inc.com/mock/1458/demo/orders',
|
||||||
|
isSync: true,
|
||||||
|
},
|
||||||
|
dataHandler: {
|
||||||
|
type: 'JSExpression',
|
||||||
|
value: 'function (response) {\nif (!response.data.success){\n throw new Error(response.data.message);\n }\n return response.data.data.result;\n}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
dataHandler: {
|
||||||
|
type: 'JSExpression',
|
||||||
|
value: 'function (dataMap) {\n console.info("All datasources loaded:", dataMap);\n}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
componentName: 'Form',
|
||||||
|
id: 'node$2',
|
||||||
|
props: {
|
||||||
|
labelCol: {
|
||||||
|
type: 'JSExpression',
|
||||||
|
value: 'this.state.colNum',
|
||||||
|
},
|
||||||
|
style: {},
|
||||||
|
ref: 'testForm',
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
componentName: 'Form.Item',
|
||||||
|
id: 'node$3',
|
||||||
|
props: {
|
||||||
|
label: '姓名:',
|
||||||
|
name: 'name',
|
||||||
|
initValue: '李雷',
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
componentName: 'Input',
|
||||||
|
id: 'node$4',
|
||||||
|
props: {
|
||||||
|
placeholder: '请输入',
|
||||||
|
size: 'medium',
|
||||||
|
style: {
|
||||||
|
width: 320,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
componentName: 'Form.Item',
|
||||||
|
id: 'node$5',
|
||||||
|
props: {
|
||||||
|
label: '年龄:',
|
||||||
|
name: 'age',
|
||||||
|
initValue: '22',
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
componentName: 'NumberPicker',
|
||||||
|
id: 'node$6',
|
||||||
|
props: {
|
||||||
|
size: 'medium',
|
||||||
|
type: 'normal',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
componentName: 'Form.Item',
|
||||||
|
id: 'node$7',
|
||||||
|
props: {
|
||||||
|
label: '职业:',
|
||||||
|
name: 'profession',
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
componentName: 'Select',
|
||||||
|
id: 'node$8',
|
||||||
|
props: {
|
||||||
|
dataSource: [
|
||||||
|
{
|
||||||
|
label: '教师',
|
||||||
|
value: 't',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '医生',
|
||||||
|
value: 'd',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '歌手',
|
||||||
|
value: 's',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
componentName: 'Div',
|
||||||
|
id: 'node$9',
|
||||||
|
props: {
|
||||||
|
style: {
|
||||||
|
textAlign: 'center',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
componentName: 'Button.Group',
|
||||||
|
id: 'node$a',
|
||||||
|
props: {},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
componentName: 'Button',
|
||||||
|
id: 'node$b',
|
||||||
|
condition: {
|
||||||
|
type: 'JSExpression',
|
||||||
|
value: 'this.index >= 1',
|
||||||
|
},
|
||||||
|
loop: ['a', 'b', 'c'],
|
||||||
|
props: {
|
||||||
|
type: 'primary',
|
||||||
|
style: {
|
||||||
|
margin: '0 5px 0 5px',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
type: 'JSExpression',
|
||||||
|
value: 'this.item',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
constants: {
|
||||||
|
ENV: 'prod',
|
||||||
|
DOMAIN: 'xxx.alibaba-inc.com',
|
||||||
|
},
|
||||||
|
css: 'body {font-size: 12px;} .table { width: 100px;}',
|
||||||
|
config: {
|
||||||
|
sdkVersion: '1.0.3',
|
||||||
|
historyMode: 'hash',
|
||||||
|
targetRootID: 'J_Container',
|
||||||
|
layout: {
|
||||||
|
componentName: 'BasicLayout',
|
||||||
|
props: {
|
||||||
|
logo: '...',
|
||||||
|
name: '测试网站',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
theme: {
|
||||||
|
package: '@alife/theme-fusion',
|
||||||
|
version: '^0.1.0',
|
||||||
|
primary: '#ff9966',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
meta: {
|
||||||
|
name: 'demo应用',
|
||||||
|
git_group: 'appGroup',
|
||||||
|
project_name: 'app_demo',
|
||||||
|
description: '这是一个测试应用',
|
||||||
|
spma: 'spa23d',
|
||||||
|
creator: 'Test',
|
||||||
|
},
|
||||||
|
}
|
||||||
9
modules/code-generator/jest.config.js
Normal file
9
modules/code-generator/jest.config.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
module.exports = {
|
||||||
|
preset: 'ts-jest',
|
||||||
|
testEnvironment: 'node',
|
||||||
|
transformIgnorePatterns: ['/node_modules/(?!core-js)/'],
|
||||||
|
moduleFileExtensions: ['ts', 'tsx', 'js', 'json'],
|
||||||
|
collectCoverage: false,
|
||||||
|
collectCoverageFrom: ['src/**/*.{ts,tsx}', '!**/node_modules/**', '!**/vendor/**'],
|
||||||
|
testMatch: ['<rootDir>/tests/**/*.test.ts'],
|
||||||
|
};
|
||||||
138
modules/code-generator/package.json
Normal file
138
modules/code-generator/package.json
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
{
|
||||||
|
"name": "@alilc/lowcode-code-generator",
|
||||||
|
"version": "1.0.0-beta.20",
|
||||||
|
"description": "出码引擎 for LowCode Engine",
|
||||||
|
"license": "MIT",
|
||||||
|
"main": "lib/index.js",
|
||||||
|
"module": "es/index.js",
|
||||||
|
"typings": "types/index.d.ts",
|
||||||
|
"files": [
|
||||||
|
"bin",
|
||||||
|
"lib",
|
||||||
|
"es",
|
||||||
|
"demo",
|
||||||
|
"dist",
|
||||||
|
"types",
|
||||||
|
"standalone",
|
||||||
|
"standalone-worker",
|
||||||
|
"standalone-loader",
|
||||||
|
"loader",
|
||||||
|
"CHANGELOG.md",
|
||||||
|
"README.md",
|
||||||
|
"CONTRIBUTING.md",
|
||||||
|
"example-schema.json",
|
||||||
|
"example-schema.json5"
|
||||||
|
],
|
||||||
|
"bin": {
|
||||||
|
"lowcode-code-generator": "bin/lowcode-code-generator.js"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"start": "jest --watchAll",
|
||||||
|
"build": "npm run clean && node scripts/build",
|
||||||
|
"build:standalone": "node scripts/build-standalone",
|
||||||
|
"clean": "rimraf es lib dist types generated demo coverage output test-cases/*/*/actual",
|
||||||
|
"lint": "eslint --ext .jsx,.js,.ts,.tsx src/",
|
||||||
|
"lintfix": "eslint --ext .jsx,.js,.ts,.tsx --fix src/",
|
||||||
|
"check:types": "tsc --noEmit",
|
||||||
|
"template": "node ./scripts/build-template-static-files.js",
|
||||||
|
"test": "npm run test:normal && npm run test:standalone",
|
||||||
|
"test:normal": "jest",
|
||||||
|
"test:standalone": "node scripts/test-standalone",
|
||||||
|
"test:cov": "jest --coverage",
|
||||||
|
"test:update-snapshots": "cross-env UPDATE_EXPECTED=true jest -u",
|
||||||
|
"analyze:standalone": "ANALYZE=true node scripts/build-standalone",
|
||||||
|
"release:beta": "standard-version -t @alilc/lowcode-code-generator\\@ --prerelease beta && git push --follow-tags && npm publish --tag beta",
|
||||||
|
"release": "standard-version -t @alilc/lowcode-code-generator\\@ && git push --follow-tags && npm publish",
|
||||||
|
"prepublishOnly": "npm run build",
|
||||||
|
"demo": "node bin/lowcode-code-generator.js -i example-schema.json -o demo -s icejs"
|
||||||
|
},
|
||||||
|
"husky": {
|
||||||
|
"hooks": {
|
||||||
|
"pre-commit": "lint-staged",
|
||||||
|
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lint-staged": {
|
||||||
|
"**/*.{js,jsx,ts,tsx}": "eslint"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@alilc/lowcode-types": "^1.0.0-beta.19",
|
||||||
|
"@babel/generator": "^7.12.11",
|
||||||
|
"@babel/parser": "^7.12.11",
|
||||||
|
"@babel/runtime": "^7.12.5",
|
||||||
|
"@babel/traverse": "^7.12.12",
|
||||||
|
"@babel/types": "^7.12.12",
|
||||||
|
"@types/debug": "^4.1.7",
|
||||||
|
"@types/fs-extra": "^9.0.12",
|
||||||
|
"@types/glob": "^7.2.0",
|
||||||
|
"@types/lodash": "^4.14.162",
|
||||||
|
"@types/node-fetch": "2.x",
|
||||||
|
"@types/qs": "^6.9.6",
|
||||||
|
"@types/semver": "^7.3.4",
|
||||||
|
"buffer": "^6.0.3",
|
||||||
|
"chalk": "^4.1.0",
|
||||||
|
"change-case": "^3.1.0",
|
||||||
|
"commander": "^6.1.0",
|
||||||
|
"debug": "^4.3.2",
|
||||||
|
"fs-extra": "9.x",
|
||||||
|
"glob": "^7.2.0",
|
||||||
|
"html-entities": "^2.3.2",
|
||||||
|
"json5": "^2.2.0",
|
||||||
|
"jsonc": "^2.0.0",
|
||||||
|
"jszip": "^3.5.0",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
|
"lodash-es": "^4.17.21",
|
||||||
|
"mock-fs": "^5.1.2",
|
||||||
|
"moment": "^2.29.1",
|
||||||
|
"node-fetch": "2.x",
|
||||||
|
"path-browserify": "^1.0.1",
|
||||||
|
"prettier": "^2.5.1",
|
||||||
|
"qs": "^6.10.1",
|
||||||
|
"semver": "^7.3.4",
|
||||||
|
"short-uuid": "^3.1.1",
|
||||||
|
"tslib": "^2.3.1"
|
||||||
|
},
|
||||||
|
"browser": {
|
||||||
|
"path": "path-browserify",
|
||||||
|
"lodash": "lodash-es",
|
||||||
|
"prettier": "prettier/standalone"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@iceworks/spec": "^1.4.2",
|
||||||
|
"@types/babel__traverse": "^7.11.0",
|
||||||
|
"@types/jest": "^27.0.2",
|
||||||
|
"@types/lodash": "^4.14.162",
|
||||||
|
"@types/node": "^14.14.20",
|
||||||
|
"@types/prettier": "^2.4.2",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^4.12.0",
|
||||||
|
"@typescript-eslint/parser": "^4.12.0",
|
||||||
|
"concurrently": "^6.5.1",
|
||||||
|
"cross-env": "^7.0.3",
|
||||||
|
"esbuild": "^0.14.5",
|
||||||
|
"esbuild-plugin-alias": "^0.2.1",
|
||||||
|
"esbuild-plugin-ignore": "^1.1.0",
|
||||||
|
"esbuild-visualizer": "^0.3.1",
|
||||||
|
"eslint": "^7.17.0",
|
||||||
|
"eslint-config-ali": "^11.4.1",
|
||||||
|
"eslint-plugin-import": "^2.22.1",
|
||||||
|
"eslint-plugin-react": "^7.22.0",
|
||||||
|
"eslint-plugin-react-hooks": "^4.2.0",
|
||||||
|
"jest": "^27.4.7",
|
||||||
|
"jest-util": "^27.4.2",
|
||||||
|
"rimraf": "^3.0.2",
|
||||||
|
"standard-version": "^9.1.1",
|
||||||
|
"ts-jest": "^27.1.3",
|
||||||
|
"ts-loader": "^6.2.2",
|
||||||
|
"ts-node": "^8.10.2",
|
||||||
|
"tsconfig-paths": "^3.9.0",
|
||||||
|
"typescript": "4.x",
|
||||||
|
"yargs-parser": "^20.2.9"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.0.0"
|
||||||
|
},
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public",
|
||||||
|
"registry": "https://registry.npmjs.org/"
|
||||||
|
}
|
||||||
|
}
|
||||||
56
modules/code-generator/scripts/build-cli.js
Normal file
56
modules/code-generator/scripts/build-cli.js
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
/* eslint-disable no-console */
|
||||||
|
/* eslint-disable @typescript-eslint/no-require-imports */
|
||||||
|
const esbuild = require('esbuild');
|
||||||
|
const ignorePlugin = require('esbuild-plugin-ignore');
|
||||||
|
|
||||||
|
// 执行脚本
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
console.log('building...');
|
||||||
|
const result = await esbuild.build({
|
||||||
|
entryPoints: ['src/cli/index.ts'],
|
||||||
|
outfile: 'dist/cli.js',
|
||||||
|
bundle: true,
|
||||||
|
platform: 'node',
|
||||||
|
target: ['node10'],
|
||||||
|
format: 'cjs',
|
||||||
|
sourcemap: true,
|
||||||
|
sourcesContent: true,
|
||||||
|
plugins: [
|
||||||
|
ignorePlugin([
|
||||||
|
// @alilc/lowcode-types 中误依赖了 react,这里忽略下
|
||||||
|
{
|
||||||
|
resourceRegExp: /^react$/,
|
||||||
|
contextRegExp: /./,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
resourceRegExp: /setter-config/,
|
||||||
|
contextRegExp: /lowcode-types/,
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
define: {},
|
||||||
|
treeShaking: true,
|
||||||
|
minify: false,
|
||||||
|
minifyWhitespace: false,
|
||||||
|
minifyIdentifiers: false,
|
||||||
|
minifySyntax: false,
|
||||||
|
legalComments: 'external',
|
||||||
|
external: Object.keys(require('../package.json').dependencies),
|
||||||
|
});
|
||||||
|
if (result.errors.length > 0) {
|
||||||
|
throw result.errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.warnings.length > 0) {
|
||||||
|
result.warnings.forEach((warnings) => {
|
||||||
|
console.warn(warnings);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('done');
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
})();
|
||||||
73
modules/code-generator/scripts/build-standalone-loader.js
Normal file
73
modules/code-generator/scripts/build-standalone-loader.js
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
/* eslint-disable no-console */
|
||||||
|
/* eslint-disable @typescript-eslint/no-require-imports */
|
||||||
|
const esbuild = require('esbuild');
|
||||||
|
|
||||||
|
const packageVersion = require('../package.json').version;
|
||||||
|
console.log('build standalone-loader: packageVersion=%s', packageVersion);
|
||||||
|
|
||||||
|
const enableAnalyze = process.env.ANALYZE === 'true';
|
||||||
|
const buildConfig = {
|
||||||
|
entryPoints: ['src/standalone-loader.ts'],
|
||||||
|
outfile: 'dist/standalone-loader.js',
|
||||||
|
metafile: enableAnalyze,
|
||||||
|
bundle: true,
|
||||||
|
target: ['chrome69'],
|
||||||
|
format: 'cjs',
|
||||||
|
sourcemap: true,
|
||||||
|
sourcesContent: true,
|
||||||
|
external: Object.keys(require('../package.json').dependencies),
|
||||||
|
define: {
|
||||||
|
process: JSON.stringify({
|
||||||
|
env: {
|
||||||
|
NODE_ENV: 'production',
|
||||||
|
STANDALONE: 'true',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
__PACKAGE_VERSION__: JSON.stringify(packageVersion),
|
||||||
|
},
|
||||||
|
minify: false,
|
||||||
|
minifyWhitespace: false,
|
||||||
|
minifyIdentifiers: false,
|
||||||
|
minifySyntax: false,
|
||||||
|
legalComments: 'external',
|
||||||
|
treeShaking: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 执行脚本
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
console.log('building cjs...');
|
||||||
|
const result = await esbuild.build({
|
||||||
|
...buildConfig,
|
||||||
|
});
|
||||||
|
if (result.errors.length > 0) {
|
||||||
|
throw result.errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.warnings.length > 0) {
|
||||||
|
result.warnings.forEach((warnings) => {
|
||||||
|
console.warn(warnings);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const result2 = await esbuild.build({
|
||||||
|
...buildConfig,
|
||||||
|
outfile: buildConfig.outfile.replace(/\.js$/, '.esm.js'),
|
||||||
|
format: 'esm',
|
||||||
|
});
|
||||||
|
if (result2.errors.length > 0) {
|
||||||
|
throw result2.errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result2.warnings.length > 0) {
|
||||||
|
result2.warnings.forEach((warnings) => {
|
||||||
|
console.warn(warnings);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('done');
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
})();
|
||||||
107
modules/code-generator/scripts/build-standalone-worker.js
Normal file
107
modules/code-generator/scripts/build-standalone-worker.js
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
/* eslint-disable no-console */
|
||||||
|
/* eslint-disable @typescript-eslint/no-require-imports */
|
||||||
|
const esbuild = require('esbuild');
|
||||||
|
const { spawnSync } = require('child_process');
|
||||||
|
const ignorePlugin = require('esbuild-plugin-ignore');
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const enableAnalyze = process.env.ANALYZE === 'true';
|
||||||
|
const buildConfig = {
|
||||||
|
entryPoints: ['src/standalone-worker.ts'],
|
||||||
|
outfile: 'dist/standalone-worker.js',
|
||||||
|
metafile: enableAnalyze,
|
||||||
|
bundle: true,
|
||||||
|
target: ['chrome69'],
|
||||||
|
format: 'iife',
|
||||||
|
sourcemap: true,
|
||||||
|
sourcesContent: true,
|
||||||
|
plugins: [
|
||||||
|
ignorePlugin([
|
||||||
|
{
|
||||||
|
resourceRegExp: /^fs$/,
|
||||||
|
contextRegExp: /./,
|
||||||
|
},
|
||||||
|
// @alilc/lowcode-types 中误依赖了 react,这里忽略下
|
||||||
|
{
|
||||||
|
resourceRegExp: /^react$/,
|
||||||
|
contextRegExp: /./,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
resourceRegExp: /setter-config/,
|
||||||
|
contextRegExp: /lowcode-types|..[\\/]types/,
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
define: {
|
||||||
|
process: JSON.stringify({
|
||||||
|
env: {
|
||||||
|
NODE_ENV: 'production',
|
||||||
|
STANDALONE: 'true',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
treeShaking: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 执行脚本
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
console.log('building...');
|
||||||
|
const result = await esbuild.build({
|
||||||
|
...buildConfig,
|
||||||
|
minify: false,
|
||||||
|
minifyWhitespace: false,
|
||||||
|
minifyIdentifiers: false,
|
||||||
|
minifySyntax: false,
|
||||||
|
legalComments: 'external',
|
||||||
|
});
|
||||||
|
if (result.errors.length > 0) {
|
||||||
|
throw result.errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.warnings.length > 0) {
|
||||||
|
result.warnings.forEach((warnings) => {
|
||||||
|
console.warn(warnings);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enableAnalyze) {
|
||||||
|
const metaFile = buildConfig.outfile.replace(/\.js/, '.meta.json');
|
||||||
|
const statsFile = buildConfig.outfile.replace(/\.js/, '.stats.html');
|
||||||
|
fs.writeFileSync(metaFile, JSON.stringify(result.metafile || {}), { encoding: 'utf-8' });
|
||||||
|
spawnSync('npx', ['esbuild-visualizer', '--metadata', metaFile, '--filename', statsFile], {
|
||||||
|
shell: true,
|
||||||
|
stdio: 'inherit',
|
||||||
|
});
|
||||||
|
spawnSync('open', [statsFile], {
|
||||||
|
shell: true,
|
||||||
|
stdio: 'inherit',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const outFileContent = fs.readFileSync(buildConfig.outfile, 'utf-8');
|
||||||
|
|
||||||
|
console.log('minifying...');
|
||||||
|
const minifiedOutFile = buildConfig.outfile.replace(/\.js$/, '.min.js');
|
||||||
|
const minifiedResult = esbuild.transformSync(outFileContent, {
|
||||||
|
minify: true,
|
||||||
|
sourcemap: true,
|
||||||
|
sourcesContent: true,
|
||||||
|
sourcefile: path.basename(buildConfig.outfile),
|
||||||
|
});
|
||||||
|
|
||||||
|
fs.writeFileSync(minifiedOutFile, minifiedResult.code, { encoding: 'utf-8' });
|
||||||
|
fs.writeFileSync(`${minifiedOutFile}.map`, minifiedResult.map, { encoding: 'utf-8' });
|
||||||
|
|
||||||
|
minifiedResult.warnings.forEach((warnings) => {
|
||||||
|
console.log(warnings);
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('done');
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
})();
|
||||||
135
modules/code-generator/scripts/build-standalone.js
Normal file
135
modules/code-generator/scripts/build-standalone.js
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
/* eslint-disable no-console */
|
||||||
|
/* eslint-disable @typescript-eslint/no-require-imports */
|
||||||
|
const esbuild = require('esbuild');
|
||||||
|
const { spawnSync } = require('child_process');
|
||||||
|
const ignorePlugin = require('esbuild-plugin-ignore');
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const UMD_GLOBAL_NAME = 'AliLowCodeCodeGenerator';
|
||||||
|
|
||||||
|
const enableAnalyze = process.env.ANALYZE === 'true';
|
||||||
|
const buildConfig = {
|
||||||
|
entryPoints: ['src/standalone.ts'],
|
||||||
|
outfile: 'dist/standalone.js',
|
||||||
|
metafile: enableAnalyze,
|
||||||
|
bundle: true,
|
||||||
|
target: ['chrome69'],
|
||||||
|
format: 'cjs',
|
||||||
|
sourcemap: true,
|
||||||
|
sourcesContent: true,
|
||||||
|
plugins: [
|
||||||
|
ignorePlugin([
|
||||||
|
{
|
||||||
|
resourceRegExp: /^fs$/,
|
||||||
|
contextRegExp: /./,
|
||||||
|
},
|
||||||
|
// @alilc/lowcode-types 中误依赖了 react,这里忽略下
|
||||||
|
{
|
||||||
|
resourceRegExp: /^react$/,
|
||||||
|
contextRegExp: /./,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
resourceRegExp: /setter-config/,
|
||||||
|
contextRegExp: /lowcode-types|..[\\/]types/,
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
define: {
|
||||||
|
process: JSON.stringify({
|
||||||
|
env: {
|
||||||
|
NODE_ENV: 'production',
|
||||||
|
STANDALONE: 'true',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
treeShaking: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 执行脚本
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
console.log('building...');
|
||||||
|
const result = await esbuild.build({
|
||||||
|
...buildConfig,
|
||||||
|
minify: false,
|
||||||
|
minifyWhitespace: false,
|
||||||
|
minifyIdentifiers: false,
|
||||||
|
minifySyntax: false,
|
||||||
|
legalComments: 'external',
|
||||||
|
});
|
||||||
|
if (result.errors.length > 0) {
|
||||||
|
throw result.errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.warnings.length > 0) {
|
||||||
|
result.warnings.forEach((warnings) => {
|
||||||
|
console.warn(warnings);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enableAnalyze) {
|
||||||
|
const metaFile = buildConfig.outfile.replace(/\.js/, '.meta.json');
|
||||||
|
const statsFile = buildConfig.outfile.replace(/\.js/, '.stats.html');
|
||||||
|
fs.writeFileSync(metaFile, JSON.stringify(result.metafile || {}), { encoding: 'utf-8' });
|
||||||
|
spawnSync('npx', ['esbuild-visualizer', '--metadata', metaFile, '--filename', statsFile], {
|
||||||
|
shell: true,
|
||||||
|
stdio: 'inherit',
|
||||||
|
});
|
||||||
|
spawnSync('open', [statsFile], {
|
||||||
|
shell: true,
|
||||||
|
stdio: 'inherit',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const outFileContent = transformCjsToUmdFile(buildConfig.outfile);
|
||||||
|
|
||||||
|
console.log('minifying...');
|
||||||
|
const minifiedOutFile = buildConfig.outfile.replace(/\.js$/, '.min.js');
|
||||||
|
const minifiedResult = esbuild.transformSync(outFileContent, {
|
||||||
|
minify: true,
|
||||||
|
sourcemap: true,
|
||||||
|
sourcesContent: true,
|
||||||
|
sourcefile: path.basename(buildConfig.outfile),
|
||||||
|
});
|
||||||
|
|
||||||
|
fs.writeFileSync(minifiedOutFile, minifiedResult.code, { encoding: 'utf-8' });
|
||||||
|
fs.writeFileSync(`${minifiedOutFile}.map`, minifiedResult.map, { encoding: 'utf-8' });
|
||||||
|
|
||||||
|
minifiedResult.warnings.forEach((warnings) => {
|
||||||
|
console.log(warnings);
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('done');
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
// esbuild 没有直接提供 UMD 格式,所以这里我们自行包装转换下
|
||||||
|
function transformCjsToUmdFile(file) {
|
||||||
|
const globalName = UMD_GLOBAL_NAME;
|
||||||
|
const fileContent = fs.readFileSync(file, { encoding: 'utf-8' });
|
||||||
|
const transformedFileContent = `(function (global, factory) {
|
||||||
|
if (typeof exports === 'object' && typeof module !== 'undefined'){
|
||||||
|
factory(module, exports);
|
||||||
|
} else if (typeof define === 'function' && define.amd) {
|
||||||
|
define(['module', 'exports'], factory);
|
||||||
|
} else {
|
||||||
|
global = global || self;
|
||||||
|
var m = { exports: {} };
|
||||||
|
factory(m, m.exports);
|
||||||
|
global.${globalName} = m.exports;
|
||||||
|
}
|
||||||
|
}(this, function (module, exports) {
|
||||||
|
'use strict';
|
||||||
|
${fileContent};
|
||||||
|
return module.exports;
|
||||||
|
}));
|
||||||
|
`;
|
||||||
|
|
||||||
|
fs.writeFileSync(file, transformedFileContent, { encoding: 'utf-8' });
|
||||||
|
return transformedFileContent;
|
||||||
|
}
|
||||||
125
modules/code-generator/scripts/build-template-static-files.js
Normal file
125
modules/code-generator/scripts/build-template-static-files.js
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
/* eslint-disable no-console */
|
||||||
|
/* eslint-disable @typescript-eslint/quotes */
|
||||||
|
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||||
|
/* eslint-disable @typescript-eslint/no-require-imports */
|
||||||
|
// @ts-check
|
||||||
|
// 这个文件是用来构建模板中的静态文件的
|
||||||
|
const fs = require('fs');
|
||||||
|
const glob = require('glob');
|
||||||
|
const path = require('path');
|
||||||
|
const JSON5 = require('json5');
|
||||||
|
const { spawnSync } = require('child_process');
|
||||||
|
|
||||||
|
const PROJECT_ROOT = path.join(__dirname, '..');
|
||||||
|
|
||||||
|
const TEMPLATES = [
|
||||||
|
{
|
||||||
|
sourceDir: path.join(PROJECT_ROOT, 'static-files/rax'),
|
||||||
|
outputDir: path.join(PROJECT_ROOT, 'src/plugins/project/framework/rax/template'),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
try {
|
||||||
|
TEMPLATES.forEach(buildTemplateStaticFiles);
|
||||||
|
console.log('All done.');
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildTemplateStaticFiles({ sourceDir, outputDir }) {
|
||||||
|
console.log('processing %s template...', path.dirname(sourceDir));
|
||||||
|
|
||||||
|
// 扫描所有的目录
|
||||||
|
const sourceFiles = glob.sync('**/*', {
|
||||||
|
nodir: true,
|
||||||
|
dot: true,
|
||||||
|
cwd: sourceDir,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('got %d files: %o', sourceFiles.length, sourceFiles);
|
||||||
|
|
||||||
|
const staticFiles = {
|
||||||
|
imports: [],
|
||||||
|
runs: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
// 生成对应的文件
|
||||||
|
sourceFiles.forEach((sourceFileName, index) => {
|
||||||
|
console.log('processing %s', sourceFileName);
|
||||||
|
const sourceFileContent = fs.readFileSync(path.join(sourceDir, sourceFileName), 'utf-8');
|
||||||
|
const sourceFileRealName = sourceFileName.replace(/\.template$/, '');
|
||||||
|
const outputFileName = `${sourceFileRealName}.ts`;
|
||||||
|
const outputFileFullPath = path.join(outputDir, 'files', outputFileName);
|
||||||
|
|
||||||
|
const sourceFileExtName = path.extname(sourceFileRealName);
|
||||||
|
const sourceFileBaseName = path.basename(sourceFileRealName, sourceFileExtName);
|
||||||
|
|
||||||
|
// 确保目录存在
|
||||||
|
fs.mkdirSync(path.dirname(outputFileFullPath), { recursive: true });
|
||||||
|
|
||||||
|
// 写入文件
|
||||||
|
fs.writeFileSync(
|
||||||
|
outputFileFullPath,
|
||||||
|
[
|
||||||
|
`/* eslint-disable max-len */`,
|
||||||
|
`/* Note: this file is generated by "npm run template", please dont modify this file directly */`,
|
||||||
|
`/* -- instead, you should modify "${path.relative(
|
||||||
|
PROJECT_ROOT,
|
||||||
|
path.join(sourceDir, sourceFileName),
|
||||||
|
)}" and run "npm run template" */`,
|
||||||
|
`import { ResultFile } from '@alilc/lowcode-types';`,
|
||||||
|
'',
|
||||||
|
`export default function getFile(): [string[], ResultFile] {`,
|
||||||
|
` return ${JSON5.stringify([
|
||||||
|
// 文件目录:
|
||||||
|
path.dirname(sourceFileRealName).split(path.sep).filter(Boolean),
|
||||||
|
// 文件名和内容:
|
||||||
|
{
|
||||||
|
name: sourceFileBaseName,
|
||||||
|
ext: sourceFileExtName.replace(/^\./, ''),
|
||||||
|
content: sourceFileContent,
|
||||||
|
},
|
||||||
|
])};`,
|
||||||
|
`}`,
|
||||||
|
'',
|
||||||
|
].join('\n'),
|
||||||
|
{
|
||||||
|
encoding: 'utf-8',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
staticFiles.imports.push(`import file${index} from './files/${sourceFileRealName}';`);
|
||||||
|
|
||||||
|
staticFiles.runs.push(` runFileGenerator(root, file${index})`);
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('generating static-files.ts...');
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(outputDir, 'static-files.ts'),
|
||||||
|
[
|
||||||
|
`/* Note: this file is generated by "npm run template", please dont modify this file directly */`,
|
||||||
|
`import { ResultDir } from '@alilc/lowcode-types';
|
||||||
|
|
||||||
|
import { createResultDir } from '../../../../../utils/resultHelper';
|
||||||
|
import { runFileGenerator } from '../../../../../utils/templateHelper';`,
|
||||||
|
...staticFiles.imports,
|
||||||
|
'',
|
||||||
|
`export function generateStaticFiles(root = createResultDir('.')): ResultDir {`,
|
||||||
|
...staticFiles.runs,
|
||||||
|
` return root;`,
|
||||||
|
`}`,
|
||||||
|
'',
|
||||||
|
].join('\n'),
|
||||||
|
{ encoding: 'utf-8' },
|
||||||
|
);
|
||||||
|
|
||||||
|
// prettier 一把
|
||||||
|
console.log('run prettier...');
|
||||||
|
spawnSync('npx', ['prettier', '--write', `${outputDir}`], {
|
||||||
|
stdio: 'inherit',
|
||||||
|
shell: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('done %s', path.basename(sourceDir));
|
||||||
|
}
|
||||||
10
modules/code-generator/scripts/build-types
Executable file
10
modules/code-generator/scripts/build-types
Executable file
@ -0,0 +1,10 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
echo building types...
|
||||||
|
tsc --outDir types --declaration --emitDeclarationOnly && \
|
||||||
|
echo built types... && \
|
||||||
|
rm -rf types/packages && \
|
||||||
|
mv types/modules/code-generator/src/* types/ && \
|
||||||
|
rm -rf types/modules
|
||||||
|
|
||||||
|
|
||||||
75
modules/code-generator/scripts/build.js
Normal file
75
modules/code-generator/scripts/build.js
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
/* eslint-disable no-console */
|
||||||
|
/* eslint-disable @typescript-eslint/no-require-imports */
|
||||||
|
const _ = require('lodash');
|
||||||
|
const esbuild = require('esbuild');
|
||||||
|
const concurrently = require('concurrently');
|
||||||
|
const argv = require('yargs-parser')(process.argv.slice(2));
|
||||||
|
const packageJson = require('../package.json');
|
||||||
|
|
||||||
|
if (!argv.format) {
|
||||||
|
buildAll();
|
||||||
|
} else {
|
||||||
|
buildFormat(argv.format, argv.out || 'dist');
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildAll() {
|
||||||
|
concurrently(
|
||||||
|
[
|
||||||
|
{ name: 'build:types', command: 'sh scripts/build-types' },
|
||||||
|
{ name: 'build:cjs', command: 'node scripts/build --format=cjs --out=lib' },
|
||||||
|
{ name: 'build:esm', command: 'node scripts/build --format=esm --out=es' },
|
||||||
|
{ name: 'build:standalone', command: 'node scripts/build-standalone' },
|
||||||
|
{ name: 'build:standalone-worker', command: 'node scripts/build-standalone-worker' },
|
||||||
|
{ name: 'build:standalone-loader', command: 'node scripts/build-standalone-loader' },
|
||||||
|
{ name: 'build:cli', command: 'node scripts/build-cli' },
|
||||||
|
],
|
||||||
|
{
|
||||||
|
prefix: 'name',
|
||||||
|
killOthers: ['failure'],
|
||||||
|
restartTries: 0,
|
||||||
|
},
|
||||||
|
).then(
|
||||||
|
() => {
|
||||||
|
console.log('all done.');
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
process.exit(1);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildFormat(format, outDir) {
|
||||||
|
try {
|
||||||
|
console.log('building %s...', format);
|
||||||
|
const startTime = Date.now();
|
||||||
|
const result = esbuild.buildSync({
|
||||||
|
entryPoints: ['src/index.ts'],
|
||||||
|
outfile: `${outDir}/index.js`,
|
||||||
|
bundle: true,
|
||||||
|
platform: 'node',
|
||||||
|
target: ['node10'],
|
||||||
|
format,
|
||||||
|
sourcemap: true,
|
||||||
|
sourcesContent: true,
|
||||||
|
define: {},
|
||||||
|
treeShaking: true,
|
||||||
|
external: _.keys(packageJson.dependencies),
|
||||||
|
minify: false,
|
||||||
|
legalComments: 'external',
|
||||||
|
});
|
||||||
|
if (result.errors.length > 0) {
|
||||||
|
throw result.errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.warnings.length > 0) {
|
||||||
|
result.warnings.forEach((warnings) => {
|
||||||
|
console.warn(warnings);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('built %s in %ds', format, ((Date.now() - startTime) / 1000).toFixed(2));
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
24
modules/code-generator/scripts/move-files-to-build-dest.js
Normal file
24
modules/code-generator/scripts/move-files-to-build-dest.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-require-imports */
|
||||||
|
const fs = require('fs');
|
||||||
|
const { spawnSync } = require('child_process');
|
||||||
|
|
||||||
|
const BUILD_DEST = process.env.BUILD_DEST || '.package';
|
||||||
|
|
||||||
|
fs.mkdirSync(BUILD_DEST, { recursive: true });
|
||||||
|
|
||||||
|
const distFiles = [...require('../package.json').files, 'package.json'];
|
||||||
|
|
||||||
|
distFiles.forEach((file) => {
|
||||||
|
console.log('mv %s', file);
|
||||||
|
if (file === BUILD_DEST) {
|
||||||
|
fs.mkdirSync(`${BUILD_DEST}/${file}`, { recursive: true });
|
||||||
|
spawnSync('mv', [`${file}/*`, `${BUILD_DEST}/${file}/`], { shell: true, stdio: 'inherit' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
distFiles.forEach((file) => {
|
||||||
|
console.log('mv %s', file);
|
||||||
|
if (file !== BUILD_DEST) {
|
||||||
|
spawnSync('mv', [file, `${BUILD_DEST}/${file}`], { shell: true, stdio: 'inherit' });
|
||||||
|
}
|
||||||
|
});
|
||||||
17
modules/code-generator/scripts/test-standalone.js
Normal file
17
modules/code-generator/scripts/test-standalone.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// 测试 standalone 模式的基本功能
|
||||||
|
// 注1:指定文件测试就直接在后面加文件: node scripts/test-standalone.js tests/public/rax-app.test.ts
|
||||||
|
// 注2:指定测试用例就直接在后面加`-t xxx`: node scripts/test-standalone.js tests/public/rax-app.test.ts -t demo01
|
||||||
|
const { spawnSync } = require('child_process');
|
||||||
|
|
||||||
|
// 一定要先构建
|
||||||
|
spawnSync('npm', ['run', 'build:standalone'], { shell: true, stdio: 'inherit' });
|
||||||
|
|
||||||
|
// 然后只执行特定的一些测试用例
|
||||||
|
spawnSync('npx', ['jest', ...process.argv.slice(2)], {
|
||||||
|
env: {
|
||||||
|
...process.env,
|
||||||
|
TEST_TARGET: 'standalone',
|
||||||
|
},
|
||||||
|
shell: true,
|
||||||
|
stdio: 'inherit',
|
||||||
|
});
|
||||||
35
modules/code-generator/src/analyzer/componentAnalyzer.ts
Normal file
35
modules/code-generator/src/analyzer/componentAnalyzer.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import type { NodeSchema, CompositeObject } from '@alilc/lowcode-types';
|
||||||
|
import type { TComponentAnalyzer } from '../types';
|
||||||
|
|
||||||
|
import { handleSubNodes } from '../utils/schema';
|
||||||
|
|
||||||
|
export const componentAnalyzer: TComponentAnalyzer = (container) => {
|
||||||
|
let hasRefAttr = false;
|
||||||
|
const nodeValidator = (n: NodeSchema) => {
|
||||||
|
if (n.props) {
|
||||||
|
const props = n.props as CompositeObject;
|
||||||
|
if (props.ref) {
|
||||||
|
hasRefAttr = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
nodeValidator(container);
|
||||||
|
|
||||||
|
if (!hasRefAttr && container.children) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-invalid-void-type
|
||||||
|
handleSubNodes<void>(
|
||||||
|
container.children,
|
||||||
|
{
|
||||||
|
node: nodeValidator,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
rerun: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
isUsingRef: hasRefAttr,
|
||||||
|
};
|
||||||
|
};
|
||||||
2
modules/code-generator/src/cli/index.ts
Normal file
2
modules/code-generator/src/cli/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from './run';
|
||||||
|
export * from './init-solution';
|
||||||
65
modules/code-generator/src/cli/init-solution.ts
Normal file
65
modules/code-generator/src/cli/init-solution.ts
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
/* eslint-disable no-console */
|
||||||
|
import * as fs from 'fs-extra';
|
||||||
|
import * as path from 'path';
|
||||||
|
import chalk from 'chalk';
|
||||||
|
import * as changeCase from 'change-case';
|
||||||
|
import { getErrorMessage } from '../utils/errors';
|
||||||
|
import { getLowcodeSolutionTemplateFiles } from './solutions/example-solution';
|
||||||
|
|
||||||
|
|
||||||
|
export async function initSolution(args: string[], options: {
|
||||||
|
quiet?: boolean;
|
||||||
|
verbose?: boolean;
|
||||||
|
}) {
|
||||||
|
try {
|
||||||
|
const cwd = process.cwd();
|
||||||
|
let solutionName = args[0] || 'hello';
|
||||||
|
let solutionPath = path.resolve(cwd, solutionName);
|
||||||
|
if (solutionName === '.') {
|
||||||
|
solutionName = path.basename(cwd);
|
||||||
|
solutionPath = cwd;
|
||||||
|
}
|
||||||
|
|
||||||
|
const modifyFileContent = (content: string) =>
|
||||||
|
content
|
||||||
|
.replace(/hello-world/g, changeCase.paramCase(solutionName))
|
||||||
|
.replace(/HelloWorld/g, changeCase.pascalCase(solutionName))
|
||||||
|
.replace(/Hello World/g, changeCase.titleCase(solutionName));
|
||||||
|
|
||||||
|
await ensureDirExists(solutionPath);
|
||||||
|
|
||||||
|
const templateFiles = getLowcodeSolutionTemplateFiles();
|
||||||
|
for (const templateFile of templateFiles) {
|
||||||
|
if (options.verbose) {
|
||||||
|
console.log('%s', chalk.gray(`creating file ${templateFile.file}`));
|
||||||
|
}
|
||||||
|
|
||||||
|
const templateFilePath = path.join(solutionPath, templateFile.file);
|
||||||
|
await ensureDirExists(path.dirname(templateFilePath));
|
||||||
|
await fs.writeFile(templateFilePath, modifyFileContent(templateFile.content), { encoding: 'utf-8' });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!options.quiet) {
|
||||||
|
console.log('%s', chalk.green(`solution ${solutionName} created successfully`));
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
} catch (e) {
|
||||||
|
console.log(chalk.red(getErrorMessage(e) || `Unexpected error: ${e}`));
|
||||||
|
if (typeof e === 'object' && (e as { stack: string } | null)?.stack && options.verbose) {
|
||||||
|
console.log(chalk.gray((e as { stack: string }).stack));
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function ensureDirExists(dirPath: string) {
|
||||||
|
try {
|
||||||
|
await fs.mkdir(dirPath, { recursive: true });
|
||||||
|
} catch (e) {
|
||||||
|
if ((e as { code: string }).code === 'EEXIST') {
|
||||||
|
return;// ignore existing error
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
133
modules/code-generator/src/cli/run.ts
Normal file
133
modules/code-generator/src/cli/run.ts
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
/* eslint-disable no-console */
|
||||||
|
import chalk from 'chalk';
|
||||||
|
import * as fs from 'fs-extra';
|
||||||
|
import JSON5 from 'json5';
|
||||||
|
import { jsonc } from 'jsonc';
|
||||||
|
import { spawnSync } from 'child_process';
|
||||||
|
import * as path from 'path';
|
||||||
|
|
||||||
|
import { getErrorMessage } from '../utils/errors';
|
||||||
|
import CodeGenerator from '..';
|
||||||
|
import type { IProjectBuilder } from '..';
|
||||||
|
import type { ProjectSchema } from '@alilc/lowcode-types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行出码 CLI 命令
|
||||||
|
* @param args 入参数组
|
||||||
|
* @param options 选项
|
||||||
|
* @returns {Promise<number>} 错误码
|
||||||
|
*/
|
||||||
|
export async function run(
|
||||||
|
args: string[],
|
||||||
|
options: {
|
||||||
|
solution: string;
|
||||||
|
input?: string;
|
||||||
|
output?: string;
|
||||||
|
quiet?: boolean;
|
||||||
|
verbose?: boolean;
|
||||||
|
},
|
||||||
|
): Promise<number> {
|
||||||
|
try {
|
||||||
|
const schemaFile = options.input || args[0];
|
||||||
|
if (!schemaFile) {
|
||||||
|
throw new Error(
|
||||||
|
'a schema file must be specified by `--input <schema.json>` or by the first positional argument',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((options.input && args.length > 0) || args.length > 1) {
|
||||||
|
throw new Error(
|
||||||
|
'only one schema file can be specified, either by `--input <schema.json>` or by the first positional argument',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取 Schema
|
||||||
|
const schema = await loadSchemaFile(schemaFile);
|
||||||
|
|
||||||
|
// 创建一个项目构建器
|
||||||
|
const createProjectBuilder = await getProjectBuilderFactory(options.solution, {
|
||||||
|
quiet: options.quiet,
|
||||||
|
});
|
||||||
|
const builder = createProjectBuilder();
|
||||||
|
|
||||||
|
// 生成代码
|
||||||
|
const generatedSourceCodes = await builder.generateProject(schema);
|
||||||
|
|
||||||
|
// 输出到磁盘
|
||||||
|
const publisher = CodeGenerator.publishers.disk();
|
||||||
|
|
||||||
|
await publisher.publish({
|
||||||
|
project: generatedSourceCodes,
|
||||||
|
outputPath: options.output || 'generated',
|
||||||
|
projectSlug: 'example',
|
||||||
|
createProjectFolder: false,
|
||||||
|
});
|
||||||
|
return 0;
|
||||||
|
} catch (e) {
|
||||||
|
console.log(chalk.red(getErrorMessage(e) || `Unexpected error: ${e}`));
|
||||||
|
if (typeof e === 'object' && (e as { stack: string } | null)?.stack && options.verbose) {
|
||||||
|
console.log(chalk.gray((e as { stack: string }).stack));
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getProjectBuilderFactory(
|
||||||
|
solution: string,
|
||||||
|
{ quiet }: { quiet?: boolean },
|
||||||
|
): Promise<() => IProjectBuilder> {
|
||||||
|
if (solution in CodeGenerator.solutions) {
|
||||||
|
return CodeGenerator.solutions[solution as 'icejs' | 'rax'];
|
||||||
|
}
|
||||||
|
|
||||||
|
const solutionPackageName = isLocalSolution(solution)
|
||||||
|
? solution
|
||||||
|
: `${solution.startsWith('@') ? solution : `@alilc/lowcode-solution-${solution}`}`;
|
||||||
|
|
||||||
|
if (!isLocalSolution(solution)) {
|
||||||
|
if (!quiet) {
|
||||||
|
console.log(`"${solution}" is not internal, installing it as ${solutionPackageName}...`);
|
||||||
|
}
|
||||||
|
|
||||||
|
spawnSync('tnpm', ['i', solutionPackageName], {
|
||||||
|
stdio: quiet ? 'ignore' : 'inherit',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||||
|
const solutionExports = require(!isLocalSolution(solution)
|
||||||
|
? solutionPackageName
|
||||||
|
: `${path.isAbsolute(solution) ? solution : path.join(process.cwd(), solution)}`);
|
||||||
|
|
||||||
|
const projectBuilderFactory =
|
||||||
|
solutionExports.createProjectBuilder ||
|
||||||
|
solutionExports.createAppBuilder ||
|
||||||
|
solutionExports.default;
|
||||||
|
|
||||||
|
if (typeof projectBuilderFactory !== 'function') {
|
||||||
|
throw new Error(
|
||||||
|
`"${solutionPackageName}" should export project builder factory via named export 'createProjectBuilder' or via default export`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return projectBuilderFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isLocalSolution(solution: string) {
|
||||||
|
return solution.startsWith('.') || solution.startsWith('/') || solution.startsWith('~');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadSchemaFile(schemaFile: string): Promise<ProjectSchema> {
|
||||||
|
if (!schemaFile) {
|
||||||
|
throw new Error('invalid schema file name');
|
||||||
|
}
|
||||||
|
|
||||||
|
const schemaFileContent = await fs.readFile(schemaFile, 'utf8');
|
||||||
|
|
||||||
|
if (/\.json5/.test(schemaFile)) {
|
||||||
|
return JSON5.parse(schemaFileContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 默认用 JSONC 的格式解析(兼容 JSON)
|
||||||
|
return jsonc.parse(schemaFileContent);
|
||||||
|
}
|
||||||
789
modules/code-generator/src/cli/solutions/example-solution.ts
Normal file
789
modules/code-generator/src/cli/solutions/example-solution.ts
Normal file
@ -0,0 +1,789 @@
|
|||||||
|
export function getLowcodeSolutionTemplateFiles() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
file: '.editorconfig',
|
||||||
|
content: `root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
end_of_line = lf
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
|
quote_type = single
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
file: '.eslintignore',
|
||||||
|
content: `# 忽略目录
|
||||||
|
node_modules/
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
test-cases/
|
||||||
|
test/
|
||||||
|
tests/
|
||||||
|
output/
|
||||||
|
es/
|
||||||
|
lib/
|
||||||
|
coverage/
|
||||||
|
|
||||||
|
# 忽略文件
|
||||||
|
**/*.min.js
|
||||||
|
**/*-min.js
|
||||||
|
**/*.bundle.js
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
file: '.eslintrc.js',
|
||||||
|
content: `module.exports = {
|
||||||
|
extends: 'eslint-config-ali/typescript/react',
|
||||||
|
rules: {
|
||||||
|
'max-len': ['error', { code: 200 }],
|
||||||
|
'comma-dangle': 0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
file: '.gitignore',
|
||||||
|
content: `# project custom
|
||||||
|
build
|
||||||
|
es
|
||||||
|
lib
|
||||||
|
dist
|
||||||
|
output
|
||||||
|
package-lock.json
|
||||||
|
deploy-space/packages
|
||||||
|
deploy-space/.env
|
||||||
|
generated
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.vscode
|
||||||
|
.idea
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Bower dependency directory (https://bower.io/)
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
lib
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# TypeScript v1 declaration files
|
||||||
|
typings/
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variables file
|
||||||
|
.env
|
||||||
|
.env.test
|
||||||
|
|
||||||
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
|
.cache
|
||||||
|
|
||||||
|
# next.js build output
|
||||||
|
.next
|
||||||
|
|
||||||
|
# nuxt.js build output
|
||||||
|
.nuxt
|
||||||
|
|
||||||
|
# vuepress build output
|
||||||
|
.vuepress/dist
|
||||||
|
|
||||||
|
# Serverless directories
|
||||||
|
.serverless/
|
||||||
|
|
||||||
|
# FuseBox cache
|
||||||
|
.fusebox/
|
||||||
|
|
||||||
|
# DynamoDB Local files
|
||||||
|
.dynamodb/
|
||||||
|
|
||||||
|
# mac config files
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# codealike
|
||||||
|
codealike.json
|
||||||
|
.node
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
file: '.prettierignore',
|
||||||
|
content: '/test-cases/',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
file: '.prettierrc',
|
||||||
|
content: `{
|
||||||
|
"tabWidth": 2,
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "es5"
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
file: 'CHANGELOG.md',
|
||||||
|
content: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
file: 'CONTRIBUTING.md',
|
||||||
|
content: `# 欢迎共建
|
||||||
|
|
||||||
|
# 注意
|
||||||
|
|
||||||
|
- 注意解决 eslint 问题
|
||||||
|
- 注意代码格式化 -- 建议安装 prettier 插件
|
||||||
|
- 发布前注意要跑通 demo 和所有的单测
|
||||||
|
|
||||||
|
## 本地调试运行 Demo
|
||||||
|
|
||||||
|
\`\`\`sh
|
||||||
|
> npm run demo
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
## 本地跑单测
|
||||||
|
|
||||||
|
\`\`\`sh
|
||||||
|
> npm test
|
||||||
|
\`\`\`
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
file: 'README.md',
|
||||||
|
content: `# 低代码出码自定义方案之 Hello World
|
||||||
|
|
||||||
|
## 直接执行
|
||||||
|
|
||||||
|
\`\`\`sh
|
||||||
|
> npx ali-lowcode-solution-hello-world demo-schema.json
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
## 本地调试运行 Demo
|
||||||
|
|
||||||
|
\`\`\`sh
|
||||||
|
> npm run demo
|
||||||
|
\`\`\`
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
file: 'demo-schema.json',
|
||||||
|
content: `{
|
||||||
|
"version": "1.0.0",
|
||||||
|
"componentsMap": [
|
||||||
|
{
|
||||||
|
"componentName": "Button",
|
||||||
|
"package": "@alifd/next",
|
||||||
|
"version": "1.19.18",
|
||||||
|
"destructuring": true,
|
||||||
|
"exportName": "Button"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"componentName": "Button.Group",
|
||||||
|
"package": "@alifd/next",
|
||||||
|
"version": "1.19.18",
|
||||||
|
"destructuring": true,
|
||||||
|
"exportName": "Button",
|
||||||
|
"subName": "Group"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"componentName": "Input",
|
||||||
|
"package": "@alifd/next",
|
||||||
|
"version": "1.19.18",
|
||||||
|
"destructuring": true,
|
||||||
|
"exportName": "Input"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"componentName": "Form",
|
||||||
|
"package": "@alifd/next",
|
||||||
|
"version": "1.19.18",
|
||||||
|
"destructuring": true,
|
||||||
|
"exportName": "Form"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"componentName": "Form.Item",
|
||||||
|
"package": "@alifd/next",
|
||||||
|
"version": "1.19.18",
|
||||||
|
"destructuring": true,
|
||||||
|
"exportName": "Form",
|
||||||
|
"subName": "Item"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"componentName": "NumberPicker",
|
||||||
|
"package": "@alifd/next",
|
||||||
|
"version": "1.19.18",
|
||||||
|
"destructuring": true,
|
||||||
|
"exportName": "NumberPicker"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"componentName": "Select",
|
||||||
|
"package": "@alifd/next",
|
||||||
|
"version": "1.19.18",
|
||||||
|
"destructuring": true,
|
||||||
|
"exportName": "Select"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"componentsTree": [
|
||||||
|
{
|
||||||
|
"componentName": "Page",
|
||||||
|
"id": "node$1",
|
||||||
|
"meta": {
|
||||||
|
"title": "测试",
|
||||||
|
"router": "/"
|
||||||
|
},
|
||||||
|
"props": {
|
||||||
|
"ref": "outterView",
|
||||||
|
"autoLoading": true
|
||||||
|
},
|
||||||
|
"fileName": "test",
|
||||||
|
"state": {
|
||||||
|
"text": "outter"
|
||||||
|
},
|
||||||
|
"lifeCycles": {
|
||||||
|
"componentDidMount": {
|
||||||
|
"type": "JSExpression",
|
||||||
|
"value": "function() { console.log('componentDidMount'); }"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"methodsModule": {
|
||||||
|
"type": "JSModule",
|
||||||
|
"source": "export function helloWorld() {\\n console.log('Hello world!');\\n}\\n"
|
||||||
|
},
|
||||||
|
"dataSource": {
|
||||||
|
"list": [
|
||||||
|
{
|
||||||
|
"id": "urlParams",
|
||||||
|
"type": "urlParams"
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"id": "user",
|
||||||
|
"type": "fetch",
|
||||||
|
"options": {
|
||||||
|
"method": "GET",
|
||||||
|
"uri": "https://shs.alibaba-inc.com/mock/1458/demo/user",
|
||||||
|
"isSync": true
|
||||||
|
},
|
||||||
|
"dataHandler": {
|
||||||
|
"type": "JSExpression",
|
||||||
|
"value": "function (response) {\\nif (!response.data.success){\\n throw new Error(response.data.message);\\n }\\n return response.data.data;\\n}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"id": "orders",
|
||||||
|
"type": "fetch",
|
||||||
|
"options": {
|
||||||
|
"method": "GET",
|
||||||
|
"uri": "https://shs.alibaba-inc.com/mock/1458/demo/orders",
|
||||||
|
"isSync": true
|
||||||
|
},
|
||||||
|
"dataHandler": {
|
||||||
|
"type": "JSExpression",
|
||||||
|
"value": "function (response) {\\nif (!response.data.success){\\n throw new Error(response.data.message);\\n }\\n return response.data.data.result;\\n}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dataHandler": {
|
||||||
|
"type": "JSExpression",
|
||||||
|
"value": "function (dataMap) {\\n console.info(\\"All datasources loaded:\\", dataMap);\\n}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"componentName": "Form",
|
||||||
|
"id": "node$2",
|
||||||
|
"props": {
|
||||||
|
"labelCol": {
|
||||||
|
"type": "JSExpression",
|
||||||
|
"value": "this.state.colNum"
|
||||||
|
},
|
||||||
|
"style": {},
|
||||||
|
"ref": "testForm"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"componentName": "Form.Item",
|
||||||
|
"id": "node$3",
|
||||||
|
"props": {
|
||||||
|
"label": "姓名:",
|
||||||
|
"name": "name",
|
||||||
|
"initValue": "李雷"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"componentName": "Input",
|
||||||
|
"id": "node$4",
|
||||||
|
"props": {
|
||||||
|
"placeholder": "请输入",
|
||||||
|
"size": "medium",
|
||||||
|
"style": {
|
||||||
|
"width": 320
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"componentName": "Form.Item",
|
||||||
|
"id": "node$5",
|
||||||
|
"props": {
|
||||||
|
"label": "年龄:",
|
||||||
|
"name": "age",
|
||||||
|
"initValue": "22"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"componentName": "NumberPicker",
|
||||||
|
"id": "node$6",
|
||||||
|
"props": {
|
||||||
|
"size": "medium",
|
||||||
|
"type": "normal"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"componentName": "Form.Item",
|
||||||
|
"id": "node$7",
|
||||||
|
"props": {
|
||||||
|
"label": "职业:",
|
||||||
|
"name": "profession"
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"componentName": "Select",
|
||||||
|
"id": "node$8",
|
||||||
|
"props": {
|
||||||
|
"dataSource": [
|
||||||
|
{
|
||||||
|
"label": "教师",
|
||||||
|
"value": "t"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "医生",
|
||||||
|
"value": "d"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "歌手",
|
||||||
|
"value": "s"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"componentName": "Div",
|
||||||
|
"id": "node$9",
|
||||||
|
"props": {
|
||||||
|
"style": {
|
||||||
|
"textAlign": "center"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"componentName": "Button.Group",
|
||||||
|
"id": "node$a",
|
||||||
|
"props": {},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"componentName": "Button",
|
||||||
|
"id": "node$b",
|
||||||
|
"condition": {
|
||||||
|
"type": "JSExpression",
|
||||||
|
"value": "this.index >= 1"
|
||||||
|
},
|
||||||
|
"loop": ["a", "b", "c"],
|
||||||
|
"props": {
|
||||||
|
"type": "primary",
|
||||||
|
"style": {
|
||||||
|
"margin": "0 5px 0 5px"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "JSExpression",
|
||||||
|
"value": "this.item"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"constants": {
|
||||||
|
"ENV": "prod",
|
||||||
|
"DOMAIN": "xxx.alibaba-inc.com"
|
||||||
|
},
|
||||||
|
"css": "body {font-size: 12px;} .table { width: 100px;}",
|
||||||
|
"config": {
|
||||||
|
"sdkVersion": "1.0.3",
|
||||||
|
"historyMode": "hash",
|
||||||
|
"targetRootID": "J_Container",
|
||||||
|
"layout": {
|
||||||
|
"componentName": "BasicLayout",
|
||||||
|
"props": {
|
||||||
|
"logo": "...",
|
||||||
|
"name": "测试网站"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"theme": {
|
||||||
|
"package": "@alife/theme-fusion",
|
||||||
|
"version": "^0.1.0",
|
||||||
|
"primary": "#ff9966"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"meta": {
|
||||||
|
"name": "demo应用",
|
||||||
|
"git_group": "appGroup",
|
||||||
|
"project_name": "app_demo",
|
||||||
|
"description": "这是一个测试应用",
|
||||||
|
"spma": "spa23d",
|
||||||
|
"creator": "月飞"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
file: 'jest.config.js',
|
||||||
|
content: `module.exports = {
|
||||||
|
preset: 'ts-jest',
|
||||||
|
testEnvironment: 'node',
|
||||||
|
testPathIgnorePatterns: ['/node_modules/', '/test-cases/', '/static-files/', '/lib/'],
|
||||||
|
};
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
file: 'package.json',
|
||||||
|
content: `{
|
||||||
|
"name": "ali-lowcode-solution-hello-world",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "AlLowCode Code Generate Solution - Hello World",
|
||||||
|
"files": [
|
||||||
|
"src",
|
||||||
|
"lib",
|
||||||
|
"tests",
|
||||||
|
"jest.config.js",
|
||||||
|
".editorconfig",
|
||||||
|
".eslintignore",
|
||||||
|
".eslintrc.js",
|
||||||
|
".gitignore",
|
||||||
|
".prettierignore",
|
||||||
|
".prettierrc",
|
||||||
|
"CHANGELOG.md",
|
||||||
|
"CONTRIBUTING.md",
|
||||||
|
"demo-schema.json",
|
||||||
|
"package.json",
|
||||||
|
"README.md",
|
||||||
|
"tsconfig.json"
|
||||||
|
],
|
||||||
|
"main": "lib/index.js",
|
||||||
|
"scripts": {
|
||||||
|
"start": "jest --watch",
|
||||||
|
"build": "npm run clean && concurrently 'npm run build:ts' 'npm run lint'",
|
||||||
|
"build:ts": "tsc",
|
||||||
|
"check:type": "tsc -p . --noEmit",
|
||||||
|
"clean": "rm -rf build dist lib generated",
|
||||||
|
"dev": "build-scripts start",
|
||||||
|
"lint": "eslint --ext .tsx,.ts,.js,.jsx src",
|
||||||
|
"lintfix": "eslint --fix --color --ext .tsx,.ts,.js,.jsx src",
|
||||||
|
"lint-staged": "lint-staged",
|
||||||
|
"prepublishOnly": "npm run build",
|
||||||
|
"postpublish": "git push origin master --tags",
|
||||||
|
"test": "jest",
|
||||||
|
"test:watch": "jest --watch",
|
||||||
|
"test:update-snapshots": "cross-env UPDATE_EXPECTED=true npx jest",
|
||||||
|
"demo": "npm run build && npx @alilc/lowcode-code-generator --solution . --output generated demo-schema.json"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git@github.com:your-name/ali-lowcode-solution-hello-world.git"
|
||||||
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"publishConfig": {
|
||||||
|
"registry": "https://registry.npm.alibaba-inc.com"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@alilc/lowcode-code-generator": "^1.0.0-beta.16",
|
||||||
|
"@alilc/lowcode-types": "^1.0.0-beta.21",
|
||||||
|
"tslib": "^2.3.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/async": "^3.2.3",
|
||||||
|
"@types/jest": "^26.0.17",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^4.28.4",
|
||||||
|
"@typescript-eslint/parser": "^4.28.4",
|
||||||
|
"async": "^3.2.0",
|
||||||
|
"babel-runtime": "^6.26.0",
|
||||||
|
"concurrently": "^5.2.0",
|
||||||
|
"cross-env": "^7.0.0",
|
||||||
|
"debug": "^4.1.1",
|
||||||
|
"eslint": "^7.31.0",
|
||||||
|
"eslint-config-ali": "^12.1.0",
|
||||||
|
"eslint-plugin-import": "^2.23.4",
|
||||||
|
"eslint-plugin-react": "^7.24.0",
|
||||||
|
"eslint-plugin-react-hooks": "^4.2.0",
|
||||||
|
"glob": "^7.2.0",
|
||||||
|
"husky": "4.2.5",
|
||||||
|
"jest": "^26.6.3",
|
||||||
|
"json5": "^2.2.0",
|
||||||
|
"lint-staged": "10.1.x",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
|
"md5": "^2.2.1",
|
||||||
|
"prettier": "^2.3.2",
|
||||||
|
"ts-jest": "^26.4.4",
|
||||||
|
"ts-node": "^9.0.0",
|
||||||
|
"typescript": "4.x"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
file: 'tsconfig.json',
|
||||||
|
content: `{
|
||||||
|
"compilerOptions": {
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"declaration": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"importHelpers": true,
|
||||||
|
"incremental": false,
|
||||||
|
"jsx": "react",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"strict": true,
|
||||||
|
"stripInternal": true,
|
||||||
|
"outDir": "./lib",
|
||||||
|
"declarationDir": "./lib",
|
||||||
|
"rootDirs": ["./src"],
|
||||||
|
"target": "es6",
|
||||||
|
"module": "commonjs",
|
||||||
|
"lib": ["esnext"],
|
||||||
|
"types": ["jest", "node"],
|
||||||
|
"noUnusedLocals": false,
|
||||||
|
"noUnusedParameters": false
|
||||||
|
},
|
||||||
|
"include": ["src/**/*", "typings/**/*"]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
file: 'src/index.ts',
|
||||||
|
content: `import CodeGen from '@alilc/lowcode-code-generator';
|
||||||
|
|
||||||
|
import examplePlugin from './plugins/example';
|
||||||
|
|
||||||
|
export default function createHelloWorldProjectBuilder() {
|
||||||
|
return CodeGen.createProjectBuilder({
|
||||||
|
template: CodeGen.solutionParts.icejs.template,
|
||||||
|
plugins: {
|
||||||
|
components: [
|
||||||
|
CodeGen.plugins.react.reactCommonDeps(),
|
||||||
|
CodeGen.plugins.common.esmodule({ fileType: 'jsx' }),
|
||||||
|
CodeGen.plugins.react.containerClass(),
|
||||||
|
CodeGen.plugins.react.containerInjectUtils(),
|
||||||
|
CodeGen.plugins.react.containerInjectDataSourceEngine(),
|
||||||
|
CodeGen.plugins.react.containerInjectI18n(),
|
||||||
|
CodeGen.plugins.react.containerInitState(),
|
||||||
|
CodeGen.plugins.react.containerLifeCycle(),
|
||||||
|
CodeGen.plugins.react.containerMethod(),
|
||||||
|
examplePlugin(),
|
||||||
|
CodeGen.plugins.react.jsx({
|
||||||
|
nodeTypeMapping: {
|
||||||
|
Div: 'div',
|
||||||
|
Component: 'div',
|
||||||
|
Page: 'div',
|
||||||
|
Block: 'div',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
CodeGen.plugins.style.css(),
|
||||||
|
],
|
||||||
|
pages: [
|
||||||
|
CodeGen.plugins.react.reactCommonDeps(),
|
||||||
|
CodeGen.plugins.common.esmodule({ fileType: 'jsx' }),
|
||||||
|
CodeGen.plugins.react.containerClass(),
|
||||||
|
CodeGen.plugins.react.containerInjectUtils(),
|
||||||
|
CodeGen.plugins.react.containerInjectDataSourceEngine(),
|
||||||
|
CodeGen.plugins.react.containerInjectI18n(),
|
||||||
|
CodeGen.plugins.react.containerInitState(),
|
||||||
|
CodeGen.plugins.react.containerLifeCycle(),
|
||||||
|
CodeGen.plugins.react.containerMethod(),
|
||||||
|
examplePlugin(),
|
||||||
|
CodeGen.plugins.react.jsx({
|
||||||
|
nodeTypeMapping: {
|
||||||
|
Div: 'div',
|
||||||
|
Component: 'div',
|
||||||
|
Page: 'div',
|
||||||
|
Block: 'div',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
CodeGen.plugins.style.css(),
|
||||||
|
],
|
||||||
|
router: [
|
||||||
|
CodeGen.plugins.common.esmodule(),
|
||||||
|
CodeGen.solutionParts.icejs.plugins.router(),
|
||||||
|
],
|
||||||
|
entry: [CodeGen.solutionParts.icejs.plugins.entry()],
|
||||||
|
constants: [CodeGen.plugins.project.constants()],
|
||||||
|
utils: [
|
||||||
|
CodeGen.plugins.common.esmodule(),
|
||||||
|
CodeGen.plugins.project.utils('react'),
|
||||||
|
],
|
||||||
|
i18n: [CodeGen.plugins.project.i18n()],
|
||||||
|
globalStyle: [CodeGen.solutionParts.icejs.plugins.globalStyle()],
|
||||||
|
htmlEntry: [CodeGen.solutionParts.icejs.plugins.entryHtml()],
|
||||||
|
packageJSON: [CodeGen.solutionParts.icejs.plugins.packageJSON()],
|
||||||
|
},
|
||||||
|
postProcessors: [CodeGen.postprocessor.prettier()],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
file: 'src/plugins/example.ts',
|
||||||
|
content: `import {
|
||||||
|
ICodeStruct,
|
||||||
|
BuilderComponentPlugin,
|
||||||
|
BuilderComponentPluginFactory,
|
||||||
|
FileType,
|
||||||
|
ChunkType,
|
||||||
|
IContainerInfo,
|
||||||
|
COMMON_CHUNK_NAME,
|
||||||
|
CLASS_DEFINE_CHUNK_NAME,
|
||||||
|
DEFAULT_LINK_AFTER,
|
||||||
|
} from '@alilc/lowcode-code-generator';
|
||||||
|
|
||||||
|
export interface PluginConfig {
|
||||||
|
fileType: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pluginFactory: BuilderComponentPluginFactory<PluginConfig> = (
|
||||||
|
config?
|
||||||
|
) => {
|
||||||
|
const cfg: PluginConfig = {
|
||||||
|
fileType: FileType.JSX,
|
||||||
|
...config,
|
||||||
|
};
|
||||||
|
|
||||||
|
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
|
||||||
|
const next: ICodeStruct = {
|
||||||
|
...pre,
|
||||||
|
};
|
||||||
|
|
||||||
|
const ir = next.ir as IContainerInfo & {
|
||||||
|
methodsModule?: {
|
||||||
|
type?: 'JSModule';
|
||||||
|
source?: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
if (ir.methodsModule?.type !== 'JSModule' || !ir.methodsModule?.source) {
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建 methods.jsx
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
subModule: 'methods',
|
||||||
|
fileType: cfg.fileType,
|
||||||
|
name: COMMON_CHUNK_NAME.CustomContent,
|
||||||
|
content: ir.methodsModule.source,
|
||||||
|
linkAfter: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
// 引入对应的模块
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: cfg.fileType,
|
||||||
|
name: COMMON_CHUNK_NAME.InternalDepsImport,
|
||||||
|
content: "import __$$methodsModule from './methods';",
|
||||||
|
linkAfter: [...DEFAULT_LINK_AFTER[COMMON_CHUNK_NAME.InternalDepsImport]],
|
||||||
|
});
|
||||||
|
|
||||||
|
// 将导出的东东都放到 class 上实例方法部分
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: cfg.fileType,
|
||||||
|
name: CLASS_DEFINE_CHUNK_NAME.ConstructorContent,
|
||||||
|
content: 'Object.assign(this, __$$methodsModule);',
|
||||||
|
linkAfter: [
|
||||||
|
...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.ConstructorContent],
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
return next;
|
||||||
|
};
|
||||||
|
|
||||||
|
return plugin;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default pluginFactory;
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
file: 'tests/basic.test.ts',
|
||||||
|
content: `test('basic functions should be ok', () => {
|
||||||
|
// 这里放一些单元测试
|
||||||
|
expect(0).toBe(0);
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
3
modules/code-generator/src/config/env.ts
Normal file
3
modules/code-generator/src/config/env.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import * as path from 'path';
|
||||||
|
|
||||||
|
export const CODE_GENERATOR_ROOT = path.join(__dirname, '../..');
|
||||||
3
modules/code-generator/src/const/file.ts
Normal file
3
modules/code-generator/src/const/file.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import { FileType } from '../types/core';
|
||||||
|
|
||||||
|
export const FILE_TYPE_FAMILY = [[FileType.TSX, FileType.TS, FileType.JSX, FileType.JS]];
|
||||||
129
modules/code-generator/src/const/generator.ts
Normal file
129
modules/code-generator/src/const/generator.ts
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
export const COMMON_CHUNK_NAME = {
|
||||||
|
ExternalDepsImport: 'CommonExternalDependencyImport',
|
||||||
|
InternalDepsImport: 'CommonInternalDependencyImport',
|
||||||
|
ImportAliasDefine: 'CommonImportAliasDefine',
|
||||||
|
FileVarDefine: 'CommonFileScopeVarDefine',
|
||||||
|
FileUtilDefine: 'CommonFileScopeMethodDefine',
|
||||||
|
FileMainContent: 'CommonFileMainContent',
|
||||||
|
FileExport: 'CommonFileExport',
|
||||||
|
StyleDepsImport: 'CommonStyleDepsImport',
|
||||||
|
StyleCssContent: 'CommonStyleCssContent',
|
||||||
|
HtmlContent: 'CommonHtmlContent',
|
||||||
|
CustomContent: 'CommonCustomContent',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CLASS_DEFINE_CHUNK_NAME = {
|
||||||
|
Start: 'CommonClassDefineStart',
|
||||||
|
ConstructorStart: 'CommonClassDefineConstructorStart',
|
||||||
|
ConstructorContent: 'CommonClassDefineConstructorContent',
|
||||||
|
ConstructorEnd: 'CommonClassDefineConstructorEnd',
|
||||||
|
StaticVar: 'CommonClassDefineStaticVar',
|
||||||
|
StaticMethod: 'CommonClassDefineStaticMethod',
|
||||||
|
InsVar: 'CommonClassDefineInsVar',
|
||||||
|
InsVarMethod: 'CommonClassDefineInsVarMethod',
|
||||||
|
InsMethod: 'CommonClassDefineInsMethod',
|
||||||
|
InsPrivateMethod: 'CommonClassDefineInsPrivateMethod',
|
||||||
|
End: 'CommonClassDefineEnd',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DEFAULT_LINK_AFTER = {
|
||||||
|
[COMMON_CHUNK_NAME.ExternalDepsImport]: [],
|
||||||
|
[COMMON_CHUNK_NAME.InternalDepsImport]: [COMMON_CHUNK_NAME.ExternalDepsImport],
|
||||||
|
[COMMON_CHUNK_NAME.ImportAliasDefine]: [
|
||||||
|
COMMON_CHUNK_NAME.ExternalDepsImport, COMMON_CHUNK_NAME.InternalDepsImport,
|
||||||
|
],
|
||||||
|
[COMMON_CHUNK_NAME.FileVarDefine]: [
|
||||||
|
COMMON_CHUNK_NAME.ExternalDepsImport,
|
||||||
|
COMMON_CHUNK_NAME.InternalDepsImport,
|
||||||
|
COMMON_CHUNK_NAME.ImportAliasDefine,
|
||||||
|
],
|
||||||
|
[COMMON_CHUNK_NAME.FileUtilDefine]: [
|
||||||
|
COMMON_CHUNK_NAME.ExternalDepsImport,
|
||||||
|
COMMON_CHUNK_NAME.InternalDepsImport,
|
||||||
|
COMMON_CHUNK_NAME.ImportAliasDefine,
|
||||||
|
COMMON_CHUNK_NAME.FileVarDefine,
|
||||||
|
],
|
||||||
|
[CLASS_DEFINE_CHUNK_NAME.Start]: [
|
||||||
|
COMMON_CHUNK_NAME.ExternalDepsImport,
|
||||||
|
COMMON_CHUNK_NAME.InternalDepsImport,
|
||||||
|
COMMON_CHUNK_NAME.ImportAliasDefine,
|
||||||
|
COMMON_CHUNK_NAME.FileVarDefine,
|
||||||
|
COMMON_CHUNK_NAME.FileUtilDefine,
|
||||||
|
],
|
||||||
|
[CLASS_DEFINE_CHUNK_NAME.ConstructorStart]: [
|
||||||
|
CLASS_DEFINE_CHUNK_NAME.Start,
|
||||||
|
CLASS_DEFINE_CHUNK_NAME.StaticVar,
|
||||||
|
CLASS_DEFINE_CHUNK_NAME.StaticMethod,
|
||||||
|
CLASS_DEFINE_CHUNK_NAME.InsVar,
|
||||||
|
CLASS_DEFINE_CHUNK_NAME.InsVarMethod,
|
||||||
|
],
|
||||||
|
[CLASS_DEFINE_CHUNK_NAME.ConstructorContent]: [CLASS_DEFINE_CHUNK_NAME.ConstructorStart],
|
||||||
|
[CLASS_DEFINE_CHUNK_NAME.ConstructorEnd]: [
|
||||||
|
CLASS_DEFINE_CHUNK_NAME.ConstructorStart,
|
||||||
|
CLASS_DEFINE_CHUNK_NAME.ConstructorContent,
|
||||||
|
],
|
||||||
|
[CLASS_DEFINE_CHUNK_NAME.StaticVar]: [CLASS_DEFINE_CHUNK_NAME.Start],
|
||||||
|
[CLASS_DEFINE_CHUNK_NAME.StaticMethod]: [
|
||||||
|
CLASS_DEFINE_CHUNK_NAME.Start, CLASS_DEFINE_CHUNK_NAME.StaticVar,
|
||||||
|
],
|
||||||
|
[CLASS_DEFINE_CHUNK_NAME.InsVar]: [
|
||||||
|
CLASS_DEFINE_CHUNK_NAME.Start,
|
||||||
|
CLASS_DEFINE_CHUNK_NAME.StaticVar,
|
||||||
|
CLASS_DEFINE_CHUNK_NAME.StaticMethod,
|
||||||
|
],
|
||||||
|
[CLASS_DEFINE_CHUNK_NAME.InsVarMethod]: [
|
||||||
|
CLASS_DEFINE_CHUNK_NAME.Start,
|
||||||
|
CLASS_DEFINE_CHUNK_NAME.StaticVar,
|
||||||
|
CLASS_DEFINE_CHUNK_NAME.StaticMethod,
|
||||||
|
CLASS_DEFINE_CHUNK_NAME.InsVar,
|
||||||
|
],
|
||||||
|
[CLASS_DEFINE_CHUNK_NAME.InsMethod]: [
|
||||||
|
CLASS_DEFINE_CHUNK_NAME.Start,
|
||||||
|
CLASS_DEFINE_CHUNK_NAME.StaticVar,
|
||||||
|
CLASS_DEFINE_CHUNK_NAME.StaticMethod,
|
||||||
|
CLASS_DEFINE_CHUNK_NAME.InsVar,
|
||||||
|
CLASS_DEFINE_CHUNK_NAME.InsVarMethod,
|
||||||
|
CLASS_DEFINE_CHUNK_NAME.ConstructorEnd,
|
||||||
|
],
|
||||||
|
[CLASS_DEFINE_CHUNK_NAME.InsPrivateMethod]: [
|
||||||
|
CLASS_DEFINE_CHUNK_NAME.Start,
|
||||||
|
CLASS_DEFINE_CHUNK_NAME.StaticVar,
|
||||||
|
CLASS_DEFINE_CHUNK_NAME.StaticMethod,
|
||||||
|
CLASS_DEFINE_CHUNK_NAME.InsVar,
|
||||||
|
CLASS_DEFINE_CHUNK_NAME.InsVarMethod,
|
||||||
|
CLASS_DEFINE_CHUNK_NAME.InsMethod,
|
||||||
|
CLASS_DEFINE_CHUNK_NAME.ConstructorEnd,
|
||||||
|
],
|
||||||
|
[CLASS_DEFINE_CHUNK_NAME.End]: [
|
||||||
|
CLASS_DEFINE_CHUNK_NAME.Start,
|
||||||
|
CLASS_DEFINE_CHUNK_NAME.StaticVar,
|
||||||
|
CLASS_DEFINE_CHUNK_NAME.StaticMethod,
|
||||||
|
CLASS_DEFINE_CHUNK_NAME.InsVar,
|
||||||
|
CLASS_DEFINE_CHUNK_NAME.InsVarMethod,
|
||||||
|
CLASS_DEFINE_CHUNK_NAME.InsMethod,
|
||||||
|
CLASS_DEFINE_CHUNK_NAME.InsPrivateMethod,
|
||||||
|
CLASS_DEFINE_CHUNK_NAME.ConstructorEnd,
|
||||||
|
],
|
||||||
|
[COMMON_CHUNK_NAME.FileMainContent]: [
|
||||||
|
COMMON_CHUNK_NAME.ExternalDepsImport,
|
||||||
|
COMMON_CHUNK_NAME.InternalDepsImport,
|
||||||
|
COMMON_CHUNK_NAME.ImportAliasDefine,
|
||||||
|
COMMON_CHUNK_NAME.FileVarDefine,
|
||||||
|
COMMON_CHUNK_NAME.FileUtilDefine,
|
||||||
|
CLASS_DEFINE_CHUNK_NAME.End,
|
||||||
|
],
|
||||||
|
[COMMON_CHUNK_NAME.FileExport]: [
|
||||||
|
COMMON_CHUNK_NAME.ExternalDepsImport,
|
||||||
|
COMMON_CHUNK_NAME.InternalDepsImport,
|
||||||
|
COMMON_CHUNK_NAME.ImportAliasDefine,
|
||||||
|
COMMON_CHUNK_NAME.FileVarDefine,
|
||||||
|
COMMON_CHUNK_NAME.FileUtilDefine,
|
||||||
|
CLASS_DEFINE_CHUNK_NAME.End,
|
||||||
|
COMMON_CHUNK_NAME.FileMainContent,
|
||||||
|
],
|
||||||
|
[COMMON_CHUNK_NAME.StyleDepsImport]: [],
|
||||||
|
[COMMON_CHUNK_NAME.StyleCssContent]: [COMMON_CHUNK_NAME.StyleDepsImport],
|
||||||
|
[COMMON_CHUNK_NAME.HtmlContent]: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const COMMON_SUB_MODULE_NAME = 'index';
|
||||||
12
modules/code-generator/src/const/index.ts
Normal file
12
modules/code-generator/src/const/index.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
export const NATIVE_ELE_PKG = 'native';
|
||||||
|
|
||||||
|
export const CONTAINER_TYPE = {
|
||||||
|
COMPONENT: 'Component',
|
||||||
|
BLOCK: 'Block',
|
||||||
|
PAGE: 'Page',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SUPPORT_SCHEMA_VERSION_LIST = ['0.0.1', '1.0.0'];
|
||||||
|
|
||||||
|
export * from './file';
|
||||||
|
export * from './generator';
|
||||||
@ -0,0 +1,49 @@
|
|||||||
|
import { IScope } from '../../../types';
|
||||||
|
import { parseExpression } from '../../../utils/expressionParser';
|
||||||
|
import { isLiteralAtomicExpr } from '../util/isLiteralAtomicExpr';
|
||||||
|
import { isSimpleStraightLiteral } from '../util/isSimpleStraightLiteral';
|
||||||
|
import { transformThis2Context } from './transformThis2Context';
|
||||||
|
|
||||||
|
export function transformJsExpr(
|
||||||
|
expr: string,
|
||||||
|
scope: IScope,
|
||||||
|
{ dontWrapEval = false, dontTransformThis2ContextAtRootScope = false } = {},
|
||||||
|
) {
|
||||||
|
if (!expr) {
|
||||||
|
return 'undefined';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isLiteralAtomicExpr(expr)) {
|
||||||
|
return expr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const exprAst = parseExpression(expr);
|
||||||
|
|
||||||
|
// 对于下面这些比较安全的字面值,可以直接返回对应的表达式,而非包一层
|
||||||
|
if (isSimpleStraightLiteral(exprAst)) {
|
||||||
|
return expr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dontWrapEval) {
|
||||||
|
return transformThis2Context(exprAst, scope, {
|
||||||
|
ignoreRootScope: dontTransformThis2ContextAtRootScope,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (exprAst.type) {
|
||||||
|
// 对于直接写个函数的,则不用再包下,因为这样不会抛出异常的
|
||||||
|
case 'ArrowFunctionExpression':
|
||||||
|
case 'FunctionExpression':
|
||||||
|
return transformThis2Context(exprAst, scope, {
|
||||||
|
ignoreRootScope: dontTransformThis2ContextAtRootScope,
|
||||||
|
});
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 其他的都需要包一层
|
||||||
|
return `__$$eval(() => (${transformThis2Context(exprAst, scope, {
|
||||||
|
ignoreRootScope: dontTransformThis2ContextAtRootScope,
|
||||||
|
})}))`;
|
||||||
|
}
|
||||||
@ -0,0 +1,29 @@
|
|||||||
|
import { Expression } from '@babel/types';
|
||||||
|
import generate from '@babel/generator';
|
||||||
|
import { IScope } from '../../../types';
|
||||||
|
import { parseExpressionConvertThis2Context } from '../../../utils/expressionParser';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将所有的 this.xxx 替换为 __$$context.xxx
|
||||||
|
* @param expr
|
||||||
|
*/
|
||||||
|
export function transformThis2Context(
|
||||||
|
expr: string | Expression,
|
||||||
|
scope: IScope,
|
||||||
|
{ ignoreRootScope = false } = {},
|
||||||
|
): string {
|
||||||
|
if (ignoreRootScope && scope.parent == null) {
|
||||||
|
return typeof expr === 'string' ? expr : generate(expr).code;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 下面这种字符串替换的方式虽然简单直接,但是对于复杂场景会误匹配,故后期改成了解析 AST 然后修改 AST 最后再重新生成代码的方式
|
||||||
|
// return expr
|
||||||
|
// .replace(/\bthis\.item\./g, () => 'item.')
|
||||||
|
// .replace(/\bthis\.index\./g, () => 'index.')
|
||||||
|
// .replace(/\bthis\./g, () => '__$$context.');
|
||||||
|
return parseExpressionConvertThis2Context(
|
||||||
|
expr,
|
||||||
|
'__$$context',
|
||||||
|
scope.bindings?.getAllBindings() || [],
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
/**
|
||||||
|
* 判断是否是原子类型的表达式
|
||||||
|
*/
|
||||||
|
export function isLiteralAtomicExpr(expr: string): boolean {
|
||||||
|
return (
|
||||||
|
expr === 'null' ||
|
||||||
|
expr === 'undefined' ||
|
||||||
|
expr === 'true' ||
|
||||||
|
expr === 'false' ||
|
||||||
|
/^-?\d+(\.\d+)?$/.test(expr)
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
import { Expression } from '@babel/types';
|
||||||
|
|
||||||
|
/** 判断是非是一些简单直接的字面值 */
|
||||||
|
export function isSimpleStraightLiteral(expr: Expression): boolean {
|
||||||
|
switch (expr.type) {
|
||||||
|
case 'BigIntLiteral':
|
||||||
|
case 'BooleanLiteral':
|
||||||
|
case 'DecimalLiteral':
|
||||||
|
case 'NullLiteral':
|
||||||
|
case 'NumericLiteral':
|
||||||
|
case 'RegExpLiteral':
|
||||||
|
case 'StringLiteral':
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
123
modules/code-generator/src/generator/ChunkBuilder.ts
Normal file
123
modules/code-generator/src/generator/ChunkBuilder.ts
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
import { BuilderComponentPlugin, IChunkBuilder, ICodeChunk, ICodeStruct, FileType } from '../types';
|
||||||
|
|
||||||
|
import { COMMON_SUB_MODULE_NAME } from '../const/generator';
|
||||||
|
import { FILE_TYPE_FAMILY } from '../const/file';
|
||||||
|
|
||||||
|
interface ChunkGroupInfo {
|
||||||
|
chunk: ICodeChunk;
|
||||||
|
familyIdx?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
function whichFamily(type: FileType): [number, FileType[]] | undefined {
|
||||||
|
const idx = FILE_TYPE_FAMILY.findIndex((family) => family.indexOf(type) >= 0);
|
||||||
|
if (idx < 0) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return [idx, FILE_TYPE_FAMILY[idx]];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const groupChunks = (chunks: ICodeChunk[]): ICodeChunk[][] => {
|
||||||
|
const tmp: Record<string, Record<number, number>> = {};
|
||||||
|
const col = chunks.reduce((chunksSet: Record<string, ChunkGroupInfo[]>, chunk) => {
|
||||||
|
const fileKey = chunk.subModule || COMMON_SUB_MODULE_NAME;
|
||||||
|
if (!chunksSet[fileKey]) {
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
chunksSet[fileKey] = [];
|
||||||
|
}
|
||||||
|
const res = whichFamily(chunk.fileType as FileType);
|
||||||
|
const info: ChunkGroupInfo = {
|
||||||
|
chunk,
|
||||||
|
};
|
||||||
|
if (res) {
|
||||||
|
const [familyIdx, family] = res;
|
||||||
|
const rank = family.indexOf(chunk.fileType as FileType);
|
||||||
|
if (tmp[fileKey]) {
|
||||||
|
if (tmp[fileKey][familyIdx] !== undefined) {
|
||||||
|
if (tmp[fileKey][familyIdx] > rank) {
|
||||||
|
tmp[fileKey][familyIdx] = rank;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tmp[fileKey][familyIdx] = rank;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tmp[fileKey] = {};
|
||||||
|
tmp[fileKey][familyIdx] = rank;
|
||||||
|
}
|
||||||
|
info.familyIdx = familyIdx;
|
||||||
|
}
|
||||||
|
|
||||||
|
chunksSet[fileKey].push(info);
|
||||||
|
return chunksSet;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
const result: ICodeChunk[][] = [];
|
||||||
|
Object.keys(col).forEach((key) => {
|
||||||
|
const byType: Record<string, ICodeChunk[]> = {};
|
||||||
|
col[key].forEach((info) => {
|
||||||
|
let t: string = info.chunk.fileType;
|
||||||
|
if (info.familyIdx !== undefined) {
|
||||||
|
t = FILE_TYPE_FAMILY[info.familyIdx][tmp[key][info.familyIdx]];
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
info.chunk.fileType = t;
|
||||||
|
}
|
||||||
|
if (!byType[t]) {
|
||||||
|
byType[t] = [];
|
||||||
|
}
|
||||||
|
byType[t].push(info.chunk);
|
||||||
|
});
|
||||||
|
result.push(...Object.keys(byType).map((t) => byType[t]));
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 代码片段构建器
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @class ChunkBuilder
|
||||||
|
* @template T
|
||||||
|
*/
|
||||||
|
export class ChunkBuilder implements IChunkBuilder {
|
||||||
|
private plugins: BuilderComponentPlugin[];
|
||||||
|
|
||||||
|
constructor(plugins: BuilderComponentPlugin[] = []) {
|
||||||
|
this.plugins = plugins;
|
||||||
|
}
|
||||||
|
|
||||||
|
async run(
|
||||||
|
ir: unknown,
|
||||||
|
initialStructure: ICodeStruct = {
|
||||||
|
ir,
|
||||||
|
chunks: [],
|
||||||
|
depNames: [],
|
||||||
|
contextData: {},
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
const structure = initialStructure;
|
||||||
|
|
||||||
|
const finalStructure: ICodeStruct = await this.plugins.reduce(
|
||||||
|
async (previousPluginOperation: Promise<ICodeStruct>, plugin) => {
|
||||||
|
const modifiedStructure = await previousPluginOperation;
|
||||||
|
return plugin(modifiedStructure);
|
||||||
|
},
|
||||||
|
Promise.resolve(structure),
|
||||||
|
);
|
||||||
|
|
||||||
|
const chunks = groupChunks(finalStructure.chunks);
|
||||||
|
|
||||||
|
return {
|
||||||
|
chunks,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
getPlugins() {
|
||||||
|
return this.plugins;
|
||||||
|
}
|
||||||
|
|
||||||
|
addPlugin(plugin: BuilderComponentPlugin) {
|
||||||
|
this.plugins.push(plugin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ChunkBuilder;
|
||||||
103
modules/code-generator/src/generator/CodeBuilder.ts
Normal file
103
modules/code-generator/src/generator/CodeBuilder.ts
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
import {
|
||||||
|
ChunkContent,
|
||||||
|
ChunkType,
|
||||||
|
CodeGeneratorError,
|
||||||
|
CodeGeneratorFunction,
|
||||||
|
ICodeBuilder,
|
||||||
|
ICodeChunk,
|
||||||
|
} from '../types';
|
||||||
|
|
||||||
|
export class CodeBuilder implements ICodeBuilder {
|
||||||
|
private chunkDefinitions: ICodeChunk[] = [];
|
||||||
|
|
||||||
|
private generators: { [key: string]: CodeGeneratorFunction<ChunkContent> } = {
|
||||||
|
[ChunkType.STRING]: (str: string) => str, // no-op for string chunks
|
||||||
|
[ChunkType.JSON]: (json: Record<string, unknown>) => JSON.stringify(json), // stringify json to string
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(chunkDefinitions: ICodeChunk[] = []) {
|
||||||
|
this.chunkDefinitions = chunkDefinitions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Links all chunks together based on their requirements. Returns an array
|
||||||
|
* of ordered chunk names which need to be compiled and glued together.
|
||||||
|
*/
|
||||||
|
link(chunkDefinitions: ICodeChunk[] = []): string {
|
||||||
|
const chunks = chunkDefinitions || this.chunkDefinitions;
|
||||||
|
if (chunks.length <= 0) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const unprocessedChunks = chunks.map((chunk) => {
|
||||||
|
return {
|
||||||
|
name: chunk.name,
|
||||||
|
type: chunk.type,
|
||||||
|
content: chunk.content,
|
||||||
|
linkAfter: this.cleanupInvalidChunks(chunk.linkAfter, chunks),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const resultingString: string[] = [];
|
||||||
|
|
||||||
|
while (unprocessedChunks.length > 0) {
|
||||||
|
let indexToRemove = 0;
|
||||||
|
for (let index = 0; index < unprocessedChunks.length; index++) {
|
||||||
|
if (unprocessedChunks[index].linkAfter.length <= 0) {
|
||||||
|
indexToRemove = index;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unprocessedChunks[indexToRemove].linkAfter.length > 0) {
|
||||||
|
throw new CodeGeneratorError(
|
||||||
|
'Operation aborted. Reason: cyclic dependency between chunks.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { type, content, name } = unprocessedChunks[indexToRemove];
|
||||||
|
const compiledContent = this.generateByType(type, content);
|
||||||
|
if (compiledContent) {
|
||||||
|
resultingString.push(`${compiledContent}\n`);
|
||||||
|
}
|
||||||
|
|
||||||
|
unprocessedChunks.splice(indexToRemove, 1);
|
||||||
|
if (!unprocessedChunks.some((ch) => ch.name === name)) {
|
||||||
|
unprocessedChunks.forEach(
|
||||||
|
// remove the processed chunk from all the linkAfter arrays from the remaining chunks
|
||||||
|
(ch) => {
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
ch.linkAfter = ch.linkAfter.filter((after) => after !== name);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resultingString.join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
generateByType(type: string, content: unknown): string {
|
||||||
|
if (!content) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
if (Array.isArray(content)) {
|
||||||
|
return content.map((contentItem) => this.generateByType(type, contentItem)).join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.generators[type]) {
|
||||||
|
throw new Error(
|
||||||
|
`Attempted to generate unknown type ${type}. Please register a generator for this type in builder/index.ts`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.generators[type](content);
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove invalid chunks (which did not end up being created) from the linkAfter fields
|
||||||
|
// one use-case is when you want to remove the import plugin
|
||||||
|
private cleanupInvalidChunks(linkAfter: string[], chunks: ICodeChunk[]) {
|
||||||
|
return linkAfter.filter((chunkName) => chunks.some((chunk) => chunk.name === chunkName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CodeBuilder;
|
||||||
109
modules/code-generator/src/generator/ModuleBuilder.ts
Normal file
109
modules/code-generator/src/generator/ModuleBuilder.ts
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
import { ProjectSchema, ResultFile, ResultDir } from '@alilc/lowcode-types';
|
||||||
|
|
||||||
|
import {
|
||||||
|
BuilderComponentPlugin,
|
||||||
|
CodeGeneratorError,
|
||||||
|
ICodeChunk,
|
||||||
|
ICompiledModule,
|
||||||
|
IModuleBuilder,
|
||||||
|
IParseResult,
|
||||||
|
ISchemaParser,
|
||||||
|
PostProcessor,
|
||||||
|
} from '../types';
|
||||||
|
|
||||||
|
import { COMMON_SUB_MODULE_NAME } from '../const/generator';
|
||||||
|
|
||||||
|
import { SchemaParser } from '../parser/SchemaParser';
|
||||||
|
import { ChunkBuilder } from './ChunkBuilder';
|
||||||
|
import { CodeBuilder } from './CodeBuilder';
|
||||||
|
import { createResultFile, createResultDir, addFile } from '../utils/resultHelper';
|
||||||
|
|
||||||
|
export function createModuleBuilder(
|
||||||
|
options: {
|
||||||
|
plugins: BuilderComponentPlugin[];
|
||||||
|
postProcessors: PostProcessor[];
|
||||||
|
mainFileName?: string;
|
||||||
|
} = {
|
||||||
|
plugins: [],
|
||||||
|
postProcessors: [],
|
||||||
|
},
|
||||||
|
): IModuleBuilder {
|
||||||
|
const chunkGenerator = new ChunkBuilder(options.plugins);
|
||||||
|
const linker = new CodeBuilder();
|
||||||
|
|
||||||
|
const generateModule = async (input: unknown): Promise<ICompiledModule> => {
|
||||||
|
const moduleMainName = options.mainFileName || COMMON_SUB_MODULE_NAME;
|
||||||
|
if (chunkGenerator.getPlugins().length <= 0) {
|
||||||
|
throw new CodeGeneratorError(
|
||||||
|
'No plugins found. Component generation cannot work without any plugins!',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let files: ResultFile[] = [];
|
||||||
|
|
||||||
|
const { chunks } = await chunkGenerator.run(input);
|
||||||
|
chunks.forEach((fileChunkList) => {
|
||||||
|
const content = linker.link(fileChunkList);
|
||||||
|
const file = createResultFile(
|
||||||
|
fileChunkList[0].subModule || moduleMainName,
|
||||||
|
fileChunkList[0].fileType,
|
||||||
|
content,
|
||||||
|
);
|
||||||
|
files.push(file);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (options.postProcessors.length > 0) {
|
||||||
|
files = files.map((file) => {
|
||||||
|
let { content } = file;
|
||||||
|
const type = file.ext;
|
||||||
|
options.postProcessors.forEach((processer) => {
|
||||||
|
content = processer(content, type);
|
||||||
|
});
|
||||||
|
|
||||||
|
return createResultFile(file.name, type, content);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
files,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const generateModuleCode = async (schema: ProjectSchema | string): Promise<ResultDir> => {
|
||||||
|
// Init
|
||||||
|
const schemaParser: ISchemaParser = new SchemaParser();
|
||||||
|
const parseResult: IParseResult = schemaParser.parse(schema);
|
||||||
|
|
||||||
|
const containerInfo = parseResult.containers[0];
|
||||||
|
const { files } = await generateModule(containerInfo);
|
||||||
|
|
||||||
|
const dir = createResultDir(containerInfo.moduleName);
|
||||||
|
files.forEach((file) => addFile(dir, file));
|
||||||
|
|
||||||
|
return dir;
|
||||||
|
};
|
||||||
|
|
||||||
|
const linkCodeChunks = (chunks: Record<string, ICodeChunk[]>, fileName: string) => {
|
||||||
|
const files: ResultFile[] = [];
|
||||||
|
|
||||||
|
Object.keys(chunks).forEach((fileKey) => {
|
||||||
|
const fileChunkList = chunks[fileKey];
|
||||||
|
const content = linker.link(fileChunkList);
|
||||||
|
const file = createResultFile(
|
||||||
|
fileChunkList[0].subModule || fileName,
|
||||||
|
fileChunkList[0].fileType,
|
||||||
|
content,
|
||||||
|
);
|
||||||
|
files.push(file);
|
||||||
|
});
|
||||||
|
|
||||||
|
return files;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
generateModule,
|
||||||
|
generateModuleCode,
|
||||||
|
linkCodeChunks,
|
||||||
|
addPlugin: chunkGenerator.addPlugin.bind(chunkGenerator),
|
||||||
|
};
|
||||||
|
}
|
||||||
294
modules/code-generator/src/generator/ProjectBuilder.ts
Normal file
294
modules/code-generator/src/generator/ProjectBuilder.ts
Normal file
@ -0,0 +1,294 @@
|
|||||||
|
import { ResultDir, ResultFile, ProjectSchema } from '@alilc/lowcode-types';
|
||||||
|
|
||||||
|
import {
|
||||||
|
IModuleBuilder,
|
||||||
|
IParseResult,
|
||||||
|
IProjectBuilder,
|
||||||
|
IProjectPlugins,
|
||||||
|
IProjectTemplate,
|
||||||
|
ISchemaParser,
|
||||||
|
PostProcessor,
|
||||||
|
} from '../types';
|
||||||
|
|
||||||
|
import { SchemaParser } from '../parser/SchemaParser';
|
||||||
|
import { createResultDir, addDirectory, addFile } from '../utils/resultHelper';
|
||||||
|
|
||||||
|
import { createModuleBuilder } from './ModuleBuilder';
|
||||||
|
import { ProjectPreProcessor, ProjectPostProcessor } from '../types/core';
|
||||||
|
import { CodeGeneratorError } from '../types/error';
|
||||||
|
|
||||||
|
interface IModuleInfo {
|
||||||
|
moduleName?: string;
|
||||||
|
path: string[];
|
||||||
|
files: ResultFile[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProjectBuilderInitOptions {
|
||||||
|
/** 项目模板 */
|
||||||
|
template: IProjectTemplate;
|
||||||
|
/** 项目插件 */
|
||||||
|
plugins: IProjectPlugins;
|
||||||
|
/** 模块后置处理器 */
|
||||||
|
postProcessors: PostProcessor[];
|
||||||
|
/** Schema 解析器 */
|
||||||
|
schemaParser?: ISchemaParser;
|
||||||
|
/** 项目级别的前置处理器 */
|
||||||
|
projectPreProcessors?: ProjectPreProcessor[];
|
||||||
|
/** 项目级别的后置处理器 */
|
||||||
|
projectPostProcessors?: ProjectPostProcessor[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ProjectBuilder implements IProjectBuilder {
|
||||||
|
/** 项目模板 */
|
||||||
|
private template: IProjectTemplate;
|
||||||
|
|
||||||
|
/** 项目插件 */
|
||||||
|
private plugins: IProjectPlugins;
|
||||||
|
|
||||||
|
/** 模块后置处理器 */
|
||||||
|
private postProcessors: PostProcessor[];
|
||||||
|
|
||||||
|
/** Schema 解析器 */
|
||||||
|
private schemaParser: ISchemaParser;
|
||||||
|
|
||||||
|
/** 项目级别的前置处理器 */
|
||||||
|
private projectPreProcessors: ProjectPreProcessor[];
|
||||||
|
|
||||||
|
/** 项目级别的后置处理器 */
|
||||||
|
private projectPostProcessors: ProjectPostProcessor[];
|
||||||
|
|
||||||
|
constructor({
|
||||||
|
template,
|
||||||
|
plugins,
|
||||||
|
postProcessors,
|
||||||
|
schemaParser = new SchemaParser(),
|
||||||
|
projectPreProcessors = [],
|
||||||
|
projectPostProcessors = [],
|
||||||
|
}: ProjectBuilderInitOptions) {
|
||||||
|
this.template = template;
|
||||||
|
this.plugins = plugins;
|
||||||
|
this.postProcessors = postProcessors;
|
||||||
|
this.schemaParser = schemaParser;
|
||||||
|
this.projectPreProcessors = projectPreProcessors;
|
||||||
|
this.projectPostProcessors = projectPostProcessors;
|
||||||
|
}
|
||||||
|
|
||||||
|
async generateProject(originalSchema: ProjectSchema | string): Promise<ResultDir> {
|
||||||
|
// Init
|
||||||
|
const { schemaParser } = this;
|
||||||
|
const builders = this.createModuleBuilders();
|
||||||
|
|
||||||
|
const projectRoot = await this.template.generateTemplate();
|
||||||
|
|
||||||
|
let schema: ProjectSchema =
|
||||||
|
typeof originalSchema === 'string' ? JSON.parse(originalSchema) : originalSchema;
|
||||||
|
|
||||||
|
// Validate
|
||||||
|
if (!schemaParser.validate(schema)) {
|
||||||
|
throw new CodeGeneratorError('Schema is invalid');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse / Format
|
||||||
|
|
||||||
|
// Preprocess
|
||||||
|
for (const preProcessor of this.projectPreProcessors) {
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
schema = await preProcessor(schema);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect Deps
|
||||||
|
// Parse JSExpression
|
||||||
|
const parseResult: IParseResult = schemaParser.parse(schema);
|
||||||
|
let buildResult: IModuleInfo[] = [];
|
||||||
|
|
||||||
|
// Generator Code module
|
||||||
|
// components
|
||||||
|
// pages
|
||||||
|
const containerBuildResult: IModuleInfo[] = await Promise.all<IModuleInfo>(
|
||||||
|
parseResult.containers.map(async (containerInfo) => {
|
||||||
|
let builder: IModuleBuilder;
|
||||||
|
let path: string[];
|
||||||
|
if (containerInfo.containerType === 'Page') {
|
||||||
|
builder = builders.pages;
|
||||||
|
path = this.template.slots.pages.path;
|
||||||
|
} else {
|
||||||
|
builder = builders.components;
|
||||||
|
path = this.template.slots.components.path;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { files } = await builder.generateModule(containerInfo);
|
||||||
|
|
||||||
|
return {
|
||||||
|
moduleName: containerInfo.moduleName,
|
||||||
|
path,
|
||||||
|
files,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
buildResult = buildResult.concat(containerBuildResult);
|
||||||
|
|
||||||
|
// router
|
||||||
|
if (parseResult.globalRouter && builders.router) {
|
||||||
|
const { files } = await builders.router.generateModule(parseResult.globalRouter);
|
||||||
|
|
||||||
|
buildResult.push({
|
||||||
|
path: this.template.slots.router.path,
|
||||||
|
files,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// entry
|
||||||
|
if (parseResult.project && builders.entry) {
|
||||||
|
const { files } = await builders.entry.generateModule(parseResult.project);
|
||||||
|
|
||||||
|
buildResult.push({
|
||||||
|
path: this.template.slots.entry.path,
|
||||||
|
files,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// appConfig
|
||||||
|
if (builders.appConfig) {
|
||||||
|
const { files } = await builders.appConfig.generateModule(parseResult);
|
||||||
|
|
||||||
|
buildResult.push({
|
||||||
|
path: this.template.slots.appConfig.path,
|
||||||
|
files,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildConfig
|
||||||
|
if (builders.buildConfig) {
|
||||||
|
const { files } = await builders.buildConfig.generateModule(parseResult);
|
||||||
|
|
||||||
|
buildResult.push({
|
||||||
|
path: this.template.slots.buildConfig.path,
|
||||||
|
files,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// constants?
|
||||||
|
if (parseResult.project && builders.constants && this.template.slots.constants) {
|
||||||
|
const { files } = await builders.constants.generateModule(parseResult.project);
|
||||||
|
|
||||||
|
buildResult.push({
|
||||||
|
path: this.template.slots.constants.path,
|
||||||
|
files,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// utils?
|
||||||
|
if (parseResult.globalUtils && builders.utils && this.template.slots.utils) {
|
||||||
|
const { files } = await builders.utils.generateModule(parseResult.globalUtils);
|
||||||
|
|
||||||
|
buildResult.push({
|
||||||
|
path: this.template.slots.utils.path,
|
||||||
|
files,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// i18n?
|
||||||
|
if (builders.i18n && this.template.slots.i18n) {
|
||||||
|
const { files } = await builders.i18n.generateModule(parseResult.project);
|
||||||
|
|
||||||
|
buildResult.push({
|
||||||
|
path: this.template.slots.i18n.path,
|
||||||
|
files,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// globalStyle
|
||||||
|
if (parseResult.project && builders.globalStyle) {
|
||||||
|
const { files } = await builders.globalStyle.generateModule(parseResult.project);
|
||||||
|
|
||||||
|
buildResult.push({
|
||||||
|
path: this.template.slots.globalStyle.path,
|
||||||
|
files,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// htmlEntry
|
||||||
|
if (parseResult.project && builders.htmlEntry) {
|
||||||
|
const { files } = await builders.htmlEntry.generateModule(parseResult.project);
|
||||||
|
|
||||||
|
buildResult.push({
|
||||||
|
path: this.template.slots.htmlEntry.path,
|
||||||
|
files,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// packageJSON
|
||||||
|
if (parseResult.project && builders.packageJSON) {
|
||||||
|
const { files } = await builders.packageJSON.generateModule(parseResult.project);
|
||||||
|
|
||||||
|
buildResult.push({
|
||||||
|
path: this.template.slots.packageJSON.path,
|
||||||
|
files,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: 更多 slots 的处理??是不是可以考虑把 template 中所有的 slots 都处理下?
|
||||||
|
|
||||||
|
// Post Process
|
||||||
|
|
||||||
|
// Combine Modules
|
||||||
|
buildResult.forEach((moduleInfo) => {
|
||||||
|
let targetDir = getDirFromRoot(projectRoot, moduleInfo.path);
|
||||||
|
if (moduleInfo.moduleName) {
|
||||||
|
const dir = createResultDir(moduleInfo.moduleName);
|
||||||
|
addDirectory(targetDir, dir);
|
||||||
|
targetDir = dir;
|
||||||
|
}
|
||||||
|
moduleInfo.files.forEach((file) => addFile(targetDir, file));
|
||||||
|
});
|
||||||
|
|
||||||
|
// post-processors
|
||||||
|
let finalResult = projectRoot;
|
||||||
|
for (const projectPostProcessor of this.projectPostProcessors) {
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
finalResult = await projectPostProcessor(finalResult, schema, originalSchema);
|
||||||
|
}
|
||||||
|
|
||||||
|
return finalResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
private createModuleBuilders(): Record<string, IModuleBuilder> {
|
||||||
|
const builders: Record<string, IModuleBuilder> = {};
|
||||||
|
|
||||||
|
Object.keys(this.plugins).forEach((pluginName) => {
|
||||||
|
if (this.plugins[pluginName].length > 0) {
|
||||||
|
const options: { mainFileName?: string } = {};
|
||||||
|
if (this.template.slots[pluginName] && this.template.slots[pluginName].fileName) {
|
||||||
|
options.mainFileName = this.template.slots[pluginName].fileName;
|
||||||
|
}
|
||||||
|
builders[pluginName] = createModuleBuilder({
|
||||||
|
plugins: this.plugins[pluginName],
|
||||||
|
postProcessors: this.postProcessors,
|
||||||
|
...options,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return builders;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createProjectBuilder(initOptions: ProjectBuilderInitOptions): IProjectBuilder {
|
||||||
|
return new ProjectBuilder(initOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDirFromRoot(root: ResultDir, path: string[]): ResultDir {
|
||||||
|
let current: ResultDir = root;
|
||||||
|
path.forEach((p) => {
|
||||||
|
const exist = current.dirs.find((d) => d.name === p);
|
||||||
|
if (exist) {
|
||||||
|
current = exist;
|
||||||
|
} else {
|
||||||
|
const newDir = createResultDir(p);
|
||||||
|
addDirectory(current, newDir);
|
||||||
|
current = newDir;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return current;
|
||||||
|
}
|
||||||
122
modules/code-generator/src/index.ts
Normal file
122
modules/code-generator/src/index.ts
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
/**
|
||||||
|
* 低代码引擎的出码模块,负责将编排产出的 Schema 转换成实际可执行的代码。
|
||||||
|
* 注意:为了保持 API 的稳定性, 这里所有导出的 API 均要显式命名方式导出
|
||||||
|
* (即用 export { xxx } from 'xx' 的方式,不要直接 export * from 'xxx')
|
||||||
|
* 而且所有导出的 API 务必在 tests/public 中编写单元测试
|
||||||
|
*/
|
||||||
|
import { createProjectBuilder } from './generator/ProjectBuilder';
|
||||||
|
import { createModuleBuilder } from './generator/ModuleBuilder';
|
||||||
|
import { createDiskPublisher } from './publisher/disk';
|
||||||
|
import { createZipPublisher } from './publisher/zip';
|
||||||
|
import createIceJsProjectBuilder, { plugins as reactPlugins } from './solutions/icejs';
|
||||||
|
import createRaxAppProjectBuilder, { plugins as raxPlugins } from './solutions/rax-app';
|
||||||
|
|
||||||
|
// 引入说明
|
||||||
|
import { REACT_CHUNK_NAME } from './plugins/component/react/const';
|
||||||
|
import { COMMON_CHUNK_NAME, CLASS_DEFINE_CHUNK_NAME, DEFAULT_LINK_AFTER } from './const/generator';
|
||||||
|
|
||||||
|
// 引入通用插件组
|
||||||
|
import esmodule from './plugins/common/esmodule';
|
||||||
|
import requireUtils from './plugins/common/requireUtils';
|
||||||
|
|
||||||
|
import css from './plugins/component/style/css';
|
||||||
|
import constants from './plugins/project/constants';
|
||||||
|
import i18n from './plugins/project/i18n';
|
||||||
|
import utils from './plugins/project/utils';
|
||||||
|
import prettier from './postprocessor/prettier';
|
||||||
|
|
||||||
|
// 引入常用工具
|
||||||
|
import * as utilsCommon from './utils/common';
|
||||||
|
import * as utilsCompositeType from './utils/compositeType';
|
||||||
|
import * as utilsJsExpression from './utils/jsExpression';
|
||||||
|
import * as utilsJsSlot from './utils/jsSlot';
|
||||||
|
import * as utilsNodeToJSX from './utils/nodeToJSX';
|
||||||
|
import * as utilsResultHelper from './utils/resultHelper';
|
||||||
|
import * as utilsTemplateHelper from './utils/templateHelper';
|
||||||
|
import * as utilsValidate from './utils/validate';
|
||||||
|
import * as utilsSchema from './utils/schema';
|
||||||
|
|
||||||
|
import * as CONSTANTS from './const';
|
||||||
|
|
||||||
|
// 引入内置解决方案模块
|
||||||
|
import icejs from './plugins/project/framework/icejs';
|
||||||
|
import rax from './plugins/project/framework/rax';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
createProjectBuilder,
|
||||||
|
createModuleBuilder,
|
||||||
|
solutions: {
|
||||||
|
icejs: createIceJsProjectBuilder,
|
||||||
|
rax: createRaxAppProjectBuilder,
|
||||||
|
},
|
||||||
|
solutionParts: {
|
||||||
|
icejs,
|
||||||
|
rax,
|
||||||
|
},
|
||||||
|
publishers: {
|
||||||
|
disk: createDiskPublisher,
|
||||||
|
zip: createZipPublisher,
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
common: {
|
||||||
|
/**
|
||||||
|
* 处理 ES Module
|
||||||
|
* @deprecated please use esModule
|
||||||
|
*/
|
||||||
|
esmodule,
|
||||||
|
esModule: esmodule,
|
||||||
|
requireUtils,
|
||||||
|
},
|
||||||
|
react: {
|
||||||
|
...reactPlugins,
|
||||||
|
},
|
||||||
|
rax: {
|
||||||
|
...raxPlugins,
|
||||||
|
},
|
||||||
|
style: {
|
||||||
|
css,
|
||||||
|
},
|
||||||
|
project: {
|
||||||
|
constants,
|
||||||
|
i18n,
|
||||||
|
utils,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
postprocessor: {
|
||||||
|
prettier,
|
||||||
|
},
|
||||||
|
utils: {
|
||||||
|
common: utilsCommon,
|
||||||
|
compositeType: utilsCompositeType,
|
||||||
|
jsExpression: utilsJsExpression,
|
||||||
|
jsSlot: utilsJsSlot,
|
||||||
|
nodeToJSX: utilsNodeToJSX,
|
||||||
|
resultHelper: utilsResultHelper,
|
||||||
|
templateHelper: utilsTemplateHelper,
|
||||||
|
validate: utilsValidate,
|
||||||
|
schema: utilsSchema,
|
||||||
|
},
|
||||||
|
chunkNames: {
|
||||||
|
COMMON_CHUNK_NAME,
|
||||||
|
CLASS_DEFINE_CHUNK_NAME,
|
||||||
|
REACT_CHUNK_NAME,
|
||||||
|
},
|
||||||
|
defaultLinkAfter: {
|
||||||
|
COMMON_DEFAULT_LINK_AFTER: DEFAULT_LINK_AFTER,
|
||||||
|
},
|
||||||
|
constants: CONSTANTS,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 一些类型定义
|
||||||
|
export * from './types';
|
||||||
|
|
||||||
|
// 一些常量定义
|
||||||
|
export * from './const';
|
||||||
|
|
||||||
|
// 一些工具函数
|
||||||
|
export * from './analyzer/componentAnalyzer';
|
||||||
|
export * from './parser/SchemaParser';
|
||||||
|
export * from './generator/ChunkBuilder';
|
||||||
|
export * from './generator/CodeBuilder';
|
||||||
|
export * from './generator/ModuleBuilder';
|
||||||
|
export * from './generator/ProjectBuilder';
|
||||||
355
modules/code-generator/src/parser/SchemaParser.ts
Normal file
355
modules/code-generator/src/parser/SchemaParser.ts
Normal file
@ -0,0 +1,355 @@
|
|||||||
|
/**
|
||||||
|
* 解析器是对输入的固定格式数据做拆解,使其符合引擎后续步骤预期,完成统一处理逻辑的步骤。
|
||||||
|
* 本解析器面向的是标准 schema 协议。
|
||||||
|
*/
|
||||||
|
import changeCase from 'change-case';
|
||||||
|
import {
|
||||||
|
UtilItem,
|
||||||
|
NodeDataType,
|
||||||
|
NodeSchema,
|
||||||
|
ContainerSchema,
|
||||||
|
ProjectSchema,
|
||||||
|
PropsMap,
|
||||||
|
NodeData,
|
||||||
|
NpmInfo,
|
||||||
|
} from '@alilc/lowcode-types';
|
||||||
|
import {
|
||||||
|
IPageMeta,
|
||||||
|
CodeGeneratorError,
|
||||||
|
CompatibilityError,
|
||||||
|
DependencyType,
|
||||||
|
IContainerInfo,
|
||||||
|
IDependency,
|
||||||
|
IExternalDependency,
|
||||||
|
IInternalDependency,
|
||||||
|
InternalDependencyType,
|
||||||
|
IParseResult,
|
||||||
|
ISchemaParser,
|
||||||
|
INpmPackage,
|
||||||
|
IRouterInfo,
|
||||||
|
} from '../types';
|
||||||
|
|
||||||
|
import { SUPPORT_SCHEMA_VERSION_LIST } from '../const';
|
||||||
|
|
||||||
|
import { getErrorMessage } from '../utils/errors';
|
||||||
|
import { handleSubNodes } from '../utils/schema';
|
||||||
|
import { uniqueArray } from '../utils/common';
|
||||||
|
import { componentAnalyzer } from '../analyzer/componentAnalyzer';
|
||||||
|
import { ensureValidClassName } from '../utils/validate';
|
||||||
|
|
||||||
|
const defaultContainer: IContainerInfo = {
|
||||||
|
containerType: 'Component',
|
||||||
|
componentName: 'Component',
|
||||||
|
moduleName: 'Index',
|
||||||
|
fileName: 'Index',
|
||||||
|
css: '',
|
||||||
|
props: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
function getRootComponentName(typeName: string, maps: Record<string, IExternalDependency>): string {
|
||||||
|
if (maps[typeName]) {
|
||||||
|
const rec = maps[typeName];
|
||||||
|
if (rec.destructuring) {
|
||||||
|
return rec.componentName || typeName;
|
||||||
|
}
|
||||||
|
|
||||||
|
const peerName = Object.keys(maps).find((depName: string) => {
|
||||||
|
const depInfo = maps[depName];
|
||||||
|
return (
|
||||||
|
depName !== typeName &&
|
||||||
|
!depInfo.destructuring &&
|
||||||
|
depInfo.package === rec.package &&
|
||||||
|
depInfo.version === rec.version &&
|
||||||
|
depInfo.main === rec.main &&
|
||||||
|
depInfo.exportName === rec.exportName &&
|
||||||
|
depInfo.subName === rec.subName
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return peerName || typeName;
|
||||||
|
}
|
||||||
|
return typeName;
|
||||||
|
}
|
||||||
|
|
||||||
|
function processChildren(schema: NodeSchema): void {
|
||||||
|
if (schema.props) {
|
||||||
|
if (Array.isArray(schema.props)) {
|
||||||
|
// FIXME: is array type props description
|
||||||
|
} else {
|
||||||
|
const nodeProps = schema.props as PropsMap;
|
||||||
|
if (nodeProps.children) {
|
||||||
|
if (!schema.children) {
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
schema.children = nodeProps.children as NodeDataType;
|
||||||
|
} else {
|
||||||
|
let _children: NodeData[] = [];
|
||||||
|
|
||||||
|
if (Array.isArray(schema.children)) {
|
||||||
|
_children = _children.concat(schema.children);
|
||||||
|
} else {
|
||||||
|
_children.push(schema.children);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(nodeProps.children)) {
|
||||||
|
_children = _children.concat(nodeProps.children as NodeData[]);
|
||||||
|
} else {
|
||||||
|
_children.push(nodeProps.children as NodeData);
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
schema.children = _children;
|
||||||
|
}
|
||||||
|
delete nodeProps.children;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SchemaParser implements ISchemaParser {
|
||||||
|
validate(schema: ProjectSchema): boolean {
|
||||||
|
if (SUPPORT_SCHEMA_VERSION_LIST.indexOf(schema.version) < 0) {
|
||||||
|
throw new CompatibilityError(`Not support schema with version [${schema.version}]`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
parse(schemaSrc: ProjectSchema | string): IParseResult {
|
||||||
|
// TODO: collect utils depends in JSExpression
|
||||||
|
const compDeps: Record<string, IExternalDependency> = {};
|
||||||
|
const internalDeps: Record<string, IInternalDependency> = {};
|
||||||
|
let utilsDeps: IExternalDependency[] = [];
|
||||||
|
|
||||||
|
const schema = this.decodeSchema(schemaSrc);
|
||||||
|
|
||||||
|
// 解析三方组件依赖
|
||||||
|
schema.componentsMap.forEach((info) => {
|
||||||
|
if (info.componentName) {
|
||||||
|
compDeps[info.componentName] = {
|
||||||
|
...info,
|
||||||
|
dependencyType: DependencyType.External,
|
||||||
|
componentName: info.componentName,
|
||||||
|
exportName: info.exportName ?? info.componentName,
|
||||||
|
version: info.version || '*',
|
||||||
|
destructuring: info.destructuring ?? false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let containers: IContainerInfo[];
|
||||||
|
// Test if this is a lowcode component without container
|
||||||
|
if (schema.componentsTree.length > 0) {
|
||||||
|
const firstRoot: ContainerSchema = schema.componentsTree[0] as ContainerSchema;
|
||||||
|
|
||||||
|
if (!('fileName' in firstRoot) || !firstRoot.fileName) {
|
||||||
|
// 整个 schema 描述一个容器,且无根节点定义
|
||||||
|
const container: IContainerInfo = {
|
||||||
|
...firstRoot,
|
||||||
|
...defaultContainer,
|
||||||
|
props: firstRoot.props || defaultContainer.props,
|
||||||
|
css: firstRoot.css || defaultContainer.css,
|
||||||
|
moduleName: (firstRoot as IContainerInfo).moduleName || defaultContainer.moduleName,
|
||||||
|
children: schema.componentsTree as NodeSchema[],
|
||||||
|
};
|
||||||
|
containers = [container];
|
||||||
|
} else {
|
||||||
|
// 普通带 1 到多个容器的 schema
|
||||||
|
containers = schema.componentsTree.map((n) => {
|
||||||
|
const subRoot = n as ContainerSchema;
|
||||||
|
const container: IContainerInfo = {
|
||||||
|
...subRoot,
|
||||||
|
componentName: getRootComponentName(subRoot.componentName, compDeps),
|
||||||
|
containerType: subRoot.componentName,
|
||||||
|
moduleName: ensureValidClassName(changeCase.pascalCase(subRoot.fileName)),
|
||||||
|
};
|
||||||
|
return container;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new CodeGeneratorError("Can't find anything to generate.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分析引用能力的依赖
|
||||||
|
containers = containers.map((con) => ({
|
||||||
|
...con,
|
||||||
|
analyzeResult: componentAnalyzer(con as ContainerSchema),
|
||||||
|
}));
|
||||||
|
|
||||||
|
// 建立所有容器的内部依赖索引
|
||||||
|
containers.forEach((container) => {
|
||||||
|
let type;
|
||||||
|
switch (container.containerType) {
|
||||||
|
case 'Page':
|
||||||
|
type = InternalDependencyType.PAGE;
|
||||||
|
break;
|
||||||
|
case 'Block':
|
||||||
|
type = InternalDependencyType.BLOCK;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
type = InternalDependencyType.COMPONENT;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dep: IInternalDependency = {
|
||||||
|
type,
|
||||||
|
moduleName: container.moduleName,
|
||||||
|
destructuring: false,
|
||||||
|
exportName: container.moduleName,
|
||||||
|
dependencyType: DependencyType.Internal,
|
||||||
|
};
|
||||||
|
|
||||||
|
internalDeps[dep.moduleName] = dep;
|
||||||
|
});
|
||||||
|
|
||||||
|
const containersDeps = ([] as IDependency[]).concat(...containers.map((c) => c.deps || []));
|
||||||
|
// TODO: 不应该在出码部分解决?
|
||||||
|
// 处理 children 写在了 props 里的情况
|
||||||
|
containers.forEach((container) => {
|
||||||
|
if (container.children) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-invalid-void-type
|
||||||
|
handleSubNodes<void>(
|
||||||
|
container.children,
|
||||||
|
{
|
||||||
|
node: (i: NodeSchema) => processChildren(i),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
rerun: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 分析容器内部组件依赖
|
||||||
|
containers.forEach((container) => {
|
||||||
|
const depNames = this.getComponentNames(container);
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
container.deps = uniqueArray<string>(depNames, (i: string) => i)
|
||||||
|
.map((depName) => internalDeps[depName] || compDeps[depName])
|
||||||
|
.filter(Boolean);
|
||||||
|
// container.deps = Object.keys(compDeps).map((depName) => compDeps[depName]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 分析路由配置
|
||||||
|
const routes: IRouterInfo['routes'] = containers
|
||||||
|
.filter((container) => container.containerType === 'Page')
|
||||||
|
.map((page) => {
|
||||||
|
const { meta } = page;
|
||||||
|
if (meta) {
|
||||||
|
return {
|
||||||
|
path: (meta as IPageMeta).router || `/${page.fileName}`, // 如果无法找到页面路由信息,则用 fileName 做兜底
|
||||||
|
fileName: page.fileName,
|
||||||
|
componentName: page.moduleName,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
path: '',
|
||||||
|
fileName: page.fileName,
|
||||||
|
componentName: page.moduleName,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const routerDeps = routes
|
||||||
|
.map((r) => internalDeps[r.componentName] || compDeps[r.componentName])
|
||||||
|
.filter((dep) => !!dep);
|
||||||
|
|
||||||
|
// 分析 Utils 依赖
|
||||||
|
let utils: UtilItem[];
|
||||||
|
if (schema.utils) {
|
||||||
|
utils = schema.utils;
|
||||||
|
utilsDeps = schema.utils
|
||||||
|
.filter(
|
||||||
|
(u): u is { name: string; type: 'npm' | 'tnpm'; content: NpmInfo } =>
|
||||||
|
u.type !== 'function',
|
||||||
|
)
|
||||||
|
.map(
|
||||||
|
(u): IExternalDependency => ({
|
||||||
|
...u.content,
|
||||||
|
componentName: u.name,
|
||||||
|
version: u.content.version || '*',
|
||||||
|
destructuring: u.content.destructuring ?? false,
|
||||||
|
exportName: u.content.exportName ?? u.name,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
utils = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分析项目 npm 依赖
|
||||||
|
let npms: INpmPackage[] = [];
|
||||||
|
containers.forEach((con) => {
|
||||||
|
const p = (con.deps || [])
|
||||||
|
.map((dep) => {
|
||||||
|
return dep.dependencyType === DependencyType.External ? dep : null;
|
||||||
|
})
|
||||||
|
.filter((dep) => dep !== null);
|
||||||
|
const npmInfos: INpmPackage[] = p.filter(Boolean).map((i) => ({
|
||||||
|
package: (i as IExternalDependency).package,
|
||||||
|
version: (i as IExternalDependency).version,
|
||||||
|
}));
|
||||||
|
npms.push(...npmInfos);
|
||||||
|
});
|
||||||
|
|
||||||
|
npms.push(
|
||||||
|
...utilsDeps.map((utilsDep) => ({
|
||||||
|
package: utilsDep.package,
|
||||||
|
version: utilsDep.version,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
|
||||||
|
npms = uniqueArray<INpmPackage>(npms, (i) => i.package).filter(Boolean);
|
||||||
|
|
||||||
|
return {
|
||||||
|
containers,
|
||||||
|
globalUtils: {
|
||||||
|
utils,
|
||||||
|
deps: utilsDeps,
|
||||||
|
},
|
||||||
|
globalI18n: schema.i18n,
|
||||||
|
globalRouter: {
|
||||||
|
routes,
|
||||||
|
deps: routerDeps,
|
||||||
|
},
|
||||||
|
project: {
|
||||||
|
css: schema.css,
|
||||||
|
constants: schema.constants,
|
||||||
|
config: schema.config || {},
|
||||||
|
meta: schema.meta || {},
|
||||||
|
i18n: schema.i18n,
|
||||||
|
containersDeps,
|
||||||
|
utilsDeps,
|
||||||
|
packages: npms || [],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
getComponentNames(children: NodeDataType): string[] {
|
||||||
|
return handleSubNodes<string>(
|
||||||
|
children,
|
||||||
|
{
|
||||||
|
node: (i: NodeSchema) => i.componentName,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
rerun: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
decodeSchema(schemaSrc: string | ProjectSchema): ProjectSchema {
|
||||||
|
let schema: ProjectSchema;
|
||||||
|
if (typeof schemaSrc === 'string') {
|
||||||
|
try {
|
||||||
|
schema = JSON.parse(schemaSrc);
|
||||||
|
} catch (error) {
|
||||||
|
throw new CodeGeneratorError(
|
||||||
|
`Parse schema failed: ${getErrorMessage(error) || 'unknown reason'}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
schema = schemaSrc;
|
||||||
|
}
|
||||||
|
return schema;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SchemaParser;
|
||||||
443
modules/code-generator/src/plugins/common/esmodule.ts
Normal file
443
modules/code-generator/src/plugins/common/esmodule.ts
Normal file
@ -0,0 +1,443 @@
|
|||||||
|
import { flatMap } from 'lodash';
|
||||||
|
import { COMMON_CHUNK_NAME } from '../../const/generator';
|
||||||
|
|
||||||
|
import {
|
||||||
|
BuilderComponentPlugin,
|
||||||
|
BuilderComponentPluginFactory,
|
||||||
|
ChunkType,
|
||||||
|
CodeGeneratorError,
|
||||||
|
DependencyType,
|
||||||
|
FileType,
|
||||||
|
ICodeChunk,
|
||||||
|
ICodeStruct,
|
||||||
|
IDependency,
|
||||||
|
IExternalDependency,
|
||||||
|
IInternalDependency,
|
||||||
|
IWithDependency,
|
||||||
|
} from '../../types';
|
||||||
|
|
||||||
|
import { isValidIdentifier } from '../../utils/validate';
|
||||||
|
|
||||||
|
// TODO: main 这个信息到底怎么用,是不是外部包不需要使用?
|
||||||
|
const DEP_MAIN_BLOCKLIST = ['lib', 'lib/index', 'es', 'es/index', 'main'];
|
||||||
|
const DEFAULT_EXPORT_NAME = '__default__';
|
||||||
|
|
||||||
|
function groupDepsByPack(deps: IDependency[]): Record<string, IDependency[]> {
|
||||||
|
const depMap: Record<string, IDependency[]> = {};
|
||||||
|
|
||||||
|
const addDep = (pkg: string, dep: IDependency) => {
|
||||||
|
if (!depMap[pkg]) {
|
||||||
|
depMap[pkg] = [];
|
||||||
|
}
|
||||||
|
depMap[pkg].push(dep);
|
||||||
|
};
|
||||||
|
|
||||||
|
deps.forEach((dep) => {
|
||||||
|
if (dep.dependencyType === DependencyType.Internal) {
|
||||||
|
addDep(`${(dep as IInternalDependency).moduleName}${dep.main ? `/${dep.main}` : ''}`, dep);
|
||||||
|
} else {
|
||||||
|
let depMain = '';
|
||||||
|
// TODO: 部分类型的 main 暂时认为没用
|
||||||
|
if (dep.main && DEP_MAIN_BLOCKLIST.indexOf(dep.main) < 0) {
|
||||||
|
depMain = dep.main;
|
||||||
|
}
|
||||||
|
if (depMain.substring(0, 1) === '/') {
|
||||||
|
depMain = depMain.substring(1);
|
||||||
|
}
|
||||||
|
addDep(`${(dep as IExternalDependency).package}${depMain ? `/${depMain}` : ''}`, dep);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return depMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IDependencyItem {
|
||||||
|
exportName: string;
|
||||||
|
aliasName?: string;
|
||||||
|
isDefault?: boolean;
|
||||||
|
subName?: string;
|
||||||
|
nodeIdentifier?: string; // 与使用处的映射关系,理论上是不可变更的,如需变更需要提供额外信息
|
||||||
|
source: IDependency;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IExportItem {
|
||||||
|
exportName: string;
|
||||||
|
aliasNames: string[];
|
||||||
|
isDefault?: boolean;
|
||||||
|
needOriginExport: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDependencyIdentifier(info: IDependencyItem): string {
|
||||||
|
return info.aliasName || info.exportName;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildPackageImport(
|
||||||
|
pkg: string,
|
||||||
|
deps: IDependency[],
|
||||||
|
targetFileType: string,
|
||||||
|
useAliasName: boolean,
|
||||||
|
): ICodeChunk[] {
|
||||||
|
// 如果压根没有包,则不生成对应的 import 语句(生成了没有任何意义)
|
||||||
|
if (!pkg || pkg === 'undefined' || pkg === 'null') {
|
||||||
|
// TODO: 要不要加个 warning?
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const chunks: ICodeChunk[] = [];
|
||||||
|
|
||||||
|
const exportItems: Record<string, IExportItem> = {};
|
||||||
|
const defaultExportNames: string[] = [];
|
||||||
|
|
||||||
|
const depsInfo: IDependencyItem[] = deps.map((dep) => {
|
||||||
|
const info: IDependencyItem = {
|
||||||
|
exportName: dep.exportName,
|
||||||
|
isDefault: !dep.destructuring,
|
||||||
|
subName: dep.subName || undefined,
|
||||||
|
nodeIdentifier: dep.componentName || undefined,
|
||||||
|
source: dep,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 下面 5 个逻辑是清理不必要的冗余信息,做到数据结构归一化
|
||||||
|
if (info.isDefault) {
|
||||||
|
if (defaultExportNames.indexOf(info.exportName) < 0) {
|
||||||
|
defaultExportNames.push(info.exportName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!info.subName) {
|
||||||
|
if (info.nodeIdentifier === info.exportName) {
|
||||||
|
info.nodeIdentifier = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info.isDefault) {
|
||||||
|
info.aliasName = info.nodeIdentifier || info.exportName;
|
||||||
|
info.exportName = DEFAULT_EXPORT_NAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info.nodeIdentifier) {
|
||||||
|
info.aliasName = info.nodeIdentifier;
|
||||||
|
info.nodeIdentifier = undefined;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (info.isDefault) {
|
||||||
|
info.aliasName = info.exportName;
|
||||||
|
info.exportName = DEFAULT_EXPORT_NAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info.nodeIdentifier === `${info.exportName}.${info.subName}`) {
|
||||||
|
info.nodeIdentifier = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return info;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 建立 export 项目的列表
|
||||||
|
depsInfo.forEach((info) => {
|
||||||
|
if (!exportItems[info.exportName]) {
|
||||||
|
exportItems[info.exportName] = {
|
||||||
|
exportName: info.exportName,
|
||||||
|
isDefault: info.isDefault,
|
||||||
|
aliasNames: [],
|
||||||
|
needOriginExport: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!info.nodeIdentifier && !info.aliasName) {
|
||||||
|
exportItems[info.exportName].needOriginExport = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 建立别名字典
|
||||||
|
depsInfo.forEach((info) => {
|
||||||
|
if (info.aliasName) {
|
||||||
|
const { aliasNames } = exportItems[info.exportName];
|
||||||
|
if (aliasNames.indexOf(info.aliasName) < 0) {
|
||||||
|
aliasNames.push(info.aliasName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// fix: 父组件ImportAliasDefine, 与子组件import的父组件冲突情况
|
||||||
|
depsInfo.forEach((info) => {
|
||||||
|
if (info.nodeIdentifier) {
|
||||||
|
const exportItem = exportItems[info.exportName];
|
||||||
|
if (!exportItem.needOriginExport && exportItem.aliasNames.length > 0) {
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
info.aliasName = exportItem.aliasNames[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 发现 nodeIdentifier 与 exportName 或者 aliasName 冲突的场景
|
||||||
|
const nodeIdentifiers = depsInfo.map((info) => info.nodeIdentifier).filter(Boolean);
|
||||||
|
const conflictInfos = flatMap(
|
||||||
|
Object.keys(exportItems),
|
||||||
|
(exportName) => {
|
||||||
|
const exportItem = exportItems[exportName];
|
||||||
|
const usedNames = [
|
||||||
|
...exportItem.aliasNames,
|
||||||
|
...(exportItem.needOriginExport || exportItem.aliasNames.length <= 0 ? [exportName] : []),
|
||||||
|
];
|
||||||
|
const conflictNames = usedNames.filter((n) => nodeIdentifiers.indexOf(n) >= 0);
|
||||||
|
if (conflictNames.length > 0) {
|
||||||
|
return [
|
||||||
|
...(conflictNames.indexOf(exportName) >= 0 ? [[exportName, true, exportItem]] : []),
|
||||||
|
...conflictNames.filter((n) => n !== exportName).map((n) => [n, false, exportItem]),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const conflictExports = conflictInfos.filter((c) => c[1]).map((c) => c[0] as string);
|
||||||
|
const conflictAlias = conflictInfos.filter((c) => !c[1]).map((c) => c[0] as string);
|
||||||
|
|
||||||
|
const solutions: Record<string, string> = {};
|
||||||
|
|
||||||
|
depsInfo.forEach((info) => {
|
||||||
|
if (info.aliasName && conflictAlias.indexOf(info.aliasName) >= 0) {
|
||||||
|
// find solution
|
||||||
|
let solution = solutions[info.aliasName];
|
||||||
|
if (!solution) {
|
||||||
|
solution = `${info.aliasName}Alias`;
|
||||||
|
const conflictItem = (conflictInfos.find((c) => c[0] === info.aliasName) ||
|
||||||
|
[])[2] as IExportItem;
|
||||||
|
conflictItem.aliasNames = conflictItem.aliasNames.filter((a) => a !== info.aliasName);
|
||||||
|
conflictItem.aliasNames.push(solution);
|
||||||
|
solutions[info.aliasName] = solution;
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
info.aliasName = solution;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (conflictExports.indexOf(info.exportName) >= 0) {
|
||||||
|
// find solution
|
||||||
|
let solution = solutions[info.exportName];
|
||||||
|
if (!solution) {
|
||||||
|
solution = `${info.exportName}Export`;
|
||||||
|
const conflictItem = (conflictInfos.find((c) => c[0] === info.exportName) ||
|
||||||
|
[])[2] as IExportItem;
|
||||||
|
conflictItem.aliasNames.push(solution);
|
||||||
|
conflictItem.needOriginExport = false;
|
||||||
|
solutions[info.exportName] = solution;
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
info.aliasName = solution;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 判断是否所有依赖都有合法的 Identifier
|
||||||
|
depsInfo.forEach((info) => {
|
||||||
|
const name = info.aliasName || info.exportName;
|
||||||
|
if (!isValidIdentifier(name)) {
|
||||||
|
throw new CodeGeneratorError(`Invalid Identifier [${name}]`);
|
||||||
|
}
|
||||||
|
if (info.nodeIdentifier && !isValidIdentifier(info.nodeIdentifier)) {
|
||||||
|
throw new CodeGeneratorError(`Invalid Identifier [${info.nodeIdentifier}]`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const aliasDefineStatements: Record<string, string> = {};
|
||||||
|
if (useAliasName) {
|
||||||
|
Object.keys(exportItems).forEach((exportName) => {
|
||||||
|
const aliasList = exportItems[exportName]?.aliasNames || [];
|
||||||
|
if (aliasList.length > 0) {
|
||||||
|
const srcName = exportItems[exportName].needOriginExport ? exportName : aliasList[0];
|
||||||
|
const aliasNameList = exportItems[exportName].needOriginExport
|
||||||
|
? aliasList
|
||||||
|
: aliasList.slice(1);
|
||||||
|
aliasNameList.forEach((a) => {
|
||||||
|
if (!aliasDefineStatements[a]) {
|
||||||
|
aliasDefineStatements[a] = `const ${a} = ${srcName};`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDefaultExportName(info: IDependencyItem): string {
|
||||||
|
if (info.isDefault) {
|
||||||
|
return defaultExportNames[0];
|
||||||
|
}
|
||||||
|
return info.exportName;
|
||||||
|
}
|
||||||
|
|
||||||
|
depsInfo.forEach((info) => {
|
||||||
|
// 如果是子组件,则导出父组件,并且根据自组件命名规则,判断是否需要定义标识符
|
||||||
|
if (info.nodeIdentifier) {
|
||||||
|
// 前提,存在 nodeIdentifier 一定是有 subName 的,不然前面会优化掉
|
||||||
|
const ownerName = getDependencyIdentifier(info);
|
||||||
|
|
||||||
|
chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: targetFileType,
|
||||||
|
name: COMMON_CHUNK_NAME.ImportAliasDefine,
|
||||||
|
content: useAliasName ? `const ${info.nodeIdentifier} = ${ownerName}.${info.subName};` : '',
|
||||||
|
linkAfter: [COMMON_CHUNK_NAME.ExternalDepsImport, COMMON_CHUNK_NAME.InternalDepsImport],
|
||||||
|
ext: {
|
||||||
|
originalName: `${getDefaultExportName(info)}.${info.subName}`,
|
||||||
|
aliasName: info.nodeIdentifier,
|
||||||
|
dependency: info.source,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else if (info.aliasName) {
|
||||||
|
let contentStatement = '';
|
||||||
|
if (aliasDefineStatements[info.aliasName]) {
|
||||||
|
contentStatement = aliasDefineStatements[info.aliasName];
|
||||||
|
delete aliasDefineStatements[info.aliasName];
|
||||||
|
}
|
||||||
|
|
||||||
|
chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: targetFileType,
|
||||||
|
name: COMMON_CHUNK_NAME.ImportAliasDefine,
|
||||||
|
content: contentStatement,
|
||||||
|
linkAfter: [COMMON_CHUNK_NAME.ExternalDepsImport, COMMON_CHUNK_NAME.InternalDepsImport],
|
||||||
|
ext: {
|
||||||
|
originalName: getDefaultExportName(info),
|
||||||
|
aliasName: info.aliasName,
|
||||||
|
dependency: info.source,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 可能会剩余一些存在二次转换的定义
|
||||||
|
Object.keys(aliasDefineStatements).forEach((a) => {
|
||||||
|
chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: targetFileType,
|
||||||
|
name: COMMON_CHUNK_NAME.ImportAliasDefine,
|
||||||
|
content: aliasDefineStatements[a],
|
||||||
|
linkAfter: [COMMON_CHUNK_NAME.ExternalDepsImport, COMMON_CHUNK_NAME.InternalDepsImport],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const exportItemList = Object.keys(exportItems).map((k) => exportItems[k]);
|
||||||
|
const defaultExport = exportItemList.filter((item) => item.isDefault);
|
||||||
|
const otherExports = exportItemList.filter((item) => !item.isDefault);
|
||||||
|
|
||||||
|
const statementL = ['import'];
|
||||||
|
if (defaultExport.length > 0) {
|
||||||
|
if (useAliasName) {
|
||||||
|
statementL.push(defaultExportNames[0]);
|
||||||
|
} else {
|
||||||
|
statementL.push(defaultExport[0].aliasNames[0]);
|
||||||
|
}
|
||||||
|
if (otherExports.length > 0) {
|
||||||
|
statementL.push(', ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (otherExports.length > 0) {
|
||||||
|
const items = otherExports.map((item) => {
|
||||||
|
return !useAliasName || item.needOriginExport || item.aliasNames.length <= 0
|
||||||
|
? item.exportName
|
||||||
|
: `${item.exportName} as ${item.aliasNames[0]}`;
|
||||||
|
});
|
||||||
|
statementL.push(`{ ${items.join(', ')} }`);
|
||||||
|
}
|
||||||
|
statementL.push('from');
|
||||||
|
|
||||||
|
const getInternalDependencyModuleId = () => `@/${(deps[0] as IInternalDependency).type}/${pkg}`;
|
||||||
|
|
||||||
|
if (deps[0].dependencyType === DependencyType.Internal) {
|
||||||
|
// TODO: Internal Deps path use project slot setting
|
||||||
|
statementL.push(`'${getInternalDependencyModuleId()}';`);
|
||||||
|
chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: targetFileType,
|
||||||
|
name: COMMON_CHUNK_NAME.InternalDepsImport,
|
||||||
|
content: statementL.join(' '),
|
||||||
|
linkAfter: [COMMON_CHUNK_NAME.ExternalDepsImport],
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
statementL.push(`'${pkg}';`);
|
||||||
|
chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: targetFileType,
|
||||||
|
name: COMMON_CHUNK_NAME.ExternalDepsImport,
|
||||||
|
content: statementL.join(' '),
|
||||||
|
linkAfter: [],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理下一些额外的 default 方式的导入
|
||||||
|
if (defaultExportNames.length > 1) {
|
||||||
|
if (deps[0].dependencyType === DependencyType.Internal) {
|
||||||
|
defaultExportNames.slice(1).forEach((exportName) => {
|
||||||
|
chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: targetFileType,
|
||||||
|
name: COMMON_CHUNK_NAME.InternalDepsImport,
|
||||||
|
content: `import ${exportName} from '${getInternalDependencyModuleId()}';`,
|
||||||
|
linkAfter: [COMMON_CHUNK_NAME.ExternalDepsImport],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
defaultExportNames.slice(1).forEach((exportName) => {
|
||||||
|
chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: targetFileType,
|
||||||
|
name: COMMON_CHUNK_NAME.ExternalDepsImport,
|
||||||
|
content: `import ${exportName} from '${pkg}';`,
|
||||||
|
linkAfter: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: targetFileType,
|
||||||
|
name: COMMON_CHUNK_NAME.ImportAliasDefine,
|
||||||
|
content: '',
|
||||||
|
linkAfter: [],
|
||||||
|
ext: {
|
||||||
|
aliasName: exportName,
|
||||||
|
originalName: exportName,
|
||||||
|
dependency: {
|
||||||
|
package: pkg,
|
||||||
|
componentName: exportName,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return chunks;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PluginConfig {
|
||||||
|
fileType?: string; // 导出的文件类型
|
||||||
|
useAliasName?: boolean; // 是否使用 componentName 重命名组件 identifier
|
||||||
|
}
|
||||||
|
|
||||||
|
const pluginFactory: BuilderComponentPluginFactory<PluginConfig> = (config?: PluginConfig) => {
|
||||||
|
const cfg = {
|
||||||
|
fileType: FileType.JS,
|
||||||
|
useAliasName: true,
|
||||||
|
...(config || {}),
|
||||||
|
};
|
||||||
|
|
||||||
|
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
|
||||||
|
const next: ICodeStruct = {
|
||||||
|
...pre,
|
||||||
|
};
|
||||||
|
|
||||||
|
const ir = next.ir as IWithDependency;
|
||||||
|
|
||||||
|
if (ir && ir.deps && ir.deps.length > 0) {
|
||||||
|
const packs = groupDepsByPack(ir.deps);
|
||||||
|
|
||||||
|
Object.keys(packs).forEach((pkg) => {
|
||||||
|
const chunks = buildPackageImport(pkg, packs[pkg], cfg.fileType, cfg.useAliasName);
|
||||||
|
next.chunks.push(...chunks);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return next;
|
||||||
|
};
|
||||||
|
|
||||||
|
return plugin;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default pluginFactory;
|
||||||
25
modules/code-generator/src/plugins/common/requireUtils.ts
Normal file
25
modules/code-generator/src/plugins/common/requireUtils.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { COMMON_CHUNK_NAME } from '../../const/generator';
|
||||||
|
|
||||||
|
import { BuilderComponentPlugin, BuilderComponentPluginFactory, ChunkType, FileType, ICodeStruct } from '../../types';
|
||||||
|
|
||||||
|
// TODO: How to merge this logic to common deps
|
||||||
|
const pluginFactory: BuilderComponentPluginFactory<unknown> = () => {
|
||||||
|
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
|
||||||
|
const next: ICodeStruct = {
|
||||||
|
...pre,
|
||||||
|
};
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: FileType.JSX,
|
||||||
|
name: COMMON_CHUNK_NAME.InternalDepsImport,
|
||||||
|
content: 'import * from \'react\';',
|
||||||
|
linkAfter: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
return next;
|
||||||
|
};
|
||||||
|
return plugin;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default pluginFactory;
|
||||||
@ -0,0 +1,35 @@
|
|||||||
|
import { COMMON_CHUNK_NAME } from '../../../const/generator';
|
||||||
|
|
||||||
|
import {
|
||||||
|
BuilderComponentPlugin,
|
||||||
|
BuilderComponentPluginFactory,
|
||||||
|
ChunkType,
|
||||||
|
FileType,
|
||||||
|
ICodeStruct,
|
||||||
|
} from '../../../types';
|
||||||
|
|
||||||
|
const pluginFactory: BuilderComponentPluginFactory<unknown> = () => {
|
||||||
|
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
|
||||||
|
const next: ICodeStruct = {
|
||||||
|
...pre,
|
||||||
|
};
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: FileType.JSX,
|
||||||
|
name: COMMON_CHUNK_NAME.ExternalDepsImport,
|
||||||
|
content: `
|
||||||
|
// 注意: 出码引擎注入的临时变量默认都以 "__$$" 开头,禁止在搭建的代码中直接访问。
|
||||||
|
// 例外:rax 框架的导出名和各种组件名除外。
|
||||||
|
import { createElement, Component } from 'rax';
|
||||||
|
import { getSearchParams as __$$getSearchParams } from 'rax-app';
|
||||||
|
`,
|
||||||
|
linkAfter: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
return next;
|
||||||
|
};
|
||||||
|
return plugin;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default pluginFactory;
|
||||||
18
modules/code-generator/src/plugins/component/rax/const.ts
Normal file
18
modules/code-generator/src/plugins/component/rax/const.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
export const RAX_CHUNK_NAME = {
|
||||||
|
ClassDidMountBegin: 'RaxComponentClassDidMountBegin',
|
||||||
|
ClassDidMountContent: 'RaxComponentClassDidMountContent',
|
||||||
|
ClassDidMountEnd: 'RaxComponentClassDidMountEnd',
|
||||||
|
ClassWillUnmountBegin: 'RaxComponentClassWillUnmountBegin',
|
||||||
|
ClassWillUnmountContent: 'RaxComponentClassWillUnmountContent',
|
||||||
|
ClassWillUnmountEnd: 'RaxComponentClassWillUnmountEnd',
|
||||||
|
ClassRenderBegin: 'RaxComponentClassRenderBegin',
|
||||||
|
ClassRenderPre: 'RaxComponentClassRenderPre',
|
||||||
|
ClassRenderJSX: 'RaxComponentClassRenderJSX',
|
||||||
|
ClassRenderEnd: 'RaxComponentClassRenderEnd',
|
||||||
|
MethodsBegin: 'RaxComponentMethodsBegin',
|
||||||
|
MethodsContent: 'RaxComponentMethodsContent',
|
||||||
|
MethodsEnd: 'RaxComponentMethodsEnd',
|
||||||
|
LifeCyclesBegin: 'RaxComponentLifeCyclesBegin',
|
||||||
|
LifeCyclesContent: 'RaxComponentLifeCyclesContent',
|
||||||
|
LifeCyclesEnd: 'RaxComponentLifeCyclesEnd',
|
||||||
|
};
|
||||||
@ -0,0 +1,170 @@
|
|||||||
|
import changeCase from 'change-case';
|
||||||
|
import {
|
||||||
|
COMMON_CHUNK_NAME,
|
||||||
|
CLASS_DEFINE_CHUNK_NAME,
|
||||||
|
DEFAULT_LINK_AFTER,
|
||||||
|
} from '../../../const/generator';
|
||||||
|
import { RAX_CHUNK_NAME } from './const';
|
||||||
|
|
||||||
|
import {
|
||||||
|
BuilderComponentPlugin,
|
||||||
|
BuilderComponentPluginFactory,
|
||||||
|
ChunkType,
|
||||||
|
FileType,
|
||||||
|
ICodeStruct,
|
||||||
|
IContainerInfo,
|
||||||
|
} from '../../../types';
|
||||||
|
import { ensureValidClassName } from '../../../utils/validate';
|
||||||
|
|
||||||
|
const pluginFactory: BuilderComponentPluginFactory<unknown> = () => {
|
||||||
|
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
|
||||||
|
const next: ICodeStruct = {
|
||||||
|
...pre,
|
||||||
|
};
|
||||||
|
|
||||||
|
const ir = next.ir as IContainerInfo;
|
||||||
|
|
||||||
|
// 将模块名转换成 PascalCase 的格式,并添加特定后缀,防止命名冲突
|
||||||
|
const componentClassName = ensureValidClassName(
|
||||||
|
`${changeCase.pascalCase(ir.moduleName)}$$Page`,
|
||||||
|
);
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: FileType.JSX,
|
||||||
|
name: CLASS_DEFINE_CHUNK_NAME.Start,
|
||||||
|
content: `class ${componentClassName} extends Component {`,
|
||||||
|
linkAfter: [
|
||||||
|
COMMON_CHUNK_NAME.ExternalDepsImport,
|
||||||
|
COMMON_CHUNK_NAME.InternalDepsImport,
|
||||||
|
COMMON_CHUNK_NAME.ImportAliasDefine,
|
||||||
|
COMMON_CHUNK_NAME.FileVarDefine,
|
||||||
|
COMMON_CHUNK_NAME.FileUtilDefine,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: FileType.JSX,
|
||||||
|
name: CLASS_DEFINE_CHUNK_NAME.End,
|
||||||
|
content: '}',
|
||||||
|
linkAfter: [
|
||||||
|
CLASS_DEFINE_CHUNK_NAME.Start,
|
||||||
|
CLASS_DEFINE_CHUNK_NAME.InsPrivateMethod,
|
||||||
|
RAX_CHUNK_NAME.ClassRenderEnd,
|
||||||
|
RAX_CHUNK_NAME.MethodsEnd,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: FileType.JSX,
|
||||||
|
name: CLASS_DEFINE_CHUNK_NAME.ConstructorStart,
|
||||||
|
content: 'constructor(props, context) { super(props); ',
|
||||||
|
linkAfter: DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.ConstructorStart],
|
||||||
|
});
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: FileType.JSX,
|
||||||
|
name: CLASS_DEFINE_CHUNK_NAME.ConstructorEnd,
|
||||||
|
content: '} /* end of constructor */',
|
||||||
|
linkAfter: DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.ConstructorEnd],
|
||||||
|
});
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: FileType.JSX,
|
||||||
|
name: RAX_CHUNK_NAME.ClassDidMountBegin,
|
||||||
|
content: 'componentDidMount() {',
|
||||||
|
linkAfter: [
|
||||||
|
CLASS_DEFINE_CHUNK_NAME.Start,
|
||||||
|
CLASS_DEFINE_CHUNK_NAME.InsVar,
|
||||||
|
CLASS_DEFINE_CHUNK_NAME.InsMethod,
|
||||||
|
CLASS_DEFINE_CHUNK_NAME.ConstructorEnd,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: FileType.JSX,
|
||||||
|
name: RAX_CHUNK_NAME.ClassDidMountEnd,
|
||||||
|
content: '} /* end of componentDidMount */',
|
||||||
|
linkAfter: [RAX_CHUNK_NAME.ClassDidMountBegin, RAX_CHUNK_NAME.ClassDidMountContent],
|
||||||
|
});
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: FileType.JSX,
|
||||||
|
name: RAX_CHUNK_NAME.ClassWillUnmountBegin,
|
||||||
|
content: 'componentWillUnmount() {',
|
||||||
|
linkAfter: [
|
||||||
|
CLASS_DEFINE_CHUNK_NAME.Start,
|
||||||
|
CLASS_DEFINE_CHUNK_NAME.InsVar,
|
||||||
|
CLASS_DEFINE_CHUNK_NAME.InsMethod,
|
||||||
|
RAX_CHUNK_NAME.ClassDidMountEnd,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: FileType.JSX,
|
||||||
|
name: RAX_CHUNK_NAME.ClassWillUnmountEnd,
|
||||||
|
content: '} /* end of componentWillUnmount */',
|
||||||
|
linkAfter: [RAX_CHUNK_NAME.ClassWillUnmountBegin, RAX_CHUNK_NAME.ClassWillUnmountContent],
|
||||||
|
});
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: FileType.JSX,
|
||||||
|
name: RAX_CHUNK_NAME.ClassRenderBegin,
|
||||||
|
content: 'render() {',
|
||||||
|
linkAfter: [RAX_CHUNK_NAME.ClassDidMountEnd, RAX_CHUNK_NAME.ClassWillUnmountEnd],
|
||||||
|
});
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: FileType.JSX,
|
||||||
|
name: RAX_CHUNK_NAME.ClassRenderEnd,
|
||||||
|
content: '} /* end of render */',
|
||||||
|
linkAfter: [
|
||||||
|
RAX_CHUNK_NAME.ClassRenderBegin,
|
||||||
|
RAX_CHUNK_NAME.ClassRenderPre,
|
||||||
|
RAX_CHUNK_NAME.ClassRenderJSX,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: FileType.JSX,
|
||||||
|
name: CLASS_DEFINE_CHUNK_NAME.InsPrivateMethod,
|
||||||
|
content: `
|
||||||
|
_i18nText(t) {
|
||||||
|
const locale = this._context.getLocale();
|
||||||
|
return t[locale] ?? t[String(locale).replace('-', '_')] ?? t[t.use || 'zh_CN'] ?? t.en_US;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
linkAfter: [RAX_CHUNK_NAME.ClassRenderEnd],
|
||||||
|
});
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: FileType.JSX,
|
||||||
|
name: COMMON_CHUNK_NAME.FileExport,
|
||||||
|
content: `export default ${componentClassName};`,
|
||||||
|
linkAfter: [
|
||||||
|
COMMON_CHUNK_NAME.ExternalDepsImport,
|
||||||
|
COMMON_CHUNK_NAME.InternalDepsImport,
|
||||||
|
COMMON_CHUNK_NAME.ImportAliasDefine,
|
||||||
|
COMMON_CHUNK_NAME.FileVarDefine,
|
||||||
|
COMMON_CHUNK_NAME.FileUtilDefine,
|
||||||
|
CLASS_DEFINE_CHUNK_NAME.End,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
return next;
|
||||||
|
};
|
||||||
|
return plugin;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default pluginFactory;
|
||||||
@ -0,0 +1,66 @@
|
|||||||
|
import { CLASS_DEFINE_CHUNK_NAME, DEFAULT_LINK_AFTER } from '../../../const/generator';
|
||||||
|
|
||||||
|
import { generateCompositeType } from '../../../utils/compositeType';
|
||||||
|
import { Scope } from '../../../utils/Scope';
|
||||||
|
|
||||||
|
import {
|
||||||
|
BuilderComponentPlugin,
|
||||||
|
BuilderComponentPluginFactory,
|
||||||
|
ChunkType,
|
||||||
|
FileType,
|
||||||
|
ICodeStruct,
|
||||||
|
IContainerInfo,
|
||||||
|
} from '../../../types';
|
||||||
|
|
||||||
|
export interface PluginConfig {
|
||||||
|
fileType: string;
|
||||||
|
implementType: 'inConstructor' | 'insMember' | 'hooks';
|
||||||
|
}
|
||||||
|
|
||||||
|
const pluginFactory: BuilderComponentPluginFactory<PluginConfig> = (config?) => {
|
||||||
|
const cfg: PluginConfig = {
|
||||||
|
fileType: FileType.JSX,
|
||||||
|
implementType: 'insMember',
|
||||||
|
...config,
|
||||||
|
};
|
||||||
|
|
||||||
|
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
|
||||||
|
const next: ICodeStruct = {
|
||||||
|
...pre,
|
||||||
|
};
|
||||||
|
|
||||||
|
const ir = next.ir as IContainerInfo;
|
||||||
|
const scope = Scope.createRootScope();
|
||||||
|
|
||||||
|
const state = ir.state || {};
|
||||||
|
const fields = Object.keys(state).map<string>((stateName) => {
|
||||||
|
// TODO: 这里用什么 handlers?
|
||||||
|
const value = generateCompositeType(state[stateName], scope);
|
||||||
|
return `${JSON.stringify(stateName)}: ${value}`;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (cfg.implementType === 'inConstructor') {
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: cfg.fileType,
|
||||||
|
name: CLASS_DEFINE_CHUNK_NAME.ConstructorContent,
|
||||||
|
content: `this.state = { ${fields.join(',')} };`,
|
||||||
|
linkAfter: [...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.ConstructorContent]],
|
||||||
|
});
|
||||||
|
} else if (cfg.implementType === 'insMember') {
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: cfg.fileType,
|
||||||
|
name: CLASS_DEFINE_CHUNK_NAME.InsVar,
|
||||||
|
content: `state = { ${fields.join(',')} };`,
|
||||||
|
linkAfter: [...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.InsVar]],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// TODO: hooks state??
|
||||||
|
|
||||||
|
return next;
|
||||||
|
};
|
||||||
|
return plugin;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default pluginFactory;
|
||||||
@ -0,0 +1,134 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/indent */
|
||||||
|
import { CLASS_DEFINE_CHUNK_NAME, COMMON_CHUNK_NAME } from '../../../const/generator';
|
||||||
|
|
||||||
|
import {
|
||||||
|
BuilderComponentPlugin,
|
||||||
|
BuilderComponentPluginFactory,
|
||||||
|
ChunkType,
|
||||||
|
FileType,
|
||||||
|
ICodeStruct,
|
||||||
|
IContainerInfo,
|
||||||
|
} from '../../../types';
|
||||||
|
import { RAX_CHUNK_NAME } from './const';
|
||||||
|
|
||||||
|
export interface PluginConfig {
|
||||||
|
fileType: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pluginFactory: BuilderComponentPluginFactory<PluginConfig> = (config?) => {
|
||||||
|
const cfg: PluginConfig = {
|
||||||
|
fileType: FileType.JSX,
|
||||||
|
...config,
|
||||||
|
};
|
||||||
|
|
||||||
|
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
|
||||||
|
const next: ICodeStruct = {
|
||||||
|
...pre,
|
||||||
|
};
|
||||||
|
|
||||||
|
const ir = next.ir as IContainerInfo;
|
||||||
|
const useRef = !!ir.analyzeResult?.isUsingRef;
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: cfg.fileType,
|
||||||
|
name: COMMON_CHUNK_NAME.InternalDepsImport,
|
||||||
|
content: "import __$$constants from '../../constants';",
|
||||||
|
linkAfter: [COMMON_CHUNK_NAME.ExternalDepsImport],
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: i18n 是可选的,如果没有 i18n 这个文件怎么办?该怎么判断?
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: cfg.fileType,
|
||||||
|
name: COMMON_CHUNK_NAME.InternalDepsImport,
|
||||||
|
content: "import * as __$$i18n from '../../i18n';",
|
||||||
|
linkAfter: [COMMON_CHUNK_NAME.ExternalDepsImport],
|
||||||
|
});
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: cfg.fileType,
|
||||||
|
name: CLASS_DEFINE_CHUNK_NAME.InsVar,
|
||||||
|
content: `
|
||||||
|
_context = this._createContext();
|
||||||
|
`,
|
||||||
|
linkAfter: [CLASS_DEFINE_CHUNK_NAME.Start],
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: 按照目前的实现方案,代码的插拔能力太弱了,需要有一些变化。
|
||||||
|
// Step 1: 增加前置的分析器
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: cfg.fileType,
|
||||||
|
name: CLASS_DEFINE_CHUNK_NAME.InsPrivateMethod,
|
||||||
|
content: `
|
||||||
|
_createContext() {
|
||||||
|
const self = this;
|
||||||
|
const context = {
|
||||||
|
get state() {
|
||||||
|
return self.state;
|
||||||
|
},
|
||||||
|
setState(newState, callback) {
|
||||||
|
self.setState(newState, callback);
|
||||||
|
},
|
||||||
|
get dataSourceMap() {
|
||||||
|
return self._dataSourceEngine.dataSourceMap || {};
|
||||||
|
},
|
||||||
|
async reloadDataSource() {
|
||||||
|
await self._dataSourceEngine.reloadDataSource();
|
||||||
|
},
|
||||||
|
get utils() {
|
||||||
|
return self._utils;
|
||||||
|
},
|
||||||
|
get page() {
|
||||||
|
return context;
|
||||||
|
},
|
||||||
|
get component() {
|
||||||
|
return context;
|
||||||
|
},
|
||||||
|
get props() {
|
||||||
|
return self.props;
|
||||||
|
},
|
||||||
|
get constants() {
|
||||||
|
return __$$constants;
|
||||||
|
},
|
||||||
|
i18n: __$$i18n.i18n,
|
||||||
|
i18nFormat: __$$i18n.i18nFormat,
|
||||||
|
getLocale: __$$i18n.getLocale,
|
||||||
|
setLocale(locale) {
|
||||||
|
__$$i18n.setLocale(locale);
|
||||||
|
self.forceUpdate();
|
||||||
|
},${
|
||||||
|
useRef
|
||||||
|
? `
|
||||||
|
$(refName) {
|
||||||
|
return self._refsManager.get(refName);
|
||||||
|
},
|
||||||
|
$$(refName) {
|
||||||
|
return self._refsManager.getAll(refName);
|
||||||
|
},
|
||||||
|
get _refsManager() {
|
||||||
|
if (!self._refsManager) {
|
||||||
|
self._refsManager = new RefsManager();
|
||||||
|
}
|
||||||
|
return self._refsManager;
|
||||||
|
},
|
||||||
|
`
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
...this._methods,
|
||||||
|
};
|
||||||
|
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
linkAfter: [RAX_CHUNK_NAME.ClassRenderEnd],
|
||||||
|
});
|
||||||
|
|
||||||
|
return next;
|
||||||
|
};
|
||||||
|
return plugin;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default pluginFactory;
|
||||||
@ -0,0 +1,188 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/indent */
|
||||||
|
|
||||||
|
import {
|
||||||
|
CompositeValue,
|
||||||
|
JSExpression,
|
||||||
|
InterpretDataSourceConfig,
|
||||||
|
isJSExpression,
|
||||||
|
isJSFunction,
|
||||||
|
} from '@alilc/lowcode-types';
|
||||||
|
import changeCase from 'change-case';
|
||||||
|
|
||||||
|
import { CLASS_DEFINE_CHUNK_NAME, COMMON_CHUNK_NAME } from '../../../const/generator';
|
||||||
|
import { Scope } from '../../../utils/Scope';
|
||||||
|
|
||||||
|
import {
|
||||||
|
BuilderComponentPlugin,
|
||||||
|
BuilderComponentPluginFactory,
|
||||||
|
ChunkType,
|
||||||
|
FileType,
|
||||||
|
ICodeStruct,
|
||||||
|
IScope,
|
||||||
|
} from '../../../types';
|
||||||
|
|
||||||
|
import { generateCompositeType } from '../../../utils/compositeType';
|
||||||
|
import { parseExpressionConvertThis2Context } from '../../../utils/expressionParser';
|
||||||
|
import { isContainerSchema } from '../../../utils/schema';
|
||||||
|
import { RaxFrameworkOptions } from '../../project/framework/rax/types/RaxFrameworkOptions';
|
||||||
|
import { RAX_CHUNK_NAME } from './const';
|
||||||
|
|
||||||
|
export interface PluginConfig extends RaxFrameworkOptions {
|
||||||
|
fileType?: string;
|
||||||
|
dataSourceHandlersPackageMap?: Record<string, string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pluginFactory: BuilderComponentPluginFactory<PluginConfig> = (config?) => {
|
||||||
|
const cfg: PluginConfig = {
|
||||||
|
fileType: FileType.JSX,
|
||||||
|
...config,
|
||||||
|
dataSourceHandlersPackageMap:
|
||||||
|
config?.dataSourceHandlersPackageMap || config?.datasourceConfig?.handlersPackages,
|
||||||
|
};
|
||||||
|
|
||||||
|
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
|
||||||
|
const next: ICodeStruct = {
|
||||||
|
...pre,
|
||||||
|
};
|
||||||
|
|
||||||
|
const scope = Scope.createRootScope();
|
||||||
|
const dataSourceConfig = isContainerSchema(pre.ir) ? pre.ir.dataSource : null;
|
||||||
|
const dataSourceItems: InterpretDataSourceConfig[] =
|
||||||
|
(dataSourceConfig && dataSourceConfig.list) || [];
|
||||||
|
const dataSourceEngineOptions = { runtimeConfig: true };
|
||||||
|
if (dataSourceItems.length > 0) {
|
||||||
|
const requestHandlersMap: Record<string, JSExpression> = {};
|
||||||
|
|
||||||
|
dataSourceItems.forEach((ds) => {
|
||||||
|
const dsType = ds.type || 'fetch';
|
||||||
|
if (!(dsType in requestHandlersMap) && dsType !== 'custom') {
|
||||||
|
const handlerFactoryName = `__$$create${changeCase.pascal(dsType)}RequestHandler`;
|
||||||
|
|
||||||
|
requestHandlersMap[dsType] = {
|
||||||
|
type: 'JSExpression',
|
||||||
|
value: `${handlerFactoryName}(${
|
||||||
|
dsType === 'urlParams' ? '__$$getSearchParams()' : ''
|
||||||
|
})`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlerFactoryExportName = `create${changeCase.pascal(dsType)}Handler`;
|
||||||
|
const handlerPkgName =
|
||||||
|
cfg.dataSourceHandlersPackageMap?.[dsType] ||
|
||||||
|
`@alilc/lowcode-datasource-${changeCase.kebab(dsType)}-handler`;
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: FileType.JSX,
|
||||||
|
name: COMMON_CHUNK_NAME.ExternalDepsImport,
|
||||||
|
content: `
|
||||||
|
import { ${handlerFactoryExportName} as ${handlerFactoryName} } from '${handlerPkgName}';
|
||||||
|
`,
|
||||||
|
linkAfter: [],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.assign(dataSourceEngineOptions, { requestHandlersMap });
|
||||||
|
}
|
||||||
|
|
||||||
|
const datasourceEnginePackageName =
|
||||||
|
cfg.datasourceConfig?.enginePackage || '@alilc/lowcode-datasource-engine';
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: FileType.JSX,
|
||||||
|
name: COMMON_CHUNK_NAME.ExternalDepsImport,
|
||||||
|
content: `
|
||||||
|
import { create as __$$createDataSourceEngine } from '${datasourceEnginePackageName}/runtime';
|
||||||
|
`,
|
||||||
|
linkAfter: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: cfg.fileType!,
|
||||||
|
name: CLASS_DEFINE_CHUNK_NAME.InsVar,
|
||||||
|
content: `
|
||||||
|
_dataSourceConfig = this._defineDataSourceConfig();
|
||||||
|
_dataSourceEngine = __$$createDataSourceEngine(
|
||||||
|
this._dataSourceConfig,
|
||||||
|
this._context,
|
||||||
|
${generateCompositeType(dataSourceEngineOptions, scope)}
|
||||||
|
);`,
|
||||||
|
linkAfter: [CLASS_DEFINE_CHUNK_NAME.Start],
|
||||||
|
});
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: cfg.fileType!,
|
||||||
|
name: RAX_CHUNK_NAME.ClassDidMountContent,
|
||||||
|
content: `
|
||||||
|
this._dataSourceEngine.reloadDataSource();
|
||||||
|
`,
|
||||||
|
linkAfter: [RAX_CHUNK_NAME.ClassDidMountBegin],
|
||||||
|
});
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: cfg.fileType!,
|
||||||
|
name: CLASS_DEFINE_CHUNK_NAME.InsPrivateMethod,
|
||||||
|
content: `
|
||||||
|
_defineDataSourceConfig() {
|
||||||
|
const __$$context = this._context;
|
||||||
|
return (${generateCompositeType(
|
||||||
|
{
|
||||||
|
...dataSourceConfig,
|
||||||
|
list: [
|
||||||
|
...dataSourceItems.map((item) => ({
|
||||||
|
// 数据源引擎默认的 errorHandler 是空的,而且并不会触发组件重新渲染……
|
||||||
|
// 这会导致页面状态不能正常展示,故这里处理下:
|
||||||
|
errorHandler: {
|
||||||
|
type: 'JSFunction',
|
||||||
|
value: `function (err){
|
||||||
|
setTimeout(() => {
|
||||||
|
this.setState({ __refresh: Date.now() + Math.random() });
|
||||||
|
}, 0);
|
||||||
|
throw err;
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
...item,
|
||||||
|
isInit:
|
||||||
|
typeof item.isInit === 'boolean' || typeof item.isInit === 'undefined'
|
||||||
|
? item.isInit ?? true
|
||||||
|
: wrapAsFunction(item.isInit, scope),
|
||||||
|
options: wrapAsFunction(item.options, scope),
|
||||||
|
})),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
scope,
|
||||||
|
{
|
||||||
|
handlers: {
|
||||||
|
function: (jsFunc) => parseExpressionConvertThis2Context(jsFunc.value, '__$$context'),
|
||||||
|
expression: (jsExpr) => parseExpressionConvertThis2Context(jsExpr.value, '__$$context'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)});
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
linkAfter: [RAX_CHUNK_NAME.ClassRenderEnd],
|
||||||
|
});
|
||||||
|
|
||||||
|
return next;
|
||||||
|
};
|
||||||
|
return plugin;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default pluginFactory;
|
||||||
|
|
||||||
|
function wrapAsFunction(value: CompositeValue, scope: IScope): CompositeValue {
|
||||||
|
if (isJSExpression(value) || isJSFunction(value)) {
|
||||||
|
return {
|
||||||
|
type: 'JSExpression',
|
||||||
|
value: `function(){ return ((${value.value}))}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: 'JSExpression',
|
||||||
|
value: `function(){return((${generateCompositeType(value, scope)}))}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -0,0 +1,69 @@
|
|||||||
|
import { CLASS_DEFINE_CHUNK_NAME, COMMON_CHUNK_NAME } from '../../../const/generator';
|
||||||
|
|
||||||
|
import {
|
||||||
|
BuilderComponentPlugin,
|
||||||
|
BuilderComponentPluginFactory,
|
||||||
|
ChunkType,
|
||||||
|
FileType,
|
||||||
|
ICodeStruct,
|
||||||
|
IContainerInfo,
|
||||||
|
} from '../../../types';
|
||||||
|
import { RAX_CHUNK_NAME } from './const';
|
||||||
|
|
||||||
|
export interface PluginConfig {
|
||||||
|
fileType: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pluginFactory: BuilderComponentPluginFactory<PluginConfig> = (config?) => {
|
||||||
|
const cfg: PluginConfig = {
|
||||||
|
fileType: FileType.JSX,
|
||||||
|
...config,
|
||||||
|
};
|
||||||
|
|
||||||
|
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
|
||||||
|
const next: ICodeStruct = {
|
||||||
|
...pre,
|
||||||
|
};
|
||||||
|
|
||||||
|
const ir = next.ir as IContainerInfo;
|
||||||
|
const useRef = !!ir.analyzeResult?.isUsingRef;
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: cfg.fileType,
|
||||||
|
name: COMMON_CHUNK_NAME.InternalDepsImport,
|
||||||
|
// TODO: 下面这个路径有没有更好的方式来获取?而非写死
|
||||||
|
content: `
|
||||||
|
import __$$projectUtils${useRef ? ', { RefsManager }' : ''} from '../../utils';
|
||||||
|
`,
|
||||||
|
linkAfter: [COMMON_CHUNK_NAME.ExternalDepsImport],
|
||||||
|
});
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: cfg.fileType,
|
||||||
|
name: CLASS_DEFINE_CHUNK_NAME.InsVar,
|
||||||
|
content: '_utils = this._defineUtils();',
|
||||||
|
linkAfter: [CLASS_DEFINE_CHUNK_NAME.Start],
|
||||||
|
});
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: cfg.fileType,
|
||||||
|
name: CLASS_DEFINE_CHUNK_NAME.InsPrivateMethod,
|
||||||
|
|
||||||
|
content: `
|
||||||
|
_defineUtils() {
|
||||||
|
return {
|
||||||
|
...__$$projectUtils,
|
||||||
|
};
|
||||||
|
}`,
|
||||||
|
linkAfter: [RAX_CHUNK_NAME.ClassRenderEnd],
|
||||||
|
});
|
||||||
|
|
||||||
|
return next;
|
||||||
|
};
|
||||||
|
return plugin;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default pluginFactory;
|
||||||
@ -0,0 +1,144 @@
|
|||||||
|
import _ from 'lodash';
|
||||||
|
import { isJSExpression, isJSFunction } from '@alilc/lowcode-types';
|
||||||
|
|
||||||
|
import { CLASS_DEFINE_CHUNK_NAME } from '../../../const/generator';
|
||||||
|
import { RAX_CHUNK_NAME } from './const';
|
||||||
|
|
||||||
|
import {
|
||||||
|
BuilderComponentPlugin,
|
||||||
|
BuilderComponentPluginFactory,
|
||||||
|
FileType,
|
||||||
|
ChunkType,
|
||||||
|
ICodeStruct,
|
||||||
|
IContainerInfo,
|
||||||
|
} from '../../../types';
|
||||||
|
import { debug } from '../../../utils/debug';
|
||||||
|
|
||||||
|
export interface PluginConfig {
|
||||||
|
fileType: string;
|
||||||
|
exportNameMapping: Record<string, string>;
|
||||||
|
normalizeNameMapping: Record<string, string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pluginFactory: BuilderComponentPluginFactory<PluginConfig> = (config?) => {
|
||||||
|
const cfg: PluginConfig = {
|
||||||
|
fileType: FileType.JSX,
|
||||||
|
exportNameMapping: {},
|
||||||
|
normalizeNameMapping: {
|
||||||
|
didMount: 'componentDidMount',
|
||||||
|
willUnmount: 'componentWillUnmount',
|
||||||
|
},
|
||||||
|
...config,
|
||||||
|
};
|
||||||
|
|
||||||
|
const exportNameMapping = new Map(Object.entries(cfg.exportNameMapping));
|
||||||
|
const normalizeNameMapping = new Map(Object.entries(cfg.normalizeNameMapping));
|
||||||
|
|
||||||
|
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
|
||||||
|
const next: ICodeStruct = {
|
||||||
|
...pre,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Rax 先只支持 didMount 和 willUnmount 吧
|
||||||
|
|
||||||
|
const ir = next.ir as IContainerInfo;
|
||||||
|
const { lifeCycles } = ir;
|
||||||
|
|
||||||
|
if (lifeCycles && !_.isEmpty(lifeCycles)) {
|
||||||
|
Object.entries(lifeCycles).forEach(([lifeCycleName, lifeCycleMethodExpr]) => {
|
||||||
|
// 过滤掉非法数据(有些场景下会误传入空字符串或 null)
|
||||||
|
if (
|
||||||
|
!isJSFunction(lifeCycles[lifeCycleName]) &&
|
||||||
|
!isJSExpression(lifeCycles[lifeCycleName])
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizeName = normalizeNameMapping.get(lifeCycleName) || lifeCycleName;
|
||||||
|
const exportName = exportNameMapping.get(lifeCycleName) || lifeCycleName;
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: cfg.fileType,
|
||||||
|
name: RAX_CHUNK_NAME.LifeCyclesContent,
|
||||||
|
content: `${exportName}: (${lifeCycleMethodExpr.value}),`,
|
||||||
|
linkAfter: [RAX_CHUNK_NAME.LifeCyclesBegin],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (normalizeName === 'constructor') {
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: cfg.fileType,
|
||||||
|
name: CLASS_DEFINE_CHUNK_NAME.ConstructorContent,
|
||||||
|
content: `this._lifeCycles.${exportName}();`,
|
||||||
|
linkAfter: [CLASS_DEFINE_CHUNK_NAME.ConstructorStart],
|
||||||
|
});
|
||||||
|
} else if (normalizeName === 'componentDidMount') {
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: cfg.fileType,
|
||||||
|
name: RAX_CHUNK_NAME.ClassDidMountContent,
|
||||||
|
content: `this._lifeCycles.${exportName}();`,
|
||||||
|
linkAfter: [RAX_CHUNK_NAME.ClassDidMountBegin],
|
||||||
|
});
|
||||||
|
} else if (normalizeName === 'componentWillUnmount') {
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: cfg.fileType,
|
||||||
|
name: RAX_CHUNK_NAME.ClassWillUnmountContent,
|
||||||
|
content: `this._lifeCycles.${exportName}();`,
|
||||||
|
linkAfter: [RAX_CHUNK_NAME.ClassWillUnmountBegin],
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
debug(`[CodeGen]: unknown life cycle: ${lifeCycleName}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: cfg.fileType,
|
||||||
|
name: CLASS_DEFINE_CHUNK_NAME.InsVar,
|
||||||
|
content: '_lifeCycles = this._defineLifeCycles();',
|
||||||
|
linkAfter: [CLASS_DEFINE_CHUNK_NAME.Start],
|
||||||
|
});
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: cfg.fileType,
|
||||||
|
name: RAX_CHUNK_NAME.LifeCyclesBegin,
|
||||||
|
content: `
|
||||||
|
_defineLifeCycles() {
|
||||||
|
const __$$lifeCycles = ({
|
||||||
|
`,
|
||||||
|
linkAfter: [RAX_CHUNK_NAME.ClassRenderEnd, CLASS_DEFINE_CHUNK_NAME.InsPrivateMethod],
|
||||||
|
});
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: cfg.fileType,
|
||||||
|
name: RAX_CHUNK_NAME.LifeCyclesEnd,
|
||||||
|
content: `
|
||||||
|
});
|
||||||
|
|
||||||
|
// 为所有的方法绑定上下文
|
||||||
|
Object.entries(__$$lifeCycles).forEach(([lifeCycleName, lifeCycleMethod]) => {
|
||||||
|
if (typeof lifeCycleMethod === 'function') {
|
||||||
|
__$$lifeCycles[lifeCycleName] = (...args) => {
|
||||||
|
return lifeCycleMethod.apply(this._context, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return __$$lifeCycles;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
linkAfter: [RAX_CHUNK_NAME.LifeCyclesBegin, RAX_CHUNK_NAME.LifeCyclesContent],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return next;
|
||||||
|
};
|
||||||
|
return plugin;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default pluginFactory;
|
||||||
@ -0,0 +1,84 @@
|
|||||||
|
import { CLASS_DEFINE_CHUNK_NAME } from '../../../const/generator';
|
||||||
|
|
||||||
|
import {
|
||||||
|
BuilderComponentPlugin,
|
||||||
|
BuilderComponentPluginFactory,
|
||||||
|
ChunkType,
|
||||||
|
FileType,
|
||||||
|
ICodeStruct,
|
||||||
|
IContainerInfo,
|
||||||
|
} from '../../../types';
|
||||||
|
|
||||||
|
import { RAX_CHUNK_NAME } from './const';
|
||||||
|
|
||||||
|
export interface PluginConfig {
|
||||||
|
fileType: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pluginFactory: BuilderComponentPluginFactory<PluginConfig> = (config?) => {
|
||||||
|
const cfg: PluginConfig = {
|
||||||
|
fileType: FileType.JSX,
|
||||||
|
...config,
|
||||||
|
};
|
||||||
|
|
||||||
|
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
|
||||||
|
const next: ICodeStruct = {
|
||||||
|
...pre,
|
||||||
|
};
|
||||||
|
|
||||||
|
const ir = next.ir as IContainerInfo;
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: cfg.fileType,
|
||||||
|
name: CLASS_DEFINE_CHUNK_NAME.InsVar,
|
||||||
|
content: `
|
||||||
|
_methods = this._defineMethods();
|
||||||
|
`,
|
||||||
|
linkAfter: [CLASS_DEFINE_CHUNK_NAME.Start],
|
||||||
|
});
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: cfg.fileType,
|
||||||
|
name: RAX_CHUNK_NAME.MethodsBegin,
|
||||||
|
content: `
|
||||||
|
_defineMethods() {
|
||||||
|
return ({
|
||||||
|
`,
|
||||||
|
linkAfter: [
|
||||||
|
RAX_CHUNK_NAME.ClassRenderEnd,
|
||||||
|
CLASS_DEFINE_CHUNK_NAME.InsPrivateMethod,
|
||||||
|
RAX_CHUNK_NAME.LifeCyclesEnd,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: cfg.fileType,
|
||||||
|
name: RAX_CHUNK_NAME.MethodsEnd,
|
||||||
|
content: `
|
||||||
|
});
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
linkAfter: [RAX_CHUNK_NAME.MethodsBegin, RAX_CHUNK_NAME.MethodsContent],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (ir.methods && Object.keys(ir.methods).length > 0) {
|
||||||
|
Object.entries(ir.methods).forEach(([methodName, methodDefine]) => {
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: cfg.fileType,
|
||||||
|
name: RAX_CHUNK_NAME.MethodsContent,
|
||||||
|
content: `${methodName}: (${methodDefine.value}),`,
|
||||||
|
linkAfter: [RAX_CHUNK_NAME.MethodsBegin],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return next;
|
||||||
|
};
|
||||||
|
return plugin;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default pluginFactory;
|
||||||
319
modules/code-generator/src/plugins/component/rax/jsx.ts
Normal file
319
modules/code-generator/src/plugins/component/rax/jsx.ts
Normal file
@ -0,0 +1,319 @@
|
|||||||
|
import {
|
||||||
|
NodeSchema,
|
||||||
|
JSExpression,
|
||||||
|
NpmInfo,
|
||||||
|
CompositeValue,
|
||||||
|
isJSExpression,
|
||||||
|
} from '@alilc/lowcode-types';
|
||||||
|
|
||||||
|
import _ from 'lodash';
|
||||||
|
import changeCase from 'change-case';
|
||||||
|
import {
|
||||||
|
BuilderComponentPlugin,
|
||||||
|
BuilderComponentPluginFactory,
|
||||||
|
ChunkType,
|
||||||
|
CodePiece,
|
||||||
|
FileType,
|
||||||
|
ICodeChunk,
|
||||||
|
ICodeStruct,
|
||||||
|
IContainerInfo,
|
||||||
|
PIECE_TYPE,
|
||||||
|
HandlerSet,
|
||||||
|
IScope,
|
||||||
|
NodeGeneratorConfig,
|
||||||
|
NodePlugin,
|
||||||
|
AttrPlugin,
|
||||||
|
} from '../../../types';
|
||||||
|
|
||||||
|
import { RAX_CHUNK_NAME } from './const';
|
||||||
|
import { COMMON_CHUNK_NAME } from '../../../const/generator';
|
||||||
|
|
||||||
|
import { generateExpression } from '../../../utils/jsExpression';
|
||||||
|
import {
|
||||||
|
createNodeGenerator,
|
||||||
|
generateConditionReactCtrl,
|
||||||
|
generateReactExprInJS,
|
||||||
|
} from '../../../utils/nodeToJSX';
|
||||||
|
import { generateCompositeType } from '../../../utils/compositeType';
|
||||||
|
import { Scope } from '../../../utils/Scope';
|
||||||
|
import { parseExpressionGetGlobalVariables } from '../../../utils/expressionParser';
|
||||||
|
import { transformThis2Context } from '../../../core/jsx/handlers/transformThis2Context';
|
||||||
|
import { transformJsExpr } from '../../../core/jsx/handlers/transformJsExpression';
|
||||||
|
|
||||||
|
export interface PluginConfig {
|
||||||
|
fileType: string;
|
||||||
|
|
||||||
|
/** 是否要忽略小程序 */
|
||||||
|
ignoreMiniApp?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: componentName 若并非大写字符打头,甚至并非是一个有效的 JS 标识符怎么办??
|
||||||
|
// FIXME: 我想了下,这块应该放到解析阶段就去做掉,对所有 componentName 做 identifier validate,然后对不合法的做统一替换。
|
||||||
|
const pluginFactory: BuilderComponentPluginFactory<PluginConfig> = (config?) => {
|
||||||
|
const cfg: PluginConfig = {
|
||||||
|
fileType: FileType.JSX,
|
||||||
|
...config,
|
||||||
|
};
|
||||||
|
|
||||||
|
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
|
||||||
|
const next: ICodeStruct = {
|
||||||
|
...pre,
|
||||||
|
};
|
||||||
|
|
||||||
|
const ir = next.ir as IContainerInfo;
|
||||||
|
const rootScope = Scope.createRootScope();
|
||||||
|
|
||||||
|
// Rax 构建到小程序的时候,不能给组件起起别名,得直接引用,故这里将所有的别名替换掉
|
||||||
|
// 先收集下所有的 alias 的映射
|
||||||
|
const componentsNameAliasMap = new Map<string, string>();
|
||||||
|
next.chunks.forEach((chunk) => {
|
||||||
|
if (isImportAliasDefineChunk(chunk)) {
|
||||||
|
componentsNameAliasMap.set(chunk.ext.aliasName, chunk.ext.originalName);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 注意:这里其实隐含了一个假设:schema 中的 componentName 应该是一个有效的 JS 标识符,而且是大写字母打头的
|
||||||
|
// FIXME: 为了快速修复临时加的逻辑,需要用 pre-process 的方式替代处理。
|
||||||
|
const mapComponentNameToAliasOrKeepIt = (componentName: string) =>
|
||||||
|
componentsNameAliasMap.get(componentName) || componentName;
|
||||||
|
|
||||||
|
// 然后过滤掉所有的别名 chunks
|
||||||
|
next.chunks = next.chunks.filter((chunk) => !isImportAliasDefineChunk(chunk));
|
||||||
|
|
||||||
|
// 如果直接按目前的 React 的方式之间出码 JSX 的话,会有 3 个问题:
|
||||||
|
// 1. 小程序出码的时候,循环变量没法拿到
|
||||||
|
// 2. 小程序出码的时候,很容易出现 Uncaught TypeError: Cannot read property 'avatar' of undefined 这样的异常(如下图的 50 行) -- 因为若直接出码,Rax 构建到小程序的时候会立即计算所有在视图中用到的变量
|
||||||
|
// 3. 通过 this.xxx 能拿到的东西太多了,而且自定义的 methods 可能会无意间破坏 Rax 框架或小程序框架在页面 this 上的东东
|
||||||
|
const customHandlers: HandlerSet<string> = {
|
||||||
|
expression(input: JSExpression, scope: IScope) {
|
||||||
|
return transformJsExpr(generateExpression(input, scope), scope);
|
||||||
|
},
|
||||||
|
function(input, scope: IScope) {
|
||||||
|
return transformThis2Context(input.value || 'null', scope);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// 创建代码生成器
|
||||||
|
const commonNodeGenerator = createNodeGenerator({
|
||||||
|
handlers: customHandlers,
|
||||||
|
tagMapping: mapComponentNameToAliasOrKeepIt,
|
||||||
|
nodePlugins: [generateReactExprInJS, generateConditionReactCtrl, generateRaxLoopCtrl],
|
||||||
|
attrPlugins: [generateNodeAttrForRax.bind({ cfg })],
|
||||||
|
});
|
||||||
|
|
||||||
|
// 生成 JSX 代码
|
||||||
|
const jsxContent = commonNodeGenerator(ir, rootScope);
|
||||||
|
|
||||||
|
if (!cfg.ignoreMiniApp) {
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: cfg.fileType,
|
||||||
|
name: COMMON_CHUNK_NAME.ExternalDepsImport,
|
||||||
|
content: "import { isMiniApp as __$$isMiniApp } from 'universal-env';",
|
||||||
|
linkAfter: [],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: cfg.fileType,
|
||||||
|
name: RAX_CHUNK_NAME.ClassRenderPre,
|
||||||
|
// TODO: setState, dataSourceMap, reloadDataSource, utils, i18n, i18nFormat, getLocale, setLocale 这些在 Rax 的编译模式下不能在视图中直接访问,需要转化成 this.xxx
|
||||||
|
content: `
|
||||||
|
const __$$context = this._context;
|
||||||
|
const { state, setState, dataSourceMap, reloadDataSource, utils, constants, i18n, i18nFormat, getLocale, setLocale } = __$$context;
|
||||||
|
`,
|
||||||
|
linkAfter: [RAX_CHUNK_NAME.ClassRenderBegin],
|
||||||
|
});
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: cfg.fileType,
|
||||||
|
name: RAX_CHUNK_NAME.ClassRenderJSX,
|
||||||
|
content: `return ${jsxContent};`,
|
||||||
|
linkAfter: [RAX_CHUNK_NAME.ClassRenderBegin, RAX_CHUNK_NAME.ClassRenderPre],
|
||||||
|
});
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: cfg.fileType,
|
||||||
|
name: COMMON_CHUNK_NAME.CustomContent,
|
||||||
|
content: `
|
||||||
|
|
||||||
|
function __$$eval(expr) {
|
||||||
|
try {
|
||||||
|
return expr();
|
||||||
|
} catch (err) {
|
||||||
|
try {
|
||||||
|
if (window.handleEvalError) {
|
||||||
|
window.handleEvalError('Failed to evaluate: ', expr, err);
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function __$$evalArray(expr) {
|
||||||
|
const res = __$$eval(expr);
|
||||||
|
return Array.isArray(res) ? res : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function __$$createChildContext(oldContext, ext) {
|
||||||
|
return Object.assign({}, oldContext, ext);
|
||||||
|
}
|
||||||
|
|
||||||
|
`,
|
||||||
|
linkAfter: [COMMON_CHUNK_NAME.FileExport],
|
||||||
|
});
|
||||||
|
|
||||||
|
return next;
|
||||||
|
};
|
||||||
|
|
||||||
|
return plugin;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default pluginFactory;
|
||||||
|
|
||||||
|
function isImportAliasDefineChunk(chunk: ICodeChunk): chunk is ICodeChunk & {
|
||||||
|
ext: {
|
||||||
|
aliasName: string;
|
||||||
|
originalName: string;
|
||||||
|
dependency: NpmInfo;
|
||||||
|
};
|
||||||
|
} {
|
||||||
|
return (
|
||||||
|
chunk.name === COMMON_CHUNK_NAME.ImportAliasDefine &&
|
||||||
|
!!chunk.ext &&
|
||||||
|
typeof chunk.ext.aliasName === 'string' &&
|
||||||
|
typeof chunk.ext.originalName === 'string' &&
|
||||||
|
!!(chunk.ext.dependency as NpmInfo | null)?.componentName
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateRaxLoopCtrl(
|
||||||
|
nodeItem: NodeSchema,
|
||||||
|
scope: IScope,
|
||||||
|
config?: NodeGeneratorConfig,
|
||||||
|
next?: NodePlugin,
|
||||||
|
): CodePiece[] {
|
||||||
|
if (nodeItem.loop) {
|
||||||
|
const loopItemName = nodeItem.loopArgs?.[0] || 'item';
|
||||||
|
const loopIndexName = nodeItem.loopArgs?.[1] || 'index';
|
||||||
|
const subScope = scope.createSubScope([loopItemName, loopIndexName]);
|
||||||
|
const pieces: CodePiece[] = next ? next(nodeItem, subScope, config) : [];
|
||||||
|
|
||||||
|
const loopDataExpr = `__$$evalArray(() => (${transformThis2Context(
|
||||||
|
generateCompositeType(nodeItem.loop, scope, { handlers: config?.handlers }),
|
||||||
|
scope,
|
||||||
|
)}))`;
|
||||||
|
|
||||||
|
pieces.unshift({
|
||||||
|
value: `${loopDataExpr}.map((${loopItemName}, ${loopIndexName}) => ((__$$context) => (`,
|
||||||
|
type: PIECE_TYPE.BEFORE,
|
||||||
|
});
|
||||||
|
|
||||||
|
pieces.push({
|
||||||
|
value: `))(__$$createChildContext(__$$context, { ${loopItemName}, ${loopIndexName} })))`,
|
||||||
|
type: PIECE_TYPE.AFTER,
|
||||||
|
});
|
||||||
|
|
||||||
|
return pieces;
|
||||||
|
}
|
||||||
|
|
||||||
|
return next ? next(nodeItem, scope, config) : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateNodeAttrForRax(
|
||||||
|
this: { cfg: PluginConfig },
|
||||||
|
attrData: { attrName: string; attrValue: CompositeValue },
|
||||||
|
scope: IScope,
|
||||||
|
config?: NodeGeneratorConfig,
|
||||||
|
next?: AttrPlugin,
|
||||||
|
): CodePiece[] {
|
||||||
|
if (!this.cfg.ignoreMiniApp && /^on/.test(attrData.attrName)) {
|
||||||
|
// else: onXxx 的都是事件处理函数需要特殊处理下
|
||||||
|
return generateEventHandlerAttrForRax(attrData.attrName, attrData.attrValue, scope, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attrData.attrName === 'ref') {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
name: attrData.attrName,
|
||||||
|
value: `__$$context._refsManager.linkRef('${attrData.attrValue}')`,
|
||||||
|
type: PIECE_TYPE.ATTR,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return next ? next(attrData, scope, config) : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateEventHandlerAttrForRax(
|
||||||
|
attrName: string,
|
||||||
|
attrValue: CompositeValue,
|
||||||
|
scope: IScope,
|
||||||
|
config?: NodeGeneratorConfig,
|
||||||
|
): CodePiece[] {
|
||||||
|
// -- 事件处理函数中 JSExpression 转成 JSFunction 来处理,避免当 JSExpression 处理的时候多包一层 eval 而导致 Rax 转码成小程序的时候出问题
|
||||||
|
const valueExpr = generateCompositeType(
|
||||||
|
isJSExpression(attrValue) ? { type: 'JSFunction', value: attrValue.value } : attrValue,
|
||||||
|
scope,
|
||||||
|
{
|
||||||
|
handlers: config?.handlers,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// 查询当前作用域下的变量
|
||||||
|
const currentScopeVariables = scope.bindings?.getAllBindings() || [];
|
||||||
|
if (currentScopeVariables.length <= 0) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
type: PIECE_TYPE.ATTR,
|
||||||
|
name: attrName,
|
||||||
|
value: valueExpr,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提取出所有的未定义的全局变量
|
||||||
|
const undeclaredVariablesInValueExpr = parseExpressionGetGlobalVariables(valueExpr);
|
||||||
|
const referencedLocalVariables = _.intersection(
|
||||||
|
undeclaredVariablesInValueExpr,
|
||||||
|
currentScopeVariables,
|
||||||
|
);
|
||||||
|
if (referencedLocalVariables.length <= 0) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
type: PIECE_TYPE.ATTR,
|
||||||
|
name: attrName,
|
||||||
|
value: valueExpr,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
const wrappedAttrValueExpr = [
|
||||||
|
'(...__$$args) => {',
|
||||||
|
' if (__$$isMiniApp) {',
|
||||||
|
' const __$$event = __$$args[0];',
|
||||||
|
...referencedLocalVariables.map(
|
||||||
|
(localVar) => `const ${localVar} = __$$event.target.dataset.${localVar};`,
|
||||||
|
),
|
||||||
|
` return (${valueExpr}).apply(this, __$$args);`,
|
||||||
|
' } else {',
|
||||||
|
` return (${valueExpr}).apply(this, __$$args);`,
|
||||||
|
' }',
|
||||||
|
'}',
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
return [
|
||||||
|
...referencedLocalVariables.map((localVar) => ({
|
||||||
|
type: PIECE_TYPE.ATTR,
|
||||||
|
name: `data-${changeCase.snake(localVar)}`,
|
||||||
|
value: localVar,
|
||||||
|
})),
|
||||||
|
{
|
||||||
|
type: PIECE_TYPE.ATTR,
|
||||||
|
name: attrName,
|
||||||
|
value: wrappedAttrValueExpr,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
export const REACT_CHUNK_NAME = {
|
||||||
|
ClassRenderStart: 'ReactComponentClassRenderStart',
|
||||||
|
ClassRenderPre: 'ReactComponentClassRenderPre',
|
||||||
|
ClassRenderEnd: 'ReactComponentClassRenderEnd',
|
||||||
|
ClassRenderJSX: 'ReactComponentClassRenderJSX',
|
||||||
|
ClassDidMountStart: 'ReactComponentClassDidMountStart',
|
||||||
|
ClassDidMountEnd: 'ReactComponentClassDidMountEnd',
|
||||||
|
ClassDidMountContent: 'ReactComponentClassDidMountContent',
|
||||||
|
};
|
||||||
@ -0,0 +1,133 @@
|
|||||||
|
import changeCase from 'change-case';
|
||||||
|
import {
|
||||||
|
COMMON_CHUNK_NAME,
|
||||||
|
CLASS_DEFINE_CHUNK_NAME,
|
||||||
|
DEFAULT_LINK_AFTER,
|
||||||
|
} from '../../../const/generator';
|
||||||
|
import { REACT_CHUNK_NAME } from './const';
|
||||||
|
|
||||||
|
import {
|
||||||
|
BuilderComponentPlugin,
|
||||||
|
BuilderComponentPluginFactory,
|
||||||
|
ChunkType,
|
||||||
|
FileType,
|
||||||
|
ICodeStruct,
|
||||||
|
IContainerInfo,
|
||||||
|
} from '../../../types';
|
||||||
|
import { ensureValidClassName } from '../../../utils/validate';
|
||||||
|
|
||||||
|
const pluginFactory: BuilderComponentPluginFactory<unknown> = () => {
|
||||||
|
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
|
||||||
|
const next: ICodeStruct = {
|
||||||
|
...pre,
|
||||||
|
};
|
||||||
|
|
||||||
|
const ir = next.ir as IContainerInfo;
|
||||||
|
|
||||||
|
// 将模块名转换成 PascalCase 的格式,并添加特定后缀,防止命名冲突
|
||||||
|
const componentClassName = ensureValidClassName(
|
||||||
|
`${changeCase.pascalCase(ir.moduleName)}$$Page`,
|
||||||
|
);
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: FileType.JSX,
|
||||||
|
name: CLASS_DEFINE_CHUNK_NAME.Start,
|
||||||
|
content: `class ${componentClassName} extends React.Component {`,
|
||||||
|
linkAfter: [
|
||||||
|
COMMON_CHUNK_NAME.ExternalDepsImport,
|
||||||
|
COMMON_CHUNK_NAME.InternalDepsImport,
|
||||||
|
COMMON_CHUNK_NAME.ImportAliasDefine,
|
||||||
|
COMMON_CHUNK_NAME.FileVarDefine,
|
||||||
|
COMMON_CHUNK_NAME.FileUtilDefine,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: FileType.JSX,
|
||||||
|
name: CLASS_DEFINE_CHUNK_NAME.End,
|
||||||
|
content: '}',
|
||||||
|
linkAfter: [
|
||||||
|
...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.End],
|
||||||
|
REACT_CHUNK_NAME.ClassRenderEnd,
|
||||||
|
REACT_CHUNK_NAME.ClassDidMountEnd,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: FileType.JSX,
|
||||||
|
name: CLASS_DEFINE_CHUNK_NAME.ConstructorStart,
|
||||||
|
content: 'constructor(props, context) { super(props); ',
|
||||||
|
linkAfter: [...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.ConstructorStart]],
|
||||||
|
});
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: FileType.JSX,
|
||||||
|
name: CLASS_DEFINE_CHUNK_NAME.ConstructorEnd,
|
||||||
|
content: '}',
|
||||||
|
linkAfter: [...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.ConstructorEnd]],
|
||||||
|
});
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: FileType.JSX,
|
||||||
|
name: REACT_CHUNK_NAME.ClassDidMountStart,
|
||||||
|
content: 'componentDidMount() {',
|
||||||
|
linkAfter: [...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.End]],
|
||||||
|
});
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: FileType.JSX,
|
||||||
|
name: REACT_CHUNK_NAME.ClassDidMountEnd,
|
||||||
|
content: '}',
|
||||||
|
linkAfter: [REACT_CHUNK_NAME.ClassDidMountContent, REACT_CHUNK_NAME.ClassDidMountStart],
|
||||||
|
});
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: FileType.JSX,
|
||||||
|
name: REACT_CHUNK_NAME.ClassRenderStart,
|
||||||
|
content: 'render() {',
|
||||||
|
linkAfter: [
|
||||||
|
...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.End],
|
||||||
|
REACT_CHUNK_NAME.ClassDidMountEnd,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: FileType.JSX,
|
||||||
|
name: REACT_CHUNK_NAME.ClassRenderEnd,
|
||||||
|
content: '}',
|
||||||
|
linkAfter: [
|
||||||
|
REACT_CHUNK_NAME.ClassRenderStart,
|
||||||
|
REACT_CHUNK_NAME.ClassRenderPre,
|
||||||
|
REACT_CHUNK_NAME.ClassRenderJSX,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: FileType.JSX,
|
||||||
|
name: COMMON_CHUNK_NAME.FileExport,
|
||||||
|
content: `export default ${componentClassName};`,
|
||||||
|
linkAfter: [
|
||||||
|
COMMON_CHUNK_NAME.ExternalDepsImport,
|
||||||
|
COMMON_CHUNK_NAME.InternalDepsImport,
|
||||||
|
COMMON_CHUNK_NAME.ImportAliasDefine,
|
||||||
|
COMMON_CHUNK_NAME.FileVarDefine,
|
||||||
|
COMMON_CHUNK_NAME.FileUtilDefine,
|
||||||
|
CLASS_DEFINE_CHUNK_NAME.End,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
return next;
|
||||||
|
};
|
||||||
|
return plugin;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default pluginFactory;
|
||||||
@ -0,0 +1,63 @@
|
|||||||
|
import { CLASS_DEFINE_CHUNK_NAME, DEFAULT_LINK_AFTER } from '../../../const/generator';
|
||||||
|
|
||||||
|
import { generateCompositeType } from '../../../utils/compositeType';
|
||||||
|
import { Scope } from '../../../utils/Scope';
|
||||||
|
|
||||||
|
import {
|
||||||
|
BuilderComponentPlugin,
|
||||||
|
BuilderComponentPluginFactory,
|
||||||
|
ChunkType,
|
||||||
|
FileType,
|
||||||
|
ICodeStruct,
|
||||||
|
IContainerInfo,
|
||||||
|
} from '../../../types';
|
||||||
|
|
||||||
|
export interface PluginConfig {
|
||||||
|
fileType: string;
|
||||||
|
implementType: 'inConstructor' | 'insMember' | 'hooks';
|
||||||
|
}
|
||||||
|
|
||||||
|
const pluginFactory: BuilderComponentPluginFactory<PluginConfig> = (config?) => {
|
||||||
|
const cfg: PluginConfig = {
|
||||||
|
fileType: FileType.JSX,
|
||||||
|
implementType: 'inConstructor',
|
||||||
|
...config,
|
||||||
|
};
|
||||||
|
|
||||||
|
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
|
||||||
|
const next: ICodeStruct = {
|
||||||
|
...pre,
|
||||||
|
};
|
||||||
|
|
||||||
|
const ir = next.ir as IContainerInfo;
|
||||||
|
const scope = Scope.createRootScope();
|
||||||
|
|
||||||
|
const state = ir.state || {};
|
||||||
|
const fields = Object.keys(state).map<string>((stateName) => {
|
||||||
|
const value = generateCompositeType(state[stateName], scope);
|
||||||
|
return `${stateName}: ${value},`;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (cfg.implementType === 'inConstructor') {
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: cfg.fileType,
|
||||||
|
name: CLASS_DEFINE_CHUNK_NAME.ConstructorContent,
|
||||||
|
content: `this.state = { ${fields.join('')} };`,
|
||||||
|
linkAfter: [...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.ConstructorContent]],
|
||||||
|
});
|
||||||
|
} else if (cfg.implementType === 'insMember') {
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: cfg.fileType,
|
||||||
|
name: CLASS_DEFINE_CHUNK_NAME.InsVar,
|
||||||
|
content: `state = { ${fields.join('')} };`,
|
||||||
|
linkAfter: [...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.InsVar]],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return next;
|
||||||
|
};
|
||||||
|
return plugin;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default pluginFactory;
|
||||||
@ -0,0 +1,179 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/indent */
|
||||||
|
|
||||||
|
import {
|
||||||
|
CompositeValue,
|
||||||
|
JSExpression,
|
||||||
|
InterpretDataSourceConfig,
|
||||||
|
isJSExpression,
|
||||||
|
isJSFunction,
|
||||||
|
} from '@alilc/lowcode-types';
|
||||||
|
import changeCase from 'change-case';
|
||||||
|
|
||||||
|
import {
|
||||||
|
CLASS_DEFINE_CHUNK_NAME,
|
||||||
|
COMMON_CHUNK_NAME,
|
||||||
|
DEFAULT_LINK_AFTER,
|
||||||
|
} from '../../../const/generator';
|
||||||
|
import { Scope } from '../../../utils/Scope';
|
||||||
|
|
||||||
|
import {
|
||||||
|
BuilderComponentPlugin,
|
||||||
|
BuilderComponentPluginFactory,
|
||||||
|
ChunkType,
|
||||||
|
FileType,
|
||||||
|
ICodeStruct,
|
||||||
|
IScope,
|
||||||
|
} from '../../../types';
|
||||||
|
|
||||||
|
import { generateCompositeType } from '../../../utils/compositeType';
|
||||||
|
import { parseExpressionConvertThis2Context } from '../../../utils/expressionParser';
|
||||||
|
import { isContainerSchema } from '../../../utils/schema';
|
||||||
|
import { REACT_CHUNK_NAME } from './const';
|
||||||
|
|
||||||
|
export interface PluginConfig {
|
||||||
|
fileType: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pluginFactory: BuilderComponentPluginFactory<PluginConfig> = (config?) => {
|
||||||
|
const cfg: PluginConfig = {
|
||||||
|
fileType: FileType.JSX,
|
||||||
|
...config,
|
||||||
|
};
|
||||||
|
|
||||||
|
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
|
||||||
|
const next: ICodeStruct = {
|
||||||
|
...pre,
|
||||||
|
};
|
||||||
|
|
||||||
|
const scope = Scope.createRootScope();
|
||||||
|
const dataSourceConfig = isContainerSchema(pre.ir) ? pre.ir.dataSource : null;
|
||||||
|
const dataSourceItems: InterpretDataSourceConfig[] =
|
||||||
|
(dataSourceConfig && dataSourceConfig.list) || [];
|
||||||
|
const dataSourceEngineOptions = { runtimeConfig: true };
|
||||||
|
if (dataSourceItems.length > 0) {
|
||||||
|
const requestHandlersMap: Record<string, JSExpression> = {};
|
||||||
|
|
||||||
|
dataSourceItems.forEach((ds) => {
|
||||||
|
const dsType = ds.type || 'fetch';
|
||||||
|
if (!(dsType in requestHandlersMap) && dsType !== 'custom') {
|
||||||
|
const handlerFactoryName = `__$$create${changeCase.pascal(dsType)}RequestHandler`;
|
||||||
|
|
||||||
|
requestHandlersMap[dsType] = {
|
||||||
|
type: 'JSExpression',
|
||||||
|
value:
|
||||||
|
handlerFactoryName + (dsType === 'urlParams' ? '(window.location.search)' : '()'),
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlerFactoryExportName = `create${changeCase.pascal(dsType)}Handler`;
|
||||||
|
const handlerPkgName = `@alilc/lowcode-datasource-${changeCase.kebab(dsType)}-handler`;
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: FileType.JSX,
|
||||||
|
name: COMMON_CHUNK_NAME.ExternalDepsImport,
|
||||||
|
content: `
|
||||||
|
import { ${handlerFactoryExportName} as ${handlerFactoryName} } from '${handlerPkgName}';
|
||||||
|
`,
|
||||||
|
linkAfter: [],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.assign(dataSourceEngineOptions, { requestHandlersMap });
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: FileType.JSX,
|
||||||
|
name: COMMON_CHUNK_NAME.ExternalDepsImport,
|
||||||
|
content: `
|
||||||
|
import { create as __$$createDataSourceEngine } from '@alilc/lowcode-datasource-engine/runtime';
|
||||||
|
`,
|
||||||
|
linkAfter: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: cfg.fileType,
|
||||||
|
name: CLASS_DEFINE_CHUNK_NAME.InsVar,
|
||||||
|
content: `
|
||||||
|
_dataSourceConfig = this._defineDataSourceConfig();
|
||||||
|
_dataSourceEngine = __$$createDataSourceEngine(
|
||||||
|
this._dataSourceConfig,
|
||||||
|
this,
|
||||||
|
${generateCompositeType(dataSourceEngineOptions, scope)}
|
||||||
|
);
|
||||||
|
|
||||||
|
get dataSourceMap() {
|
||||||
|
return this._dataSourceEngine.dataSourceMap || {};
|
||||||
|
}
|
||||||
|
|
||||||
|
reloadDataSource = async () => {
|
||||||
|
await this._dataSourceEngine.reloadDataSource();
|
||||||
|
}
|
||||||
|
|
||||||
|
`,
|
||||||
|
linkAfter: [...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.InsVar]],
|
||||||
|
});
|
||||||
|
|
||||||
|
next.chunks.unshift({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: cfg.fileType,
|
||||||
|
name: REACT_CHUNK_NAME.ClassDidMountContent,
|
||||||
|
content: `
|
||||||
|
this._dataSourceEngine.reloadDataSource();
|
||||||
|
`,
|
||||||
|
linkAfter: [REACT_CHUNK_NAME.ClassDidMountStart],
|
||||||
|
});
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: cfg.fileType,
|
||||||
|
name: CLASS_DEFINE_CHUNK_NAME.InsMethod,
|
||||||
|
content: `
|
||||||
|
_defineDataSourceConfig() {
|
||||||
|
const _this = this;
|
||||||
|
return (${generateCompositeType(
|
||||||
|
{
|
||||||
|
...dataSourceConfig,
|
||||||
|
list: [
|
||||||
|
...dataSourceItems.map((item) => ({
|
||||||
|
...item,
|
||||||
|
isInit: wrapAsFunction(item.isInit, scope),
|
||||||
|
options: wrapAsFunction(item.options, scope),
|
||||||
|
})),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
scope,
|
||||||
|
{
|
||||||
|
handlers: {
|
||||||
|
function: (jsFunc) => parseExpressionConvertThis2Context(jsFunc.value, '_this'),
|
||||||
|
expression: (jsExpr) => parseExpressionConvertThis2Context(jsExpr.value, '_this'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)});
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
linkAfter: [...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.InsMethod]],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return next;
|
||||||
|
};
|
||||||
|
return plugin;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default pluginFactory;
|
||||||
|
|
||||||
|
function wrapAsFunction(value: CompositeValue, scope: IScope): CompositeValue {
|
||||||
|
if (isJSExpression(value) || isJSFunction(value)) {
|
||||||
|
return {
|
||||||
|
type: 'JSExpression',
|
||||||
|
value: `function(){ return ((${value.value}))}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: 'JSExpression',
|
||||||
|
value: `function(){return((${generateCompositeType(value, scope)}))}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -0,0 +1,58 @@
|
|||||||
|
import {
|
||||||
|
CLASS_DEFINE_CHUNK_NAME,
|
||||||
|
COMMON_CHUNK_NAME,
|
||||||
|
DEFAULT_LINK_AFTER,
|
||||||
|
} from '../../../const/generator';
|
||||||
|
|
||||||
|
import {
|
||||||
|
BuilderComponentPlugin,
|
||||||
|
BuilderComponentPluginFactory,
|
||||||
|
ChunkType,
|
||||||
|
FileType,
|
||||||
|
ICodeStruct,
|
||||||
|
} from '../../../types';
|
||||||
|
|
||||||
|
export interface PluginConfig {
|
||||||
|
fileType: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pluginFactory: BuilderComponentPluginFactory<PluginConfig> = (config?) => {
|
||||||
|
const cfg: PluginConfig = {
|
||||||
|
fileType: FileType.JSX,
|
||||||
|
...config,
|
||||||
|
};
|
||||||
|
|
||||||
|
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
|
||||||
|
const next: ICodeStruct = {
|
||||||
|
...pre,
|
||||||
|
};
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: cfg.fileType,
|
||||||
|
name: COMMON_CHUNK_NAME.InternalDepsImport,
|
||||||
|
// TODO: 下面这个路径有没有更好的方式来获取?而非写死
|
||||||
|
content: `
|
||||||
|
import { i18n as _$$i18n } from '../../i18n';
|
||||||
|
`,
|
||||||
|
linkAfter: [COMMON_CHUNK_NAME.ExternalDepsImport],
|
||||||
|
});
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: cfg.fileType,
|
||||||
|
name: CLASS_DEFINE_CHUNK_NAME.InsMethod,
|
||||||
|
content: `
|
||||||
|
i18n = (i18nKey) => {
|
||||||
|
return _$$i18n(i18nKey);
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
linkAfter: [...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.InsMethod]],
|
||||||
|
});
|
||||||
|
|
||||||
|
return next;
|
||||||
|
};
|
||||||
|
return plugin;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default pluginFactory;
|
||||||
@ -0,0 +1,93 @@
|
|||||||
|
import {
|
||||||
|
CLASS_DEFINE_CHUNK_NAME,
|
||||||
|
COMMON_CHUNK_NAME,
|
||||||
|
DEFAULT_LINK_AFTER,
|
||||||
|
} from '../../../const/generator';
|
||||||
|
|
||||||
|
import {
|
||||||
|
BuilderComponentPlugin,
|
||||||
|
BuilderComponentPluginFactory,
|
||||||
|
ChunkType,
|
||||||
|
FileType,
|
||||||
|
ICodeStruct,
|
||||||
|
IContainerInfo,
|
||||||
|
} from '../../../types';
|
||||||
|
|
||||||
|
export interface PluginConfig {
|
||||||
|
fileType: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pluginFactory: BuilderComponentPluginFactory<PluginConfig> = (config?) => {
|
||||||
|
const cfg: PluginConfig = {
|
||||||
|
fileType: FileType.JSX,
|
||||||
|
...config,
|
||||||
|
};
|
||||||
|
|
||||||
|
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
|
||||||
|
const next: ICodeStruct = {
|
||||||
|
...pre,
|
||||||
|
};
|
||||||
|
|
||||||
|
const ir = next.ir as IContainerInfo;
|
||||||
|
next.contextData.useRefApi = true;
|
||||||
|
const useRef = !!ir.analyzeResult?.isUsingRef;
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: cfg.fileType,
|
||||||
|
name: COMMON_CHUNK_NAME.InternalDepsImport,
|
||||||
|
// TODO: 下面这个路径有没有更好的方式来获取?而非写死
|
||||||
|
content: `
|
||||||
|
import utils${useRef ? ', { RefsManager }' : ''} from '../../utils';
|
||||||
|
`,
|
||||||
|
linkAfter: [COMMON_CHUNK_NAME.ExternalDepsImport],
|
||||||
|
});
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: cfg.fileType,
|
||||||
|
name: CLASS_DEFINE_CHUNK_NAME.ConstructorContent,
|
||||||
|
content: 'this.utils = utils;',
|
||||||
|
linkAfter: [CLASS_DEFINE_CHUNK_NAME.ConstructorStart],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (useRef) {
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: cfg.fileType,
|
||||||
|
name: CLASS_DEFINE_CHUNK_NAME.ConstructorContent,
|
||||||
|
content: 'this._refsManager = new RefsManager();',
|
||||||
|
linkAfter: [...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.ConstructorContent]],
|
||||||
|
});
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: cfg.fileType,
|
||||||
|
name: CLASS_DEFINE_CHUNK_NAME.InsMethod,
|
||||||
|
content: `
|
||||||
|
$ = (refName) => {
|
||||||
|
return this._refsManager.get(refName);
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
linkAfter: [...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.InsMethod]],
|
||||||
|
});
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: cfg.fileType,
|
||||||
|
name: CLASS_DEFINE_CHUNK_NAME.InsMethod,
|
||||||
|
content: `
|
||||||
|
$$ = (refName) => {
|
||||||
|
return this._refsManager.getAll(refName);
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
linkAfter: [...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.InsMethod]],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return next;
|
||||||
|
};
|
||||||
|
return plugin;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default pluginFactory;
|
||||||
@ -0,0 +1,108 @@
|
|||||||
|
import { CLASS_DEFINE_CHUNK_NAME, DEFAULT_LINK_AFTER } from '../../../const/generator';
|
||||||
|
import { REACT_CHUNK_NAME } from './const';
|
||||||
|
|
||||||
|
import { generateFunction } from '../../../utils/jsExpression';
|
||||||
|
|
||||||
|
import {
|
||||||
|
BuilderComponentPlugin,
|
||||||
|
BuilderComponentPluginFactory,
|
||||||
|
ChunkType,
|
||||||
|
FileType,
|
||||||
|
ICodeChunk,
|
||||||
|
ICodeStruct,
|
||||||
|
IContainerInfo,
|
||||||
|
} from '../../../types';
|
||||||
|
import { isJSFunction, isJSExpression } from '@alilc/lowcode-types';
|
||||||
|
|
||||||
|
export interface PluginConfig {
|
||||||
|
fileType: string;
|
||||||
|
exportNameMapping: Record<string, string>;
|
||||||
|
normalizeNameMapping: Record<string, string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pluginFactory: BuilderComponentPluginFactory<PluginConfig> = (config?) => {
|
||||||
|
const cfg: PluginConfig = {
|
||||||
|
fileType: FileType.JSX,
|
||||||
|
exportNameMapping: {},
|
||||||
|
normalizeNameMapping: {},
|
||||||
|
...config,
|
||||||
|
};
|
||||||
|
|
||||||
|
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
|
||||||
|
const next: ICodeStruct = {
|
||||||
|
...pre,
|
||||||
|
};
|
||||||
|
|
||||||
|
const ir = next.ir as IContainerInfo;
|
||||||
|
|
||||||
|
if (ir.lifeCycles) {
|
||||||
|
const { lifeCycles } = ir;
|
||||||
|
const chunks = Object.keys(lifeCycles).map<ICodeChunk | null>((lifeCycleName) => {
|
||||||
|
// 过滤掉非法数据(有些场景下会误传入空字符串或 null)
|
||||||
|
if (
|
||||||
|
!isJSFunction(lifeCycles[lifeCycleName]) &&
|
||||||
|
!isJSExpression(lifeCycles[lifeCycleName])
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let normalizeName;
|
||||||
|
// constructor会取到对象的构造函数
|
||||||
|
if (lifeCycleName === 'constructor') {
|
||||||
|
normalizeName = lifeCycleName;
|
||||||
|
} else {
|
||||||
|
normalizeName = cfg.normalizeNameMapping[lifeCycleName] || lifeCycleName;
|
||||||
|
}
|
||||||
|
|
||||||
|
const exportName = cfg.exportNameMapping[lifeCycleName] || lifeCycleName;
|
||||||
|
if (normalizeName === 'constructor') {
|
||||||
|
return {
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: cfg.fileType,
|
||||||
|
name: CLASS_DEFINE_CHUNK_NAME.ConstructorContent,
|
||||||
|
content: generateFunction(lifeCycles[lifeCycleName], { isBlock: true }),
|
||||||
|
linkAfter: [...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.ConstructorStart]],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (normalizeName === 'componentDidMount') {
|
||||||
|
return {
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: cfg.fileType,
|
||||||
|
name: REACT_CHUNK_NAME.ClassDidMountContent,
|
||||||
|
content: generateFunction(lifeCycles[lifeCycleName], { isBlock: true }),
|
||||||
|
linkAfter: [REACT_CHUNK_NAME.ClassDidMountStart],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (normalizeName === 'render') {
|
||||||
|
return {
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: cfg.fileType,
|
||||||
|
name: REACT_CHUNK_NAME.ClassRenderPre,
|
||||||
|
content: generateFunction(lifeCycles[lifeCycleName], { isBlock: true }),
|
||||||
|
linkAfter: [REACT_CHUNK_NAME.ClassRenderStart],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: cfg.fileType,
|
||||||
|
name: CLASS_DEFINE_CHUNK_NAME.InsMethod,
|
||||||
|
content: generateFunction(lifeCycles[lifeCycleName], {
|
||||||
|
name: exportName,
|
||||||
|
isMember: true,
|
||||||
|
}),
|
||||||
|
linkAfter: [...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.InsMethod]],
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
next.chunks.push(...chunks.filter((x): x is ICodeChunk => x !== null));
|
||||||
|
}
|
||||||
|
|
||||||
|
return next;
|
||||||
|
};
|
||||||
|
return plugin;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default pluginFactory;
|
||||||
@ -0,0 +1,50 @@
|
|||||||
|
import { CLASS_DEFINE_CHUNK_NAME, DEFAULT_LINK_AFTER } from '../../../const/generator';
|
||||||
|
|
||||||
|
import { generateFunction } from '../../../utils/jsExpression';
|
||||||
|
|
||||||
|
import {
|
||||||
|
BuilderComponentPlugin,
|
||||||
|
BuilderComponentPluginFactory,
|
||||||
|
ChunkType,
|
||||||
|
FileType,
|
||||||
|
ICodeChunk,
|
||||||
|
ICodeStruct,
|
||||||
|
IContainerInfo,
|
||||||
|
} from '../../../types';
|
||||||
|
|
||||||
|
export interface PluginConfig {
|
||||||
|
fileType: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pluginFactory: BuilderComponentPluginFactory<PluginConfig> = (config?) => {
|
||||||
|
const cfg: PluginConfig = {
|
||||||
|
fileType: FileType.JSX,
|
||||||
|
...config,
|
||||||
|
};
|
||||||
|
|
||||||
|
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
|
||||||
|
const next: ICodeStruct = {
|
||||||
|
...pre,
|
||||||
|
};
|
||||||
|
|
||||||
|
const ir = next.ir as IContainerInfo;
|
||||||
|
|
||||||
|
if (ir.methods) {
|
||||||
|
const { methods } = ir;
|
||||||
|
const chunks = Object.keys(methods).map<ICodeChunk>((methodName) => ({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: cfg.fileType,
|
||||||
|
name: CLASS_DEFINE_CHUNK_NAME.InsMethod,
|
||||||
|
content: generateFunction(methods[methodName], { name: methodName, isMember: true }),
|
||||||
|
linkAfter: [...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.InsMethod]],
|
||||||
|
}));
|
||||||
|
|
||||||
|
next.chunks.push(...chunks);
|
||||||
|
}
|
||||||
|
|
||||||
|
return next;
|
||||||
|
};
|
||||||
|
return plugin;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default pluginFactory;
|
||||||
131
modules/code-generator/src/plugins/component/react/jsx.ts
Normal file
131
modules/code-generator/src/plugins/component/react/jsx.ts
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
import {
|
||||||
|
BuilderComponentPlugin,
|
||||||
|
BuilderComponentPluginFactory,
|
||||||
|
ChunkType,
|
||||||
|
FileType,
|
||||||
|
HandlerSet,
|
||||||
|
ICodeStruct,
|
||||||
|
IContainerInfo,
|
||||||
|
IScope,
|
||||||
|
NodeGeneratorConfig,
|
||||||
|
PIECE_TYPE,
|
||||||
|
} from '../../../types';
|
||||||
|
|
||||||
|
import { REACT_CHUNK_NAME } from './const';
|
||||||
|
import { COMMON_CHUNK_NAME } from '../../../const/generator';
|
||||||
|
|
||||||
|
import { createReactNodeGenerator } from '../../../utils/nodeToJSX';
|
||||||
|
import { Scope } from '../../../utils/Scope';
|
||||||
|
import { JSExpression } from '@alilc/lowcode-types';
|
||||||
|
import { generateExpression } from '../../../utils/jsExpression';
|
||||||
|
import { transformJsExpr } from '../../../core/jsx/handlers/transformJsExpression';
|
||||||
|
import { transformThis2Context } from '../../../core/jsx/handlers/transformThis2Context';
|
||||||
|
import { generateCompositeType } from '../../../utils/compositeType';
|
||||||
|
|
||||||
|
export interface PluginConfig {
|
||||||
|
fileType?: string;
|
||||||
|
nodeTypeMapping?: Record<string, string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pluginFactory: BuilderComponentPluginFactory<PluginConfig> = (config?) => {
|
||||||
|
const cfg = {
|
||||||
|
fileType: FileType.JSX,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||||
|
nodeTypeMapping: {} as Record<string, string>,
|
||||||
|
...config,
|
||||||
|
};
|
||||||
|
|
||||||
|
const { nodeTypeMapping } = cfg;
|
||||||
|
|
||||||
|
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
|
||||||
|
const next: ICodeStruct = {
|
||||||
|
...pre,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 这里会将内部的一些子上下文的访问(this.xxx)转换为 __$$context.xxx 的形式
|
||||||
|
// 与 Rax 所不同的是,这里不会将最顶层的 this 转换掉
|
||||||
|
const customHandlers: HandlerSet<string> = {
|
||||||
|
expression(input: JSExpression, scope: IScope) {
|
||||||
|
return transformJsExpr(generateExpression(input, scope), scope, {
|
||||||
|
dontWrapEval: true,
|
||||||
|
dontTransformThis2ContextAtRootScope: true,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function(input, scope: IScope) {
|
||||||
|
return transformThis2Context(
|
||||||
|
generateCompositeType(
|
||||||
|
{
|
||||||
|
type: 'JSFunction',
|
||||||
|
value: input.value || 'null',
|
||||||
|
},
|
||||||
|
Scope.createRootScope(),
|
||||||
|
),
|
||||||
|
scope,
|
||||||
|
{ ignoreRootScope: true },
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const generatorPlugins: NodeGeneratorConfig = {
|
||||||
|
handlers: customHandlers,
|
||||||
|
tagMapping: (v) => nodeTypeMapping[v] || v,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (next.contextData.useRefApi) {
|
||||||
|
generatorPlugins.attrPlugins = [
|
||||||
|
(attrData, scope, pluginCfg, nextFunc) => {
|
||||||
|
if (attrData.attrName === 'ref') {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
name: attrData.attrName,
|
||||||
|
value: `this._refsManager.linkRef('${attrData.attrValue}')`,
|
||||||
|
type: PIECE_TYPE.ATTR,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return nextFunc ? nextFunc(attrData, scope, pluginCfg) : [];
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
const generator = createReactNodeGenerator(generatorPlugins);
|
||||||
|
|
||||||
|
const ir = next.ir as IContainerInfo;
|
||||||
|
const scope: IScope = Scope.createRootScope();
|
||||||
|
const jsxContent = generator(ir, scope);
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: cfg.fileType,
|
||||||
|
name: REACT_CHUNK_NAME.ClassRenderJSX,
|
||||||
|
content: `
|
||||||
|
const __$$context = this;
|
||||||
|
const { state } = this;
|
||||||
|
return ${jsxContent};
|
||||||
|
`,
|
||||||
|
linkAfter: [REACT_CHUNK_NAME.ClassRenderStart, REACT_CHUNK_NAME.ClassRenderPre],
|
||||||
|
});
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: cfg.fileType,
|
||||||
|
name: COMMON_CHUNK_NAME.CustomContent,
|
||||||
|
content: `
|
||||||
|
function __$$createChildContext(oldContext, ext) {
|
||||||
|
const childContext = {
|
||||||
|
...oldContext,
|
||||||
|
...ext,
|
||||||
|
};
|
||||||
|
childContext.__proto__ = oldContext;
|
||||||
|
return childContext;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
linkAfter: [COMMON_CHUNK_NAME.FileExport],
|
||||||
|
});
|
||||||
|
return next;
|
||||||
|
};
|
||||||
|
return plugin;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default pluginFactory;
|
||||||
@ -0,0 +1,30 @@
|
|||||||
|
import { COMMON_CHUNK_NAME } from '../../../const/generator';
|
||||||
|
|
||||||
|
import {
|
||||||
|
BuilderComponentPlugin,
|
||||||
|
BuilderComponentPluginFactory,
|
||||||
|
ChunkType,
|
||||||
|
FileType,
|
||||||
|
ICodeStruct,
|
||||||
|
} from '../../../types';
|
||||||
|
|
||||||
|
const pluginFactory: BuilderComponentPluginFactory<unknown> = () => {
|
||||||
|
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
|
||||||
|
const next: ICodeStruct = {
|
||||||
|
...pre,
|
||||||
|
};
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: FileType.JSX,
|
||||||
|
name: COMMON_CHUNK_NAME.ExternalDepsImport,
|
||||||
|
content: 'import React from \'react\';',
|
||||||
|
linkAfter: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
return next;
|
||||||
|
};
|
||||||
|
return plugin;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default pluginFactory;
|
||||||
52
modules/code-generator/src/plugins/component/style/css.ts
Normal file
52
modules/code-generator/src/plugins/component/style/css.ts
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import { COMMON_CHUNK_NAME } from '../../../const/generator';
|
||||||
|
|
||||||
|
import {
|
||||||
|
BuilderComponentPlugin,
|
||||||
|
BuilderComponentPluginFactory,
|
||||||
|
ChunkType,
|
||||||
|
FileType,
|
||||||
|
ICodeStruct,
|
||||||
|
IContainerInfo,
|
||||||
|
} from '../../../types';
|
||||||
|
|
||||||
|
export interface PluginConfig {
|
||||||
|
fileType: string;
|
||||||
|
moduleFileType: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pluginFactory: BuilderComponentPluginFactory<PluginConfig> = (config?) => {
|
||||||
|
const cfg: PluginConfig = {
|
||||||
|
fileType: FileType.CSS,
|
||||||
|
moduleFileType: FileType.JSX,
|
||||||
|
...config,
|
||||||
|
};
|
||||||
|
|
||||||
|
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
|
||||||
|
const next: ICodeStruct = {
|
||||||
|
...pre,
|
||||||
|
};
|
||||||
|
|
||||||
|
const ir = next.ir as IContainerInfo;
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: cfg.fileType,
|
||||||
|
name: COMMON_CHUNK_NAME.StyleCssContent,
|
||||||
|
content: ir.css,
|
||||||
|
linkAfter: [COMMON_CHUNK_NAME.StyleDepsImport],
|
||||||
|
});
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: cfg.moduleFileType,
|
||||||
|
name: COMMON_CHUNK_NAME.InternalDepsImport,
|
||||||
|
content: `import './index.${cfg.fileType}';`,
|
||||||
|
linkAfter: [COMMON_CHUNK_NAME.ExternalDepsImport],
|
||||||
|
});
|
||||||
|
|
||||||
|
return next;
|
||||||
|
};
|
||||||
|
return plugin;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default pluginFactory;
|
||||||
59
modules/code-generator/src/plugins/project/constants.ts
Normal file
59
modules/code-generator/src/plugins/project/constants.ts
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import { COMMON_CHUNK_NAME } from '../../const/generator';
|
||||||
|
import { generateCompositeType } from '../../utils/compositeType';
|
||||||
|
import {
|
||||||
|
BuilderComponentPlugin,
|
||||||
|
BuilderComponentPluginFactory,
|
||||||
|
ChunkType,
|
||||||
|
FileType,
|
||||||
|
ICodeStruct,
|
||||||
|
IProjectInfo,
|
||||||
|
} from '../../types';
|
||||||
|
import { Scope } from '../../utils/Scope';
|
||||||
|
|
||||||
|
const pluginFactory: BuilderComponentPluginFactory<unknown> = () => {
|
||||||
|
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
|
||||||
|
const next: ICodeStruct = {
|
||||||
|
...pre,
|
||||||
|
};
|
||||||
|
|
||||||
|
const ir = next.ir as IProjectInfo;
|
||||||
|
const scope = Scope.createRootScope();
|
||||||
|
const constantStr = generateCompositeType(ir.constants || {}, scope);
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: FileType.JS,
|
||||||
|
name: COMMON_CHUNK_NAME.FileVarDefine,
|
||||||
|
content: `
|
||||||
|
const __$$constants = (${constantStr});
|
||||||
|
`,
|
||||||
|
linkAfter: [
|
||||||
|
COMMON_CHUNK_NAME.ExternalDepsImport,
|
||||||
|
COMMON_CHUNK_NAME.InternalDepsImport,
|
||||||
|
COMMON_CHUNK_NAME.ImportAliasDefine,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: FileType.JS,
|
||||||
|
name: COMMON_CHUNK_NAME.FileExport,
|
||||||
|
content: `
|
||||||
|
export default __$$constants;
|
||||||
|
`,
|
||||||
|
linkAfter: [
|
||||||
|
COMMON_CHUNK_NAME.ExternalDepsImport,
|
||||||
|
COMMON_CHUNK_NAME.InternalDepsImport,
|
||||||
|
COMMON_CHUNK_NAME.ImportAliasDefine,
|
||||||
|
COMMON_CHUNK_NAME.FileVarDefine,
|
||||||
|
COMMON_CHUNK_NAME.FileUtilDefine,
|
||||||
|
COMMON_CHUNK_NAME.FileMainContent,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
return next;
|
||||||
|
};
|
||||||
|
return plugin;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default pluginFactory;
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
import template from './template';
|
||||||
|
import entry from './plugins/entry';
|
||||||
|
import entryHtml from './plugins/entryHtml';
|
||||||
|
import globalStyle from './plugins/globalStyle';
|
||||||
|
import packageJSON from './plugins/packageJSON';
|
||||||
|
import router from './plugins/router';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
template,
|
||||||
|
plugins: {
|
||||||
|
entry,
|
||||||
|
entryHtml,
|
||||||
|
globalStyle,
|
||||||
|
packageJSON,
|
||||||
|
router,
|
||||||
|
},
|
||||||
|
};
|
||||||
@ -0,0 +1,56 @@
|
|||||||
|
import { COMMON_CHUNK_NAME } from '../../../../../const/generator';
|
||||||
|
|
||||||
|
import {
|
||||||
|
BuilderComponentPlugin,
|
||||||
|
BuilderComponentPluginFactory,
|
||||||
|
ChunkType,
|
||||||
|
FileType,
|
||||||
|
ICodeStruct,
|
||||||
|
} from '../../../../../types';
|
||||||
|
|
||||||
|
const pluginFactory: BuilderComponentPluginFactory<unknown> = () => {
|
||||||
|
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
|
||||||
|
const next: ICodeStruct = {
|
||||||
|
...pre,
|
||||||
|
};
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: FileType.JS,
|
||||||
|
name: COMMON_CHUNK_NAME.ExternalDepsImport,
|
||||||
|
content: `
|
||||||
|
import { createApp } from 'ice';
|
||||||
|
`,
|
||||||
|
linkAfter: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: FileType.JS,
|
||||||
|
name: COMMON_CHUNK_NAME.FileMainContent,
|
||||||
|
content: `
|
||||||
|
const appConfig = {
|
||||||
|
app: {
|
||||||
|
rootId: 'app',
|
||||||
|
},
|
||||||
|
router: {
|
||||||
|
type: 'hash',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
createApp(appConfig);
|
||||||
|
`,
|
||||||
|
linkAfter: [
|
||||||
|
COMMON_CHUNK_NAME.ExternalDepsImport,
|
||||||
|
COMMON_CHUNK_NAME.InternalDepsImport,
|
||||||
|
COMMON_CHUNK_NAME.ImportAliasDefine,
|
||||||
|
COMMON_CHUNK_NAME.FileVarDefine,
|
||||||
|
COMMON_CHUNK_NAME.FileUtilDefine,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
return next;
|
||||||
|
};
|
||||||
|
return plugin;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default pluginFactory;
|
||||||
@ -0,0 +1,46 @@
|
|||||||
|
import { COMMON_CHUNK_NAME } from '../../../../../const/generator';
|
||||||
|
|
||||||
|
import {
|
||||||
|
BuilderComponentPlugin,
|
||||||
|
BuilderComponentPluginFactory,
|
||||||
|
ChunkType,
|
||||||
|
FileType,
|
||||||
|
ICodeStruct,
|
||||||
|
IProjectInfo,
|
||||||
|
} from '../../../../../types';
|
||||||
|
|
||||||
|
const pluginFactory: BuilderComponentPluginFactory<unknown> = () => {
|
||||||
|
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
|
||||||
|
const next: ICodeStruct = {
|
||||||
|
...pre,
|
||||||
|
};
|
||||||
|
|
||||||
|
const ir = next.ir as IProjectInfo;
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: FileType.HTML,
|
||||||
|
name: COMMON_CHUNK_NAME.HtmlContent,
|
||||||
|
content: `
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta http-equiv="x-ua-compatible" content="ie=edge,chrome=1" />
|
||||||
|
<meta name="viewport" content="width=device-width" />
|
||||||
|
<title>${ir?.meta?.name || 'Ice App'}</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`,
|
||||||
|
linkAfter: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
return next;
|
||||||
|
};
|
||||||
|
return plugin;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default pluginFactory;
|
||||||
@ -0,0 +1,56 @@
|
|||||||
|
import { COMMON_CHUNK_NAME } from '../../../../../const/generator';
|
||||||
|
|
||||||
|
import {
|
||||||
|
BuilderComponentPlugin,
|
||||||
|
BuilderComponentPluginFactory,
|
||||||
|
ChunkType,
|
||||||
|
FileType,
|
||||||
|
ICodeStruct,
|
||||||
|
IProjectInfo,
|
||||||
|
} from '../../../../../types';
|
||||||
|
|
||||||
|
const pluginFactory: BuilderComponentPluginFactory<unknown> = () => {
|
||||||
|
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
|
||||||
|
const next: ICodeStruct = {
|
||||||
|
...pre,
|
||||||
|
};
|
||||||
|
|
||||||
|
const ir = next.ir as IProjectInfo;
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: FileType.SCSS,
|
||||||
|
name: COMMON_CHUNK_NAME.StyleDepsImport,
|
||||||
|
content: `
|
||||||
|
// 引入默认全局样式
|
||||||
|
@import '@alifd/next/reset.scss';
|
||||||
|
`,
|
||||||
|
linkAfter: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: FileType.SCSS,
|
||||||
|
name: COMMON_CHUNK_NAME.StyleCssContent,
|
||||||
|
content: `
|
||||||
|
body {
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
linkAfter: [COMMON_CHUNK_NAME.StyleDepsImport],
|
||||||
|
});
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: FileType.SCSS,
|
||||||
|
name: COMMON_CHUNK_NAME.StyleCssContent,
|
||||||
|
content: ir.css || '',
|
||||||
|
linkAfter: [COMMON_CHUNK_NAME.StyleDepsImport],
|
||||||
|
});
|
||||||
|
|
||||||
|
return next;
|
||||||
|
};
|
||||||
|
return plugin;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default pluginFactory;
|
||||||
@ -0,0 +1,101 @@
|
|||||||
|
import { PackageJSON } from '@alilc/lowcode-types';
|
||||||
|
|
||||||
|
import { COMMON_CHUNK_NAME } from '../../../../../const/generator';
|
||||||
|
|
||||||
|
import {
|
||||||
|
BuilderComponentPlugin,
|
||||||
|
BuilderComponentPluginFactory,
|
||||||
|
ChunkType,
|
||||||
|
FileType,
|
||||||
|
ICodeStruct,
|
||||||
|
IProjectInfo,
|
||||||
|
} from '../../../../../types';
|
||||||
|
|
||||||
|
interface IIceJsPackageJSON extends PackageJSON {
|
||||||
|
ideMode: {
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
iceworks: {
|
||||||
|
type: string;
|
||||||
|
adapter: string;
|
||||||
|
};
|
||||||
|
originTemplate: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pluginFactory: BuilderComponentPluginFactory<unknown> = () => {
|
||||||
|
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
|
||||||
|
const next: ICodeStruct = {
|
||||||
|
...pre,
|
||||||
|
};
|
||||||
|
|
||||||
|
const ir = next.ir as IProjectInfo;
|
||||||
|
|
||||||
|
const packageJson: IIceJsPackageJSON = {
|
||||||
|
name: '@alifd/scaffold-lite-js',
|
||||||
|
version: '0.1.5',
|
||||||
|
description: '轻量级模板,使用 JavaScript,仅包含基础的 Layout。',
|
||||||
|
dependencies: {
|
||||||
|
moment: '^2.24.0',
|
||||||
|
react: '^16.4.1',
|
||||||
|
'react-dom': '^16.4.1',
|
||||||
|
'@alifd/theme-design-pro': '^0.x',
|
||||||
|
'@alilc/lowcode-datasource-engine': '*',
|
||||||
|
// TODO: 如何动态获取下面这些依赖?
|
||||||
|
'@alilc/lowcode-datasource-url-params-handler': '*',
|
||||||
|
'@alilc/lowcode-datasource-fetch-handler': '*',
|
||||||
|
'@alilc/lowcode-datasource-mtop-handler': '*',
|
||||||
|
'@alilc/lowcode-datasource-mopen-handler': '*',
|
||||||
|
'intl-messageformat': '^9.3.6',
|
||||||
|
},
|
||||||
|
devDependencies: {
|
||||||
|
'@ice/spec': '^1.0.0',
|
||||||
|
'build-plugin-fusion': '^0.1.0',
|
||||||
|
'build-plugin-moment-locales': '^0.1.0',
|
||||||
|
eslint: '^6.0.1',
|
||||||
|
'ice.js': '^1.0.0',
|
||||||
|
stylelint: '^13.2.0',
|
||||||
|
'@ali/build-plugin-ice-def': '^0.1.0',
|
||||||
|
},
|
||||||
|
scripts: {
|
||||||
|
start: 'icejs start',
|
||||||
|
build: 'icejs build',
|
||||||
|
lint: 'npm run eslint && npm run stylelint',
|
||||||
|
eslint: 'eslint --cache --ext .js,.jsx ./',
|
||||||
|
stylelint: 'stylelint ./**/*.scss',
|
||||||
|
},
|
||||||
|
ideMode: {
|
||||||
|
name: 'ice-react',
|
||||||
|
},
|
||||||
|
iceworks: {
|
||||||
|
type: 'react',
|
||||||
|
adapter: 'adapter-react-v3',
|
||||||
|
},
|
||||||
|
engines: {
|
||||||
|
node: '>=8.0.0',
|
||||||
|
},
|
||||||
|
repository: {
|
||||||
|
type: 'git',
|
||||||
|
url: 'http://gitlab.alibaba-inc.com/msd/leak-scan/tree/master',
|
||||||
|
},
|
||||||
|
private: true,
|
||||||
|
originTemplate: '@alifd/scaffold-lite-js',
|
||||||
|
};
|
||||||
|
|
||||||
|
ir.packages.forEach((packageInfo) => {
|
||||||
|
packageJson.dependencies[packageInfo.package] = packageInfo.version;
|
||||||
|
});
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.JSON,
|
||||||
|
fileType: FileType.JSON,
|
||||||
|
name: COMMON_CHUNK_NAME.FileMainContent,
|
||||||
|
content: packageJson,
|
||||||
|
linkAfter: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
return next;
|
||||||
|
};
|
||||||
|
return plugin;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default pluginFactory;
|
||||||
@ -0,0 +1,84 @@
|
|||||||
|
import { COMMON_CHUNK_NAME } from '../../../../../const/generator';
|
||||||
|
|
||||||
|
import {
|
||||||
|
BuilderComponentPlugin,
|
||||||
|
BuilderComponentPluginFactory,
|
||||||
|
ChunkType,
|
||||||
|
FileType,
|
||||||
|
ICodeStruct,
|
||||||
|
IRouterInfo,
|
||||||
|
} from '../../../../../types';
|
||||||
|
|
||||||
|
const pluginFactory: BuilderComponentPluginFactory<unknown> = () => {
|
||||||
|
const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
|
||||||
|
const next: ICodeStruct = {
|
||||||
|
...pre,
|
||||||
|
};
|
||||||
|
|
||||||
|
const ir = next.ir as IRouterInfo;
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: FileType.JS,
|
||||||
|
name: COMMON_CHUNK_NAME.InternalDepsImport,
|
||||||
|
content: `
|
||||||
|
import BasicLayout from '@/layouts/BasicLayout';
|
||||||
|
`,
|
||||||
|
linkAfter: [COMMON_CHUNK_NAME.ExternalDepsImport],
|
||||||
|
});
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: FileType.JS,
|
||||||
|
name: COMMON_CHUNK_NAME.FileVarDefine,
|
||||||
|
content: `
|
||||||
|
const routerConfig = [
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
component: BasicLayout,
|
||||||
|
children: [
|
||||||
|
${ir.routes
|
||||||
|
.map(
|
||||||
|
(route) => `
|
||||||
|
{
|
||||||
|
path: '${route.path}',
|
||||||
|
component: ${route.componentName},
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
)
|
||||||
|
.join(',')}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
`,
|
||||||
|
linkAfter: [
|
||||||
|
COMMON_CHUNK_NAME.ExternalDepsImport,
|
||||||
|
COMMON_CHUNK_NAME.InternalDepsImport,
|
||||||
|
COMMON_CHUNK_NAME.ImportAliasDefine,
|
||||||
|
COMMON_CHUNK_NAME.FileUtilDefine,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
next.chunks.push({
|
||||||
|
type: ChunkType.STRING,
|
||||||
|
fileType: FileType.JS,
|
||||||
|
name: COMMON_CHUNK_NAME.FileExport,
|
||||||
|
content: `
|
||||||
|
export default routerConfig;
|
||||||
|
`,
|
||||||
|
linkAfter: [
|
||||||
|
COMMON_CHUNK_NAME.ExternalDepsImport,
|
||||||
|
COMMON_CHUNK_NAME.InternalDepsImport,
|
||||||
|
COMMON_CHUNK_NAME.FileUtilDefine,
|
||||||
|
COMMON_CHUNK_NAME.ImportAliasDefine,
|
||||||
|
COMMON_CHUNK_NAME.FileVarDefine,
|
||||||
|
COMMON_CHUNK_NAME.FileMainContent,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
return next;
|
||||||
|
};
|
||||||
|
return plugin;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default pluginFactory;
|
||||||
@ -0,0 +1,73 @@
|
|||||||
|
import { ResultFile } from '@alilc/lowcode-types';
|
||||||
|
import { createResultFile } from '../../../../../../utils/resultHelper';
|
||||||
|
|
||||||
|
export default function getFile(): [string[], ResultFile] {
|
||||||
|
const file = createResultFile(
|
||||||
|
'README',
|
||||||
|
'md',
|
||||||
|
`
|
||||||
|
## Scaffold Lite
|
||||||
|
|
||||||
|
> 轻量级模板,使用 JavaScript,仅包含基础的 Layout。
|
||||||
|
|
||||||
|
## 使用
|
||||||
|
|
||||||
|
\`\`\`bash
|
||||||
|
# 安装依赖
|
||||||
|
$ npm install
|
||||||
|
|
||||||
|
# 启动服务
|
||||||
|
$ npm start # visit http://localhost:3333
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
[More docs](https://ice.work/docs/guide/about).
|
||||||
|
|
||||||
|
## 目录
|
||||||
|
|
||||||
|
\`\`\`md
|
||||||
|
├── build/ # 构建产物
|
||||||
|
├── mock/ # 本地模拟数据
|
||||||
|
│ ├── index.[j,t]s
|
||||||
|
├── public/
|
||||||
|
│ ├── index.html # 应用入口 HTML
|
||||||
|
│ └── favicon.png # Favicon
|
||||||
|
├── src/ # 源码路径
|
||||||
|
│ ├── components/ # 自定义业务组件
|
||||||
|
│ │ └── Guide/
|
||||||
|
│ │ ├── index.[j,t]sx
|
||||||
|
│ │ ├── index.module.scss
|
||||||
|
│ ├── layouts/ # 布局组件
|
||||||
|
│ │ └── BasicLayout/
|
||||||
|
│ │ ├── index.[j,t]sx
|
||||||
|
│ │ └── index.module.scss
|
||||||
|
│ ├── pages/ # 页面
|
||||||
|
│ │ └── Home/ # home 页面,约定路由转成小写
|
||||||
|
│ │ ├── components/ # 页面级自定义业务组件
|
||||||
|
│ │ ├── models.[j,t]sx # 页面级数据状态
|
||||||
|
│ │ ├── index.[j,t]sx # 页面入口
|
||||||
|
│ │ └── index.module.scss # 页面样式文件
|
||||||
|
│ ├── configs/ # [可选] 配置文件
|
||||||
|
│ │ └── menu.[j,t]s # [可选] 菜单配置
|
||||||
|
│ ├── models/ # [可选] 应用级数据状态
|
||||||
|
│ │ └── user.[j,t]s
|
||||||
|
│ ├── utils/ # [可选] 工具库
|
||||||
|
│ ├── global.scss # 全局样式
|
||||||
|
│ ├── routes.[j,t]s # 路由配置
|
||||||
|
│ └── app.[j,t]s[x] # 应用入口脚本
|
||||||
|
├── build.json # 工程配置
|
||||||
|
├── README.md
|
||||||
|
├── package.json
|
||||||
|
├── .editorconfig
|
||||||
|
├── .eslintignore
|
||||||
|
├── .eslintrc.[j,t]s
|
||||||
|
├── .gitignore
|
||||||
|
├── .stylelintignore
|
||||||
|
├── .stylelintrc.[j,t]s
|
||||||
|
├── .gitignore
|
||||||
|
└── [j,t]sconfig.json
|
||||||
|
\`\`\`
|
||||||
|
`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return [[], file];
|
||||||
|
}
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
import { ResultFile } from '@alilc/lowcode-types';
|
||||||
|
|
||||||
|
export default function getFile(): [string[], ResultFile] {
|
||||||
|
return [
|
||||||
|
[],
|
||||||
|
{
|
||||||
|
name: 'abc',
|
||||||
|
ext: 'json',
|
||||||
|
content: `
|
||||||
|
{
|
||||||
|
"type": "ice-app",
|
||||||
|
"builder": "@ali/builder-ice-app"
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
@ -0,0 +1,33 @@
|
|||||||
|
import { ResultFile } from '@alilc/lowcode-types';
|
||||||
|
|
||||||
|
export default function getFile(): [string[], ResultFile] {
|
||||||
|
return [
|
||||||
|
[],
|
||||||
|
{
|
||||||
|
name: 'build',
|
||||||
|
ext: 'json',
|
||||||
|
content: `
|
||||||
|
{
|
||||||
|
"entry": "src/app.js",
|
||||||
|
"plugins": [
|
||||||
|
[
|
||||||
|
"build-plugin-fusion",
|
||||||
|
{
|
||||||
|
"themePackage": "@alifd/theme-design-pro"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"build-plugin-moment-locales",
|
||||||
|
{
|
||||||
|
"locales": [
|
||||||
|
"zh-cn"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"@ali/build-plugin-ice-def"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
import { ResultFile } from '@alilc/lowcode-types';
|
||||||
|
import { createResultFile } from '../../../../../../utils/resultHelper';
|
||||||
|
|
||||||
|
export default function getFile(): [string[], ResultFile] {
|
||||||
|
const file = createResultFile(
|
||||||
|
'.editorconfig',
|
||||||
|
'',
|
||||||
|
`
|
||||||
|
# http://editorconfig.org
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
|
`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return [[], file];
|
||||||
|
}
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
import { ResultFile } from '@alilc/lowcode-types';
|
||||||
|
import { createResultFile } from '../../../../../../utils/resultHelper';
|
||||||
|
|
||||||
|
export default function getFile(): [string[], ResultFile] {
|
||||||
|
const file = createResultFile(
|
||||||
|
'.eslintignore',
|
||||||
|
'',
|
||||||
|
`
|
||||||
|
# 忽略目录
|
||||||
|
build/
|
||||||
|
tests/
|
||||||
|
demo/
|
||||||
|
.ice/
|
||||||
|
|
||||||
|
# node 覆盖率文件
|
||||||
|
coverage/
|
||||||
|
|
||||||
|
# 忽略文件
|
||||||
|
**/*-min.js
|
||||||
|
**/*.min.js
|
||||||
|
|
||||||
|
package-lock.json
|
||||||
|
yarn.lock
|
||||||
|
`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return [[], file];
|
||||||
|
}
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
import { ResultFile } from '@alilc/lowcode-types';
|
||||||
|
import { createResultFile } from '../../../../../../utils/resultHelper';
|
||||||
|
|
||||||
|
export default function getFile(): [string[], ResultFile] {
|
||||||
|
const file = createResultFile(
|
||||||
|
'.eslintrc',
|
||||||
|
'js',
|
||||||
|
`
|
||||||
|
const { eslint } = require('@ice/spec');
|
||||||
|
|
||||||
|
module.exports = eslint;
|
||||||
|
`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return [[], file];
|
||||||
|
}
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
import { ResultFile } from '@alilc/lowcode-types';
|
||||||
|
import { createResultFile } from '../../../../../../utils/resultHelper';
|
||||||
|
|
||||||
|
export default function getFile(): [string[], ResultFile] {
|
||||||
|
const file = createResultFile(
|
||||||
|
'.gitignore',
|
||||||
|
'',
|
||||||
|
`
|
||||||
|
# See https://help.github.com/ignore-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# production
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
tmp/
|
||||||
|
lib/
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.idea/
|
||||||
|
.happypack
|
||||||
|
.DS_Store
|
||||||
|
*.swp
|
||||||
|
*.dia~
|
||||||
|
.ice
|
||||||
|
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
index.module.scss.d.ts
|
||||||
|
`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return [[], file];
|
||||||
|
}
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
import { ResultFile } from '@alilc/lowcode-types';
|
||||||
|
import { createResultFile } from '../../../../../../utils/resultHelper';
|
||||||
|
|
||||||
|
export default function getFile(): [string[], ResultFile] {
|
||||||
|
const file = createResultFile(
|
||||||
|
'jsconfig',
|
||||||
|
'json',
|
||||||
|
`
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": ".",
|
||||||
|
"jsx": "react",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"],
|
||||||
|
"ice": [".ice/index.ts"],
|
||||||
|
"ice/*": [".ice/pages/*"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return [[], file];
|
||||||
|
}
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
import { ResultFile } from '@alilc/lowcode-types';
|
||||||
|
import { createResultFile } from '../../../../../../utils/resultHelper';
|
||||||
|
|
||||||
|
export default function getFile(): [string[], ResultFile] {
|
||||||
|
const file = createResultFile(
|
||||||
|
'.prettierignore',
|
||||||
|
'',
|
||||||
|
`
|
||||||
|
build/
|
||||||
|
tests/
|
||||||
|
demo/
|
||||||
|
.ice/
|
||||||
|
coverage/
|
||||||
|
**/*-min.js
|
||||||
|
**/*.min.js
|
||||||
|
package-lock.json
|
||||||
|
yarn.lock
|
||||||
|
`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return [[], file];
|
||||||
|
}
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
import { ResultFile } from '@alilc/lowcode-types';
|
||||||
|
import { createResultFile } from '../../../../../../utils/resultHelper';
|
||||||
|
|
||||||
|
export default function getFile(): [string[], ResultFile] {
|
||||||
|
const file = createResultFile(
|
||||||
|
'.prettierrc',
|
||||||
|
'js',
|
||||||
|
`
|
||||||
|
const { prettier } = require('@ice/spec');
|
||||||
|
|
||||||
|
module.exports = prettier;
|
||||||
|
`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return [[], file];
|
||||||
|
}
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
import { ResultFile } from '@alilc/lowcode-types';
|
||||||
|
import { createResultFile } from '../../../../../../../../../../../utils/resultHelper';
|
||||||
|
|
||||||
|
export default function getFile(): [string[], ResultFile] {
|
||||||
|
const file = createResultFile(
|
||||||
|
'index',
|
||||||
|
'jsx',
|
||||||
|
`
|
||||||
|
import React from 'react';
|
||||||
|
import styles from './index.module.scss';
|
||||||
|
|
||||||
|
export default function Footer() {
|
||||||
|
return (
|
||||||
|
<p className={styles.footer}>
|
||||||
|
<span className={styles.logo}>Alibaba Fusion</span>
|
||||||
|
<br />
|
||||||
|
<span className={styles.copyright}>© 2019-现在 Alibaba Fusion & ICE</span>
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return [['src', 'layouts', 'BasicLayout', 'components', 'Footer'], file];
|
||||||
|
}
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
import { ResultFile } from '@alilc/lowcode-types';
|
||||||
|
import { createResultFile } from '../../../../../../../../../../../utils/resultHelper';
|
||||||
|
|
||||||
|
export default function getFile(): [string[], ResultFile] {
|
||||||
|
const file = createResultFile(
|
||||||
|
'index',
|
||||||
|
'module.scss',
|
||||||
|
`
|
||||||
|
.footer {
|
||||||
|
line-height: 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copyright {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return [['src', 'layouts', 'BasicLayout', 'components', 'Footer'], file];
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user