API 是组件的属性解释,给开发者作为组件属性配置的参考。为了保持 API 的一致性,我们制定这个 API 命名规范。对于业界通用的,约定俗成的命名,我们遵循社区的约定。对于业界有多种规则难以确定的,我们确定其中一种,大家共同遵守。
-#### 通用规则
+##### 通用规则
- 所有的 API 采用小驼峰的书写规则,如 `onChange`、`direction`、`defaultVisible`。
- 标签名采用大驼峰书写规则,如 `Menu`、`Slider`、`DatePicker`。
-#### 通用命名
+##### 通用命名
| API 名称 | 类型 | 描述 | 常见变量 |
| :------------- | :------------- | :----------------------------------------------------------- | :---------------------------------------------------- |
@@ -250,7 +249,7 @@ API 是组件的属性解释,给开发者作为组件属性配置的参考。
| direction | enum | 方向,取值采用缩写的方式。 | hoz(水平), ver(垂直) |
| align | enum | 对齐方式 | tl, tc, tr, cl, cc, cr, bl, bc, br |
| status | enum | 状态 | normal, success, error, warning |
-| size | enum | 大小 | small, medium, large 更大或更小可用(xxs, xs, xl, xxl) |
+| size | enum | 大小 | small, medium, large 更大或更小可用 (xxs, xs, xl, xxl) |
| type | enum or string | 分类:1. dom 结构不变、只有皮肤的变化 2.组件类型只有并列的几类 | normal, primary, secondary |
| visible | boolean | 是否显示 | |
| defaultVisible | boolean | 是否显示(非受控) | |
@@ -262,11 +261,11 @@ API 是组件的属性解释,给开发者作为组件属性配置的参考。
| has+'属性' | boolean | 拥有某个属性 | 例如 `hasArrow`, `hasHeader`, `hasClose` 等等 |
-#### 多选枚举
+##### 多选枚举
当某个 API 的接口,允许用户指定多个枚举值的时候,我们把这个接口定义为多选枚举。一个很典型的例子是某个弹层组件的 `closable` 属性,我们会允许:键盘 esc 按键、点击 mask、点击 close 按钮、点击组件以外的任何区域进行关闭。
-不要有一个 API 值,支持多种类型。例如某个弹层的组件,我们会允许 esc、点击 mask、点击 close 按钮等进行关闭。此时 API 设计可以通过多个 API 承载,例如:
+不要有一个 API 值,支持多种类型。例如某个弹层的组件,我们会允许 esc、点击 mask、点击 close 按钮等进行关闭。此时 API 设计可以通过多个 API 承载,例如:
```js
closable?: boolean; // 默认为 true
@@ -281,19 +280,19 @@ true 表示触发规则都会关闭,false 表示触发规则不会关闭。
- `
`,任何情况下都不关闭,只能通过受控设置 visible
- `
`,用户按 esc 或者点击关闭按钮会关闭
-#### 事件
+##### 事件
-- 标准事件或者自定义的符合 w3c 标准的事件,命名必须 on 开头, 即 `on` + 事件名,如 onExpand。
+- 标准事件或者自定义的符合 w3c 标准的事件,命名必须 on 开头, 即 `on` + 事件名,如 onExpand。
-#### 表单规范
+##### 表单规范
- 支持[受控模式](https://reactjs.org/docs/forms.html#controlled-components)(value + onChange) (A)
- value 控制组件数据展现
- onChange 组件发生变化时候的回调函数(第一个参数可以给到 value)
-- `value={undefined}` 的时候清空数据, field 的 reset 函数会给所有组件下发 undefined 数据 (AA)
+- `value={undefined}`的时候清空数据,field 的 reset 函数会给所有组件下发 undefined 数据 (AA))
- 一次完整操作抛一次 onChange 事件 `建议` 比如有 Process 表示进展中的状态,建议增加 API `onProcess`;如果有 Start 表示启动状态,建议增加 API `onStart` (AA)
-#### 属性的传递
+##### 属性的传递
**1. 原子组件(Atomic Component)**
> 最小粒子,不能再拆分的组件
@@ -355,9 +354,9 @@ $ iceworks sync
文件命名采取 [bcp47](https://tools.ietf.org/html/bcp47) 规范
-#### 目录规范
+##### 目录规范
-在 `src` 目录新增 `locale` 目录用于管理不同语言的文案.
+在 `src` 目录新增 `locale` 目录用于管理不同语言的文案。
```
|- BizHello
@@ -368,7 +367,7 @@ $ iceworks sync
|------ ja-JP.js
```
-#### 定义不同的语言
+##### 定义不同的语言
```javascript
// zh-CN.js
@@ -391,7 +390,7 @@ export default {
};
```
-#### 组件支持多语言建议方案
+##### 组件支持多语言建议方案
```jsx
// index.jsx
@@ -418,7 +417,7 @@ export default class BizHello extends Component {
}
```
-#### 组件支持全局替换国际化文案
+##### 组件支持全局替换国际化文案
配合 ConfigProvider 支持全局替换国际化文案。
@@ -452,7 +451,7 @@ export default ConfigProvider.config(BizHello, {
业务组件中如果有自定义的需要跟随主题色的 UI,一定要引入变量的形式,增加组件的流通性。
-#### src/index.scss
+##### src/index.scss
```css
/* 如果需要引入主题变量引入此段 */
@@ -499,12 +498,12 @@ export default {
api 属性标准参考 [https://fusion.design/help.html#/dev-biz](https://fusion.design/help.html#/dev-biz)
-#### 2.1.7 无障碍访问规范(AAA)
+#### 2.1.7 无障碍访问规范 (AAA)
-无障碍需要符合 [WCAG 2.1 A级标准](https://www.w3.org/TR/WCAG21/),可参考 [W3C 无障碍最佳实践](https://www.w3.org/TR/wai-aria-practices-1.1/)、[Fusion 无障碍指引 2.3.1](https://alibaba-fusion.github.io/next/part1/basics.html) 章节等。
+无障碍需要符合 [WCAG 2.1 A 级标准](https://www.w3.org/TR/WCAG21/),可参考 [W3C 无障碍最佳实践](https://www.w3.org/TR/wai-aria-practices-1.1/)、[Fusion 无障碍指引 2.3.1](https://alibaba-fusion.github.io/next/part1/basics.html) 章节等。
-#### 增加 a11y.md 无障碍 demo
+##### 增加 a11y.md 无障碍 demo
必须借助 API 才能完成无障碍工作的组件必须为开发者提供无障碍的使用文档,请[参考](https://fusion.design/pc/component/select?themeid=2#accessibility-container)组件 API 中 `ARIA and Keyboard` ,建议在 `demo` 目录新增 `a11y.md` 文件用于演示组件的无障碍使用。
@@ -518,7 +517,7 @@ component
详细指引查看无障碍开发指南 [https://alibaba-fusion.github.io/next/part1/basics.html](https://alibaba-fusion.github.io/next/part1/basics.html)。
-#### 通过键盘快速访问
+##### 通过键盘快速访问
一般键盘事件有 Up Arrow/Down Arrow/Enter/Esc/Tab
@@ -532,7 +531,7 @@ component
| Esc | 关闭列表 |
-#### 对读屏软件友好
+##### 对读屏软件友好
- 对于组件,我们为开发者内置 `role` 和特定 `aria-_属性`,开发者也可以对非组件 API 属性都可以透传至 DOM 元素,进行修改 `role` 和 `aria-_参数`,但是要注意对应关系,请[参考](https://alibaba-fusion.github.io/next/part1/WAI-ARIA.html)。
- 对一些特殊的组件传递参数才能支持无障碍,设置 `id`,`autoFocus` 和传参数,如下:
@@ -552,7 +551,7 @@ component
| -------------- | ------------------------------------------------------------------------------------------------- | ------ |
| version | 协议版本号 | String |
| componentsMap | 描述组件映射关系的集合 | Array |
-| componentsTree | 低代码业务组件树描述,是长度固定为1的数组, 即数组内仅包含根容器的描述(低代码业务组件容器类型) | Array |
+| componentsTree | 低代码业务组件树描述,是长度固定为 1 的数组,即数组内仅包含根容器的描述(低代码业务组件容器类型) | Array |
| utils | 工具类扩展映射关系 | Array |
| i18n | 国际化语料 | Object |
@@ -572,7 +571,7 @@ component
"name": "lucy",
},
"static": {}, // 用于定义自定组件的 static 属性
- "defaultProps": { // 默认 props: 选填仅用于定义低代码业务组件的默认属性固定对象
+ "defaultProps": { // 默认 props:选填仅用于定义低代码业务组件的默认属性固定对象
"name": "xxx"
},
"children": [{
@@ -609,8 +608,6 @@ component
- **组件属性信息 (A):** 描述组件属性信息,通常包含参数、说明、类型、默认值 4 项内容。
- **能力配置/体验增强:** 推荐用于优化搭建产品编辑体验,定制编辑能力的配置信息。
-整体结构概览: [http://lowcode-engine.cn/doc?url=sde3wf](http://lowcode-engine.cn/doc?url=sde3wf)
-
##### 2.2.2.2 基础信息(A)
| 字段 | 字段描述 | 字段类型 | 允许空 |
@@ -836,14 +833,15 @@ props 数组下对象字段描述:
| name | 属性名 | String | type = 'field' 生效 |
| defaultValue | 默认值 | Any(视字段类型而定) | type = 'field' 生效 |
| supportVariable | 是否支持配置变量 | Boolean | type = 'field' 生效 |
-| condition | 配置当前 prop 是否展示 | (target: SettingTarget) => boolean; | - |
-| setter | 单个控件(setter)描述,搭建基础协议组件的描述对象,支持 JSExpression / JSFunction / JSSlot | `String\|Object\|Function` | type = 'field' 生效 |
+| condition | 配置当前 prop 是否展示 | (target: IPublicModelSettingField) => boolean; | - |
+| ignoreDefaultValue | 配置当前 prop 是否忽略默认值处理逻辑,如果返回值是 true 引擎不会处理默认值 | (target: IPublicModelSettingField) => boolean; | - |
+| setter | 单个控件 (setter) 描述,搭建基础协议组件的描述对象,支持 JSExpression / JSFunction / JSSlot | `String\|Object\|Function` | type = 'field' 生效 |
| extraProps | 其他配置属性(不做流通要求) | Object | 其他配置 |
-| extraProps.getValue | setter 渲染时被调用,setter 会根据该函数的返回值设置 setter 当前值 | Function | (target: SettingTarget, value: any) => any; |
-| extraProps.setValue | setter 内容修改时调用,开发者可在该函数内部修改节点 schema 或者进行其他操作 | Function | (target: SettingTarget, value: any) => void; |
+| extraProps.getValue | setter 渲染时被调用,setter 会根据该函数的返回值设置 setter 当前值 | Function | (target: IPublicModelSettingField, value: any) => any; |
+| extraProps.setValue | setter 内容修改时调用,开发者可在该函数内部修改节点 schema 或者进行其他操作 | Function | (target: IPublicModelSettingField, value: any) => void; |
-根据属性值类型 propType,确定对应控件类型 (setter) ,详见 [https://lowcode-engine.cn/docV2/grfylu](https://lowcode-engine.cn/docV2/grfylu)
+根据属性值类型 propType,确定对应控件类型 (setter) 。
###### 2.2.2.4.2 通用扩展面板支持性配置 supports (AA)
@@ -885,7 +883,7 @@ props 数组下对象字段描述:
| nestingRule.descendantBlacklist | 后裔节点类型黑名单 | `String\|Function` |
| nestingRule.ancestorWhitelist | 祖父节点类型白名单 | `String\|Function` |
| isNullNode(AAA) | 是否存在渲染的根节点 | Boolean |
-| isLayout(AAA) | 是否是layout布局组件 | Boolean |
+| isLayout(AAA) | 是否是 layout 布局组件 | Boolean |
| rootSelector(AAA) | 组件选中框的 cssSelector | String |
| disableBehaviors(AAA) | 用于屏蔽在设计器中选中组件时提供的操作项,默认操作项有 copy、hide、remove | String[] |
| actions(AAA) | 用于详细配置上述操作项的内容 | Object |
@@ -925,21 +923,21 @@ props 数组下对象字段描述:
| 字段 | 用途 | 类型 | 备注 |
| ------------------------------- | --------------------------------------------------------------------------------------------------- | ------- | --- |
-|initialChildren |组件拖入“设计器”时根据此配置自动生成 children 节点 schema |NodeData[]/Function NodeData[] | ((target: SettingTarget) => NodeData[]);|
-|getResizingHandlers| 用于配置设计器中组件 resize 操作工具的样式和内容| Function| (currentNode: any) => Array<{ type: 'N' | 'W' | 'S' | 'E' | 'NW' | 'NE' | 'SE' | 'SW'; content?: ReactElement; propTarget?: string; appearOn?: 'mouse-enter' | 'mouse-hover' | 'selected' | 'always'; }> / ReactElement[];
-|callbacks| 配置 callbacks 可捕获引擎抛出的一些事件,例如 onNodeAdd、onResize 等| Callback| -
-|callbacks.onNodeAdd| 在容器中拖入组件时触发的事件回调| Function| (e: MouseEvent, currentNode: any) => any
-|callbacks.onNodeRemove| 在容器中删除组件时触发的事件回调| Function| (e: MouseEvent, currentNode: any) => any
-|callbacks.onResize| 调整容器尺寸时触发的事件回调,常常与 getResizingHandlers搭配使用| Function| 详见 Types 定义
-|callbacks.onResizeStart| 调整容器尺寸开始时触发的事件回调,常常与 getResizingHandlers搭配使用| Function| 详见 Types 定义
-|callbacks.onResizeEnd| 调整容器尺寸结束时触发的事件回调,常常与 getResizingHandlers搭配使用| Function| 详见 Types 定义
-|callbacks.onSubtreeModified| 容器节点结构树发生变化时触发的回调| Function| (currentNode: any, options: any) => void;
-|callbacks.onMouseDownHook| 鼠标按下操作回调| Function| (e: MouseEvent, currentNode: any) => any;
-|callbacks.onClickHook| 鼠标单击操作回调| Function| (e: MouseEvent, currentNode: any) => any;
-|callbacks.onDblClickHook| 鼠标双击操作回调| Function| (e: MouseEvent, currentNode: any) => any;
-|callbacks.onMoveHook| 节点被拖动回调| Function| (currentNode: any) => boolean;
-|callbacks.onHoverHook| 节点被 hover 回调| Function| (currentNode: any) => boolean;
-|callbacks.onChildMoveHook| 容器节点的子节点被拖动回调| Function| (childNode: any, currentNode: any) => boolean;
+|initialChildren | 组件拖入“设计器”时根据此配置自动生成 children 节点 schema |NodeData[]/Function NodeData[] | ((target: IPublicModelSettingField) => NodeData[]);|
+|getResizingHandlers| 用于配置设计器中组件 resize 操作工具的样式和内容 | Function| (currentNode: any) => Array<{ type: 'N' | 'W' | 'S' | 'E' | 'NW' | 'NE' | 'SE' | 'SW'; content?: ReactElement; propTarget?: string; appearOn?: 'mouse-enter' | 'mouse-hover' | 'selected' | 'always'; }> / ReactElement[];
+|callbacks| 配置 callbacks 可捕获引擎抛出的一些事件,例如 onNodeAdd、onResize 等 | Callback| -
+|callbacks.onNodeAdd| 在容器中拖入组件时触发的事件回调 | Function| (e: MouseEvent, currentNode: any) => any
+|callbacks.onNodeRemove| 在容器中删除组件时触发的事件回调 | Function| (e: MouseEvent, currentNode: any) => any
+|callbacks.onResize| 调整容器尺寸时触发的事件回调,常常与 getResizingHandlers 搭配使用 | Function| 详见 Types 定义
+|callbacks.onResizeStart| 调整容器尺寸开始时触发的事件回调,常常与 getResizingHandlers 搭配使用 | Function| 详见 Types 定义
+|callbacks.onResizeEnd| 调整容器尺寸结束时触发的事件回调,常常与 getResizingHandlers 搭配使用 | Function| 详见 Types 定义
+|callbacks.onSubtreeModified| 容器节点结构树发生变化时触发的回调 | Function| (currentNode: any, options: any) => void;
+|callbacks.onMouseDownHook| 鼠标按下操作回调 | Function| (e: MouseEvent, currentNode: any) => any;
+|callbacks.onClickHook| 鼠标单击操作回调 | Function| (e: MouseEvent, currentNode: any) => any;
+|callbacks.onDblClickHook| 鼠标双击操作回调 | Function| (e: MouseEvent, currentNode: any) => any;
+|callbacks.onMoveHook| 节点被拖动回调 | Function| (currentNode: any) => boolean;
+|callbacks.onHoverHook| 节点被 hover 回调 | Function| (currentNode: any) => boolean;
+|callbacks.onChildMoveHook| 容器节点的子节点被拖动回调 | Function| (childNode: any, currentNode: any) => boolean;
描述举例:
@@ -1101,7 +1099,7 @@ export interface Advanced {
/**
* 拖入容器时,自动带入 children 列表
*/
- initialChildren?: NodeData[] | ((target: SettingTarget) => NodeData[]);
+ initialChildren?: NodeData[] | ((target: IPublicModelSettingField) => NodeData[]);
/**
* @todo 待补充文档
*/
@@ -1211,7 +1209,7 @@ export interface ComponentDescription { // 组件描述协议,通过 npm 中
- components { Array } 所有组件的描述协议列表
- sort { Object } 用于描述组件面板中的 tab 和 category
-##### 2.2.3.2 version(A)
+##### 2.2.3.2 version (A)
定义当前协议 schema 的版本号;
@@ -1219,9 +1217,9 @@ export interface ComponentDescription { // 组件描述协议,通过 npm 中
| ---------- | ------ | ---------- | -------- | ------ |
| version | String | 协议版本号 | - | 1.0.0 |
-##### 2.2.3.3 packages(A)
+##### 2.2.3.3 packages (A)
-定义低代码编辑器中加载的资源列表,包含公共库和组件(库) cdn 资源等;
+定义低代码编辑器中加载的资源列表,包含公共库和组件 (库) cdn 资源等;
| 字段 | 字段描述 | 字段类型 | 备注 |
| --------------- | ---------------------- | -------- | ------------------------------------------------------------ |
@@ -1232,7 +1230,7 @@ export interface ComponentDescription { // 组件描述协议,通过 npm 中
| packages[].editUrls (A) | 组件编辑态视图打包后的 CDN url 列表,包含 js 和 css | Array\
| 低代码引擎编辑器会加载这些 url |
| packages[].urls (AA) | 组件渲染态视图打包后的 CDN url 列表,包含 js 和 css | Array\ | 低代码引擎渲染模块会加载这些 url |
-描述举例:
+描述举例:
```json
{
@@ -1253,7 +1251,7 @@ export interface ComponentDescription { // 组件描述协议,通过 npm 中
]
},
{
- "title": "fusion组件库",
+ "title": "fusion 组件库",
"package": "@alifd/next",
"version": "1.24.18",
"urls": [
@@ -1292,11 +1290,11 @@ export interface ComponentDescription { // 组件描述协议,通过 npm 中
}
```
-##### 2.2.3.4 components (A)
+##### 2.2.3.4 components (A)
定义所有组件的描述协议列表,组件描述协议遵循本规范章节 2.2.2 的定义;
-##### 2.2.3.5 sort (A)
+##### 2.2.3.5 sort (A)
定义组件列表分组
@@ -1330,7 +1328,7 @@ export interface RemoteComponentDescription {
}
```
-## 3 物料规范-区块规范
+## 3 物料规范 - 区块规范
### 3.1 源码规范
@@ -1386,14 +1384,14 @@ block/
"category": "form",
"screenshot": "https://unpkg.com/@icedesign/user-landing-block/screenshot.png",
"views": [{ // 区块视图,配置此项后会进入 fusion cool
- "title": "视图1标题", // 区块视图标题
+ "title": "视图 1 标题", // 区块视图标题
"props": { // 区块支持的 props
"type": "primary"
},
"screenshot": "build/views/block_view1.png", // 【编译自动填充】视图截图,会在 build 时自动生成
"html": "build/views/block_view1.html", // 【编译自动填充】视图渲染后 html 结构,会在 build 时自动生成
},{
- "title": "视图2标题", // 区块视图标题
+ "title": "视图 2 标题", // 区块视图标题
"props": { // 区块支持的 props
"type": "sencondary"
},
@@ -1417,7 +1415,7 @@ block/
| i18n | 国际化语料 | Object |
-描述举例1:
+描述举例 1:
```json
{
@@ -1425,7 +1423,7 @@ block/
"componentsMap": [{ }],
"componentsTree": [{ // 区块组件树,顶层由区块容器组件包裹;
"componentName": "Block", // 区块容器组件名
- "fileName": "block1", // 区块容器1
+ "fileName": "block1", // 区块容器 1
"props": {},
"css": "body {font-size: 12px;}",
"state": {
@@ -1454,7 +1452,7 @@ block/
}
```
-描述举例2:
+描述举例 2:
```json
{
@@ -1527,13 +1525,13 @@ block/
│ │ └── app.js # 应用配置文件
│ ├── utils/ # 工具库
│ │ └── index.js # 应用第三方扩展函数
-│ ├── stores/ # [可选]全局状态管理
+│ ├── stores/ # [可选] 全局状态管理
│ │ └── user.js
-│ ├── locales/ # [可选]国际化资源
+│ ├── locales/ # [可选] 国际化资源
│ │ ├── en-US
│ │ └── zh-CN
│ ├── global.scss # 全局样式
-│ └── index.jsx # 应用入口脚本, 依赖 config/routes.js 的路由配置动态生成路由;
+│ └── index.jsx # 应用入口脚本,依赖 config/routes.js 的路由配置动态生成路由;
├── webpack.config.js # 项目工程配置,包含插件配置及自定义 `webpack` 配置等
├── README.md
├── package.json
@@ -1546,7 +1544,7 @@ block/
```
-#### 入口文件
+##### 入口文件
(/src/index.jsx)
@@ -1562,16 +1560,16 @@ const App = hot(router);
ReactDOM.render(, document.getElementById(pkg.config && pkg.config.targetRootID || 'root'));
```
-#### 应用参数配置文件
+##### 应用参数配置文件
(/src/config/app.js)
- 支持配置路由方式:historyMode
- - 支持2种路由方式:
- - 浏览器路由: browser
- - 哈希路由: hash
+ - 支持 2 种路由方式:
+ - 浏览器路由:browser
+ - 哈希路由:hash
- 支持透传路由产生的参数到所有组件的上下文 this 对象上
- - history 对象: this.history
+ - history 对象:this.history
- location 对象:this.location
- 支持内置 query 参数的解析:this.location.query
- match 对象:this.match
@@ -1599,7 +1597,7 @@ export default {
}
```
-#### 应用扩展配置规范:
+##### 应用扩展配置规范:
(/src/utils/index.js)
@@ -1621,7 +1619,7 @@ export default {
}
```
-#### 应用常量配置
+##### 应用常量配置
(/src/config/constants.js)
@@ -1631,7 +1629,7 @@ export default {
}
```
-#### 应用样式配置
+##### 应用样式配置
(/src/global.scss)
@@ -1658,15 +1656,15 @@ a {
"category": "form",
"screenshot": "https://unpkg.com/@icedesign/user-landing-block/screenshot.png",
"views": [{ // 模板视图,配置此项后会进入 fusion cool
- "title": "视图1标题", // 模板视图标题
- "path": "#/dashboard/monitor", // 读取路由列表生成,hash 路由必须加#
- "screenshot": "build/views/page0.png", // 【编译自动填充】视图截图,会在 build 时自动生成
- "html": "build/views/page0.html", // 【编译自动填充】视图渲染后 html 结构,会在 build 时自动生成
+ "title": "视图 1 标题", // 模板视图标题
+ "path": "#/dashboard/monitor", // 读取路由列表生成,hash 路由必须加#
+ "screenshot": "build/views/page0.png", // 【编译自动填充】视图截图,会在 build 时自动生成
+ "html": "build/views/page0.html", // 【编译自动填充】视图渲染后 html 结构,会在 build 时自动生成
},{
- "title": "视图2标题", // 区块视图标题
- "path": "#/dashboard/list", // 读取路由列表生成,hash 路由必须加#
- "screenshot": "build/views/page1.png", // 【编译自动填充】视图截图,会在 build 时自动生成
- "html": "build/views/page1.html", // 【编译自动填充】视图渲染后 html 结构,会在 build 时自动生成
+ "title": "视图 2 标题", // 区块视图标题
+ "path": "#/dashboard/list", // 读取路由列表生成,hash 路由必须加#
+ "screenshot": "build/views/page1.png", // 【编译自动填充】视图截图,会在 build 时自动生成
+ "html": "build/views/page1.html", // 【编译自动填充】视图渲染后 html 结构,会在 build 时自动生成
}]
}
}
diff --git a/docs/docs/video/index.md b/docs/docs/video/index.md
new file mode 100644
index 0000000000..38a0e8a5ce
--- /dev/null
+++ b/docs/docs/video/index.md
@@ -0,0 +1,16 @@
+# 官方视频
+- [2023/11/20 云栖大会|阿里开源低代码引擎及商业解决方案](https://www.bilibili.com/video/BV1Ku4y1w7Zr)
+- [2023/08/03 初识低代码引擎](https://www.bilibili.com/video/BV1gu411p7TC)
+
+# 社区视频
+- [低代码从入门到实战:低代码引擎实践](https://www.bilibili.com/video/BV1aP4y1Q7Xa/)
+- [低代码技术在研发团队的应用模式](https://www.bilibili.com/video/BV1L14y1Y72J/)
+- [阿里低代码引擎项目实战 (1)-引擎 demo 部署到 faas 服务](https://www.bilibili.com/video/BV1B44y1P7GM/)
+- [【有翻车】阿里低代码引擎项目实战 (2)-保存页面到远端存储](https://www.bilibili.com/video/BV1AS4y1K7DP/)
+- [阿里巴巴低代码引擎项目实战 (3)-自定义组件接入](https://www.bilibili.com/video/BV1dZ4y1m76S/)
+- [阿里低代码引擎项目实战 (4)-自定义插件 - 页面管理](https://www.bilibili.com/video/BV17a411i73f/)
+- [阿里低代码引擎项目实战 (4)-用户登录](https://www.bilibili.com/video/BV1Wu411e7EQ/)
+- [【有翻车】阿里低代码引擎项目实战 (5)-表单回显](https://www.bilibili.com/video/BV1UY4y1v7D7/)
+- [阿里低代码引擎项目实战 (6)-自定义插件 - 页面管理 - 后端](https://www.bilibili.com/video/BV1uZ4y1U7Ly/)
+- [阿里低代码引擎项目实战 (6)-自定义插件 - 页面管理 - 前端](https://www.bilibili.com/video/BV1Yq4y1a74P/)
+- [阿里低代码引擎项目实战 (7)-自定义插件 - 页面管理 (完结)](https://www.bilibili.com/video/BV13Y4y1e7EV/)
diff --git a/docs/docusaurus.config.js b/docs/docusaurus.config.js
index 04c99067ee..0aaa4c50f9 100644
--- a/docs/docusaurus.config.js
+++ b/docs/docusaurus.config.js
@@ -39,7 +39,6 @@ const config = {
presets: [
[
'classic',
- /** @type {import('@docusaurus/preset-classic').Options} */
({
docs: {
sidebarPath: require.resolve('./config/sidebars.js'),
@@ -55,7 +54,6 @@ const config = {
],
themeConfig:
- /** @type {import('@docusaurus/preset-classic').ThemeConfig} */
({
docs: {
sidebar: {
@@ -74,6 +72,10 @@ const config = {
},
// 语雀文档导出的图片,会进行 referrer 校验,这里设置关闭,不然加载不了语雀的图片
metadata: [{ name: 'referrer', content: 'no-referrer' }],
+ tableOfContents: {
+ minHeadingLevel: 2,
+ maxHeadingLevel: 6,
+ },
}),
themes: [
diff --git a/docs/package.json b/docs/package.json
index 93b2c43193..7facd9db8b 100644
--- a/docs/package.json
+++ b/docs/package.json
@@ -1,6 +1,6 @@
{
"name": "@alilc/lowcode-engine-docs",
- "version": "1.0.1",
+ "version": "1.2.31",
"description": "低代码引擎版本化文档",
"license": "MIT",
"files": [
@@ -16,7 +16,8 @@
"serve": "docusaurus serve",
"write-translations": "docusaurus write-translations",
"write-heading-ids": "docusaurus write-heading-ids",
- "typecheck": "tsc"
+ "typecheck": "tsc",
+ "syncOss": "node ./scripts/sync-oss.js"
},
"dependencies": {
"@docusaurus/core": "^2.2.0",
diff --git a/docs/scripts/getDocsFromDir.js b/docs/scripts/getDocsFromDir.js
index 1d3236fe69..18e67e7181 100644
--- a/docs/scripts/getDocsFromDir.js
+++ b/docs/scripts/getDocsFromDir.js
@@ -8,8 +8,14 @@ module.exports = function getDocsFromDir(dir, cateList) {
const baseDir = path.join(__dirname, '../docs/');
const docsDir = path.join(baseDir, dir);
+ function isNil(value) {
+ return value === undefined || value === null;
+ }
+
function getMarkdownOrder(filepath) {
- return (matter(fs.readFileSync(filepath, 'utf-8')).data || {}).order || 100;
+ const { data } = matter(fs.readFileSync(filepath, 'utf-8'));
+ const { sidebar_position } = data || {};
+ return isNil(sidebar_position) ? 100 : sidebar_position;
}
const docs = glob.sync('*.md?(x)', {
@@ -18,8 +24,8 @@ module.exports = function getDocsFromDir(dir, cateList) {
});
const result = docs
- .filter(doc => !/^index.md(x)?$/.test(doc))
- .map(doc => {
+ .filter((doc) => !/^index.md(x)?$/.test(doc))
+ .map((doc) => {
return path.join(docsDir, doc);
})
.sort((a, b) => {
@@ -28,7 +34,7 @@ module.exports = function getDocsFromDir(dir, cateList) {
return orderA - orderB;
})
- .map(filepath => {
+ .map((filepath) => {
// /Users/xxx/site/docs/guide/basic/router.md => guide/basic/router
const id = path
.relative(baseDir, filepath)
@@ -37,7 +43,7 @@ module.exports = function getDocsFromDir(dir, cateList) {
return id;
});
- (cateList || []).forEach(item => {
+ (cateList || []).forEach((item) => {
const { dir, subCategory, ...otherConfig } = item;
const indexList = glob.sync('index.md?(x)', {
cwd: path.join(baseDir, dir),
diff --git a/docs/scripts/sync-oss.js b/docs/scripts/sync-oss.js
new file mode 100644
index 0000000000..2d052dfa12
--- /dev/null
+++ b/docs/scripts/sync-oss.js
@@ -0,0 +1,47 @@
+#!/usr/bin/env node
+const http = require('http');
+const package = require('../package.json');
+const { version, name } = package;
+const options = {
+ method: 'PUT',
+ hostname: 'uipaas-node.alibaba-inc.com',
+ path: '/staticAssets/cdn/packages',
+ headers: {
+ 'Content-Type': 'application/json',
+ Cookie: 'locale=en-us',
+ },
+ maxRedirects: 20,
+};
+
+const onResponse = function (res) {
+ const chunks = [];
+ res.on('data', (chunk) => {
+ chunks.push(chunk);
+ });
+
+ res.on('end', () => {
+ const body = Buffer.concat(chunks);
+ console.table(JSON.stringify(JSON.parse(body.toString()), null, 2));
+ });
+
+ res.on('error', (error) => {
+ console.error(error);
+ });
+};
+
+const req = http.request(options, onResponse);
+
+const postData = JSON.stringify({
+ packages: [
+ {
+ packageName: name,
+ version,
+ },
+ ],
+ // 可以发布指定源的 npm 包,默认公网 npm
+ useTnpm: true,
+});
+
+req.write(postData);
+
+req.end();
diff --git a/lerna.json b/lerna.json
index 506c77a040..7fad993f66 100644
--- a/lerna.json
+++ b/lerna.json
@@ -1,6 +1,6 @@
{
"lerna": "4.0.0",
- "version": "1.0.17",
+ "version": "1.3.2",
"npmClient": "yarn",
"useWorkspaces": true,
"packages": [
diff --git a/modules/code-generator/README.md b/modules/code-generator/README.md
index 03ce94b54e..1d67b3aa16 100644
--- a/modules/code-generator/README.md
+++ b/modules/code-generator/README.md
@@ -94,16 +94,26 @@ await CodeGenerator.init();
4. 出码
```js
-const result = await CodeGenerator.generateCode({
+const project = await CodeGenerator.generateCode({
solution: 'icejs', // 出码方案 (目前内置有 icejs 和 rax )
schema, // 编排搭建出来的 schema
});
-console.log(result); // 出码结果(默认是递归结构描述的,可以传 flattenResult: true 以生成扁平结构的结果)
+console.log(project); // 出码结果(默认是递归结构描述的,可以传 flattenResult: true 以生成扁平结构的结果)
```
注:一般来说在浏览器中出码适合做即时预览功能。
+5. 下载 zip 包
+
+```js
+// 写入到 zip 包
+await CodeGenerator.publishers.zip().publish({
+ project, // 上一步生成的 project
+ projectSlug: 'your-project-slug', // 项目标识 -- 对应下载 your-project-slug.zip 文件
+});
+```
+
### 5)自定义出码
前端框架灵活多变,默认内置的出码方案很难满足所有人的需求,好在此代码生成器支持非常灵活的插件机制 -- 欢迎参考 ./src/plugins/xxx 来编写您自己的出码插件,然后参考 ./src/solutions/xxx 将各种插件组合成一套适合您的业务场景的出码方案。
diff --git a/modules/code-generator/babel.config.js b/modules/code-generator/babel.config.js
new file mode 100644
index 0000000000..c5986f2bc0
--- /dev/null
+++ b/modules/code-generator/babel.config.js
@@ -0,0 +1 @@
+module.exports = require('../../babel.config');
\ No newline at end of file
diff --git a/modules/code-generator/bin/lowcode-code-generator.js b/modules/code-generator/bin/lowcode-code-generator.js
index 6259a5a6fc..79c36c49a0 100755
--- a/modules/code-generator/bin/lowcode-code-generator.js
+++ b/modules/code-generator/bin/lowcode-code-generator.js
@@ -14,6 +14,7 @@ program
.option('-c, --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)
+ .option('--solution-options ', 'specify the solution options', '{}')
.arguments('[input-schema] ali lowcode schema JSON file')
.action(function doGenerate(inputSchema, command) {
var options = command.opts();
diff --git a/modules/code-generator/example-schema.json b/modules/code-generator/example-schema.json
index aaf3a3e0d8..5e31b1bea9 100644
--- a/modules/code-generator/example-schema.json
+++ b/modules/code-generator/example-schema.json
@@ -62,17 +62,17 @@
"router": "/"
},
"props": {
- "ref": "outterView",
+ "ref": "outerView",
"autoLoading": true
},
"fileName": "test",
"state": {
- "text": "outter"
+ "text": "outer"
},
"lifeCycles": {
"componentDidMount": {
- "type": "JSExpression",
- "value": "function() { console.log('componentDidMount'); }"
+ "type": "JSFunction",
+ "value": "function componentDidMount() { console.log('componentDidMount'); }"
}
},
"dataSource": {
@@ -91,7 +91,7 @@
"isSync": true
},
"dataHandler": {
- "type": "JSExpression",
+ "type": "JSFunction",
"value": "function (response) {\nif (!response.data.success){\n throw new Error(response.data.message);\n }\n return response.data.data;\n}"
}
},
@@ -105,13 +105,13 @@
"isSync": true
},
"dataHandler": {
- "type": "JSExpression",
+ "type": "JSFunction",
"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",
+ "type": "JSFunction",
"value": "function (dataMap) {\n console.info(\"All datasources loaded:\", dataMap);\n}"
}
},
diff --git a/modules/code-generator/example-schema.json5 b/modules/code-generator/example-schema.json5
index f71096fc41..4ab02999ec 100644
--- a/modules/code-generator/example-schema.json5
+++ b/modules/code-generator/example-schema.json5
@@ -62,17 +62,17 @@
router: '/',
},
props: {
- ref: 'outterView',
+ ref: 'outerView',
autoLoading: true,
},
fileName: 'test',
state: {
- text: 'outter',
+ text: 'outer',
},
lifeCycles: {
componentDidMount: {
- type: 'JSExpression',
- value: "function() { console.log('componentDidMount'); }",
+ type: 'JSFunction',
+ value: "function componentDidMount() { console.log('componentDidMount'); }",
},
},
dataSource: {
@@ -91,7 +91,7 @@
isSync: true,
},
dataHandler: {
- type: 'JSExpression',
+ type: 'JSFunction',
value: 'function (response) {\nif (!response.data.success){\n throw new Error(response.data.message);\n }\n return response.data.data;\n}',
},
},
@@ -105,13 +105,13 @@
isSync: true,
},
dataHandler: {
- type: 'JSExpression',
+ type: 'JSFunction',
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',
+ type: 'JSFunction',
value: 'function (dataMap) {\n console.info("All datasources loaded:", dataMap);\n}',
},
},
diff --git a/modules/code-generator/package.json b/modules/code-generator/package.json
index 3e7898e390..cd114fa067 100644
--- a/modules/code-generator/package.json
+++ b/modules/code-generator/package.json
@@ -1,6 +1,6 @@
{
"name": "@alilc/lowcode-code-generator",
- "version": "1.0.7-beta.3",
+ "version": "1.1.7",
"description": "出码引擎 for LowCode Engine",
"license": "MIT",
"main": "lib/index.js",
@@ -80,6 +80,7 @@
"change-case": "^3.1.0",
"commander": "^6.1.0",
"debug": "^4.3.2",
+ "file-saver": "^2.0.5",
"fp-ts": "^2.11.9",
"fs-extra": "9.x",
"glob": "^7.2.0",
@@ -98,6 +99,7 @@
"qs": "^6.10.1",
"semver": "^7.3.4",
"short-uuid": "^3.1.1",
+ "babel-jest": "^26.5.2",
"tslib": "^2.3.1"
},
"browser": {
@@ -108,6 +110,7 @@
"devDependencies": {
"@iceworks/spec": "^1.4.2",
"@types/babel__traverse": "^7.11.0",
+ "@types/file-saver": "^2.0.7",
"@types/jest": "^27.0.2",
"@types/lodash": "^4.14.162",
"@types/node": "^14.14.20",
@@ -125,11 +128,11 @@
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-react": "^7.22.0",
"eslint-plugin-react-hooks": "^4.2.0",
- "jest": "^27.4.7",
+ "jest": "^26.5.2",
"jest-util": "^27.4.2",
"rimraf": "^3.0.2",
"standard-version": "^9.1.1",
- "ts-jest": "^27.1.3",
+ "ts-jest": "^26.5.2",
"ts-loader": "^6.2.2",
"ts-node": "^8.10.2",
"tsconfig-paths": "^3.9.0",
@@ -142,5 +145,11 @@
"publishConfig": {
"access": "public",
"registry": "https://registry.npmjs.org/"
- }
+ },
+ "repository": {
+ "type": "http",
+ "url": "https://github.com/alibaba/lowcode-engine/tree/main/modules/code-generator"
+ },
+ "bugs": "https://github.com/alibaba/lowcode-engine/issues",
+ "homepage": "https://github.com/alibaba/lowcode-engine/#readme"
}
diff --git a/modules/code-generator/src/analyzer/componentAnalyzer.ts b/modules/code-generator/src/analyzer/componentAnalyzer.ts
index 952295a15f..69e8ad482d 100644
--- a/modules/code-generator/src/analyzer/componentAnalyzer.ts
+++ b/modules/code-generator/src/analyzer/componentAnalyzer.ts
@@ -1,13 +1,13 @@
-import type { NodeSchema, CompositeObject } from '@alilc/lowcode-types';
+import type { IPublicTypeNodeSchema, IPublicTypeCompositeObject } 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) => {
+ const nodeValidator = (n: IPublicTypeNodeSchema) => {
if (n.props) {
- const props = n.props as CompositeObject;
+ const props = n.props as IPublicTypeCompositeObject;
if (props.ref) {
hasRefAttr = true;
}
diff --git a/modules/code-generator/src/cli/run.ts b/modules/code-generator/src/cli/run.ts
index db66b46013..ec6814f767 100644
--- a/modules/code-generator/src/cli/run.ts
+++ b/modules/code-generator/src/cli/run.ts
@@ -9,7 +9,7 @@ import * as path from 'path';
import { getErrorMessage } from '../utils/errors';
import CodeGenerator from '..';
import type { IProjectBuilder } from '..';
-import type { ProjectSchema } from '@alilc/lowcode-types';
+import type { IPublicTypeProjectSchema } from '@alilc/lowcode-types';
/**
* 执行出码 CLI 命令
@@ -25,6 +25,7 @@ export async function run(
output?: string;
quiet?: boolean;
verbose?: boolean;
+ solutionOptions?: string;
},
): Promise {
try {
@@ -41,6 +42,19 @@ export async function run(
);
}
+ let solutionOptions = {};
+
+ if (options.solutionOptions) {
+ try {
+ solutionOptions = JSON.parse(options.solutionOptions);
+ } catch (err: any) {
+ throw new Error(
+ `solution options parse error, error message is "${err.message}"`,
+ );
+ }
+ }
+
+
// 读取 Schema
const schema = await loadSchemaFile(schemaFile);
@@ -48,7 +62,7 @@ export async function run(
const createProjectBuilder = await getProjectBuilderFactory(options.solution, {
quiet: options.quiet,
});
- const builder = createProjectBuilder();
+ const builder = createProjectBuilder(solutionOptions);
// 生成代码
const generatedSourceCodes = await builder.generateProject(schema);
@@ -75,7 +89,7 @@ export async function run(
async function getProjectBuilderFactory(
solution: string,
{ quiet }: { quiet?: boolean },
-): Promise<() => IProjectBuilder> {
+): Promise<(options: {[prop: string]: any}) => IProjectBuilder> {
if (solution in CodeGenerator.solutions) {
return CodeGenerator.solutions[solution as 'icejs' | 'rax'];
}
@@ -117,7 +131,7 @@ function isLocalSolution(solution: string) {
return solution.startsWith('.') || solution.startsWith('/') || solution.startsWith('~');
}
-async function loadSchemaFile(schemaFile: string): Promise {
+async function loadSchemaFile(schemaFile: string): Promise {
if (!schemaFile) {
throw new Error('invalid schema file name');
}
diff --git a/modules/code-generator/src/cli/solutions/example-solution.ts b/modules/code-generator/src/cli/solutions/example-solution.ts
index 2efff780cd..bfb9d079b1 100644
--- a/modules/code-generator/src/cli/solutions/example-solution.ts
+++ b/modules/code-generator/src/cli/solutions/example-solution.ts
@@ -289,8 +289,8 @@ codealike.json
},
"lifeCycles": {
"componentDidMount": {
- "type": "JSExpression",
- "value": "function() { console.log('componentDidMount'); }"
+ "type": "JSFunction",
+ "value": "function componentDidMount() {\\n console.log('componentDidMount');\\n}"
}
},
"methodsModule": {
@@ -559,8 +559,8 @@ codealike.json
"registry": "https://registry.npm.xxx.com"
},
"dependencies": {
- "@alilc/lowcode-code-generator": "^1.0.0-beta.16",
- "@alilc/lowcode-types": "^1.0.0-beta.21",
+ "@alilc/lowcode-code-generator": "^1.0.0",
+ "@alilc/lowcode-types": "^1.0.0",
"tslib": "^2.3.0"
},
"devDependencies": {
@@ -635,18 +635,20 @@ export default function createHelloWorldProjectBuilder() {
template: CodeGen.solutionParts.icejs.template,
plugins: {
components: [
- CodeGen.plugins.react.reactCommonDeps(),
- CodeGen.plugins.common.esmodule({ fileType: 'jsx' }),
- CodeGen.plugins.react.containerClass(),
- CodeGen.plugins.react.containerInjectContext(),
- CodeGen.plugins.react.containerInjectUtils(),
- CodeGen.plugins.react.containerInjectDataSourceEngine(),
- CodeGen.plugins.react.containerInjectI18n(),
- CodeGen.plugins.react.containerInitState(),
- CodeGen.plugins.react.containerLifeCycle(),
- CodeGen.plugins.react.containerMethod(),
+ CodeGen.plugins.icejs.reactCommonDeps(),
+ CodeGen.plugins.common.esModule({ fileType: 'jsx' }),
+ CodeGen.plugins.common.styleImport(),
+ CodeGen.plugins.icejs.containerClass(),
+ CodeGen.plugins.icejs.containerInjectContext(),
+ CodeGen.plugins.icejs.containerInjectUtils(),
+ CodeGen.plugins.icejs.containerInjectDataSourceEngine(),
+ CodeGen.plugins.icejs.containerInjectI18n(),
+ CodeGen.plugins.icejs.containerInjectConstants(),
+ CodeGen.plugins.icejs.containerInitState(),
+ CodeGen.plugins.icejs.containerLifeCycle(),
+ CodeGen.plugins.icejs.containerMethod(),
examplePlugin(),
- CodeGen.plugins.react.jsx({
+ CodeGen.plugins.icejs.jsx({
nodeTypeMapping: {
Div: 'div',
Component: 'div',
@@ -657,18 +659,20 @@ export default function createHelloWorldProjectBuilder() {
CodeGen.plugins.style.css(),
],
pages: [
- CodeGen.plugins.react.reactCommonDeps(),
- CodeGen.plugins.common.esmodule({ fileType: 'jsx' }),
- CodeGen.plugins.react.containerClass(),
- CodeGen.plugins.react.containerInjectContext(),
- CodeGen.plugins.react.containerInjectUtils(),
- CodeGen.plugins.react.containerInjectDataSourceEngine(),
- CodeGen.plugins.react.containerInjectI18n(),
- CodeGen.plugins.react.containerInitState(),
- CodeGen.plugins.react.containerLifeCycle(),
- CodeGen.plugins.react.containerMethod(),
+ CodeGen.plugins.icejs.reactCommonDeps(),
+ CodeGen.plugins.common.esModule({ fileType: 'jsx' }),
+ CodeGen.plugins.common.styleImport(),
+ CodeGen.plugins.icejs.containerClass(),
+ CodeGen.plugins.icejs.containerInjectContext(),
+ CodeGen.plugins.icejs.containerInjectUtils(),
+ CodeGen.plugins.icejs.containerInjectDataSourceEngine(),
+ CodeGen.plugins.icejs.containerInjectI18n(),
+ CodeGen.plugins.icejs.containerInjectConstants(),
+ CodeGen.plugins.icejs.containerInitState(),
+ CodeGen.plugins.icejs.containerLifeCycle(),
+ CodeGen.plugins.icejs.containerMethod(),
examplePlugin(),
- CodeGen.plugins.react.jsx({
+ CodeGen.plugins.icejs.jsx({
nodeTypeMapping: {
Div: 'div',
Component: 'div',
@@ -679,13 +683,13 @@ export default function createHelloWorldProjectBuilder() {
CodeGen.plugins.style.css(),
],
router: [
- CodeGen.plugins.common.esmodule(),
+ 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.common.esModule(),
CodeGen.plugins.project.utils('react'),
],
i18n: [CodeGen.plugins.project.i18n()],
diff --git a/modules/code-generator/src/const/index.ts b/modules/code-generator/src/const/index.ts
index 103d912db6..24449ca429 100644
--- a/modules/code-generator/src/const/index.ts
+++ b/modules/code-generator/src/const/index.ts
@@ -8,5 +8,26 @@ export const CONTAINER_TYPE = {
export const SUPPORT_SCHEMA_VERSION_LIST = ['0.0.1', '1.0.0'];
+// built-in slot names which have been handled in ProjectBuilder
+export const BUILTIN_SLOT_NAMES = [
+ 'pages',
+ 'components',
+ 'router',
+ 'entry',
+ 'appConfig',
+ 'buildConfig',
+ 'constants',
+ 'utils',
+ 'i18n',
+ 'globalStyle',
+ 'htmlEntry',
+ 'packageJSON',
+ 'demo',
+];
+
+export const isBuiltinSlotName = function (name: string) {
+ return BUILTIN_SLOT_NAMES.includes(name);
+};
+
export * from './file';
export * from './generator';
diff --git a/modules/code-generator/src/generator/ModuleBuilder.ts b/modules/code-generator/src/generator/ModuleBuilder.ts
index f4554ef612..e172f716e5 100644
--- a/modules/code-generator/src/generator/ModuleBuilder.ts
+++ b/modules/code-generator/src/generator/ModuleBuilder.ts
@@ -1,4 +1,4 @@
-import { ProjectSchema, ResultFile, ResultDir } from '@alilc/lowcode-types';
+import { IPublicTypeProjectSchema, ResultFile, ResultDir } from '@alilc/lowcode-types';
import {
BuilderComponentPlugin,
@@ -62,10 +62,9 @@ export function createModuleBuilder(
if (options.postProcessors.length > 0) {
files = files.map((file) => {
- let { content } = file;
- const type = file.ext;
+ let { content, ext: type, name } = file;
options.postProcessors.forEach((processer) => {
- content = processer(content, type);
+ content = processer(content, type, name);
});
return createResultFile(file.name, type, content);
@@ -77,7 +76,7 @@ export function createModuleBuilder(
};
};
- const generateModuleCode = async (schema: ProjectSchema | string): Promise => {
+ const generateModuleCode = async (schema: IPublicTypeProjectSchema | string): Promise => {
// Init
const schemaParser: ISchemaParser = new SchemaParser();
const parseResult: IParseResult = schemaParser.parse(schema);
diff --git a/modules/code-generator/src/generator/ProjectBuilder.ts b/modules/code-generator/src/generator/ProjectBuilder.ts
index 2a1282d6eb..a910b49002 100644
--- a/modules/code-generator/src/generator/ProjectBuilder.ts
+++ b/modules/code-generator/src/generator/ProjectBuilder.ts
@@ -1,4 +1,4 @@
-import { ResultDir, ResultFile, ProjectSchema } from '@alilc/lowcode-types';
+import { ResultDir, ResultFile, IPublicTypeProjectSchema } from '@alilc/lowcode-types';
import {
IModuleBuilder,
@@ -16,6 +16,7 @@ import { createResultDir, addDirectory, addFile } from '../utils/resultHelper';
import { createModuleBuilder } from './ModuleBuilder';
import { ProjectPreProcessor, ProjectPostProcessor, IContextData } from '../types/core';
import { CodeGeneratorError } from '../types/error';
+import { isBuiltinSlotName } from '../const';
interface IModuleInfo {
moduleName?: string;
@@ -24,22 +25,36 @@ interface IModuleInfo {
}
export interface ProjectBuilderInitOptions {
+
/** 项目模板 */
template: IProjectTemplate;
+
/** 项目插件 */
plugins: IProjectPlugins;
+
/** 模块后置处理器 */
postProcessors: PostProcessor[];
+
/** Schema 解析器 */
schemaParser?: ISchemaParser;
+
/** 项目级别的前置处理器 */
projectPreProcessors?: ProjectPreProcessor[];
+
/** 项目级别的后置处理器 */
projectPostProcessors?: ProjectPostProcessor[];
+
/** 是否处于严格模式 */
inStrictMode?: boolean;
+
/** 一些额外的上下文数据 */
extraContextData?: Record;
+
+ /**
+ * Hook which is used to customize original options, we can reorder/add/remove plugins/processors
+ * of the existing solution.
+ */
+ customizeBuilderOptions?(originalOptions: ProjectBuilderInitOptions): ProjectBuilderInitOptions;
}
export class ProjectBuilder implements IProjectBuilder {
@@ -62,21 +77,26 @@ export class ProjectBuilder implements IProjectBuilder {
private projectPostProcessors: ProjectPostProcessor[];
/** 是否处于严格模式 */
- public readonly inStrictMode: boolean;
+ readonly inStrictMode: boolean;
/** 一些额外的上下文数据 */
- public readonly extraContextData: IContextData;
-
- constructor({
- template,
- plugins,
- postProcessors,
- schemaParser = new SchemaParser(),
- projectPreProcessors = [],
- projectPostProcessors = [],
- inStrictMode = false,
- extraContextData = {},
- }: ProjectBuilderInitOptions) {
+ readonly extraContextData: IContextData;
+
+ constructor(builderOptions: ProjectBuilderInitOptions) {
+ let customBuilderOptions = builderOptions;
+ if (typeof builderOptions.customizeBuilderOptions === 'function') {
+ customBuilderOptions = builderOptions.customizeBuilderOptions(builderOptions);
+ }
+ const {
+ template,
+ plugins,
+ postProcessors,
+ schemaParser = new SchemaParser(),
+ projectPreProcessors = [],
+ projectPostProcessors = [],
+ inStrictMode = false,
+ extraContextData = {},
+ } = customBuilderOptions;
this.template = template;
this.plugins = plugins;
this.postProcessors = postProcessors;
@@ -87,34 +107,39 @@ export class ProjectBuilder implements IProjectBuilder {
this.extraContextData = extraContextData;
}
- async generateProject(originalSchema: ProjectSchema | string): Promise {
+ async generateProject(originalSchema: IPublicTypeProjectSchema | string): Promise {
// Init
const { schemaParser } = this;
- const builders = this.createModuleBuilders();
-
- const projectRoot = await this.template.generateTemplate();
- let schema: ProjectSchema =
+ let schema: IPublicTypeProjectSchema =
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);
}
+ // Validate
+ if (!schemaParser.validate(schema)) {
+ throw new CodeGeneratorError('Schema is invalid');
+ }
+
// Collect Deps
// Parse JSExpression
const parseResult: IParseResult = schemaParser.parse(schema);
+
+ const projectRoot = await this.template.generateTemplate(parseResult);
+
let buildResult: IModuleInfo[] = [];
+ const builders = this.createModuleBuilders({
+ extraContextData: {
+ projectRemark: parseResult?.project?.projectRemark,
+ template: this.template,
+ },
+ });
// Generator Code module
// components
// pages
@@ -241,14 +266,25 @@ export class ProjectBuilder implements IProjectBuilder {
});
}
- // TODO: 更多 slots 的处理??是不是可以考虑把 template 中所有的 slots 都处理下?
+ // demo
+ if (parseResult.project && builders.demo) {
+ const { files } = await builders.demo.generateModule(parseResult.project);
+ buildResult.push({
+ path: this.template.slots.demo.path,
+ files,
+ });
+ }
- // Post Process
+ // handle extra slots
+ await this.generateExtraSlots(builders, parseResult, buildResult);
+ // Post Process
+ const isSingleComponent = parseResult?.project?.projectRemark?.isSingleComponent;
// Combine Modules
buildResult.forEach((moduleInfo) => {
let targetDir = getDirFromRoot(projectRoot, moduleInfo.path);
- if (moduleInfo.moduleName) {
+ // if project only contain single component, skip creation of directory.
+ if (moduleInfo.moduleName && !isSingleComponent) {
const dir = createResultDir(moduleInfo.moduleName);
addDirectory(targetDir, dir);
targetDir = dir;
@@ -260,13 +296,17 @@ export class ProjectBuilder implements IProjectBuilder {
let finalResult = projectRoot;
for (const projectPostProcessor of this.projectPostProcessors) {
// eslint-disable-next-line no-await-in-loop
- finalResult = await projectPostProcessor(finalResult, schema, originalSchema);
+ finalResult = await projectPostProcessor(finalResult, schema, originalSchema, {
+ template: this.template,
+ parseResult,
+ });
}
return finalResult;
}
- private createModuleBuilders(): Record {
+ private createModuleBuilders(extraContextData: Record = {}):
+ Record {
const builders: Record = {};
Object.keys(this.plugins).forEach((pluginName) => {
@@ -279,10 +319,12 @@ export class ProjectBuilder implements IProjectBuilder {
plugins: this.plugins[pluginName],
postProcessors: this.postProcessors,
contextData: {
+ // template: this.template,
inStrictMode: this.inStrictMode,
tolerateEvalErrors: true,
evalErrorsHandler: '',
...this.extraContextData,
+ ...extraContextData,
},
...options,
});
@@ -291,6 +333,22 @@ export class ProjectBuilder implements IProjectBuilder {
return builders;
}
+
+ private async generateExtraSlots(
+ builders: Record,
+ parseResult: IParseResult,
+ buildResult: IModuleInfo[],
+ ) {
+ for (const slotName in this.template.slots) {
+ if (!isBuiltinSlotName(slotName)) {
+ const { files } = await builders[slotName].generateModule(parseResult);
+ buildResult.push({
+ path: this.template.slots[slotName].path,
+ files,
+ });
+ }
+ }
+ }
}
export function createProjectBuilder(initOptions: ProjectBuilderInitOptions): IProjectBuilder {
diff --git a/modules/code-generator/src/index.ts b/modules/code-generator/src/index.ts
index 7cf861e6f8..da9b12d877 100644
--- a/modules/code-generator/src/index.ts
+++ b/modules/code-generator/src/index.ts
@@ -1,6 +1,6 @@
/**
* 低代码引擎的出码模块,负责将编排产出的 Schema 转换成实际可执行的代码。
- * 注意:为了保持 API 的稳定性, 这里所有导出的 API 均要显式命名方式导出
+ * 注意:为了保持 API 的稳定性,这里所有导出的 API 均要显式命名方式导出
* (即用 export { xxx } from 'xx' 的方式,不要直接 export * from 'xxx')
* 而且所有导出的 API 务必在 tests/public 中编写单元测试
*/
@@ -8,7 +8,8 @@ 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 createIceJsProjectBuilder, { plugins as icejsPlugins } from './solutions/icejs';
+import createIceJs3ProjectBuilder, { plugins as icejs3Plugins } from './solutions/icejs3';
import createRaxAppProjectBuilder, { plugins as raxPlugins } from './solutions/rax-app';
// 引入说明
@@ -18,6 +19,7 @@ import { COMMON_CHUNK_NAME, CLASS_DEFINE_CHUNK_NAME, DEFAULT_LINK_AFTER } from '
// 引入通用插件组
import esmodule from './plugins/common/esmodule';
import requireUtils from './plugins/common/requireUtils';
+import styleImport from './plugins/common/styleImport';
import css from './plugins/component/style/css';
import constants from './plugins/project/constants';
@@ -32,6 +34,7 @@ import * as CONSTANTS from './const';
// 引入内置解决方案模块
import icejs from './plugins/project/framework/icejs';
+import icejs3 from './plugins/project/framework/icejs3';
import rax from './plugins/project/framework/rax';
export default {
@@ -39,10 +42,12 @@ export default {
createModuleBuilder,
solutions: {
icejs: createIceJsProjectBuilder,
+ icejs3: createIceJs3ProjectBuilder,
rax: createRaxAppProjectBuilder,
},
solutionParts: {
icejs,
+ icejs3,
rax,
},
publishers: {
@@ -51,6 +56,7 @@ export default {
},
plugins: {
common: {
+
/**
* 处理 ES Module
* @deprecated please use esModule
@@ -58,12 +64,7 @@ export default {
esmodule,
esModule: esmodule,
requireUtils,
- },
- react: {
- ...reactPlugins,
- },
- rax: {
- ...raxPlugins,
+ styleImport,
},
style: {
css,
@@ -73,6 +74,22 @@ export default {
i18n,
utils,
},
+ icejs: {
+ ...icejsPlugins,
+ },
+ icejs3: {
+ ...icejs3Plugins,
+ },
+ rax: {
+ ...raxPlugins,
+ },
+
+ /**
+ * @deprecated please use icejs
+ */
+ react: {
+ ...icejsPlugins,
+ },
},
postprocessor: {
prettier,
diff --git a/modules/code-generator/src/parser/SchemaParser.ts b/modules/code-generator/src/parser/SchemaParser.ts
index 2e526d88bc..b4f7424ce5 100644
--- a/modules/code-generator/src/parser/SchemaParser.ts
+++ b/modules/code-generator/src/parser/SchemaParser.ts
@@ -4,14 +4,14 @@
*/
import changeCase from 'change-case';
import {
- UtilItem,
- NodeDataType,
- NodeSchema,
- ContainerSchema,
- ProjectSchema,
- PropsMap,
- NodeData,
- NpmInfo,
+ IPublicTypeUtilItem,
+ IPublicTypeNodeDataType,
+ IPublicTypeNodeSchema,
+ IPublicTypeContainerSchema,
+ IPublicTypeProjectSchema,
+ IPublicTypePropsMap,
+ IPublicTypeNodeData,
+ IPublicTypeNpmInfo,
} from '@alilc/lowcode-types';
import {
IPageMeta,
@@ -32,10 +32,11 @@ import {
import { SUPPORT_SCHEMA_VERSION_LIST } from '../const';
import { getErrorMessage } from '../utils/errors';
-import { handleSubNodes } from '../utils/schema';
+import { handleSubNodes, isValidContainerType, ContainerType } from '../utils/schema';
import { uniqueArray } from '../utils/common';
import { componentAnalyzer } from '../analyzer/componentAnalyzer';
import { ensureValidClassName } from '../utils/validate';
+import type { ProjectRemark } from '../types/intermediate';
const defaultContainer: IContainerInfo = {
containerType: 'Component',
@@ -71,18 +72,18 @@ function getRootComponentName(typeName: string, maps: Record, depName: string) {
+ const dep = internalDeps[depName];
+ return (dep && dep.type !== InternalDependencyType.PAGE) ? dep : null;
+}
+
export class SchemaParser implements ISchemaParser {
- validate(schema: ProjectSchema): boolean {
+ validate(schema: IPublicTypeProjectSchema): boolean {
if (SUPPORT_SCHEMA_VERSION_LIST.indexOf(schema.version) < 0) {
throw new CompatibilityError(`Not support schema with version [${schema.version}]`);
}
@@ -114,7 +120,7 @@ export class SchemaParser implements ISchemaParser {
return true;
}
- parse(schemaSrc: ProjectSchema | string): IParseResult {
+ parse(schemaSrc: IPublicTypeProjectSchema | string): IParseResult {
// TODO: collect utils depends in JSExpression
const compDeps: Record = {};
const internalDeps: Record = {};
@@ -139,9 +145,9 @@ export class SchemaParser implements ISchemaParser {
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;
+ const firstRoot: IPublicTypeContainerSchema = schema.componentsTree[0] as IPublicTypeContainerSchema;
- if (!('fileName' in firstRoot) || !firstRoot.fileName) {
+ if (!firstRoot.fileName && !isValidContainerType(firstRoot)) {
// 整个 schema 描述一个容器,且无根节点定义
const container: IContainerInfo = {
...firstRoot,
@@ -149,18 +155,19 @@ export class SchemaParser implements ISchemaParser {
props: firstRoot.props || defaultContainer.props,
css: firstRoot.css || defaultContainer.css,
moduleName: (firstRoot as IContainerInfo).moduleName || defaultContainer.moduleName,
- children: schema.componentsTree as NodeSchema[],
+ children: schema.componentsTree as IPublicTypeNodeSchema[],
};
containers = [container];
} else {
// 普通带 1 到多个容器的 schema
containers = schema.componentsTree.map((n) => {
- const subRoot = n as ContainerSchema;
+ const subRoot = n as IPublicTypeContainerSchema;
const container: IContainerInfo = {
...subRoot,
componentName: getRootComponentName(subRoot.componentName, compDeps),
containerType: subRoot.componentName,
- moduleName: ensureValidClassName(changeCase.pascalCase(subRoot.fileName)),
+ moduleName: ensureValidClassName(subRoot.componentName === ContainerType.Component ?
+ subRoot.fileName : changeCase.pascalCase(subRoot.fileName)),
};
return container;
});
@@ -172,7 +179,7 @@ export class SchemaParser implements ISchemaParser {
// 分析引用能力的依赖
containers = containers.map((con) => ({
...con,
- analyzeResult: componentAnalyzer(con as ContainerSchema),
+ analyzeResult: componentAnalyzer(con as IPublicTypeContainerSchema),
}));
// 建立所有容器的内部依赖索引
@@ -210,7 +217,7 @@ export class SchemaParser implements ISchemaParser {
handleSubNodes(
container.children,
{
- node: (i: NodeSchema) => processChildren(i),
+ node: (i: IPublicTypeNodeSchema) => processChildren(i),
},
{
rerun: true,
@@ -219,12 +226,11 @@ export class SchemaParser implements ISchemaParser {
}
});
- // 分析容器内部组件依赖
containers.forEach((container) => {
const depNames = this.getComponentNames(container);
// eslint-disable-next-line no-param-reassign
container.deps = uniqueArray(depNames, (i: string) => i)
- .map((depName) => internalDeps[depName] || compDeps[depName])
+ .map((depName) => getInternalDep(internalDeps, depName) || compDeps[depName])
.filter(Boolean);
// container.deps = Object.keys(compDeps).map((depName) => compDeps[depName]);
});
@@ -254,13 +260,12 @@ export class SchemaParser implements ISchemaParser {
.filter((dep) => !!dep);
// 分析 Utils 依赖
- let utils: UtilItem[];
+ let utils: IPublicTypeUtilItem[];
if (schema.utils) {
utils = schema.utils;
utilsDeps = schema.utils
.filter(
- (u): u is { name: string; type: 'npm' | 'tnpm'; content: NpmInfo } =>
- u.type !== 'function',
+ (u): u is { name: string; type: 'npm' | 'tnpm'; content: IPublicTypeNpmInfo } => u.type !== 'function',
)
.map(
(u): IExternalDependency => ({
@@ -320,15 +325,22 @@ export class SchemaParser implements ISchemaParser {
utilsDeps,
packages: npms || [],
dataSourcesTypes: this.collectDataSourcesTypes(schema),
+ projectRemark: this.getProjectRemark(containers),
},
};
}
- getComponentNames(children: NodeDataType): string[] {
+ getProjectRemark(containers: IContainerInfo[]): ProjectRemark {
+ return {
+ isSingleComponent: containers.length === 1 && containers[0].containerType === 'Component',
+ };
+ }
+
+ getComponentNames(children: IPublicTypeNodeDataType): string[] {
return handleSubNodes(
children,
{
- node: (i: NodeSchema) => i.componentName,
+ node: (i: IPublicTypeNodeSchema) => i.componentName,
},
{
rerun: true,
@@ -336,8 +348,8 @@ export class SchemaParser implements ISchemaParser {
);
}
- decodeSchema(schemaSrc: string | ProjectSchema): ProjectSchema {
- let schema: ProjectSchema;
+ decodeSchema(schemaSrc: string | IPublicTypeProjectSchema): IPublicTypeProjectSchema {
+ let schema: IPublicTypeProjectSchema;
if (typeof schemaSrc === 'string') {
try {
schema = JSON.parse(schemaSrc);
@@ -352,7 +364,7 @@ export class SchemaParser implements ISchemaParser {
return schema;
}
- private collectDataSourcesTypes(schema: ProjectSchema): string[] {
+ private collectDataSourcesTypes(schema: IPublicTypeProjectSchema): string[] {
const dataSourcesTypes = new Set();
// 数据源的默认类型为 fetch
diff --git a/modules/code-generator/src/plugins/common/esmodule.ts b/modules/code-generator/src/plugins/common/esmodule.ts
index 04dfd1ce21..53f3f9418b 100644
--- a/modules/code-generator/src/plugins/common/esmodule.ts
+++ b/modules/code-generator/src/plugins/common/esmodule.ts
@@ -16,7 +16,7 @@ import {
IWithDependency,
} from '../../types';
-import { isValidIdentifier, isValidComponentName } from '../../utils/validate';
+import { isValidIdentifier } from '../../utils/validate';
// TODO: main 这个信息到底怎么用,是不是外部包不需要使用?
const DEP_MAIN_BLOCKLIST = ['lib', 'lib/index', 'es', 'es/index', 'main'];
@@ -261,7 +261,7 @@ function buildPackageImport(
if (!isValidIdentifier(name)) {
throw new CodeGeneratorError(`Invalid Identifier [${name}]`);
}
- if (info.nodeIdentifier && !isValidComponentName(info.nodeIdentifier)) {
+ if (info.nodeIdentifier && !isValidIdentifier(info.nodeIdentifier)) {
throw new CodeGeneratorError(`Invalid Identifier [${info.nodeIdentifier}]`);
}
});
@@ -443,6 +443,7 @@ function buildPackageImport(
export interface PluginConfig {
fileType?: string; // 导出的文件类型
useAliasName?: boolean; // 是否使用 componentName 重命名组件 identifier
+ filter?: (deps: IDependency[]) => IDependency[]; // 支持过滤能力
}
const pluginFactory: BuilderComponentPluginFactory = (config?: PluginConfig) => {
@@ -460,7 +461,8 @@ const pluginFactory: BuilderComponentPluginFactory = (config?: Plu
const ir = next.ir as IWithDependency;
if (ir && ir.deps && ir.deps.length > 0) {
- const packs = groupDepsByPack(ir.deps);
+ const deps = cfg.filter ? cfg.filter(ir.deps) : ir.deps;
+ const packs = groupDepsByPack(deps);
Object.keys(packs).forEach((pkg) => {
const chunks = buildPackageImport(pkg, packs[pkg], cfg.fileType, cfg.useAliasName);
diff --git a/modules/code-generator/src/plugins/common/styleImport.ts b/modules/code-generator/src/plugins/common/styleImport.ts
new file mode 100644
index 0000000000..23e1293025
--- /dev/null
+++ b/modules/code-generator/src/plugins/common/styleImport.ts
@@ -0,0 +1,64 @@
+import changeCase from 'change-case';
+import {
+ FileType,
+ BuilderComponentPluginFactory,
+ BuilderComponentPlugin,
+ ICodeStruct,
+ IWithDependency,
+ ChunkType,
+} from '../../types';
+
+import { COMMON_CHUNK_NAME } from '../../const/generator';
+
+const pluginFactory: BuilderComponentPluginFactory = () => {
+ const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
+ const next: ICodeStruct = {
+ ...pre,
+ };
+
+ const ir = next.ir as IWithDependency;
+ const { chunks } = next;
+
+ if (ir && ir.deps && ir.deps.length > 0) {
+ let lowcodeMaterialsStyleAdded = false;
+ let fusionUIStyleAdded = false;
+ let nextStyleAddedMap: Record = {};
+ ir.deps.forEach((dep: any) => {
+ if (dep.package === '@alifd/next' && !nextStyleAddedMap[dep.exportName]) {
+ chunks.push({
+ type: ChunkType.STRING,
+ fileType: FileType.JSX,
+ name: COMMON_CHUNK_NAME.InternalDepsImport,
+ content: `import '@alifd/next/lib/${changeCase.paramCase(dep.exportName)}/style';`,
+ linkAfter: [COMMON_CHUNK_NAME.ExternalDepsImport],
+ });
+ nextStyleAddedMap[dep.exportName] = true;
+ } else if (dep.package === '@alilc/lowcode-materials' && !lowcodeMaterialsStyleAdded) {
+ chunks.push({
+ type: ChunkType.STRING,
+ fileType: FileType.JSX,
+ name: COMMON_CHUNK_NAME.InternalDepsImport,
+ content: 'import \'@alilc/lowcode-materials/lib/style\';',
+ linkAfter: [COMMON_CHUNK_NAME.ExternalDepsImport],
+ });
+ lowcodeMaterialsStyleAdded = true;
+ } else if (dep.package === '@alifd/fusion-ui' && !fusionUIStyleAdded) {
+ chunks.push({
+ type: ChunkType.STRING,
+ fileType: FileType.JSX,
+ name: COMMON_CHUNK_NAME.InternalDepsImport,
+ content: 'import \'@alifd/fusion-ui/lib/style\';',
+ linkAfter: [COMMON_CHUNK_NAME.ExternalDepsImport],
+ });
+ fusionUIStyleAdded = true;
+ }
+ });
+ }
+
+ return next;
+ };
+
+ return plugin;
+};
+
+export default pluginFactory;
diff --git a/modules/code-generator/src/plugins/component/rax/containerInjectDataSourceEngine.ts b/modules/code-generator/src/plugins/component/rax/containerInjectDataSourceEngine.ts
index 446b1d3f89..cb7a9e8c36 100644
--- a/modules/code-generator/src/plugins/component/rax/containerInjectDataSourceEngine.ts
+++ b/modules/code-generator/src/plugins/component/rax/containerInjectDataSourceEngine.ts
@@ -1,8 +1,8 @@
/* eslint-disable @typescript-eslint/indent */
import {
- CompositeValue,
- JSExpression,
+ IPublicTypeCompositeValue,
+ IPublicTypeJSExpression,
InterpretDataSourceConfig,
isJSExpression,
isJSFunction,
@@ -56,7 +56,7 @@ const pluginFactory: BuilderComponentPluginFactory = (config?) =>
(dataSourceConfig && dataSourceConfig.list) || [];
const dataSourceEngineOptions = { runtimeConfig: true };
if (dataSourceItems.length > 0) {
- const requestHandlersMap: Record = {};
+ const requestHandlersMap: Record = {};
dataSourceItems.forEach((ds) => {
const dsType = ds.type || 'fetch';
@@ -178,7 +178,7 @@ _defineDataSourceConfig() {
export default pluginFactory;
-function wrapAsFunction(value: CompositeValue, scope: IScope): CompositeValue {
+function wrapAsFunction(value: IPublicTypeCompositeValue, scope: IScope): IPublicTypeCompositeValue {
if (isJSExpression(value) || isJSFunction(value)) {
return {
type: 'JSExpression',
diff --git a/modules/code-generator/src/plugins/component/rax/containerLifeCycle.ts b/modules/code-generator/src/plugins/component/rax/containerLifeCycle.ts
index f928c3995e..bbee6367f2 100644
--- a/modules/code-generator/src/plugins/component/rax/containerLifeCycle.ts
+++ b/modules/code-generator/src/plugins/component/rax/containerLifeCycle.ts
@@ -13,6 +13,7 @@ import {
IContainerInfo,
} from '../../../types';
import { debug } from '../../../utils/debug';
+import { isJSExpressionFn } from '../../../utils/common';
export interface PluginConfig {
fileType: string;
@@ -49,6 +50,7 @@ const pluginFactory: BuilderComponentPluginFactory = (config?) =>
// 过滤掉非法数据(有些场景下会误传入空字符串或 null)
if (
!isJSFunction(lifeCycles[lifeCycleName]) &&
+ !isJSExpressionFn(lifeCycles[lifeCycleName]) &&
!isJSExpression(lifeCycles[lifeCycleName])
) {
return;
diff --git a/modules/code-generator/src/plugins/component/rax/jsx.ts b/modules/code-generator/src/plugins/component/rax/jsx.ts
index ebc72104f0..ddae619cd6 100644
--- a/modules/code-generator/src/plugins/component/rax/jsx.ts
+++ b/modules/code-generator/src/plugins/component/rax/jsx.ts
@@ -1,8 +1,8 @@
import {
- NodeSchema,
- JSExpression,
- NpmInfo,
- CompositeValue,
+ IPublicTypeNodeSchema,
+ IPublicTypeJSExpression,
+ IPublicTypeNpmInfo,
+ IPublicTypeCompositeValue,
isJSExpression,
} from '@alilc/lowcode-types';
@@ -75,8 +75,7 @@ const pluginFactory: BuilderComponentPluginFactory = (config?) =>
// 注意:这里其实隐含了一个假设:schema 中的 componentName 应该是一个有效的 JS 标识符,而且是大写字母打头的
// FIXME: 为了快速修复临时加的逻辑,需要用 pre-process 的方式替代处理。
- const mapComponentNameToAliasOrKeepIt = (componentName: string) =>
- componentsNameAliasMap.get(componentName) || componentName;
+ const mapComponentNameToAliasOrKeepIt = (componentName: string) => componentsNameAliasMap.get(componentName) || componentName;
// 然后过滤掉所有的别名 chunks
next.chunks = next.chunks.filter((chunk) => !isImportAliasDefineChunk(chunk));
@@ -86,7 +85,7 @@ const pluginFactory: BuilderComponentPluginFactory = (config?) =>
// 2. 小程序出码的时候,很容易出现 Uncaught TypeError: Cannot read property 'avatar' of undefined 这样的异常(如下图的 50 行) -- 因为若直接出码,Rax 构建到小程序的时候会立即计算所有在视图中用到的变量
// 3. 通过 this.xxx 能拿到的东西太多了,而且自定义的 methods 可能会无意间破坏 Rax 框架或小程序框架在页面 this 上的东东
const customHandlers: HandlerSet = {
- expression(input: JSExpression, scope: IScope) {
+ expression(input: IPublicTypeJSExpression, scope: IScope) {
return transformJsExpr(generateExpression(input, scope), scope, {
dontWrapEval: !tolerateEvalErrors,
});
@@ -147,7 +146,7 @@ const pluginFactory: BuilderComponentPluginFactory = (config?) =>
function __$$eval(expr) {
try {
return expr();
- } catch (error) {
+ } catch (error) {
${evalErrorsHandler}
}
}
@@ -171,7 +170,7 @@ const pluginFactory: BuilderComponentPluginFactory = (config?) =>
return next;
function generateRaxLoopCtrl(
- nodeItem: NodeSchema,
+ nodeItem: IPublicTypeNodeSchema,
scope: IScope,
config?: NodeGeneratorConfig,
next?: NodePlugin,
@@ -218,7 +217,7 @@ function isImportAliasDefineChunk(chunk: ICodeChunk): chunk is ICodeChunk & {
ext: {
aliasName: string;
originalName: string;
- dependency: NpmInfo;
+ dependency: IPublicTypeNpmInfo;
};
} {
return (
@@ -226,13 +225,13 @@ function isImportAliasDefineChunk(chunk: ICodeChunk): chunk is ICodeChunk & {
!!chunk.ext &&
typeof chunk.ext.aliasName === 'string' &&
typeof chunk.ext.originalName === 'string' &&
- !!(chunk.ext.dependency as NpmInfo | null)?.componentName
+ !!(chunk.ext.dependency as IPublicTypeNpmInfo | null)?.componentName
);
}
function generateNodeAttrForRax(
this: { cfg: PluginConfig },
- attrData: { attrName: string; attrValue: CompositeValue },
+ attrData: { attrName: string; attrValue: IPublicTypeCompositeValue },
scope: IScope,
config?: NodeGeneratorConfig,
next?: AttrPlugin,
@@ -257,7 +256,7 @@ function generateNodeAttrForRax(
function generateEventHandlerAttrForRax(
attrName: string,
- attrValue: CompositeValue,
+ attrValue: IPublicTypeCompositeValue,
scope: IScope,
config?: NodeGeneratorConfig,
): CodePiece[] {
diff --git a/modules/code-generator/src/plugins/component/react/containerClass.ts b/modules/code-generator/src/plugins/component/react/containerClass.ts
index 0758c893e2..eab6cbebee 100644
--- a/modules/code-generator/src/plugins/component/react/containerClass.ts
+++ b/modules/code-generator/src/plugins/component/react/containerClass.ts
@@ -26,7 +26,7 @@ const pluginFactory: BuilderComponentPluginFactory = () => {
// 将模块名转换成 PascalCase 的格式,并添加特定后缀,防止命名冲突
const componentClassName = ensureValidClassName(
- `${changeCase.pascalCase(ir.moduleName)}$$Page`,
+ `${changeCase.pascalCase(ir.moduleName)}$$${ir.containerType}`,
);
next.chunks.push({
@@ -43,6 +43,18 @@ const pluginFactory: BuilderComponentPluginFactory = () => {
],
});
+ if (ir.containerType === 'Component') {
+ next.chunks.push({
+ type: ChunkType.STRING,
+ fileType: FileType.JSX,
+ name: CLASS_DEFINE_CHUNK_NAME.InsVar,
+ content: `static displayName = '${ir.moduleName}';`,
+ linkAfter: [
+ CLASS_DEFINE_CHUNK_NAME.Start,
+ ],
+ });
+ }
+
next.chunks.push({
type: ChunkType.STRING,
fileType: FileType.JSX,
diff --git a/modules/code-generator/src/plugins/component/react/containerInitState.ts b/modules/code-generator/src/plugins/component/react/containerInitState.ts
index 5f246e1156..d1dd0d1ddf 100644
--- a/modules/code-generator/src/plugins/component/react/containerInitState.ts
+++ b/modules/code-generator/src/plugins/component/react/containerInitState.ts
@@ -13,12 +13,12 @@ import {
} from '../../../types';
export interface PluginConfig {
- fileType: string;
+ fileType?: string;
implementType: 'inConstructor' | 'insMember' | 'hooks';
}
const pluginFactory: BuilderComponentPluginFactory = (config?) => {
- const cfg: PluginConfig = {
+ const cfg: PluginConfig & { fileType: string } = {
fileType: FileType.JSX,
implementType: 'inConstructor',
...config,
diff --git a/modules/code-generator/src/plugins/component/react/containerInjectDataSourceEngine.ts b/modules/code-generator/src/plugins/component/react/containerInjectDataSourceEngine.ts
index 20302dea99..c4385017b8 100644
--- a/modules/code-generator/src/plugins/component/react/containerInjectDataSourceEngine.ts
+++ b/modules/code-generator/src/plugins/component/react/containerInjectDataSourceEngine.ts
@@ -1,8 +1,8 @@
/* eslint-disable @typescript-eslint/indent */
import {
- CompositeValue,
- JSExpression,
+ IPublicTypeCompositeValue,
+ IPublicTypeJSExpression,
InterpretDataSourceConfig,
isJSExpression,
isJSFunction,
@@ -27,8 +27,9 @@ import {
import { generateCompositeType } from '../../../utils/compositeType';
import { parseExpressionConvertThis2Context } from '../../../utils/expressionParser';
-import { isContainerSchema } from '../../../utils/schema';
+import { isValidContainerType } from '../../../utils/schema';
import { REACT_CHUNK_NAME } from './const';
+import { isJSExpressionFn } from '../../../utils/common';
export interface PluginConfig {
fileType?: string;
@@ -37,6 +38,7 @@ export interface PluginConfig {
* 数据源配置
*/
datasourceConfig?: {
+
/** 数据源引擎的版本 */
engineVersion?: string;
@@ -67,12 +69,12 @@ const pluginFactory: BuilderComponentPluginFactory = (config?) =>
};
const scope = Scope.createRootScope();
- const dataSourceConfig = isContainerSchema(pre.ir) ? pre.ir.dataSource : null;
+ const dataSourceConfig = isValidContainerType(pre.ir) ? pre.ir.dataSource : null;
const dataSourceItems: InterpretDataSourceConfig[] =
(dataSourceConfig && dataSourceConfig.list) || [];
const dataSourceEngineOptions = { runtimeConfig: true };
if (dataSourceItems.length > 0) {
- const requestHandlersMap: Record = {};
+ const requestHandlersMap: Record = {};
dataSourceItems.forEach((ds) => {
const dsType = ds.type || 'fetch';
@@ -187,16 +189,16 @@ const pluginFactory: BuilderComponentPluginFactory = (config?) =>
export default pluginFactory;
-function wrapAsFunction(value: CompositeValue, scope: IScope): CompositeValue {
- if (isJSExpression(value) || isJSFunction(value)) {
+function wrapAsFunction(value: IPublicTypeCompositeValue, scope: IScope): IPublicTypeCompositeValue {
+ if (isJSExpression(value) || isJSFunction(value) || isJSExpressionFn(value)) {
return {
type: 'JSExpression',
- value: `function(){ return ((${value.value}))}`,
+ value: `function(){ return ((${value.value}))}.bind(this)`,
};
}
return {
type: 'JSExpression',
- value: `function(){return((${generateCompositeType(value, scope)}))}`,
+ value: `function(){return((${generateCompositeType(value, scope)}))}.bind(this)`,
};
}
diff --git a/modules/code-generator/src/plugins/component/react/containerInjectI18n.ts b/modules/code-generator/src/plugins/component/react/containerInjectI18n.ts
index da04029c00..aff42af15f 100644
--- a/modules/code-generator/src/plugins/component/react/containerInjectI18n.ts
+++ b/modules/code-generator/src/plugins/component/react/containerInjectI18n.ts
@@ -11,6 +11,7 @@ import {
FileType,
ICodeStruct,
} from '../../../types';
+import { getSlotRelativePath } from '../../../utils/pathHelper';
export interface PluginConfig {
fileType: string;
@@ -31,9 +32,8 @@ const pluginFactory: BuilderComponentPluginFactory = (config?) =>
type: ChunkType.STRING,
fileType: cfg.fileType,
name: COMMON_CHUNK_NAME.InternalDepsImport,
- // TODO: 下面这个路径有没有更好的方式来获取?而非写死
content: `
- import * as __$$i18n from '../../i18n';
+ import * as __$$i18n from '${getSlotRelativePath({ contextData: next.contextData, from: 'components', to: 'i18n' })}';
`,
linkAfter: [COMMON_CHUNK_NAME.ExternalDepsImport],
});
diff --git a/modules/code-generator/src/plugins/component/react/containerInjectUtils.ts b/modules/code-generator/src/plugins/component/react/containerInjectUtils.ts
index 6c614f143a..d9eb14f769 100644
--- a/modules/code-generator/src/plugins/component/react/containerInjectUtils.ts
+++ b/modules/code-generator/src/plugins/component/react/containerInjectUtils.ts
@@ -12,13 +12,17 @@ import {
ICodeStruct,
IContainerInfo,
} from '../../../types';
+import { getSlotRelativePath } from '../../../utils/pathHelper';
export interface PluginConfig {
- fileType: string;
+ fileType?: string;
+
+ /** prefer using class property to define utils */
+ preferClassProperty?: boolean;
}
const pluginFactory: BuilderComponentPluginFactory = (config?) => {
- const cfg: PluginConfig = {
+ const cfg: PluginConfig & { fileType: string } = {
fileType: FileType.JSX,
...config,
};
@@ -36,20 +40,31 @@ const pluginFactory: BuilderComponentPluginFactory = (config?) =>
type: ChunkType.STRING,
fileType: cfg.fileType,
name: COMMON_CHUNK_NAME.InternalDepsImport,
- // TODO: 下面这个路径有没有更好的方式来获取?而非写死
content: `
- import utils${useRef ? ', { RefsManager }' : ''} from '../../utils';
+ import utils${useRef ? ', { RefsManager }' : ''} from '${getSlotRelativePath({ contextData: next.contextData, from: 'components', to: '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 (cfg.preferClassProperty) {
+ // mode: class property
+ next.chunks.push({
+ type: ChunkType.STRING,
+ fileType: cfg.fileType,
+ name: CLASS_DEFINE_CHUNK_NAME.InsVar,
+ content: 'utils = utils;',
+ linkAfter: [...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.InsVar]],
+ });
+ } else {
+ // mode: assign in constructor
+ 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({
@@ -89,7 +104,7 @@ const pluginFactory: BuilderComponentPluginFactory = (config?) =>
type: ChunkType.STRING,
fileType: cfg.fileType,
name: CLASS_DEFINE_CHUNK_NAME.InsMethod,
- content: ` $ = () => null; `,
+ content: ' $ = () => null; ',
linkAfter: [...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.InsMethod]],
});
@@ -97,7 +112,7 @@ const pluginFactory: BuilderComponentPluginFactory = (config?) =>
type: ChunkType.STRING,
fileType: cfg.fileType,
name: CLASS_DEFINE_CHUNK_NAME.InsMethod,
- content: ` $$ = () => []; `,
+ content: ' $$ = () => []; ',
linkAfter: [...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.InsMethod]],
});
}
diff --git a/modules/code-generator/src/plugins/component/react/containerLifeCycle.ts b/modules/code-generator/src/plugins/component/react/containerLifeCycle.ts
index 8caa9b105a..5f7d6d22a7 100644
--- a/modules/code-generator/src/plugins/component/react/containerLifeCycle.ts
+++ b/modules/code-generator/src/plugins/component/react/containerLifeCycle.ts
@@ -13,15 +13,17 @@ import {
IContainerInfo,
} from '../../../types';
import { isJSFunction, isJSExpression } from '@alilc/lowcode-types';
+import { isJSExpressionFn } from '../../../utils/common';
export interface PluginConfig {
- fileType: string;
- exportNameMapping: Record;
- normalizeNameMapping: Record;
+ fileType?: string;
+ exportNameMapping?: Record;
+ normalizeNameMapping?: Record;
+ exclude?: string[];
}
const pluginFactory: BuilderComponentPluginFactory = (config?) => {
- const cfg: PluginConfig = {
+ const cfg = {
fileType: FileType.JSX,
exportNameMapping: {},
normalizeNameMapping: {},
@@ -41,6 +43,7 @@ const pluginFactory: BuilderComponentPluginFactory = (config?) =>
// 过滤掉非法数据(有些场景下会误传入空字符串或 null)
if (
!isJSFunction(lifeCycles[lifeCycleName]) &&
+ !isJSExpressionFn(lifeCycles[lifeCycleName]) &&
!isJSExpression(lifeCycles[lifeCycleName])
) {
return null;
@@ -54,6 +57,10 @@ const pluginFactory: BuilderComponentPluginFactory = (config?) =>
normalizeName = cfg.normalizeNameMapping[lifeCycleName] || lifeCycleName;
}
+ if (cfg?.exclude?.includes(normalizeName)) {
+ return null;
+ }
+
const exportName = cfg.exportNameMapping[lifeCycleName] || lifeCycleName;
if (normalizeName === 'constructor') {
return {
@@ -95,7 +102,7 @@ const pluginFactory: BuilderComponentPluginFactory = (config?) =>
}),
linkAfter: [...DEFAULT_LINK_AFTER[CLASS_DEFINE_CHUNK_NAME.InsMethod]],
};
- });
+ }).filter((i) => !!i);
next.chunks.push(...chunks.filter((x): x is ICodeChunk => x !== null));
}
diff --git a/modules/code-generator/src/plugins/component/react/jsx.ts b/modules/code-generator/src/plugins/component/react/jsx.ts
index 445dce7a5f..588b356ad6 100644
--- a/modules/code-generator/src/plugins/component/react/jsx.ts
+++ b/modules/code-generator/src/plugins/component/react/jsx.ts
@@ -16,7 +16,7 @@ import { COMMON_CHUNK_NAME } from '../../../const/generator';
import { createReactNodeGenerator } from '../../../utils/nodeToJSX';
import { Scope } from '../../../utils/Scope';
-import { JSExpression } from '@alilc/lowcode-types';
+import { IPublicTypeJSExpression } from '@alilc/lowcode-types';
import { generateExpression } from '../../../utils/jsExpression';
import { transformJsExpr } from '../../../core/jsx/handlers/transformJsExpression';
import { transformThis2Context } from '../../../core/jsx/handlers/transformThis2Context';
@@ -47,7 +47,7 @@ const pluginFactory: BuilderComponentPluginFactory = (config?) =>
// 这里会将内部的一些子上下文的访问(this.xxx)转换为 __$$context.xxx 的形式
// 与 Rax 所不同的是,这里不会将最顶层的 this 转换掉
const customHandlers: HandlerSet = {
- expression(input: JSExpression, scope: IScope, config) {
+ expression(input: IPublicTypeJSExpression, scope: IScope, config) {
return transformJsExpr(generateExpression(input, scope), scope, {
dontWrapEval: !(config?.tolerateEvalErrors ?? tolerateEvalErrors),
dontTransformThis2ContextAtRootScope: true,
@@ -58,7 +58,7 @@ const pluginFactory: BuilderComponentPluginFactory = (config?) =>
generateCompositeType(
{
type: 'JSFunction',
- value: input.value || 'null',
+ value: input.value || 'function () {}',
},
Scope.createRootScope(),
),
@@ -120,7 +120,7 @@ const pluginFactory: BuilderComponentPluginFactory = (config?) =>
function __$$eval(expr) {
try {
return expr();
- } catch (error) {
+ } catch (error) {
${evalErrorsHandler}
}
}
diff --git a/modules/code-generator/src/plugins/project/framework/icejs3/index.ts b/modules/code-generator/src/plugins/project/framework/icejs3/index.ts
new file mode 100644
index 0000000000..37fa7a9e39
--- /dev/null
+++ b/modules/code-generator/src/plugins/project/framework/icejs3/index.ts
@@ -0,0 +1,17 @@
+import template from './template';
+import globalStyle from './plugins/globalStyle';
+import packageJSON from './plugins/packageJSON';
+import layout from './plugins/layout';
+import appConfig from './plugins/appConfig';
+import buildConfig from './plugins/buildConfig';
+
+export default {
+ template,
+ plugins: {
+ appConfig,
+ buildConfig,
+ globalStyle,
+ packageJSON,
+ layout,
+ },
+};
diff --git a/modules/code-generator/src/plugins/project/framework/icejs3/plugins/appConfig.ts b/modules/code-generator/src/plugins/project/framework/icejs3/plugins/appConfig.ts
new file mode 100644
index 0000000000..6ec5384a5d
--- /dev/null
+++ b/modules/code-generator/src/plugins/project/framework/icejs3/plugins/appConfig.ts
@@ -0,0 +1,50 @@
+import {
+ BuilderComponentPlugin,
+ BuilderComponentPluginFactory,
+ ChunkType,
+ FileType,
+ ICodeStruct,
+} from '../../../../../types';
+import { COMMON_CHUNK_NAME } from '../../../../../const/generator';
+
+export interface AppConfigPluginConfig {
+
+}
+
+function getContent() {
+ return `import { defineAppConfig } from 'ice';
+
+// App config, see https://v3.ice.work/docs/guide/basic/app
+export default defineAppConfig(() => ({
+ // Set your configs here.
+ app: {
+ rootId: 'App',
+ },
+ router: {
+ type: 'browser',
+ basename: '/',
+ },
+}));`;
+}
+
+const pluginFactory: BuilderComponentPluginFactory = () => {
+ const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
+ const next: ICodeStruct = {
+ ...pre,
+ };
+
+ next.chunks.push({
+ type: ChunkType.STRING,
+ fileType: FileType.TS,
+ name: COMMON_CHUNK_NAME.FileMainContent,
+ content: getContent(),
+ linkAfter: [],
+ });
+
+ return next;
+ };
+
+ return plugin;
+};
+
+export default pluginFactory;
diff --git a/modules/code-generator/src/plugins/project/framework/icejs3/plugins/buildConfig.ts b/modules/code-generator/src/plugins/project/framework/icejs3/plugins/buildConfig.ts
new file mode 100644
index 0000000000..322796154a
--- /dev/null
+++ b/modules/code-generator/src/plugins/project/framework/icejs3/plugins/buildConfig.ts
@@ -0,0 +1,165 @@
+import {
+ BuilderComponentPlugin,
+ BuilderComponentPluginFactory,
+ ChunkType,
+ FileType,
+ ICodeStruct,
+} from '../../../../../types';
+import { COMMON_CHUNK_NAME } from '../../../../../const/generator';
+import { format } from '../../../../../utils/format';
+import { getThemeInfo } from '../../../../../utils/theme';
+
+export interface BuildConfigPluginConfig {
+
+ /** 包名 */
+ themePackage?: string;
+}
+
+function getContent(cfg?: BuildConfigPluginConfig, routesContent?: string) {
+ return `
+import { join } from 'path';
+import { defineConfig } from '@ice/app';
+import _ from 'lodash';
+import fusion from '@ice/plugin-fusion';
+import locales from '@ice/plugin-moment-locales';
+import type { Plugin } from '@ice/app/esm/types';
+
+interface PluginOptions {
+ id: string;
+}
+
+const plugin: Plugin = (options) => ({
+ // name 可选,插件名称
+ name: 'plugin-name',
+ // setup 必选,用于定制工程构建配置
+ setup: ({ onGetConfig, modifyUserConfig }) => {
+ modifyUserConfig('codeSplitting', 'page');
+
+ onGetConfig((config) => {
+ config.entry = {
+ web: join(process.cwd(), '.ice/entry.client.tsx'),
+ };
+
+ config.cssFilename = '[name].css';
+
+ config.configureWebpack = config.configureWebpack || [];
+ config.configureWebpack?.push((webpackConfig) => {
+ if (webpackConfig.output) {
+ webpackConfig.output.filename = '[name].js';
+ webpackConfig.output.chunkFilename = '[name].js';
+ }
+ return webpackConfig;
+ });
+
+ config.swcOptions = _.merge(config.swcOptions, {
+ compilationConfig: {
+ jsc: {
+ transform: {
+ react: {
+ runtime: 'classic',
+ },
+ },
+ },
+ }
+ });
+
+ // 解决 webpack publicPath 问题
+ config.transforms = config.transforms || [];
+ config.transforms.push((source: string, id: string) => {
+ if (id.includes('.ice/entry.client.tsx')) {
+ let code = \`
+ if (!__webpack_public_path__?.startsWith('http') && document.currentScript) {
+ // @ts-ignore
+ __webpack_public_path__ = document.currentScript.src.replace(/^(.*\\\\/)[^/]+$/, '$1');
+ window.__ICE_ASSETS_MANIFEST__ = window.__ICE_ASSETS_MANIFEST__ || {};
+ window.__ICE_ASSETS_MANIFEST__.publicPath = __webpack_public_path__;
+ }
+ \`;
+ code += source;
+ return { code };
+ }
+ });
+ });
+ },
+});
+
+// The project config, see https://v3.ice.work/docs/guide/basic/config
+const minify = process.env.NODE_ENV === 'production' ? 'swc' : false;
+export default defineConfig(() => ({
+ ssr: false,
+ ssg: false,
+ minify,
+ ${routesContent}
+ externals: {
+ react: 'React',
+ 'react-dom': 'ReactDOM',
+ 'react-dom/client': 'ReactDOM',
+ '@alifd/next': 'Next',
+ lodash: 'var window._',
+ '@alilc/lowcode-engine': 'var window.AliLowCodeEngine',
+ },
+ plugins: [
+ fusion(${cfg?.themePackage ? `{
+ importStyle: 'sass',
+ themePackage: '${getThemeInfo(cfg.themePackage).name}',
+ }` : `{
+ importStyle: 'sass',
+ }`}),
+ locales(),
+ plugin(),
+ ]
+}));
+ `;
+}
+
+function getRoutesContent(navData: any, needShell = true) {
+ const routes = [
+ 'routes: {',
+ ' defineRoutes: route => {',
+ ];
+ function _getRoutes(nav: any, _routes: string[] = []) {
+ const { slug, children } = nav;
+ if (children && children.length > 0) {
+ children.forEach((_nav: any) => _getRoutes(_nav, _routes));
+ } else if (slug) {
+ _routes.push(`route('/${slug}', '${slug}/index.jsx');`);
+ }
+ }
+ if (needShell) {
+ routes.push(" route('/', 'layout.jsx', () => {");
+ }
+ navData?.forEach((nav: any) => {
+ _getRoutes(nav, routes);
+ });
+ if (needShell) {
+ routes.push(' });');
+ }
+ routes.push(' }'); // end of defineRoutes
+ routes.push(' },'); // end of routes
+ return routes.join('\n');
+}
+
+const pluginFactory: BuilderComponentPluginFactory = (cfg?) => {
+ const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
+ const next: ICodeStruct = {
+ ...pre,
+ };
+
+ const { navConfig } = next.contextData;
+ const routesContent = navConfig?.data ? getRoutesContent(navConfig.data, true) : '';
+
+ next.chunks.push({
+ type: ChunkType.STRING,
+ fileType: FileType.MTS,
+ name: COMMON_CHUNK_NAME.FileMainContent,
+ content: format(getContent(cfg, routesContent)),
+ linkAfter: [],
+ });
+
+ return next;
+ };
+
+ return plugin;
+};
+
+export default pluginFactory;
diff --git a/modules/code-generator/src/plugins/project/framework/icejs3/plugins/globalStyle.ts b/modules/code-generator/src/plugins/project/framework/icejs3/plugins/globalStyle.ts
new file mode 100644
index 0000000000..3daaeecdc0
--- /dev/null
+++ b/modules/code-generator/src/plugins/project/framework/icejs3/plugins/globalStyle.ts
@@ -0,0 +1,56 @@
+import { COMMON_CHUNK_NAME } from '../../../../../const/generator';
+
+import {
+ BuilderComponentPlugin,
+ BuilderComponentPluginFactory,
+ ChunkType,
+ FileType,
+ ICodeStruct,
+ IProjectInfo,
+} from '../../../../../types';
+
+const pluginFactory: BuilderComponentPluginFactory = () => {
+ 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;
diff --git a/modules/code-generator/src/plugins/project/framework/icejs3/plugins/layout.ts b/modules/code-generator/src/plugins/project/framework/icejs3/plugins/layout.ts
new file mode 100644
index 0000000000..a67f227019
--- /dev/null
+++ b/modules/code-generator/src/plugins/project/framework/icejs3/plugins/layout.ts
@@ -0,0 +1,41 @@
+import {
+ BuilderComponentPlugin,
+ BuilderComponentPluginFactory,
+ ChunkType,
+ FileType,
+ ICodeStruct,
+} from '../../../../../types';
+import { COMMON_CHUNK_NAME } from '../../../../../const/generator';
+
+const pluginFactory: BuilderComponentPluginFactory = () => {
+ const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
+ const next: ICodeStruct = {
+ ...pre,
+ };
+
+ next.chunks.push({
+ type: ChunkType.STRING,
+ fileType: FileType.JSX,
+ name: COMMON_CHUNK_NAME.FileMainContent,
+ content: `
+ import { Outlet } from 'ice';
+ import BasicLayout from '@/layouts/BasicLayout';
+
+ export default function Layout() {
+ return (
+
+
+
+ );;
+ }
+ `,
+ linkAfter: [],
+ });
+
+ return next;
+ };
+
+ return plugin;
+};
+
+export default pluginFactory;
diff --git a/modules/code-generator/src/plugins/project/framework/icejs3/plugins/packageJSON.ts b/modules/code-generator/src/plugins/project/framework/icejs3/plugins/packageJSON.ts
new file mode 100644
index 0000000000..3c39ba7399
--- /dev/null
+++ b/modules/code-generator/src/plugins/project/framework/icejs3/plugins/packageJSON.ts
@@ -0,0 +1,119 @@
+import { PackageJSON } from '@alilc/lowcode-types';
+
+import { COMMON_CHUNK_NAME } from '../../../../../const/generator';
+
+import {
+ BuilderComponentPlugin,
+ BuilderComponentPluginFactory,
+ ChunkType,
+ FileType,
+ ICodeStruct,
+ IProjectInfo,
+} from '../../../../../types';
+import { buildDataSourceDependencies } from '../../../../../utils/dataSource';
+
+interface IIceJs3PackageJSON extends PackageJSON {
+ originTemplate: string;
+}
+
+export type IceJs3PackageJsonPluginConfig = {
+
+ /**
+ * 数据源配置
+ */
+ datasourceConfig?: {
+
+ /** 数据源引擎的版本 */
+ engineVersion?: string;
+
+ /** 数据源引擎的包名 */
+ enginePackage?: string;
+
+ /** 数据源 handlers 的版本 */
+ handlersVersion?: {
+ [key: string]: string;
+ };
+
+ /** 数据源 handlers 的包名 */
+ handlersPackages?: {
+ [key: string]: string;
+ };
+ };
+
+ /** 包名 */
+ packageName?: string;
+
+ /** 版本 */
+ packageVersion?: string;
+};
+
+const pluginFactory: BuilderComponentPluginFactory = (cfg) => {
+ const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
+ const next: ICodeStruct = {
+ ...pre,
+ };
+
+ const ir = next.ir as IProjectInfo;
+
+ const packageJson: IIceJs3PackageJSON = {
+ name: cfg?.packageName || 'icejs3-demo-app',
+ version: cfg?.packageVersion || '0.1.5',
+ description: 'icejs 3 轻量级模板,使用 JavaScript,仅包含基础的 Layout。',
+ dependencies: {
+ moment: '^2.24.0',
+ react: '^18.2.0',
+ 'react-dom': '^18.2.0',
+ 'react-router': '^6.9.0',
+ 'react-router-dom': '^6.9.0',
+ 'intl-messageformat': '^9.3.6',
+ '@alifd/next': '1.26.15',
+ '@ice/runtime': '~1.1.0',
+ // 数据源相关的依赖:
+ ...buildDataSourceDependencies(ir, cfg?.datasourceConfig),
+ },
+ devDependencies: {
+ '@ice/app': '~3.1.0',
+ '@types/react': '^18.0.0',
+ '@types/react-dom': '^18.0.0',
+ '@types/node': '^18.11.17',
+ '@ice/plugin-fusion': '^1.0.1',
+ '@ice/plugin-moment-locales': '^1.0.0',
+ eslint: '^6.0.1',
+ stylelint: '^13.2.0',
+ },
+ scripts: {
+ start: 'ice start',
+ build: 'ice build',
+ lint: 'npm run eslint && npm run stylelint',
+ eslint: 'eslint --cache --ext .js,.jsx ./',
+ stylelint: 'stylelint ./**/*.scss',
+ },
+ engines: {
+ node: '>=14.0.0',
+ },
+ repository: {
+ type: 'git',
+ url: 'http://gitlab.xxx.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;
diff --git a/modules/code-generator/src/plugins/project/framework/icejs3/template/files/README.md.ts b/modules/code-generator/src/plugins/project/framework/icejs3/template/files/README.md.ts
new file mode 100644
index 0000000000..e73ab2b82c
--- /dev/null
+++ b/modules/code-generator/src/plugins/project/framework/icejs3/template/files/README.md.ts
@@ -0,0 +1,12 @@
+import { ResultFile } from '@alilc/lowcode-types';
+import { createResultFile } from '../../../../../../utils/resultHelper';
+
+export default function getFile(): [string[], ResultFile] {
+ const file = createResultFile(
+ 'README',
+ 'md',
+ 'This project is generated by lowcode-code-generator & lowcode-solution-icejs3.',
+ );
+
+ return [[], file];
+}
diff --git a/modules/code-generator/src/plugins/project/framework/icejs3/template/files/browserslistrc.ts b/modules/code-generator/src/plugins/project/framework/icejs3/template/files/browserslistrc.ts
new file mode 100644
index 0000000000..a3a346e3ff
--- /dev/null
+++ b/modules/code-generator/src/plugins/project/framework/icejs3/template/files/browserslistrc.ts
@@ -0,0 +1,14 @@
+import { ResultFile } from '@alilc/lowcode-types';
+import { createResultFile } from '../../../../../../utils/resultHelper';
+
+export default function getFile(): [string[], ResultFile] {
+ const file = createResultFile(
+ '.browserslistrc',
+ '',
+ `defaults
+ios_saf 9
+ `,
+ );
+
+ return [[], file];
+}
diff --git a/modules/code-generator/src/plugins/project/framework/icejs3/template/files/document.ts b/modules/code-generator/src/plugins/project/framework/icejs3/template/files/document.ts
new file mode 100644
index 0000000000..3345625557
--- /dev/null
+++ b/modules/code-generator/src/plugins/project/framework/icejs3/template/files/document.ts
@@ -0,0 +1,41 @@
+import { ResultFile } from '@alilc/lowcode-types';
+import { createResultFile } from '../../../../../../utils/resultHelper';
+
+/* eslint-disable max-len */
+export default function getFile(): [string[], ResultFile] {
+ const file = createResultFile(
+ 'document',
+ 'tsx',
+ `import React from 'react';
+import { Meta, Title, Links, Main, Scripts } from 'ice';
+
+export default function Document() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}`,
+ );
+
+ return [['src'], file];
+}
diff --git a/modules/code-generator/src/plugins/project/framework/icejs3/template/files/gitignore.ts b/modules/code-generator/src/plugins/project/framework/icejs3/template/files/gitignore.ts
new file mode 100644
index 0000000000..5c6eb3f13b
--- /dev/null
+++ b/modules/code-generator/src/plugins/project/framework/icejs3/template/files/gitignore.ts
@@ -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];
+}
diff --git a/modules/code-generator/src/plugins/project/framework/icejs3/template/files/src/layouts/BasicLayout/components/Footer/index.jsx.ts b/modules/code-generator/src/plugins/project/framework/icejs3/template/files/src/layouts/BasicLayout/components/Footer/index.jsx.ts
new file mode 100644
index 0000000000..9647a76439
--- /dev/null
+++ b/modules/code-generator/src/plugins/project/framework/icejs3/template/files/src/layouts/BasicLayout/components/Footer/index.jsx.ts
@@ -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 (
+
+ Alibaba Fusion
+
+ © 2019-现在 Alibaba Fusion & ICE
+
+ );
+}
+ `,
+ );
+
+ return [['src', 'layouts', 'BasicLayout', 'components', 'Footer'], file];
+}
diff --git a/modules/code-generator/src/plugins/project/framework/icejs3/template/files/src/layouts/BasicLayout/components/Footer/index.style.ts b/modules/code-generator/src/plugins/project/framework/icejs3/template/files/src/layouts/BasicLayout/components/Footer/index.style.ts
new file mode 100644
index 0000000000..941be0d263
--- /dev/null
+++ b/modules/code-generator/src/plugins/project/framework/icejs3/template/files/src/layouts/BasicLayout/components/Footer/index.style.ts
@@ -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];
+}
diff --git a/modules/code-generator/src/plugins/project/framework/icejs3/template/files/src/layouts/BasicLayout/components/Logo/index.jsx.ts b/modules/code-generator/src/plugins/project/framework/icejs3/template/files/src/layouts/BasicLayout/components/Logo/index.jsx.ts
new file mode 100644
index 0000000000..9c078c92c2
--- /dev/null
+++ b/modules/code-generator/src/plugins/project/framework/icejs3/template/files/src/layouts/BasicLayout/components/Logo/index.jsx.ts
@@ -0,0 +1,27 @@
+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 { Link } from 'ice';
+import styles from './index.module.scss';
+
+export default function Logo({ image, text, url }) {
+ return (
+
+
+ {image &&

}
+
{text}
+
+
+ );
+}
+ `,
+ );
+
+ return [['src', 'layouts', 'BasicLayout', 'components', 'Logo'], file];
+}
diff --git a/modules/code-generator/src/plugins/project/framework/icejs3/template/files/src/layouts/BasicLayout/components/Logo/index.style.ts b/modules/code-generator/src/plugins/project/framework/icejs3/template/files/src/layouts/BasicLayout/components/Logo/index.style.ts
new file mode 100644
index 0000000000..dfd00dd3e0
--- /dev/null
+++ b/modules/code-generator/src/plugins/project/framework/icejs3/template/files/src/layouts/BasicLayout/components/Logo/index.style.ts
@@ -0,0 +1,31 @@
+import { ResultFile } from '@alilc/lowcode-types';
+import { createResultFile } from '../../../../../../../../../../../utils/resultHelper';
+
+export default function getFile(): [string[], ResultFile] {
+ const file = createResultFile(
+ 'index',
+ 'module.scss',
+ `
+.logo{
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: #FF7300;
+ font-weight: bold;
+ font-size: 14px;
+ line-height: 22px;
+
+ &:visited, &:link {
+ color: #FF7300;
+ }
+
+ img {
+ height: 24px;
+ margin-right: 10px;
+ }
+}
+ `,
+ );
+
+ return [['src', 'layouts', 'BasicLayout', 'components', 'Logo'], file];
+}
diff --git a/modules/code-generator/src/plugins/project/framework/icejs3/template/files/src/layouts/BasicLayout/components/PageNav/index.jsx.ts b/modules/code-generator/src/plugins/project/framework/icejs3/template/files/src/layouts/BasicLayout/components/PageNav/index.jsx.ts
new file mode 100644
index 0000000000..1713057566
--- /dev/null
+++ b/modules/code-generator/src/plugins/project/framework/icejs3/template/files/src/layouts/BasicLayout/components/PageNav/index.jsx.ts
@@ -0,0 +1,79 @@
+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 PropTypes from 'prop-types';
+import { Link, useLocation } from 'ice';
+import { Nav } from '@alifd/next';
+import { asideMenuConfig } from '../../menuConfig';
+
+const { SubNav } = Nav;
+const NavItem = Nav.Item;
+
+function getNavMenuItems(menusData) {
+ if (!menusData) {
+ return [];
+ }
+
+ return menusData
+ .filter(item => item.name && !item.hideInMenu)
+ .map((item, index) => getSubMenuOrItem(item, index));
+}
+
+function getSubMenuOrItem(item, index) {
+ if (item.children && item.children.some(child => child.name)) {
+ const childrenItems = getNavMenuItems(item.children);
+
+ if (childrenItems && childrenItems.length > 0) {
+ const subNav = (
+
+ {childrenItems}
+
+ );
+ return subNav;
+ }
+
+ return null;
+ }
+
+ const navItem = (
+
+ {item.name}
+
+ );
+ return navItem;
+}
+
+const Navigation = (props, context) => {
+ const location = useLocation();
+ const { pathname } = location;
+ const { isCollapse } = context;
+ return (
+
+ );
+};
+
+Navigation.contextTypes = {
+ isCollapse: PropTypes.bool,
+};
+export default Navigation;
+ `,
+ );
+
+ return [['src', 'layouts', 'BasicLayout', 'components', 'PageNav'], file];
+}
diff --git a/modules/code-generator/src/plugins/project/framework/icejs3/template/files/src/layouts/BasicLayout/index.jsx.ts b/modules/code-generator/src/plugins/project/framework/icejs3/template/files/src/layouts/BasicLayout/index.jsx.ts
new file mode 100644
index 0000000000..9c7318dab0
--- /dev/null
+++ b/modules/code-generator/src/plugins/project/framework/icejs3/template/files/src/layouts/BasicLayout/index.jsx.ts
@@ -0,0 +1,92 @@
+import { ResultFile } from '@alilc/lowcode-types';
+import { createResultFile } from '../../../../../../../../../utils/resultHelper';
+
+export default function getFile(): [string[], ResultFile] {
+ const file = createResultFile(
+ 'index',
+ 'jsx',
+ `
+import React, { useState } from 'react';
+import { Shell, ConfigProvider } from '@alifd/next';
+import PageNav from './components/PageNav';
+import Logo from './components/Logo';
+import Footer from './components/Footer';
+
+(function() {
+ const throttle = function(type, name, obj = window) {
+ let running = false;
+
+ const func = () => {
+ if (running) {
+ return;
+ }
+
+ running = true;
+ requestAnimationFrame(() => {
+ obj.dispatchEvent(new CustomEvent(name));
+ running = false;
+ });
+ };
+
+ obj.addEventListener(type, func);
+ };
+
+ throttle('resize', 'optimizedResize');
+})();
+
+export default function BasicLayout({ children }) {
+ const getDevice = width => {
+ const isPhone =
+ typeof navigator !== 'undefined' && navigator && navigator.userAgent.match(/phone/gi);
+
+ if (width < 680 || isPhone) {
+ return 'phone';
+ }
+ if (width < 1280 && width > 680) {
+ return 'tablet';
+ }
+ return 'desktop';
+ };
+
+ const [device, setDevice] = useState(getDevice(NaN));
+ window.addEventListener('optimizedResize', e => {
+ setDevice(getDevice(e && e.target && e.target.innerWidth));
+ });
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ {children}
+
+
+
+
+
+ );
+}
+ `,
+ );
+
+ return [['src', 'layouts', 'BasicLayout'], file];
+}
diff --git a/modules/code-generator/src/plugins/project/framework/icejs3/template/files/src/layouts/BasicLayout/menuConfig.js.ts b/modules/code-generator/src/plugins/project/framework/icejs3/template/files/src/layouts/BasicLayout/menuConfig.js.ts
new file mode 100644
index 0000000000..636539b657
--- /dev/null
+++ b/modules/code-generator/src/plugins/project/framework/icejs3/template/files/src/layouts/BasicLayout/menuConfig.js.ts
@@ -0,0 +1,22 @@
+import { ResultFile } from '@alilc/lowcode-types';
+import { createResultFile } from '../../../../../../../../../utils/resultHelper';
+
+export default function getFile(): [string[], ResultFile] {
+ const file = createResultFile(
+ 'menuConfig',
+ 'js',
+ `
+const headerMenuConfig = [];
+const asideMenuConfig = [
+ {
+ name: 'Dashboard',
+ path: '/',
+ icon: 'smile',
+ },
+];
+export { headerMenuConfig, asideMenuConfig };
+ `,
+ );
+
+ return [['src', 'layouts', 'BasicLayout'], file];
+}
diff --git a/modules/code-generator/src/plugins/project/framework/icejs3/template/files/tsconfig.ts b/modules/code-generator/src/plugins/project/framework/icejs3/template/files/tsconfig.ts
new file mode 100644
index 0000000000..f58639db20
--- /dev/null
+++ b/modules/code-generator/src/plugins/project/framework/icejs3/template/files/tsconfig.ts
@@ -0,0 +1,38 @@
+import { ResultFile } from '@alilc/lowcode-types';
+import { createResultFile } from '../../../../../../utils/resultHelper';
+
+export default function getFile(): [string[], ResultFile] {
+ const file = createResultFile(
+ 'tsconfig',
+ 'json',
+ `
+{
+ "compilerOptions": {
+ "baseUrl": "./",
+ "module": "ESNext",
+ "target": "ESNext",
+ "lib": ["DOM", "ESNext", "DOM.Iterable"],
+ "jsx": "react-jsx",
+ "moduleResolution": "node",
+ "allowSyntheticDefaultImports": true,
+ "forceConsistentCasingInFileNames": true,
+ "noImplicitReturns": true,
+ "noImplicitThis": true,
+ "noImplicitAny": false,
+ "importHelpers": true,
+ "strictNullChecks": true,
+ "suppressImplicitAnyIndexErrors": true,
+ "skipLibCheck": true,
+ "paths": {
+ "@/*": ["./src/*"],
+ "ice": [".ice"]
+ }
+ },
+ "include": ["src", ".ice"],
+ "exclude": ["build"]
+}
+ `,
+ );
+
+ return [[], file];
+}
diff --git a/modules/code-generator/src/plugins/project/framework/icejs3/template/files/typings.ts b/modules/code-generator/src/plugins/project/framework/icejs3/template/files/typings.ts
new file mode 100644
index 0000000000..c8cbe92545
--- /dev/null
+++ b/modules/code-generator/src/plugins/project/framework/icejs3/template/files/typings.ts
@@ -0,0 +1,20 @@
+import { ResultFile } from '@alilc/lowcode-types';
+import { createResultFile } from '../../../../../../utils/resultHelper';
+
+export default function getFile(): [string[], ResultFile] {
+ const file = createResultFile(
+ 'typings.d',
+ 'ts',
+ `///
+
+export {};
+declare global {
+ interface Window {
+ g_config: Record;
+ }
+}
+ `,
+ );
+
+ return [['src'], file];
+}
diff --git a/modules/code-generator/src/plugins/project/framework/icejs3/template/index.ts b/modules/code-generator/src/plugins/project/framework/icejs3/template/index.ts
new file mode 100644
index 0000000000..e29f931386
--- /dev/null
+++ b/modules/code-generator/src/plugins/project/framework/icejs3/template/index.ts
@@ -0,0 +1,57 @@
+import { IProjectTemplate } from '../../../../../types';
+import { generateStaticFiles } from './static-files';
+
+const icejs3Template: IProjectTemplate = {
+ slots: {
+ components: {
+ path: ['src', 'components'],
+ fileName: 'index',
+ },
+ pages: {
+ path: ['src', 'pages'],
+ fileName: 'index',
+ },
+ entry: {
+ path: ['src'],
+ fileName: 'app',
+ },
+ constants: {
+ path: ['src'],
+ fileName: 'constants',
+ },
+ utils: {
+ path: ['src'],
+ fileName: 'utils',
+ },
+ i18n: {
+ path: ['src'],
+ fileName: 'i18n',
+ },
+ globalStyle: {
+ path: ['src'],
+ fileName: 'global',
+ },
+ packageJSON: {
+ path: [],
+ fileName: 'package',
+ },
+ appConfig: {
+ path: ['src'],
+ fileName: 'app',
+ },
+ buildConfig: {
+ path: [],
+ fileName: 'ice.config',
+ },
+ layout: {
+ path: ['src', 'pages'],
+ fileName: 'layout',
+ },
+ },
+
+ generateTemplate() {
+ return generateStaticFiles();
+ },
+};
+
+export default icejs3Template;
diff --git a/modules/code-generator/src/plugins/project/framework/icejs3/template/static-files.ts b/modules/code-generator/src/plugins/project/framework/icejs3/template/static-files.ts
new file mode 100644
index 0000000000..8f3794b61a
--- /dev/null
+++ b/modules/code-generator/src/plugins/project/framework/icejs3/template/static-files.ts
@@ -0,0 +1,33 @@
+import { ResultDir } from '@alilc/lowcode-types';
+import { createResultDir } from '../../../../../utils/resultHelper';
+import { runFileGenerator } from '../../../../../utils/templateHelper';
+
+import file1 from './files/gitignore';
+import file2 from './files/README.md';
+import file3 from './files/browserslistrc';
+import file4 from './files/typings';
+import file5 from './files/document';
+import file6 from './files/src/layouts/BasicLayout/components/Footer/index.jsx';
+import file7 from './files/src/layouts/BasicLayout/components/Footer/index.style';
+import file8 from './files/src/layouts/BasicLayout/components/Logo/index.jsx';
+import file9 from './files/src/layouts/BasicLayout/components/Logo/index.style';
+import file10 from './files/src/layouts/BasicLayout/components/PageNav/index.jsx';
+import file11 from './files/src/layouts/BasicLayout/index.jsx';
+import file12 from './files/src/layouts/BasicLayout/menuConfig.js';
+
+export function generateStaticFiles(root = createResultDir('.')): ResultDir {
+ runFileGenerator(root, file1);
+ runFileGenerator(root, file2);
+ runFileGenerator(root, file3);
+ runFileGenerator(root, file4);
+ runFileGenerator(root, file5);
+ runFileGenerator(root, file6);
+ runFileGenerator(root, file7);
+ runFileGenerator(root, file8);
+ runFileGenerator(root, file9);
+ runFileGenerator(root, file10);
+ runFileGenerator(root, file11);
+ runFileGenerator(root, file12);
+
+ return root;
+}
diff --git a/modules/code-generator/src/plugins/project/framework/rax/plugins/packageJSON.ts b/modules/code-generator/src/plugins/project/framework/rax/plugins/packageJSON.ts
index e95a3dc0bf..401ba3f97f 100644
--- a/modules/code-generator/src/plugins/project/framework/rax/plugins/packageJSON.ts
+++ b/modules/code-generator/src/plugins/project/framework/rax/plugins/packageJSON.ts
@@ -1,4 +1,4 @@
-import { NpmInfo, PackageJSON } from '@alilc/lowcode-types';
+import { IPublicTypeNpmInfo, PackageJSON } from '@alilc/lowcode-types';
import { COMMON_CHUNK_NAME } from '../../../../../const/generator';
import {
@@ -84,9 +84,9 @@ const pluginFactory: BuilderComponentPluginFactory = (cfg)
export default pluginFactory;
-function getNpmDependencies(project: IProjectInfo): NpmInfo[] {
- const npmDeps: NpmInfo[] = [];
- const npmNameToPkgMap = new Map();
+function getNpmDependencies(project: IProjectInfo): IPublicTypeNpmInfo[] {
+ const npmDeps: IPublicTypeNpmInfo[] = [];
+ const npmNameToPkgMap = new Map();
const allDeps = project.packages;
diff --git a/modules/code-generator/src/plugins/project/i18n.ts b/modules/code-generator/src/plugins/project/i18n.ts
index ae568f9724..4c36345a5f 100644
--- a/modules/code-generator/src/plugins/project/i18n.ts
+++ b/modules/code-generator/src/plugins/project/i18n.ts
@@ -41,7 +41,7 @@ const pluginFactory: BuilderComponentPluginFactory = () => {
// 按低代码规范里面的要求进行变量替换
const format = (msg, variables) => (
typeof msg === 'string'
- ? msg.replace(/\\\$\\{(\\w+)\\}/g, (match, key) => variables?.[key] ?? '')
+ ? msg.replace(/\\\$?\\{(\\w+)\\}/g, (match, key) => variables?.[key] ?? '')
: msg
);
diff --git a/modules/code-generator/src/postprocessor/prettier/index.ts b/modules/code-generator/src/postprocessor/prettier/index.ts
index afdcc62258..079c45582e 100644
--- a/modules/code-generator/src/postprocessor/prettier/index.ts
+++ b/modules/code-generator/src/postprocessor/prettier/index.ts
@@ -9,7 +9,7 @@ const PARSERS = ['css', 'scss', 'less', 'json', 'html', 'vue'];
export interface ProcessorConfig {
customFileTypeParser: Record;
- plugins?: Array;
+ plugins?: prettier.Plugin[];
}
const factory: PostProcessorFactory = (config?: ProcessorConfig) => {
@@ -20,8 +20,10 @@ const factory: PostProcessorFactory = (config?: ProcessorConfig
const codePrettier: PostProcessor = (content: string, fileType: string) => {
let parser: prettier.BuiltInParserName | any;
- if (fileType === 'js' || fileType === 'jsx') {
- parser = 'babel';
+ if (fileType === 'js' || fileType === 'jsx' || fileType === 'ts' || fileType === 'tsx') {
+ parser = 'babel-ts';
+ } else if (fileType === 'json') {
+ parser = 'json-stringify';
} else if (PARSERS.indexOf(fileType) >= 0) {
parser = fileType;
} else if (cfg.customFileTypeParser[fileType]) {
@@ -33,6 +35,8 @@ const factory: PostProcessorFactory = (config?: ProcessorConfig
return prettier.format(content, {
parser,
plugins: [parserBabel, parserPostCss, parserHtml, ...(cfg.plugins || [])],
+ singleQuote: true,
+ jsxSingleQuote: false,
});
};
diff --git a/modules/code-generator/src/publisher/zip/index.ts b/modules/code-generator/src/publisher/zip/index.ts
index cc2f082b27..0ac0b6f67f 100644
--- a/modules/code-generator/src/publisher/zip/index.ts
+++ b/modules/code-generator/src/publisher/zip/index.ts
@@ -2,9 +2,9 @@ import { ResultDir } from '@alilc/lowcode-types';
import { PublisherFactory, IPublisher, IPublisherFactoryParams, PublisherError } from '../../types';
import { getErrorMessage } from '../../utils/errors';
import { isNodeProcess, writeZipToDisk, generateProjectZip } from './utils';
+import { saveAs } from 'file-saver';
-// export type ZipBuffer = Buffer | Blob;
-export type ZipBuffer = Buffer;
+export type ZipBuffer = Buffer | Blob;
declare type ZipPublisherResponse = string | ZipBuffer;
@@ -44,10 +44,16 @@ export const createZipPublisher: PublisherFactory => {
let zip = new JSZip();
zip = writeFolderToZip(project, zip, true);
- // const zipType = isNodeProcess() ? 'nodebuffer' : 'blob';
- const zipType = 'nodebuffer'; // 目前先只支持 node 调用
+ const zipType = isNodeProcess() ? 'nodebuffer' : 'blob';
return zip.generateAsync({ type: zipType });
};
diff --git a/modules/code-generator/src/solutions/icejs.ts b/modules/code-generator/src/solutions/icejs.ts
index 85ce7c1f52..1149098ecb 100644
--- a/modules/code-generator/src/solutions/icejs.ts
+++ b/modules/code-generator/src/solutions/icejs.ts
@@ -3,6 +3,7 @@ import { IProjectBuilder, IProjectBuilderOptions } from '../types';
import { createProjectBuilder } from '../generator/ProjectBuilder';
import esmodule from '../plugins/common/esmodule';
+import styleImport from '../plugins/common/styleImport';
import containerClass from '../plugins/component/react/containerClass';
import containerInitState from '../plugins/component/react/containerInitState';
import containerInjectContext from '../plugins/component/react/containerInjectContext';
@@ -38,11 +39,13 @@ export default function createIceJsProjectBuilder(
esmodule({
fileType: 'jsx',
}),
+ styleImport(),
containerClass(),
containerInjectContext(),
containerInjectUtils(),
containerInjectDataSourceEngine(),
containerInjectI18n(),
+ containerInjectConstants(),
containerInitState(),
containerLifeCycle(),
containerMethod(),
@@ -61,6 +64,7 @@ export default function createIceJsProjectBuilder(
esmodule({
fileType: 'jsx',
}),
+ styleImport(),
containerClass(),
containerInjectContext(),
containerInjectUtils(),
@@ -91,16 +95,18 @@ export default function createIceJsProjectBuilder(
packageJSON: [icejs.plugins.packageJSON()],
},
postProcessors: [prettier()],
+ customizeBuilderOptions: options?.customizeBuilderOptions,
});
}
export const plugins = {
containerClass,
- containerInitState,
containerInjectContext,
containerInjectUtils,
- containerInjectI18n,
containerInjectDataSourceEngine,
+ containerInjectI18n,
+ containerInjectConstants,
+ containerInitState,
containerLifeCycle,
containerMethod,
jsx,
diff --git a/modules/code-generator/src/solutions/icejs3.ts b/modules/code-generator/src/solutions/icejs3.ts
new file mode 100644
index 0000000000..a8622e74ab
--- /dev/null
+++ b/modules/code-generator/src/solutions/icejs3.ts
@@ -0,0 +1,113 @@
+import { IProjectBuilder, IProjectBuilderOptions } from '../types';
+
+import { createProjectBuilder } from '../generator/ProjectBuilder';
+
+import esmodule from '../plugins/common/esmodule';
+import styleImport from '../plugins/common/styleImport';
+import containerClass from '../plugins/component/react/containerClass';
+import containerInitState from '../plugins/component/react/containerInitState';
+import containerInjectContext from '../plugins/component/react/containerInjectContext';
+import containerInjectUtils from '../plugins/component/react/containerInjectUtils';
+import containerInjectDataSourceEngine from '../plugins/component/react/containerInjectDataSourceEngine';
+import containerInjectConstants from '../plugins/component/react/containerInjectConstants';
+import containerInjectI18n from '../plugins/component/react/containerInjectI18n';
+import containerLifeCycle from '../plugins/component/react/containerLifeCycle';
+import containerMethod from '../plugins/component/react/containerMethod';
+import jsx from '../plugins/component/react/jsx';
+import reactCommonDeps from '../plugins/component/react/reactCommonDeps';
+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 icejs3 from '../plugins/project/framework/icejs3';
+
+import { prettier } from '../postprocessor';
+
+export type IceJs3ProjectBuilderOptions = IProjectBuilderOptions;
+
+export default function createIceJsProjectBuilder(
+ options?: IceJs3ProjectBuilderOptions,
+): IProjectBuilder {
+ return createProjectBuilder({
+ inStrictMode: options?.inStrictMode,
+ extraContextData: { ...options },
+ template: icejs3.template,
+ plugins: {
+ components: [
+ reactCommonDeps(),
+ esmodule({
+ fileType: 'jsx',
+ }),
+ styleImport(),
+ containerClass(),
+ containerInjectContext(),
+ containerInjectUtils(),
+ containerInjectDataSourceEngine(),
+ containerInjectI18n(),
+ containerInjectConstants(),
+ containerInitState(),
+ containerLifeCycle(),
+ containerMethod(),
+ jsx({
+ nodeTypeMapping: {
+ Div: 'div',
+ Component: 'div',
+ Page: 'div',
+ Block: 'div',
+ },
+ }),
+ css(),
+ ],
+ pages: [
+ reactCommonDeps(),
+ esmodule({
+ fileType: 'jsx',
+ }),
+ styleImport(),
+ containerClass(),
+ containerInjectContext(),
+ containerInjectUtils(),
+ containerInjectDataSourceEngine(),
+ containerInjectI18n(),
+ containerInjectConstants(),
+ containerInitState(),
+ containerLifeCycle(),
+ containerMethod(),
+ jsx({
+ nodeTypeMapping: {
+ Div: 'div',
+ Component: 'div',
+ Page: 'div',
+ Block: 'div',
+ Box: 'div',
+ },
+ }),
+ css(),
+ ],
+ constants: [constants()],
+ utils: [esmodule(), utils('react')],
+ i18n: [i18n()],
+ globalStyle: [icejs3.plugins.globalStyle()],
+ packageJSON: [icejs3.plugins.packageJSON()],
+ buildConfig: [icejs3.plugins.buildConfig()],
+ appConfig: [icejs3.plugins.appConfig()],
+ layout: [icejs3.plugins.layout()],
+ },
+ postProcessors: [prettier()],
+ customizeBuilderOptions: options?.customizeBuilderOptions,
+ });
+}
+
+export const plugins = {
+ containerClass,
+ containerInitState,
+ containerInjectContext,
+ containerInjectUtils,
+ containerInjectI18n,
+ containerInjectDataSourceEngine,
+ containerLifeCycle,
+ containerMethod,
+ jsx,
+ commonDeps: reactCommonDeps,
+};
diff --git a/modules/code-generator/src/solutions/rax-app.ts b/modules/code-generator/src/solutions/rax-app.ts
index c2b3adac50..f7e9038353 100644
--- a/modules/code-generator/src/solutions/rax-app.ts
+++ b/modules/code-generator/src/solutions/rax-app.ts
@@ -71,6 +71,7 @@ export default function createRaxProjectBuilder(
packageJSON: [raxApp.plugins.packageJSON(options)],
},
postProcessors: [prettier()],
+ customizeBuilderOptions: options?.customizeBuilderOptions,
});
}
diff --git a/modules/code-generator/src/standalone-loader.ts b/modules/code-generator/src/standalone-loader.ts
index 882b1de9dd..0c8903ddcb 100644
--- a/modules/code-generator/src/standalone-loader.ts
+++ b/modules/code-generator/src/standalone-loader.ts
@@ -1,5 +1,5 @@
import fetch from 'node-fetch';
-import type { ProjectSchema, ResultDir } from '@alilc/lowcode-types';
+import type { IPublicTypeProjectSchema, ResultDir } from '@alilc/lowcode-types';
import type { FlattenFile } from './types/file';
declare const Worker: any;
@@ -26,7 +26,7 @@ export type Result = ResultDir | FlattenFile[];
export async function generateCode(options: {
solution: 'icejs' | 'rax';
- schema: ProjectSchema;
+ schema: IPublicTypeProjectSchema;
flattenResult?: boolean;
workerJsUrl?: string;
timeoutInMs?: number;
diff --git a/modules/code-generator/src/standalone-worker.ts b/modules/code-generator/src/standalone-worker.ts
index 71f56e67a3..e00a4877bf 100644
--- a/modules/code-generator/src/standalone-worker.ts
+++ b/modules/code-generator/src/standalone-worker.ts
@@ -1,5 +1,5 @@
/* eslint-disable no-console */
-import type { ProjectSchema } from '@alilc/lowcode-types';
+import type { IPublicTypeProjectSchema } from '@alilc/lowcode-types';
import CodeGen from './standalone';
declare const self: any;
@@ -13,7 +13,7 @@ self.onmessage = (event: any) => {
self.postMessage({ type: 'ready' });
-async function run(msg: { solution: string; schema: ProjectSchema; flattenResult?: boolean }) {
+async function run(msg: { solution: string; schema: IPublicTypeProjectSchema; flattenResult?: boolean }) {
try {
print('begin run...');
self.postMessage({ type: 'run:begin' });
diff --git a/modules/code-generator/src/standalone.ts b/modules/code-generator/src/standalone.ts
index 2f15515cf8..23d16e61f0 100644
--- a/modules/code-generator/src/standalone.ts
+++ b/modules/code-generator/src/standalone.ts
@@ -8,7 +8,8 @@ import './polyfills/buffer';
import { createProjectBuilder } from './generator/ProjectBuilder';
import { createModuleBuilder } from './generator/ModuleBuilder';
import { createZipPublisher } from './publisher/zip';
-import createIceJsProjectBuilder, { plugins as reactPlugins } from './solutions/icejs';
+import createIceJsProjectBuilder, { plugins as icejsPlugins } from './solutions/icejs';
+import createIceJs3ProjectBuilder, { plugins as icejs3Plugins } from './solutions/icejs3';
import createRaxAppProjectBuilder, { plugins as raxPlugins } from './solutions/rax-app';
// 引入说明
@@ -18,6 +19,7 @@ import { COMMON_CHUNK_NAME, CLASS_DEFINE_CHUNK_NAME, DEFAULT_LINK_AFTER } from '
// 引入通用插件组
import esmodule from './plugins/common/esmodule';
import requireUtils from './plugins/common/requireUtils';
+import styleImport from './plugins/common/styleImport';
import css from './plugins/component/style/css';
import constants from './plugins/project/constants';
@@ -32,6 +34,7 @@ import * as CONSTANTS from './const';
// 引入内置解决方案模块
import icejs from './plugins/project/framework/icejs';
+import icejs3 from './plugins/project/framework/icejs3';
import rax from './plugins/project/framework/rax';
export default {
@@ -39,10 +42,12 @@ export default {
createModuleBuilder,
solutions: {
icejs: createIceJsProjectBuilder,
+ icejs3: createIceJs3ProjectBuilder,
rax: createRaxAppProjectBuilder,
},
solutionParts: {
icejs,
+ icejs3,
rax,
},
publishers: {
@@ -50,6 +55,7 @@ export default {
},
plugins: {
common: {
+
/**
* 处理 ES Module
* @deprecated please use esModule
@@ -57,12 +63,7 @@ export default {
esmodule,
esModule: esmodule,
requireUtils,
- },
- react: {
- ...reactPlugins,
- },
- rax: {
- ...raxPlugins,
+ styleImport,
},
style: {
css,
@@ -72,6 +73,22 @@ export default {
i18n,
utils,
},
+ icejs: {
+ ...icejsPlugins,
+ },
+ icejs3: {
+ ...icejs3Plugins,
+ },
+ rax: {
+ ...raxPlugins,
+ },
+
+ /**
+ * @deprecated please use icejs
+ */
+ react: {
+ ...icejsPlugins,
+ },
},
postprocessor: {
prettier,
diff --git a/modules/code-generator/src/types/analyze.ts b/modules/code-generator/src/types/analyze.ts
index 43de307434..3bf444d29e 100644
--- a/modules/code-generator/src/types/analyze.ts
+++ b/modules/code-generator/src/types/analyze.ts
@@ -1,7 +1,7 @@
-import type { ContainerSchema } from '@alilc/lowcode-types';
+import type { IPublicTypeContainerSchema } from '@alilc/lowcode-types';
export interface ICompAnalyzeResult {
isUsingRef: boolean;
}
-export type TComponentAnalyzer = (container: ContainerSchema) => ICompAnalyzeResult;
+export type TComponentAnalyzer = (container: IPublicTypeContainerSchema) => ICompAnalyzeResult;
diff --git a/modules/code-generator/src/types/core.ts b/modules/code-generator/src/types/core.ts
index 99ca8c97ad..1219a1e767 100644
--- a/modules/code-generator/src/types/core.ts
+++ b/modules/code-generator/src/types/core.ts
@@ -1,19 +1,15 @@
import {
- JSONArray,
- JSONObject,
- CompositeArray,
- CompositeObject,
- ResultDir,
+ IPublicTypeCompositeArray,
+ IPublicTypeCompositeObject, IPublicTypeJSExpression,
+ IPublicTypeJSFunction, IPublicTypeJSONArray,
+ IPublicTypeJSONObject, IPublicTypeJSSlot, IPublicTypeNodeDataType,
+ IPublicTypeProjectSchema, ResultDir,
ResultFile,
- NodeDataType,
- ProjectSchema,
- JSExpression,
- JSFunction,
- JSSlot,
} from '@alilc/lowcode-types';
-import { IParseResult } from './intermediate';
+import type { ProjectBuilderInitOptions } from '../generator/ProjectBuilder';
import { IScopeBindings } from '../utils/ScopeBindings';
+import { IParseResult } from './intermediate';
export enum FileType {
CSS = 'css',
@@ -21,10 +17,13 @@ export enum FileType {
LESS = 'less',
HTML = 'html',
JS = 'js',
+ MJS = 'mjs',
JSX = 'jsx',
TS = 'ts',
+ MTS = 'mts',
TSX = 'tsx',
JSON = 'json',
+ MD = 'md',
}
export enum ChunkType {
@@ -64,14 +63,17 @@ export interface ICodeStruct extends IBaseCodeStruct {
/** 上下文数据,用来在插件之间共享一些数据 */
export interface IContextData extends IProjectBuilderOptions {
- /** 是否使用了 Ref 的 API (this.$/this.$$) */
- useRefApi?: boolean;
/**
* 其他自定义数据
* (三方自定义插件也可以在此放一些数据,建议起个长一点的名称,用自己的插件名做前缀,以防冲突)
*/
[key: string]: any;
+
+ /**
+ * 是否使用了 Ref 的 API (this.$/this.$$)
+ * */
+ useRefApi?: boolean;
}
export type BuilderComponentPlugin = (initStruct: ICodeStruct) => Promise;
@@ -95,7 +97,7 @@ export interface ICompiledModule {
export interface IModuleBuilder {
generateModule: (input: unknown) => Promise;
- generateModuleCode: (schema: ProjectSchema | string) => Promise;
+ generateModuleCode: (schema: IPublicTypeProjectSchema | string) => Promise;
linkCodeChunks: (chunks: Record, fileName: string) => ResultFile[];
addPlugin: (plugin: BuilderComponentPlugin) => void;
}
@@ -107,24 +109,25 @@ export interface IModuleBuilder {
* @interface ICodeGenerator
*/
export interface ICodeGenerator {
+
/**
* 出码接口,把 Schema 转换成代码文件系统描述
*
- * @param {(ProjectSchema)} schema 传入的 Schema
+ * @param {(IPublicTypeProjectSchema)} schema 传入的 Schema
* @returns {ResultDir}
* @memberof ICodeGenerator
*/
- toCode: (schema: ProjectSchema) => Promise;
+ toCode: (schema: IPublicTypeProjectSchema) => Promise;
}
export interface ISchemaParser {
- validate: (schema: ProjectSchema) => boolean;
- parse: (schema: ProjectSchema | string) => IParseResult;
+ validate: (schema: IPublicTypeProjectSchema) => boolean;
+ parse: (schema: IPublicTypeProjectSchema | string) => IParseResult;
}
export interface IProjectTemplate {
slots: Record;
- generateTemplate: () => ResultDir | Promise;
+ generateTemplate: (data: IParseResult) => ResultDir | Promise;
}
export interface IProjectSlot {
@@ -137,51 +140,65 @@ export interface IProjectPlugins {
}
export interface IProjectBuilderOptions {
- /** 是否处于严格模式(默认: 否) */
+
+ /** 是否处于严格模式 (默认:否) */
inStrictMode?: boolean;
/**
* 是否要容忍对 JSExpression 求值时的异常
* 默认:true
- * 注: 如果容忍异常,则会在求值时包裹 try-catch 块,
+ * 注:如果容忍异常,则会在求值时包裹 try-catch 块,
* catch 到异常时默认会抛出一个 CustomEvent 事件里面包含异常信息和求值的表达式
*/
tolerateEvalErrors?: boolean;
/**
* 容忍异常的时候的的错误处理语句块
- * 默认: 无
+ * 默认:无
* 您可以设置为一个语句块,比如:
* window.dispatchEvent(new CustomEvent('lowcode-eval-error', { error, expr }))
*
* 一般可以结合埋点监控模块用来监控求值异常
*
- * 其中:
+ * 其中:
* - error: 异常信息
* - expr: 求值的表达式
*/
evalErrorsHandler?: string;
+
+ /**
+ * Hook which is used to customize original options, we can reorder/add/remove plugins/processors
+ * of the existing solution.
+ */
+ customizeBuilderOptions?(originalOptions: ProjectBuilderInitOptions): ProjectBuilderInitOptions;
}
export interface IProjectBuilder {
- generateProject: (schema: ProjectSchema | string) => Promise;
+ generateProject: (schema: IPublicTypeProjectSchema | string) => Promise;
}
/** 项目级别的前置处理器 */
-export type ProjectPreProcessor = (schema: ProjectSchema) => Promise | ProjectSchema;
+export type ProjectPreProcessor = (schema: IPublicTypeProjectSchema) =>
+ Promise | IPublicTypeProjectSchema;
+
+export interface ProjectPostProcessorOptions {
+ parseResult?: IParseResult;
+ template?: IProjectTemplate;
+}
/** 项目级别的后置处理器 */
export type ProjectPostProcessor = (
result: ResultDir,
- schema: ProjectSchema,
- originalSchema: ProjectSchema | string,
+ schema: IPublicTypeProjectSchema,
+ originalSchema: IPublicTypeProjectSchema | string,
+ options: ProjectPostProcessorOptions,
) => Promise | ResultDir;
/** 模块级别的后置处理器的工厂方法 */
export type PostProcessorFactory = (config?: T) => PostProcessor;
/** 模块级别的后置处理器 */
-export type PostProcessor = (content: string, fileType: string) => string;
+export type PostProcessor = (content: string, fileType: string, name?: string) => string;
// TODO: temp interface, need modify
export interface IPluginOptions {
@@ -198,20 +215,20 @@ type CompositeTypeGenerator =
| BaseGenerator
| Array>;
-export type NodeGenerator = (nodeItem: NodeDataType, scope: IScope) => T;
+export type NodeGenerator = (nodeItem: IPublicTypeNodeDataType, scope: IScope) => T;
// FIXME: 在新的实现中,添加了第一参数 this: CustomHandlerSet 作为上下文。究其本质
// scopeBindings?: IScopeBindings;
-// 这个组合只用来用来处理 CompositeValue 类型,不是这个类型的不要放在这里
+// 这个组合只用来用来处理 IPublicTypeCompositeValue 类型,不是这个类型的不要放在这里
export interface HandlerSet {
string?: CompositeTypeGenerator;
boolean?: CompositeTypeGenerator;
number?: CompositeTypeGenerator;
- expression?: CompositeTypeGenerator;
- function?: CompositeTypeGenerator;
- slot?: CompositeTypeGenerator;
- array?: CompositeTypeGenerator;
- object?: CompositeTypeGenerator;
+ expression?: CompositeTypeGenerator;
+ function?: CompositeTypeGenerator;
+ slot?: CompositeTypeGenerator;
+ array?: CompositeTypeGenerator;
+ object?: CompositeTypeGenerator;
}
export interface CompositeValueGeneratorOptions {
diff --git a/modules/code-generator/src/types/deps.ts b/modules/code-generator/src/types/deps.ts
index cb2fb5eac4..a6531092d5 100644
--- a/modules/code-generator/src/types/deps.ts
+++ b/modules/code-generator/src/types/deps.ts
@@ -35,4 +35,4 @@ export interface IDependency {
main?: string; // 包导出组件入口文件路径 /lib/input
dependencyType?: DependencyType; // 依赖类型 内/外
componentName?: string; // 导入后名称
-}
+}
\ No newline at end of file
diff --git a/modules/code-generator/src/types/intermediate.ts b/modules/code-generator/src/types/intermediate.ts
index 43d8f0e844..7cba0bd44b 100644
--- a/modules/code-generator/src/types/intermediate.ts
+++ b/modules/code-generator/src/types/intermediate.ts
@@ -1,4 +1,9 @@
-import { I18nMap, UtilsMap, ContainerSchema, JSONObject } from '@alilc/lowcode-types';
+import {
+ IPublicTypeI18nMap,
+ IPublicTypeUtilsMap,
+ IPublicTypeContainerSchema,
+ IPublicTypeJSONObject,
+} from '@alilc/lowcode-types';
import { IDependency, INpmPackage } from './deps';
import { ICompAnalyzeResult } from './analyze';
@@ -6,7 +11,7 @@ import { ICompAnalyzeResult } from './analyze';
export interface IParseResult {
containers: IContainerInfo[];
globalUtils?: IUtilInfo;
- globalI18n?: I18nMap;
+ globalI18n?: IPublicTypeI18nMap;
globalRouter?: IRouterInfo;
project?: IProjectInfo;
}
@@ -15,14 +20,14 @@ export interface IWithDependency {
deps?: IDependency[];
}
-export interface IContainerInfo extends ContainerSchema, IWithDependency {
+export interface IContainerInfo extends IPublicTypeContainerSchema, IWithDependency {
containerType: string;
moduleName: string;
analyzeResult?: ICompAnalyzeResult;
}
export interface IUtilInfo extends IWithDependency {
- utils: UtilsMap;
+ utils: IPublicTypeUtilsMap;
}
export interface IRouterInfo extends IWithDependency {
@@ -33,16 +38,26 @@ export interface IRouterInfo extends IWithDependency {
}>;
}
+/**
+ * project's remarks
+ */
+export interface ProjectRemark {
+
+ /** if current project only contain one container which type is `Component` */
+ isSingleComponent?: boolean;
+}
+
export interface IProjectInfo {
css?: string;
containersDeps?: IDependency[];
utilsDeps?: IDependency[];
- constants?: JSONObject;
- i18n?: I18nMap;
+ constants?: IPublicTypeJSONObject;
+ i18n?: IPublicTypeI18nMap;
packages: INpmPackage[];
meta?: { name?: string; title?: string } | Record;
config?: Record;
dataSourcesTypes?: string[];
+ projectRemark?: ProjectRemark;
}
export interface IPageMeta {
diff --git a/modules/code-generator/src/types/jsx.ts b/modules/code-generator/src/types/jsx.ts
index c79f1d2070..28f948d639 100644
--- a/modules/code-generator/src/types/jsx.ts
+++ b/modules/code-generator/src/types/jsx.ts
@@ -1,4 +1,4 @@
-import { NodeSchema, CompositeValue } from '@alilc/lowcode-types';
+import { IPublicTypeNodeSchema, IPublicTypeCompositeValue } from '@alilc/lowcode-types';
import { HandlerSet, BaseGenerator, NodeGenerator } from './core';
export enum PIECE_TYPE {
@@ -17,11 +17,11 @@ export interface CodePiece {
export interface AttrData {
attrName: string;
- attrValue: CompositeValue;
+ attrValue: IPublicTypeCompositeValue;
}
-// 对 JSX 出码的理解,目前定制点包含 【包装】【标签名】【属性】
+// 对 JSX 出码的理解,目前定制点包含【包装】【标签名】【属性】
export type AttrPlugin = BaseGenerator;
-export type NodePlugin = BaseGenerator;
+export type NodePlugin = BaseGenerator;
export interface NodeGeneratorConfig {
handlers?: HandlerSet;
@@ -33,7 +33,7 @@ export interface NodeGeneratorConfig {
/**
* 是否要容忍对 JSExpression 求值时的异常
* 默认:true
- * 注: 如果容忍异常,则会在求值时包裹 try-catch 块 -- 通过 __$$eval / __$$evalArray
+ * 注:如果容忍异常,则会在求值时包裹 try-catch 块 -- 通过 __$$eval / __$$evalArray
* catch 到异常时默认会抛出一个 CustomEvent 事件里面包含异常信息和求值的表达式
*/
tolerateEvalErrors?: boolean;
diff --git a/modules/code-generator/src/utils/common.ts b/modules/code-generator/src/utils/common.ts
index cfba393e96..fa7ab30a95 100644
--- a/modules/code-generator/src/utils/common.ts
+++ b/modules/code-generator/src/utils/common.ts
@@ -1,5 +1,7 @@
+import type { IPublicTypeJSExpression, IPublicTypeJSFunction } from '@alilc/lowcode-types';
import changeCase from 'change-case';
import short from 'short-uuid';
+import { DependencyType, IDependency, IExternalDependency, IInternalDependency } from '../types';
// Doc: https://www.npmjs.com/package/change-case
@@ -39,3 +41,19 @@ export function getStaticExprValue(expr: string): T {
// eslint-disable-next-line no-new-func
return Function(`"use strict";return (${expr})`)();
}
+
+export function isJSExpressionFn(data: any): data is IPublicTypeJSFunction {
+ return data?.type === 'JSExpression' && data?.extType === 'function';
+}
+
+export function isInternalDependency(
+ dependency: IDependency,
+): dependency is IInternalDependency {
+ return dependency.dependencyType === DependencyType.Internal;
+}
+
+export function isExternalDependency(
+ dependency: IDependency,
+): dependency is IExternalDependency {
+ return dependency.dependencyType === DependencyType.External;
+}
\ No newline at end of file
diff --git a/modules/code-generator/src/utils/compositeType.ts b/modules/code-generator/src/utils/compositeType.ts
index b66e1279c0..0dd612641d 100644
--- a/modules/code-generator/src/utils/compositeType.ts
+++ b/modules/code-generator/src/utils/compositeType.ts
@@ -1,13 +1,13 @@
import {
- CompositeArray,
- CompositeValue,
- CompositeObject,
- JSFunction,
- JSExpression,
+ IPublicTypeCompositeArray,
+ IPublicTypeCompositeValue,
+ IPublicTypeCompositeObject,
+ IPublicTypeJSFunction,
+ IPublicTypeJSExpression,
isJSExpression,
isJSFunction,
isJSSlot,
- JSSlot,
+ IPublicTypeJSSlot,
} from '@alilc/lowcode-types';
import _ from 'lodash';
@@ -16,6 +16,7 @@ import { generateExpression, generateFunction } from './jsExpression';
import { generateJsSlot } from './jsSlot';
import { executeFunctionStack } from './aopHelper';
import { parseExpressionGetKeywords } from './expressionParser';
+import { isJSExpressionFn } from './common';
interface ILegaoVariable {
type: 'variable';
@@ -43,7 +44,7 @@ function isDataSource(v: unknown): v is DataSource {
}
function generateArray(
- value: CompositeArray,
+ value: IPublicTypeCompositeArray,
scope: IScope,
options: CompositeValueGeneratorOptions = {},
): string {
@@ -52,7 +53,7 @@ function generateArray(
}
function generateObject(
- value: CompositeObject,
+ value: IPublicTypeCompositeObject,
scope: IScope,
options: CompositeValueGeneratorOptions = {},
): string {
@@ -88,7 +89,7 @@ function generateBool(value: boolean): string {
return value ? 'true' : 'false';
}
-function genFunction(value: JSFunction): string {
+function genFunction(value: IPublicTypeJSFunction): string {
const globalVars = parseExpressionGetKeywords(value.value);
if (globalVars.includes('arguments')) {
@@ -98,7 +99,7 @@ function genFunction(value: JSFunction): string {
return generateFunction(value, { isArrow: true });
}
-function genJsSlot(value: JSSlot, scope: IScope, options: CompositeValueGeneratorOptions = {}) {
+function genJsSlot(value: IPublicTypeJSSlot, scope: IScope, options: CompositeValueGeneratorOptions = {}) {
if (options.nodeGenerator) {
return generateJsSlot(value, scope, options.nodeGenerator);
}
@@ -106,7 +107,7 @@ function genJsSlot(value: JSSlot, scope: IScope, options: CompositeValueGenerato
}
function generateUnknownType(
- value: CompositeValue,
+ value: IPublicTypeCompositeValue,
scope: IScope,
options: CompositeValueGeneratorOptions = {},
): string {
@@ -128,7 +129,7 @@ function generateUnknownType(
// FIXME: 这个是临时方案
// 在遇到 type variable 私有类型时,转换为 JSExpression
if (isVariable(value)) {
- const transValue: JSExpression = {
+ const transValue: IPublicTypeJSExpression = {
type: 'JSExpression',
value: value.variable,
};
@@ -159,7 +160,7 @@ function generateUnknownType(
return generateExpression(value, scope);
}
- if (isJSFunction(value)) {
+ if (isJSFunction(value) || isJSExpressionFn(value)) {
if (options.handlers?.function) {
return executeFunctionStack(value, scope, options.handlers.function, genFunction, options);
}
@@ -188,7 +189,7 @@ function generateUnknownType(
if (options.handlers?.object) {
return executeFunctionStack(value, scope, options.handlers.object, generateObject, options);
}
- return generateObject(value as CompositeObject, scope, options);
+ return generateObject(value as IPublicTypeCompositeObject, scope, options);
}
if (_.isString(value)) {
@@ -218,7 +219,7 @@ function generateUnknownType(
// 这一层曾经是对产出做最外层包装的,但其实包装逻辑不应该属于这一层
// 这一层先不去掉,做冗余,方便后续重构
export function generateCompositeType(
- value: CompositeValue,
+ value: IPublicTypeCompositeValue,
scope: IScope,
options: CompositeValueGeneratorOptions = {},
): string {
diff --git a/modules/code-generator/src/utils/dataSource.ts b/modules/code-generator/src/utils/dataSource.ts
index 610f399344..5595b8defd 100644
--- a/modules/code-generator/src/utils/dataSource.ts
+++ b/modules/code-generator/src/utils/dataSource.ts
@@ -1,20 +1,24 @@
import changeCase from 'change-case';
import type { IProjectInfo } from '../types/intermediate';
-export type DataSourceDependenciesConfig = {
+export interface DataSourceDependenciesConfig {
+
/** 数据源引擎的版本 */
engineVersion?: string;
+
/** 数据源引擎的包名 */
enginePackage?: string;
+
/** 数据源 handlers 的版本 */
handlersVersion?: {
[key: string]: string;
};
+
/** 数据源 handlers 的包名 */
handlersPackages?: {
[key: string]: string;
};
-};
+}
export function buildDataSourceDependencies(
ir: IProjectInfo,
@@ -22,13 +26,13 @@ export function buildDataSourceDependencies(
): Record {
return {
// 数据源引擎的依赖包
- [cfg.enginePackage || '@alilc/lowcode-datasource-engine']: cfg.engineVersion || 'latest',
+ [cfg.enginePackage || '@alilc/lowcode-datasource-engine']: cfg.engineVersion || '^1.0.0',
// 各种数据源的 handlers 的依赖包
...(ir.dataSourcesTypes || []).reduce(
(acc, dsType) => ({
...acc,
- [getDataSourceHandlerPackageName(dsType)]: cfg.handlersVersion?.[dsType] || 'latest',
+ [getDataSourceHandlerPackageName(dsType)]: cfg.handlersVersion?.[dsType] || '^1.0.0',
}),
{},
),
diff --git a/modules/code-generator/src/utils/expressionParser.ts b/modules/code-generator/src/utils/expressionParser.ts
index cbc950bbc8..15320b9637 100644
--- a/modules/code-generator/src/utils/expressionParser.ts
+++ b/modules/code-generator/src/utils/expressionParser.ts
@@ -167,7 +167,7 @@ export function parseExpressionGetKeywords(expr: string | null | undefined): str
],
});
- const addIdentifierIfNeeded = (x: Record | number | null | undefined) => {
+ const addIdentifierIfNeeded = (x: Node | null | undefined) => {
if (typeof x === 'object' && isIdentifier(x) && JS_KEYWORDS.includes(x.name)) {
keywordVars.add(x.name);
}
@@ -189,7 +189,7 @@ export function parseExpressionGetKeywords(expr: string | null | undefined): str
addIdentifierIfNeeded(item);
});
} else {
- addIdentifierIfNeeded(fieldValue as Record | null);
+ addIdentifierIfNeeded(fieldValue as any);
}
}
});
@@ -217,7 +217,7 @@ export function parseExpressionGetGlobalVariables(
const ast = parser.parse(`!(${expr});`);
const addUndeclaredIdentifierIfNeeded = (
- x: Record | number | null | undefined,
+ x: Node | null | undefined,
path: NodePath,
) => {
if (typeof x === 'object' && isIdentifier(x) && !path.scope.hasBinding(x.name)) {
@@ -241,7 +241,7 @@ export function parseExpressionGetGlobalVariables(
addUndeclaredIdentifierIfNeeded(item, path);
});
} else {
- addUndeclaredIdentifierIfNeeded(fieldValue as Record | null, path);
+ addUndeclaredIdentifierIfNeeded(fieldValue as any, path);
}
}
});
diff --git a/modules/code-generator/src/utils/format.ts b/modules/code-generator/src/utils/format.ts
new file mode 100644
index 0000000000..7c865e8f34
--- /dev/null
+++ b/modules/code-generator/src/utils/format.ts
@@ -0,0 +1,12 @@
+import prettier from 'prettier';
+import parserBabel from 'prettier/parser-babel';
+
+export function format(content: string, options = {}) {
+ return prettier.format(content, {
+ parser: 'babel',
+ plugins: [parserBabel],
+ singleQuote: true,
+ jsxSingleQuote: false,
+ ...options,
+ });
+}
diff --git a/modules/code-generator/src/utils/index.ts b/modules/code-generator/src/utils/index.ts
index ff5194172d..8c41d678ff 100644
--- a/modules/code-generator/src/utils/index.ts
+++ b/modules/code-generator/src/utils/index.ts
@@ -11,6 +11,8 @@ import * as schema from './schema';
import * as version from './version';
import * as scope from './Scope';
import * as expressionParser from './expressionParser';
+import * as dataSource from './dataSource';
+import * as pathHelper from './pathHelper';
export {
common,
@@ -25,4 +27,6 @@ export {
version,
scope,
expressionParser,
+ dataSource,
+ pathHelper,
};
diff --git a/modules/code-generator/src/utils/jsExpression.ts b/modules/code-generator/src/utils/jsExpression.ts
index d6dc8c0273..08d2fafd8a 100644
--- a/modules/code-generator/src/utils/jsExpression.ts
+++ b/modules/code-generator/src/utils/jsExpression.ts
@@ -2,9 +2,10 @@ import * as parser from '@babel/parser';
import generate from '@babel/generator';
import traverse from '@babel/traverse';
import * as t from '@babel/types';
-import { JSExpression, JSFunction, isJSExpression, isJSFunction } from '@alilc/lowcode-types';
+import { IPublicTypeJSExpression, IPublicTypeJSFunction, isJSExpression, isJSFunction } from '@alilc/lowcode-types';
import { CodeGeneratorError, IScope } from '../types';
import { transformExpressionLocalRef, ParseError } from './expressionParser';
+import { isJSExpressionFn } from './common';
function parseFunction(content: string): t.FunctionExpression | null {
try {
@@ -78,13 +79,18 @@ function getBodyStatements(content: string) {
throw new Error('Can not find Function Statement');
}
-export function isJsCode(value: unknown): boolean {
- return isJSExpression(value) || isJSFunction(value);
+/**
+ * 是否是广义上的 JSFunction
+ * @param value
+ * @returns
+ */
+export function isBroadJSFunction(value: unknown): boolean {
+ return isJSExpressionFn(value) || isJSFunction(value);
}
export function generateExpression(value: any, scope: IScope): string {
if (isJSExpression(value)) {
- const exprVal = (value as JSExpression).value.trim();
+ const exprVal = (value as IPublicTypeJSExpression).value.trim();
if (!exprVal) {
return 'null';
}
@@ -96,6 +102,10 @@ export function generateExpression(value: any, scope: IScope): string {
throw new CodeGeneratorError('Not a JSExpression');
}
+function getFunctionSource(cfg: IPublicTypeJSFunction): string {
+ return cfg.source || cfg.value || cfg.compiled;
+}
+
export function generateFunction(
value: any,
config: {
@@ -112,21 +122,26 @@ export function generateFunction(
isBindExpr: false,
},
) {
- if (isJsCode(value)) {
- const functionCfg = value as JSFunction;
+ if (isBroadJSFunction(value)) {
+ const functionCfg = value as IPublicTypeJSFunction;
+ const functionSource = getFunctionSource(functionCfg);
if (config.isMember) {
- return transformFuncExpr2MethodMember(config.name || '', functionCfg.value);
+ return transformFuncExpr2MethodMember(config.name || '', functionSource);
}
if (config.isBlock) {
- return getBodyStatements(functionCfg.value);
+ return getBodyStatements(functionSource);
}
if (config.isArrow) {
- return getArrowFunction(functionCfg.value);
+ return getArrowFunction(functionSource);
}
if (config.isBindExpr) {
- return `(${functionCfg.value}).bind(this)`;
+ return `(${functionSource}).bind(this)`;
}
- return functionCfg.value;
+ return functionSource;
+ }
+
+ if (isJSExpression(value)) {
+ return value.value;
}
throw new CodeGeneratorError('Not a JSFunction or JSExpression');
diff --git a/modules/code-generator/src/utils/jsSlot.ts b/modules/code-generator/src/utils/jsSlot.ts
index 49f7b31704..265f6857f2 100644
--- a/modules/code-generator/src/utils/jsSlot.ts
+++ b/modules/code-generator/src/utils/jsSlot.ts
@@ -1,4 +1,4 @@
-import { JSSlot, isJSSlot, NodeData } from '@alilc/lowcode-types';
+import { IPublicTypeJSSlot, isJSSlot, IPublicTypeNodeData } from '@alilc/lowcode-types';
import { CodeGeneratorError, NodeGenerator, IScope } from '../types';
import { unwrapJsExprQuoteInJsx } from './jsxHelpers';
@@ -8,7 +8,7 @@ function generateSingleLineComment(commentText: string): string {
export function generateJsSlot(slot: any, scope: IScope, generator: NodeGenerator): string {
if (isJSSlot(slot)) {
- const { title, params, value } = slot as JSSlot;
+ const { title, params, value } = slot as IPublicTypeJSSlot;
// slot 也是分有参数和无参数的
// - 有参数的 slot 就是类似一个 render 函数,需要创建子作用域
@@ -39,7 +39,7 @@ export function generateJsSlot(slot: any, scope: IScope, generator: NodeGenerato
}
function generateNodeDataOrArrayForJsSlot(
- value: NodeData | NodeData[],
+ value: IPublicTypeNodeData | IPublicTypeNodeData[],
generator: NodeGenerator,
scope: IScope,
) {
diff --git a/modules/code-generator/src/utils/nodeToJSX.ts b/modules/code-generator/src/utils/nodeToJSX.ts
index 0b5919902f..d29f28762c 100644
--- a/modules/code-generator/src/utils/nodeToJSX.ts
+++ b/modules/code-generator/src/utils/nodeToJSX.ts
@@ -1,6 +1,6 @@
import _ from 'lodash';
import { pipe } from 'fp-ts/function';
-import { NodeSchema, isNodeSchema, NodeDataType, CompositeValue } from '@alilc/lowcode-types';
+import { IPublicTypeNodeSchema, isNodeSchema, IPublicTypeNodeDataType, IPublicTypeCompositeValue } from '@alilc/lowcode-types';
import {
IScope,
@@ -19,6 +19,7 @@ import { executeFunctionStack } from './aopHelper';
import { encodeJsxStringNode } from './encodeJsxAttrString';
import { unwrapJsExprQuoteInJsx } from './jsxHelpers';
import { transformThis2Context } from '../core/jsx/handlers/transformThis2Context';
+import { isValidIdentifier } from './validate';
function mergeNodeGeneratorConfig(
cfg1: NodeGeneratorConfig,
@@ -57,7 +58,7 @@ export function isPureString(v: string) {
}
function generateAttrValue(
- attrData: { attrName: string; attrValue: CompositeValue },
+ attrData: { attrName: string; attrValue: IPublicTypeCompositeValue },
scope: IScope,
config?: NodeGeneratorConfig,
): CodePiece[] {
@@ -76,7 +77,7 @@ function generateAttrValue(
function generateAttr(
attrName: string,
- attrValue: CompositeValue,
+ attrValue: IPublicTypeCompositeValue,
scope: IScope,
config?: NodeGeneratorConfig,
): CodePiece[] {
@@ -115,7 +116,7 @@ function generateAttr(
}
function generateAttrs(
- nodeItem: NodeSchema,
+ nodeItem: IPublicTypeNodeSchema,
scope: IScope,
config?: NodeGeneratorConfig,
): CodePiece[] {
@@ -126,11 +127,13 @@ function generateAttrs(
if (props) {
if (!Array.isArray(props)) {
Object.keys(props).forEach((propName: string) => {
- pieces = pieces.concat(generateAttr(propName, props[propName], scope, config));
+ if (isValidIdentifier(propName)) {
+ pieces = pieces.concat(generateAttr(propName, props[propName] as IPublicTypeCompositeValue, scope, config));
+ }
});
} else {
props.forEach((prop) => {
- if (prop.name && !prop.spread) {
+ if (prop.name && isValidIdentifier(prop.name) && !prop.spread) {
pieces = pieces.concat(generateAttr(prop.name, prop.value, scope, config));
}
@@ -144,7 +147,7 @@ function generateAttrs(
}
function generateBasicNode(
- nodeItem: NodeSchema,
+ nodeItem: IPublicTypeNodeSchema,
scope: IScope,
config?: NodeGeneratorConfig,
): CodePiece[] {
@@ -160,7 +163,7 @@ function generateBasicNode(
}
function generateSimpleNode(
- nodeItem: NodeSchema,
+ nodeItem: IPublicTypeNodeSchema,
scope: IScope,
config?: NodeGeneratorConfig,
): CodePiece[] {
@@ -182,7 +185,7 @@ function generateSimpleNode(
function linkPieces(pieces: CodePiece[]): string {
const tagsPieces = pieces.filter((p) => p.type === PIECE_TYPE.TAG);
if (tagsPieces.length !== 1) {
- throw new CodeGeneratorError('One node only need one tag define');
+ throw new CodeGeneratorError('Only one tag definition required', tagsPieces);
}
const tagName = tagsPieces[0].value;
@@ -216,13 +219,13 @@ function linkPieces(pieces: CodePiece[]): string {
}
function generateNodeSchema(
- nodeItem: NodeSchema,
+ nodeItem: IPublicTypeNodeSchema,
scope: IScope,
config?: NodeGeneratorConfig,
): string {
const pieces: CodePiece[] = [];
if (config?.nodePlugins) {
- const res = executeFunctionStack(
+ const res = executeFunctionStack(
nodeItem,
scope,
config.nodePlugins,
@@ -247,11 +250,11 @@ function generateNodeSchema(
* @type NodePlugin Extended
*
* @export
- * @param {NodeSchema} nodeItem 当前 UI 节点
+ * @param {IPublicTypeNodeSchema} nodeItem 当前 UI 节点
* @returns {CodePiece[]} 实现功能的相关代码片段
*/
export function generateReactLoopCtrl(
- nodeItem: NodeSchema,
+ nodeItem: IPublicTypeNodeSchema,
scope: IScope,
config?: NodeGeneratorConfig,
next?: NodePlugin,
@@ -270,8 +273,7 @@ export function generateReactLoopCtrl(
const loopDataExpr = pipe(
nodeItem.loop,
// 将 JSExpression 转换为 JS 表达式代码:
- (expr) =>
- generateCompositeType(expr, scope, {
+ (expr) => generateCompositeType(expr, scope, {
handlers: config?.handlers,
tolerateEvalErrors: false, // 这个内部不需要包 try catch, 下面会统一加的
}),
@@ -302,11 +304,11 @@ export function generateReactLoopCtrl(
* @type NodePlugin
*
* @export
- * @param {NodeSchema} nodeItem 当前 UI 节点
+ * @param {IPublicTypeNodeSchema} nodeItem 当前 UI 节点
* @returns {CodePiece[]} 实现功能的相关代码片段
*/
export function generateConditionReactCtrl(
- nodeItem: NodeSchema,
+ nodeItem: IPublicTypeNodeSchema,
scope: IScope,
config?: NodeGeneratorConfig,
next?: NodePlugin,
@@ -337,11 +339,11 @@ export function generateConditionReactCtrl(
* @type NodePlugin
*
* @export
- * @param {NodeSchema} nodeItem 当前 UI 节点
+ * @param {IPublicTypeNodeSchema} nodeItem 当前 UI 节点
* @returns {CodePiece[]} 实现功能的相关代码片段
*/
export function generateReactExprInJS(
- nodeItem: NodeSchema,
+ nodeItem: IPublicTypeNodeSchema,
scope: IScope,
config?: NodeGeneratorConfig,
next?: NodePlugin,
@@ -366,7 +368,7 @@ export function generateReactExprInJS(
const handleChildren = (v: string[]) => v.join('');
export function createNodeGenerator(cfg: NodeGeneratorConfig = {}): NodeGenerator {
- const generateNode = (nodeItem: NodeDataType, scope: IScope): string => {
+ const generateNode = (nodeItem: IPublicTypeNodeDataType, scope: IScope): string => {
if (_.isArray(nodeItem)) {
const resList = nodeItem.map((n) => generateNode(n, scope));
return handleChildren(resList);
@@ -391,8 +393,7 @@ export function createNodeGenerator(cfg: NodeGeneratorConfig = {}): NodeGenerato
return `{${valueStr}}`;
};
- return (nodeItem: NodeDataType, scope: IScope) =>
- unwrapJsExprQuoteInJsx(generateNode(nodeItem, scope));
+ return (nodeItem: IPublicTypeNodeDataType, scope: IScope) => unwrapJsExprQuoteInJsx(generateNode(nodeItem, scope));
}
const defaultReactGeneratorConfig: NodeGeneratorConfig = {
diff --git a/modules/code-generator/src/utils/pathHelper.ts b/modules/code-generator/src/utils/pathHelper.ts
new file mode 100644
index 0000000000..8440089d89
--- /dev/null
+++ b/modules/code-generator/src/utils/pathHelper.ts
@@ -0,0 +1,41 @@
+import { IContextData } from '../types';
+
+function relativePath(from: string[], to: string[]): string[] {
+ const length = Math.min(from.length, to.length);
+ let samePartsLength = length;
+ for (let i = 0; i < length; i++) {
+ if (from[i] !== to[i]) {
+ samePartsLength = i;
+ break;
+ }
+ }
+ if (samePartsLength === 0) {
+ return to;
+ }
+ let outputParts = [];
+ for (let i = samePartsLength; i < from.length; i++) {
+ outputParts.push('..');
+ }
+ outputParts = [...outputParts, ...to.slice(samePartsLength)];
+ if (outputParts[0] !== '..') {
+ outputParts.unshift('.');
+ }
+ return outputParts;
+}
+
+export function getSlotRelativePath(options: {
+ contextData: IContextData;
+ from: string;
+ to: string;
+}) {
+ const { contextData, from, to } = options;
+ const isSingleComponent = contextData?.extraContextData?.projectRemark?.isSingleComponent;
+ const template = contextData?.extraContextData?.template;
+ let toPath = template.slots[to].path;
+ toPath = [...toPath, template.slots[to].fileName!];
+ let fromPath = template.slots[from].path;
+ if (!isSingleComponent && ['components', 'pages'].indexOf(from) !== -1) {
+ fromPath = [...fromPath, 'pageName'];
+ }
+ return relativePath(fromPath, toPath).join('/');
+}
\ No newline at end of file
diff --git a/modules/code-generator/src/utils/schema.ts b/modules/code-generator/src/utils/schema.ts
index b2faecbc64..f9529945eb 100644
--- a/modules/code-generator/src/utils/schema.ts
+++ b/modules/code-generator/src/utils/schema.ts
@@ -1,20 +1,21 @@
import * as _ from 'lodash';
import {
- JSExpression,
- NodeData,
- NodeSchema,
+ IPublicTypeJSExpression,
+ IPublicTypeNodeData,
+ IPublicTypeNodeSchema,
isJSExpression,
isJSSlot,
isDOMText,
- ContainerSchema,
- NpmInfo,
- CompositeValue,
+ IPublicTypeContainerSchema,
+ IPublicTypeNpmInfo,
+ IPublicTypeCompositeValue,
isNodeSchema,
isJSFunction,
} from '@alilc/lowcode-types';
import { CodeGeneratorError } from '../types/error';
+import { isJSExpressionFn } from './common';
-export function isContainerSchema(x: any): x is ContainerSchema {
+export function isContainerSchema(x: any): x is IPublicTypeContainerSchema {
return (
typeof x === 'object' &&
x &&
@@ -23,7 +24,7 @@ export function isContainerSchema(x: any): x is ContainerSchema {
);
}
-export function isNpmInfo(x: any): x is NpmInfo {
+export function isNpmInfo(x: any): x is IPublicTypeNpmInfo {
return typeof x === 'object' && x && typeof x.package === 'string';
}
@@ -43,11 +44,11 @@ const DEFAULT_MAX_DEPTH = 100000;
* @returns
*/
export function handleSubNodes(
- children: NodeSchema['children'],
+ children: IPublicTypeNodeSchema['children'],
handlers: {
string?: (i: string) => T;
- expression?: (i: JSExpression) => T;
- node?: (i: NodeSchema) => T;
+ expression?: (i: IPublicTypeJSExpression) => T;
+ node?: (i: IPublicTypeNodeSchema) => T;
},
options?: {
rerun?: boolean;
@@ -64,7 +65,7 @@ export function handleSubNodes(
}
if (Array.isArray(children)) {
- const list: NodeData[] = children as NodeData[];
+ const list: IPublicTypeNodeData[] = children as IPublicTypeNodeData[];
return list
.map((child) => handleSubNodes(child, handlers, { ...opt, maxDepth: maxDepth - 1 }))
.reduce((p, c) => p.concat(c), []);
@@ -84,7 +85,7 @@ export function handleSubNodes(
return handleSubNodes(children.value, handlers, { ...opt, maxDepth: maxDepth - 1 });
} else if (isNodeSchema(children)) {
const handler = handlers.node || noop;
- const child = children as NodeSchema;
+ const child = children as IPublicTypeNodeSchema;
result = handler(child);
if (child.children) {
@@ -100,7 +101,7 @@ export function handleSubNodes(
});
} else {
Object.values(child.props).forEach((value) => {
- const childRes = handleCompositeValueInProps(value);
+ const childRes = handleCompositeValueInProps(value as IPublicTypeCompositeValue);
childrenRes.push(...childRes);
});
}
@@ -115,7 +116,7 @@ export function handleSubNodes(
return childrenRes;
- function handleCompositeValueInProps(value: CompositeValue): T[] {
+ function handleCompositeValueInProps(value: IPublicTypeCompositeValue): T[] {
if (isJSSlot(value)) {
return handleSubNodes(value.value, handlers, { ...opt, maxDepth: maxDepth - 1 });
}
@@ -125,9 +126,10 @@ export function handleSubNodes(
return _.flatMap(value, (v) => handleCompositeValueInProps(v));
}
- // CompositeObject
+ // IPublicTypeCompositeObject
if (
!isJSExpression(value) &&
+ !isJSExpressionFn(value) &&
!isJSFunction(value) &&
typeof value === 'object' &&
value !== null
@@ -138,3 +140,17 @@ export function handleSubNodes(
return [];
}
}
+
+export function isValidContainerType(schema: IPublicTypeNodeSchema) {
+ return [
+ 'Page',
+ 'Component',
+ 'Block',
+ ].includes(schema.componentName);
+}
+
+export const enum ContainerType {
+ Page = 'Page',
+ Component = 'Component',
+ Block = 'Block',
+}
\ No newline at end of file
diff --git a/modules/code-generator/src/utils/theme.ts b/modules/code-generator/src/utils/theme.ts
new file mode 100644
index 0000000000..590cf711cf
--- /dev/null
+++ b/modules/code-generator/src/utils/theme.ts
@@ -0,0 +1,20 @@
+/**
+ * 获取主题信息
+ * @param theme theme 形如 @alife/theme-97 或者 @alife/theme-97@^1.0.0
+ */
+
+export interface ThemeInfo {
+ name: string;
+ version?: string;
+}
+
+export function getThemeInfo(theme: string): ThemeInfo {
+ const sepIdx = theme.indexOf('@', 1);
+ if (sepIdx === -1) {
+ return { name: theme };
+ }
+ return {
+ name: theme.slice(0, sepIdx),
+ version: theme.slice(sepIdx + 1),
+ };
+}
\ No newline at end of file
diff --git a/modules/code-generator/tests/bugfix/i18n-with-params.test.ts b/modules/code-generator/tests/bugfix/i18n-with-params.test.ts
index be2d5e9e68..b58afda8cf 100644
--- a/modules/code-generator/tests/bugfix/i18n-with-params.test.ts
+++ b/modules/code-generator/tests/bugfix/i18n-with-params.test.ts
@@ -1,7 +1,7 @@
import CodeGenerator from '../../src';
import * as fs from 'fs';
import * as path from 'path';
-import { ProjectSchema } from '@alilc/lowcode-types';
+import { IPublicTypeProjectSchema } from '@alilc/lowcode-types';
import { createDiskPublisher } from '../helpers/solutionHelper';
const testCaseBaseName = path.basename(__filename, '.test.ts');
@@ -19,7 +19,7 @@ describe(testCaseBaseName, () => {
`
@@ -31,7 +31,7 @@ describe(testCaseBaseName, () => {
function exportProject(
importPath: string,
outputPath: string,
- mergeSchema?: Partial,
+ mergeSchema?: Partial,
) {
const schemaJsonStr = fs.readFileSync(importPath, { encoding: 'utf8' });
const schema = { ...JSON.parse(schemaJsonStr), ...mergeSchema };
diff --git a/modules/code-generator/tests/bugfix/icejs-import-wrong-naming.test.ts b/modules/code-generator/tests/bugfix/icejs-import-wrong-naming.test.ts
index 51e22b3a97..efb7fd5bee 100644
--- a/modules/code-generator/tests/bugfix/icejs-import-wrong-naming.test.ts
+++ b/modules/code-generator/tests/bugfix/icejs-import-wrong-naming.test.ts
@@ -1,7 +1,7 @@
import CodeGenerator from '../../src';
import * as fs from 'fs';
import * as path from 'path';
-import { ProjectSchema } from '@alilc/lowcode-types';
+import { IPublicTypeProjectSchema } from '@alilc/lowcode-types';
import { createDiskPublisher } from '../helpers/solutionHelper';
const testCaseBaseName = path.basename(__filename, '.test.ts');
@@ -27,7 +27,7 @@ describe(testCaseBaseName, () => {
});
const generatedPageFileContent = readOutputTextFile('demo-project/src/pages/Test/index.jsx');
- expect(generatedPageFileContent).toContain(`import Foo from "example-package/lib/index.js";`);
+ expect(generatedPageFileContent).toContain('import Foo from \'example-package/lib/index.js\';');
});
test('named import with no alias', async () => {
@@ -47,7 +47,7 @@ describe(testCaseBaseName, () => {
const generatedPageFileContent = readOutputTextFile('demo-project/src/pages/Test/index.jsx');
expect(generatedPageFileContent).toContain(
- `import { Foo } from "example-package/lib/index.js";`,
+ 'import { Foo } from \'example-package/lib/index.js\';',
);
});
@@ -68,7 +68,7 @@ describe(testCaseBaseName, () => {
const generatedPageFileContent = readOutputTextFile('demo-project/src/pages/Test/index.jsx');
expect(generatedPageFileContent).toContain(
- `import { Bar as Foo } from "example-package/lib/index.js";`,
+ 'import { Bar as Foo } from \'example-package/lib/index.js\';',
);
});
@@ -88,7 +88,7 @@ describe(testCaseBaseName, () => {
});
const generatedPageFileContent = readOutputTextFile('demo-project/src/pages/Test/index.jsx');
- expect(generatedPageFileContent).toContain(`import Foo from "example-package/lib/index.js";`);
+ expect(generatedPageFileContent).toContain('import Foo from \'example-package/lib/index.js\';');
});
test('default import with sub name and export name', async () => {
@@ -107,9 +107,9 @@ describe(testCaseBaseName, () => {
});
const generatedPageFileContent = readOutputTextFile('demo-project/src/pages/Test/index.jsx');
- expect(generatedPageFileContent).toContain(`import Bar from "example-package/lib/index.js";`);
+ expect(generatedPageFileContent).toContain('import Bar from \'example-package/lib/index.js\';');
- expect(generatedPageFileContent).toContain(`const Foo = Bar.Baz;`);
+ expect(generatedPageFileContent).toContain('const Foo = Bar.Baz;');
});
test('default import with sub name without export name', async () => {
@@ -129,10 +129,10 @@ describe(testCaseBaseName, () => {
const generatedPageFileContent = readOutputTextFile('demo-project/src/pages/Test/index.jsx');
expect(generatedPageFileContent).toContain(
- `import __$examplePackage_default from "example-package/lib/index.js";`,
+ 'import __$examplePackage_default from \'example-package/lib/index.js\';',
);
- expect(generatedPageFileContent).toContain(`const Foo = __$examplePackage_default.Baz;`);
+ expect(generatedPageFileContent).toContain('const Foo = __$examplePackage_default.Baz;');
});
test('named import with sub name', async () => {
@@ -152,10 +152,10 @@ describe(testCaseBaseName, () => {
const generatedPageFileContent = readOutputTextFile('demo-project/src/pages/Test/index.jsx');
expect(generatedPageFileContent).toContain(
- `import { Bar } from "example-package/lib/index.js";`,
+ 'import { Bar } from \'example-package/lib/index.js\';',
);
- expect(generatedPageFileContent).toContain(`const Foo = Bar.Baz;`);
+ expect(generatedPageFileContent).toContain('const Foo = Bar.Baz;');
});
test('default imports with different componentName', async () => {
@@ -187,18 +187,18 @@ describe(testCaseBaseName, () => {
});
const generatedPageFileContent = readOutputTextFile('demo-project/src/pages/Test/index.jsx');
- expect(generatedPageFileContent).toContain(`import Foo from "example-package";`);
- expect(generatedPageFileContent).toContain(`import Baz from "example-package";`);
+ expect(generatedPageFileContent).toContain('import Foo from \'example-package\';');
+ expect(generatedPageFileContent).toContain('import Baz from \'example-package\';');
- expect(generatedPageFileContent).not.toContain(`const Foo =`);
- expect(generatedPageFileContent).not.toContain(`const Baz =`);
+ expect(generatedPageFileContent).not.toContain('const Foo =');
+ expect(generatedPageFileContent).not.toContain('const Baz =');
});
});
function exportProject(
importPath: string,
outputPath: string,
- mergeSchema?: Partial,
+ mergeSchema?: Partial,
) {
const schemaJsonStr = fs.readFileSync(importPath, { encoding: 'utf8' });
const schema = { ...JSON.parse(schemaJsonStr), ...mergeSchema };
diff --git a/modules/code-generator/tests/bugfix/icejs-missing-imports-1.test.ts b/modules/code-generator/tests/bugfix/icejs-missing-imports-1.test.ts
index 17c40a4fa1..cad73474ac 100644
--- a/modules/code-generator/tests/bugfix/icejs-missing-imports-1.test.ts
+++ b/modules/code-generator/tests/bugfix/icejs-missing-imports-1.test.ts
@@ -22,7 +22,7 @@ test(testCaseBaseName, async () => {
Button,
Typography,
Tag,
-} from "@alilc/antd-lowcode-materials/dist/antd-lowcode.esm.js";`);
+} from '@alilc/antd-lowcode-materials/dist/antd-lowcode.esm.js';`);
});
function exportProject(inputPath: string, outputPath: string) {
diff --git a/modules/code-generator/tests/bugfix/icejs-package-json-dependencies.test.ts b/modules/code-generator/tests/bugfix/icejs-package-json-dependencies.test.ts
index 6edd6b9124..88ca02c6ba 100644
--- a/modules/code-generator/tests/bugfix/icejs-package-json-dependencies.test.ts
+++ b/modules/code-generator/tests/bugfix/icejs-package-json-dependencies.test.ts
@@ -19,15 +19,15 @@ test(testCaseBaseName, async () => {
// 里面有的数据源则应该生成对应的 dependencies
expect(generatedPackageJson.dependencies).toMatchObject({
- '@alilc/lowcode-datasource-engine': 'latest',
- '@alilc/lowcode-datasource-fetch-handler': 'latest',
+ '@alilc/lowcode-datasource-engine': '^1.0.0',
+ '@alilc/lowcode-datasource-fetch-handler': '^1.0.0',
});
// 里面没有的,则不应该生成对应的 dependencies
expect(generatedPackageJson.dependencies).not.toMatchObject({
- '@alilc/lowcode-datasource-url-params-handler': 'latest',
- '@alilc/lowcode-datasource-mtop-handler': 'latest',
- '@alilc/lowcode-datasource-mopen-handler': 'latest',
+ '@alilc/lowcode-datasource-url-params-handler': '^1.0.0',
+ '@alilc/lowcode-datasource-mtop-handler': '^1.0.0',
+ '@alilc/lowcode-datasource-mopen-handler': '^1.0.0',
});
});
diff --git a/modules/code-generator/tests/bugfix/strict-mode-context-1.test.ts b/modules/code-generator/tests/bugfix/strict-mode-context-1.test.ts
index 9cfb09325d..ebca6a26fd 100644
--- a/modules/code-generator/tests/bugfix/strict-mode-context-1.test.ts
+++ b/modules/code-generator/tests/bugfix/strict-mode-context-1.test.ts
@@ -1,7 +1,7 @@
import CodeGenerator from '../../src';
import * as fs from 'fs';
import * as path from 'path';
-import { ProjectSchema } from '@alilc/lowcode-types';
+import { IPublicTypeProjectSchema } from '@alilc/lowcode-types';
import { createDiskPublisher } from '../helpers/solutionHelper';
import { IceJsProjectBuilderOptions } from '../../src/solutions/icejs';
@@ -33,7 +33,7 @@ describe(testCaseBaseName, () => {
function exportProject(
importPath: string,
outputPath: string,
- mergeSchema?: Partial,
+ mergeSchema?: Partial,
options?: IceJsProjectBuilderOptions,
) {
const schemaJsonStr = fs.readFileSync(importPath, { encoding: 'utf8' });
diff --git a/modules/code-generator/tests/bugfix/tolerate-eval-errors-1-loop.test.ts b/modules/code-generator/tests/bugfix/tolerate-eval-errors-1-loop.test.ts
index 11ba9de054..78f2b50b32 100644
--- a/modules/code-generator/tests/bugfix/tolerate-eval-errors-1-loop.test.ts
+++ b/modules/code-generator/tests/bugfix/tolerate-eval-errors-1-loop.test.ts
@@ -1,7 +1,7 @@
import CodeGenerator from '../../src';
import * as fs from 'fs';
import * as path from 'path';
-import { ProjectSchema } from '@alilc/lowcode-types';
+import { IPublicTypeProjectSchema } from '@alilc/lowcode-types';
import { createDiskPublisher } from '../helpers/solutionHelper';
import { IceJsProjectBuilderOptions } from '../../src/solutions/icejs';
@@ -28,7 +28,7 @@ test('loop should be generated link __$$evalArray(xxx).map', async () => {
function exportProject(
importPath: string,
outputPath: string,
- mergeSchema?: Partial,
+ mergeSchema?: Partial,
options?: IceJsProjectBuilderOptions,
) {
const schemaJsonStr = fs.readFileSync(importPath, { encoding: 'utf8' });
diff --git a/modules/code-generator/tests/bugfix/tolerate-eval-errors-2-nested-loop.test.ts b/modules/code-generator/tests/bugfix/tolerate-eval-errors-2-nested-loop.test.ts
index e8b65a5442..1842005938 100644
--- a/modules/code-generator/tests/bugfix/tolerate-eval-errors-2-nested-loop.test.ts
+++ b/modules/code-generator/tests/bugfix/tolerate-eval-errors-2-nested-loop.test.ts
@@ -1,7 +1,7 @@
import CodeGenerator from '../../src';
import * as fs from 'fs';
import * as path from 'path';
-import { ProjectSchema } from '@alilc/lowcode-types';
+import { IPublicTypeProjectSchema } from '@alilc/lowcode-types';
import { createDiskPublisher } from '../helpers/solutionHelper';
import { IceJsProjectBuilderOptions } from '../../src/solutions/icejs';
@@ -32,7 +32,7 @@ test('loop should be generated link __$$evalArray(xxx).map', async () => {
function exportProject(
importPath: string,
outputPath: string,
- mergeSchema?: Partial,
+ mergeSchema?: Partial,
options?: IceJsProjectBuilderOptions,
) {
const schemaJsonStr = fs.readFileSync(importPath, { encoding: 'utf8' });
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo1/expected/demo-project/.browserslistrc b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo1/expected/demo-project/.browserslistrc
new file mode 100644
index 0000000000..55a130413d
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo1/expected/demo-project/.browserslistrc
@@ -0,0 +1,3 @@
+defaults
+ios_saf 9
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo1/expected/demo-project/.gitignore b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo1/expected/demo-project/.gitignore
new file mode 100644
index 0000000000..4ec178818e
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo1/expected/demo-project/.gitignore
@@ -0,0 +1,25 @@
+
+# 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
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo1/expected/demo-project/README.md b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo1/expected/demo-project/README.md
new file mode 100644
index 0000000000..6d9dd75215
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo1/expected/demo-project/README.md
@@ -0,0 +1 @@
+This project is generated by lowcode-code-generator & lowcode-solution-icejs3.
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo1/expected/demo-project/ice.config.mts b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo1/expected/demo-project/ice.config.mts
new file mode 100644
index 0000000000..e1d8a28141
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo1/expected/demo-project/ice.config.mts
@@ -0,0 +1,90 @@
+import { join } from 'path';
+import { defineConfig } from '@ice/app';
+import _ from 'lodash';
+import fusion from '@ice/plugin-fusion';
+import locales from '@ice/plugin-moment-locales';
+import type { Plugin } from '@ice/app/esm/types';
+
+interface PluginOptions {
+ id: string;
+}
+
+const plugin: Plugin = (options) => ({
+ // name 可选,插件名称
+ name: 'plugin-name',
+ // setup 必选,用于定制工程构建配置
+ setup: ({ onGetConfig, modifyUserConfig }) => {
+ modifyUserConfig('codeSplitting', 'page');
+
+ onGetConfig((config) => {
+ config.entry = {
+ web: join(process.cwd(), '.ice/entry.client.tsx'),
+ };
+
+ config.cssFilename = '[name].css';
+
+ config.configureWebpack = config.configureWebpack || [];
+ config.configureWebpack?.push((webpackConfig) => {
+ if (webpackConfig.output) {
+ webpackConfig.output.filename = '[name].js';
+ webpackConfig.output.chunkFilename = '[name].js';
+ }
+ return webpackConfig;
+ });
+
+ config.swcOptions = _.merge(config.swcOptions, {
+ compilationConfig: {
+ jsc: {
+ transform: {
+ react: {
+ runtime: 'classic',
+ },
+ },
+ },
+ },
+ });
+
+ // 解决 webpack publicPath 问题
+ config.transforms = config.transforms || [];
+ config.transforms.push((source: string, id: string) => {
+ if (id.includes('.ice/entry.client.tsx')) {
+ let code = `
+ if (!__webpack_public_path__?.startsWith('http') && document.currentScript) {
+ // @ts-ignore
+ __webpack_public_path__ = document.currentScript.src.replace(/^(.*\\/)[^/]+$/, '$1');
+ window.__ICE_ASSETS_MANIFEST__ = window.__ICE_ASSETS_MANIFEST__ || {};
+ window.__ICE_ASSETS_MANIFEST__.publicPath = __webpack_public_path__;
+ }
+ `;
+ code += source;
+ return { code };
+ }
+ });
+ });
+ },
+});
+
+// The project config, see https://v3.ice.work/docs/guide/basic/config
+const minify = process.env.NODE_ENV === 'production' ? 'swc' : false;
+export default defineConfig(() => ({
+ ssr: false,
+ ssg: false,
+ minify,
+
+ externals: {
+ react: 'React',
+ 'react-dom': 'ReactDOM',
+ 'react-dom/client': 'ReactDOM',
+ '@alifd/next': 'Next',
+ lodash: 'var window._',
+ '@alilc/lowcode-engine': 'var window.AliLowCodeEngine',
+ },
+ plugins: [
+ fusion({
+ importStyle: 'sass',
+ }),
+ locales(),
+ plugin(),
+ ],
+}));
+
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo1/expected/demo-project/package.json b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo1/expected/demo-project/package.json
new file mode 100644
index 0000000000..38f24df0b1
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo1/expected/demo-project/package.json
@@ -0,0 +1,44 @@
+{
+ "name": "icejs3-demo-app",
+ "version": "0.1.5",
+ "description": "icejs 3 轻量级模板,使用 JavaScript,仅包含基础的 Layout。",
+ "dependencies": {
+ "moment": "^2.24.0",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
+ "react-router": "^6.9.0",
+ "react-router-dom": "^6.9.0",
+ "intl-messageformat": "^9.3.6",
+ "@alifd/next": "1.19.18",
+ "@ice/runtime": "~1.1.0",
+ "@alilc/lowcode-datasource-engine": "^1.0.0",
+ "@alilc/lowcode-datasource-url-params-handler": "^1.0.0",
+ "@alilc/lowcode-datasource-fetch-handler": "^1.0.0"
+ },
+ "devDependencies": {
+ "@ice/app": "~3.1.0",
+ "@types/react": "^18.0.0",
+ "@types/react-dom": "^18.0.0",
+ "@types/node": "^18.11.17",
+ "@ice/plugin-fusion": "^1.0.1",
+ "@ice/plugin-moment-locales": "^1.0.0",
+ "eslint": "^6.0.1",
+ "stylelint": "^13.2.0"
+ },
+ "scripts": {
+ "start": "ice start",
+ "build": "ice build",
+ "lint": "npm run eslint && npm run stylelint",
+ "eslint": "eslint --cache --ext .js,.jsx ./",
+ "stylelint": "stylelint ./**/*.scss"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "repository": {
+ "type": "git",
+ "url": "http://gitlab.xxx.com/msd/leak-scan/tree/master"
+ },
+ "private": true,
+ "originTemplate": "@alifd/scaffold-lite-js"
+}
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo1/expected/demo-project/src/app.ts b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo1/expected/demo-project/src/app.ts
new file mode 100644
index 0000000000..6d5856292d
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo1/expected/demo-project/src/app.ts
@@ -0,0 +1,13 @@
+import { defineAppConfig } from 'ice';
+
+// App config, see https://v3.ice.work/docs/guide/basic/app
+export default defineAppConfig(() => ({
+ // Set your configs here.
+ app: {
+ rootId: 'App',
+ },
+ router: {
+ type: 'browser',
+ basename: '/',
+ },
+}));
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo1/expected/demo-project/src/constants.js b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo1/expected/demo-project/src/constants.js
new file mode 100644
index 0000000000..91198f9044
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo1/expected/demo-project/src/constants.js
@@ -0,0 +1,3 @@
+const __$$constants = { ENV: 'prod', DOMAIN: 'xxx.xxx.com' };
+
+export default __$$constants;
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo1/expected/demo-project/src/document.tsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo1/expected/demo-project/src/document.tsx
new file mode 100644
index 0000000000..aff0231d95
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo1/expected/demo-project/src/document.tsx
@@ -0,0 +1,29 @@
+import React from 'react';
+import { Meta, Title, Links, Main, Scripts } from 'ice';
+
+export default function Document() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo1/expected/demo-project/src/global.scss b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo1/expected/demo-project/src/global.scss
new file mode 100644
index 0000000000..ed7204b4a3
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo1/expected/demo-project/src/global.scss
@@ -0,0 +1,13 @@
+// 引入默认全局样式
+@import '@alifd/next/reset.scss';
+
+body {
+ -webkit-font-smoothing: antialiased;
+}
+
+body {
+ font-size: 12px;
+}
+.table {
+ width: 100px;
+}
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo1/expected/demo-project/src/i18n.js b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo1/expected/demo-project/src/i18n.js
new file mode 100644
index 0000000000..1334d2502b
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo1/expected/demo-project/src/i18n.js
@@ -0,0 +1,77 @@
+const i18nConfig = {};
+
+let locale =
+ typeof navigator === 'object' && typeof navigator.language === 'string'
+ ? navigator.language
+ : 'zh-CN';
+
+const getLocale = () => locale;
+
+const setLocale = (target) => {
+ locale = target;
+};
+
+const isEmptyVariables = (variables) =>
+ (Array.isArray(variables) && variables.length === 0) ||
+ (typeof variables === 'object' &&
+ (!variables || Object.keys(variables).length === 0));
+
+// 按低代码规范里面的要求进行变量替换
+const format = (msg, variables) =>
+ typeof msg === 'string'
+ ? msg.replace(/\$?\{(\w+)\}/g, (match, key) => variables?.[key] ?? '')
+ : msg;
+
+const i18nFormat = ({ id, defaultMessage, fallback }, variables) => {
+ const msg =
+ i18nConfig[locale]?.[id] ??
+ i18nConfig[locale.replace('-', '_')]?.[id] ??
+ defaultMessage;
+ if (msg == null) {
+ console.warn('[i18n]: unknown message id: %o (locale=%o)', id, locale);
+ return fallback === undefined ? `${id}` : fallback;
+ }
+
+ return format(msg, variables);
+};
+
+const i18n = (id, params) => {
+ return i18nFormat({ id }, params);
+};
+
+// 将国际化的一些方法注入到目标对象&上下文中
+const _inject2 = (target) => {
+ target.i18n = i18n;
+ target.getLocale = getLocale;
+ target.setLocale = (locale) => {
+ setLocale(locale);
+ target.forceUpdate();
+ };
+ target._i18nText = (t) => {
+ // 优先取直接传过来的语料
+ const localMsg = t[locale] ?? t[String(locale).replace('-', '_')];
+ if (localMsg != null) {
+ return format(localMsg, t.params);
+ }
+
+ // 其次用项目级别的
+ const projectMsg = i18nFormat({ id: t.key, fallback: null }, t.params);
+ if (projectMsg != null) {
+ return projectMsg;
+ }
+
+ // 兜底用 use 指定的或默认语言的
+ return format(t[t.use || 'zh-CN'] ?? t.en_US, t.params);
+ };
+
+ // 注入到上下文中去
+ if (target._context && target._context !== target) {
+ Object.assign(target._context, {
+ i18n,
+ getLocale,
+ setLocale: target.setLocale,
+ });
+ }
+};
+
+export { getLocale, setLocale, i18n, i18nFormat, _inject2 };
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo1/expected/demo-project/src/layouts/BasicLayout/components/Footer/index.jsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo1/expected/demo-project/src/layouts/BasicLayout/components/Footer/index.jsx
new file mode 100644
index 0000000000..cc70d53bea
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo1/expected/demo-project/src/layouts/BasicLayout/components/Footer/index.jsx
@@ -0,0 +1,14 @@
+
+import React from 'react';
+import styles from './index.module.scss';
+
+export default function Footer() {
+ return (
+
+ Alibaba Fusion
+
+ © 2019-现在 Alibaba Fusion & ICE
+
+ );
+}
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo1/expected/demo-project/src/layouts/BasicLayout/components/Footer/index.module.scss b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo1/expected/demo-project/src/layouts/BasicLayout/components/Footer/index.module.scss
new file mode 100644
index 0000000000..81e77fda5f
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo1/expected/demo-project/src/layouts/BasicLayout/components/Footer/index.module.scss
@@ -0,0 +1,15 @@
+
+.footer {
+ line-height: 20px;
+ text-align: center;
+}
+
+.logo {
+ font-weight: bold;
+ font-size: 16px;
+}
+
+.copyright {
+ font-size: 12px;
+}
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo1/expected/demo-project/src/layouts/BasicLayout/components/Logo/index.jsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo1/expected/demo-project/src/layouts/BasicLayout/components/Logo/index.jsx
new file mode 100644
index 0000000000..265bfdaa07
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo1/expected/demo-project/src/layouts/BasicLayout/components/Logo/index.jsx
@@ -0,0 +1,16 @@
+
+import React from 'react';
+import { Link } from 'ice';
+import styles from './index.module.scss';
+
+export default function Logo({ image, text, url }) {
+ return (
+
+
+ {image &&

}
+
{text}
+
+
+ );
+}
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo1/expected/demo-project/src/layouts/BasicLayout/components/Logo/index.module.scss b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo1/expected/demo-project/src/layouts/BasicLayout/components/Logo/index.module.scss
new file mode 100644
index 0000000000..dad05a263f
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo1/expected/demo-project/src/layouts/BasicLayout/components/Logo/index.module.scss
@@ -0,0 +1,20 @@
+
+.logo{
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: #FF7300;
+ font-weight: bold;
+ font-size: 14px;
+ line-height: 22px;
+
+ &:visited, &:link {
+ color: #FF7300;
+ }
+
+ img {
+ height: 24px;
+ margin-right: 10px;
+ }
+}
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo1/expected/demo-project/src/layouts/BasicLayout/components/PageNav/index.jsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo1/expected/demo-project/src/layouts/BasicLayout/components/PageNav/index.jsx
new file mode 100644
index 0000000000..911998b0d3
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo1/expected/demo-project/src/layouts/BasicLayout/components/PageNav/index.jsx
@@ -0,0 +1,68 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { Link, useLocation } from 'ice';
+import { Nav } from '@alifd/next';
+import { asideMenuConfig } from '../../menuConfig';
+
+const { SubNav } = Nav;
+const NavItem = Nav.Item;
+
+function getNavMenuItems(menusData) {
+ if (!menusData) {
+ return [];
+ }
+
+ return menusData
+ .filter(item => item.name && !item.hideInMenu)
+ .map((item, index) => getSubMenuOrItem(item, index));
+}
+
+function getSubMenuOrItem(item, index) {
+ if (item.children && item.children.some(child => child.name)) {
+ const childrenItems = getNavMenuItems(item.children);
+
+ if (childrenItems && childrenItems.length > 0) {
+ const subNav = (
+
+ {childrenItems}
+
+ );
+ return subNav;
+ }
+
+ return null;
+ }
+
+ const navItem = (
+
+ {item.name}
+
+ );
+ return navItem;
+}
+
+const Navigation = (props, context) => {
+ const location = useLocation();
+ const { pathname } = location;
+ const { isCollapse } = context;
+ return (
+
+ );
+};
+
+Navigation.contextTypes = {
+ isCollapse: PropTypes.bool,
+};
+export default Navigation;
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo1/expected/demo-project/src/layouts/BasicLayout/index.jsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo1/expected/demo-project/src/layouts/BasicLayout/index.jsx
new file mode 100644
index 0000000000..18db44df5e
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo1/expected/demo-project/src/layouts/BasicLayout/index.jsx
@@ -0,0 +1,81 @@
+
+import React, { useState } from 'react';
+import { Shell, ConfigProvider } from '@alifd/next';
+import PageNav from './components/PageNav';
+import Logo from './components/Logo';
+import Footer from './components/Footer';
+
+(function() {
+ const throttle = function(type, name, obj = window) {
+ let running = false;
+
+ const func = () => {
+ if (running) {
+ return;
+ }
+
+ running = true;
+ requestAnimationFrame(() => {
+ obj.dispatchEvent(new CustomEvent(name));
+ running = false;
+ });
+ };
+
+ obj.addEventListener(type, func);
+ };
+
+ throttle('resize', 'optimizedResize');
+})();
+
+export default function BasicLayout({ children }) {
+ const getDevice = width => {
+ const isPhone =
+ typeof navigator !== 'undefined' && navigator && navigator.userAgent.match(/phone/gi);
+
+ if (width < 680 || isPhone) {
+ return 'phone';
+ }
+ if (width < 1280 && width > 680) {
+ return 'tablet';
+ }
+ return 'desktop';
+ };
+
+ const [device, setDevice] = useState(getDevice(NaN));
+ window.addEventListener('optimizedResize', e => {
+ setDevice(getDevice(e && e.target && e.target.innerWidth));
+ });
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ {children}
+
+
+
+
+
+ );
+}
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo1/expected/demo-project/src/layouts/BasicLayout/menuConfig.js b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo1/expected/demo-project/src/layouts/BasicLayout/menuConfig.js
new file mode 100644
index 0000000000..5332202be4
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo1/expected/demo-project/src/layouts/BasicLayout/menuConfig.js
@@ -0,0 +1,11 @@
+
+const headerMenuConfig = [];
+const asideMenuConfig = [
+ {
+ name: 'Dashboard',
+ path: '/',
+ icon: 'smile',
+ },
+];
+export { headerMenuConfig, asideMenuConfig };
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/react-app/demo9-datasource-engine/expected/demo-project/src/components/Index/index.css b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo1/expected/demo-project/src/pages/Test/index.css
similarity index 100%
rename from modules/code-generator/tests/fixtures/test-cases/react-app/demo9-datasource-engine/expected/demo-project/src/components/Index/index.css
rename to modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo1/expected/demo-project/src/pages/Test/index.css
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo1/expected/demo-project/src/pages/Test/index.jsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo1/expected/demo-project/src/pages/Test/index.jsx
new file mode 100644
index 0000000000..794ad46a48
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo1/expected/demo-project/src/pages/Test/index.jsx
@@ -0,0 +1,205 @@
+// 注意: 出码引擎注入的临时变量默认都以 "__$$" 开头,禁止在搭建的代码中直接访问。
+// 例外:react 框架的导出名和各种组件名除外。
+import React from 'react';
+
+import { Form, Input, NumberPicker, Select, Button } from '@alifd/next';
+
+import { createUrlParamsHandler as __$$createUrlParamsRequestHandler } from '@alilc/lowcode-datasource-url-params-handler';
+
+import { createFetchHandler as __$$createFetchRequestHandler } from '@alilc/lowcode-datasource-fetch-handler';
+
+import { create as __$$createDataSourceEngine } from '@alilc/lowcode-datasource-engine/runtime';
+
+import '@alifd/next/lib/form/style';
+
+import '@alifd/next/lib/input/style';
+
+import '@alifd/next/lib/number-picker/style';
+
+import '@alifd/next/lib/select/style';
+
+import '@alifd/next/lib/button/style';
+
+import utils, { RefsManager } from '../../utils';
+
+import * as __$$i18n from '../../i18n';
+
+import __$$constants from '../../constants';
+
+import './index.css';
+
+class Test$$Page extends React.Component {
+ _context = this;
+
+ _dataSourceConfig = this._defineDataSourceConfig();
+ _dataSourceEngine = __$$createDataSourceEngine(this._dataSourceConfig, this, {
+ runtimeConfig: true,
+ requestHandlersMap: {
+ urlParams: __$$createUrlParamsRequestHandler(window.location.search),
+ fetch: __$$createFetchRequestHandler(),
+ },
+ });
+
+ get dataSourceMap() {
+ return this._dataSourceEngine.dataSourceMap || {};
+ }
+
+ reloadDataSource = async () => {
+ await this._dataSourceEngine.reloadDataSource();
+ };
+
+ get constants() {
+ return __$$constants || {};
+ }
+
+ constructor(props, context) {
+ super(props);
+
+ this.utils = utils;
+
+ this._refsManager = new RefsManager();
+
+ __$$i18n._inject2(this);
+
+ this.state = { text: 'outter' };
+ }
+
+ $ = (refName) => {
+ return this._refsManager.get(refName);
+ };
+
+ $$ = (refName) => {
+ return this._refsManager.getAll(refName);
+ };
+
+ _defineDataSourceConfig() {
+ const _this = this;
+ return {
+ list: [
+ {
+ id: 'urlParams',
+ type: 'urlParams',
+ isInit: function () {
+ return undefined;
+ }.bind(_this),
+ options: function () {
+ return undefined;
+ }.bind(_this),
+ },
+ {
+ id: 'user',
+ type: 'fetch',
+ options: function () {
+ return {
+ method: 'GET',
+ uri: 'https://shs.xxx.com/mock/1458/demo/user',
+ isSync: true,
+ };
+ }.bind(_this),
+ dataHandler: function (response) {
+ if (!response.data.success) {
+ throw new Error(response.data.message);
+ }
+ return response.data.data;
+ },
+ isInit: function () {
+ return undefined;
+ }.bind(_this),
+ },
+ {
+ id: 'orders',
+ type: 'fetch',
+ options: function () {
+ return {
+ method: 'GET',
+ uri: 'https://shs.xxx.com/mock/1458/demo/orders',
+ isSync: true,
+ };
+ }.bind(_this),
+ dataHandler: function (response) {
+ if (!response.data.success) {
+ throw new Error(response.data.message);
+ }
+ return response.data.data.result;
+ },
+ isInit: function () {
+ return undefined;
+ }.bind(_this),
+ },
+ ],
+ dataHandler: function (dataMap) {
+ console.info('All datasources loaded:', dataMap);
+ },
+ };
+ }
+
+ componentDidMount() {
+ this._dataSourceEngine.reloadDataSource();
+
+ console.log('componentDidMount');
+ }
+
+ render() {
+ const __$$context = this._context || this;
+ const { state } = __$$context;
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+ {__$$evalArray(() => ['a', 'b', 'c']).map((item, index) =>
+ ((__$$context) =>
+ !!__$$eval(() => index >= 1) && (
+
+ ))(__$$createChildContext(__$$context, { item, index }))
+ )}
+
+
+
+
+ );
+ }
+}
+
+export default Test$$Page;
+
+function __$$eval(expr) {
+ try {
+ return expr();
+ } catch (error) {}
+}
+
+function __$$evalArray(expr) {
+ const res = __$$eval(expr);
+ return Array.isArray(res) ? res : [];
+}
+
+function __$$createChildContext(oldContext, ext) {
+ const childContext = {
+ ...oldContext,
+ ...ext,
+ };
+ childContext.__proto__ = oldContext;
+ return childContext;
+}
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo1/expected/demo-project/src/pages/layout.jsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo1/expected/demo-project/src/pages/layout.jsx
new file mode 100644
index 0000000000..50fbb2d1f1
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo1/expected/demo-project/src/pages/layout.jsx
@@ -0,0 +1,10 @@
+import { Outlet } from 'ice';
+import BasicLayout from '@/layouts/BasicLayout';
+
+export default function Layout() {
+ return (
+
+
+
+ );
+}
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo1/expected/demo-project/src/typings.d.ts b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo1/expected/demo-project/src/typings.d.ts
new file mode 100644
index 0000000000..a9f8de7ceb
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo1/expected/demo-project/src/typings.d.ts
@@ -0,0 +1,9 @@
+///
+
+export {};
+declare global {
+ interface Window {
+ g_config: Record;
+ }
+}
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo1/expected/demo-project/src/utils.js b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo1/expected/demo-project/src/utils.js
new file mode 100644
index 0000000000..1190717924
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo1/expected/demo-project/src/utils.js
@@ -0,0 +1,47 @@
+import { createRef } from 'react';
+
+export class RefsManager {
+ constructor() {
+ this.refInsStore = {};
+ }
+
+ clearNullRefs() {
+ Object.keys(this.refInsStore).forEach((refName) => {
+ const filteredInsList = this.refInsStore[refName].filter(
+ (insRef) => !!insRef.current
+ );
+ if (filteredInsList.length > 0) {
+ this.refInsStore[refName] = filteredInsList;
+ } else {
+ delete this.refInsStore[refName];
+ }
+ });
+ }
+
+ get(refName) {
+ this.clearNullRefs();
+ if (this.refInsStore[refName] && this.refInsStore[refName].length > 0) {
+ return this.refInsStore[refName][0].current;
+ }
+
+ return null;
+ }
+
+ getAll(refName) {
+ this.clearNullRefs();
+ if (this.refInsStore[refName] && this.refInsStore[refName].length > 0) {
+ return this.refInsStore[refName].map((i) => i.current);
+ }
+
+ return [];
+ }
+
+ linkRef(refName) {
+ const refIns = createRef();
+ this.refInsStore[refName] = this.refInsStore[refName] || [];
+ this.refInsStore[refName].push(refIns);
+ return refIns;
+ }
+}
+
+export default {};
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo1/schema.json5 b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo1/schema.json5
new file mode 100644
index 0000000000..76c52fb5e8
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo1/schema.json5
@@ -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": "JSFunction",
+ "value": "function() { console.log('componentDidMount'); }"
+ }
+ },
+ dataSource: {
+ list: [
+ {
+ id: 'urlParams',
+ type: 'urlParams',
+ },
+ // 示例数据源:https://shs.xxx.com/mock/1458/demo/user
+ {
+ id: 'user',
+ type: 'fetch',
+ options: {
+ method: 'GET',
+ uri: 'https://shs.xxx.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}',
+ },
+ },
+ // 示例数据源:https://shs.xxx.com/mock/1458/demo/orders
+ {
+ id: 'orders',
+ type: 'fetch',
+ options: {
+ method: 'GET',
+ uri: "https://shs.xxx.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.xxx.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": "月飞"
+ }
+}
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2-utils-name-alias/expected/demo-project/.browserslistrc b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2-utils-name-alias/expected/demo-project/.browserslistrc
new file mode 100644
index 0000000000..55a130413d
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2-utils-name-alias/expected/demo-project/.browserslistrc
@@ -0,0 +1,3 @@
+defaults
+ios_saf 9
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2-utils-name-alias/expected/demo-project/.gitignore b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2-utils-name-alias/expected/demo-project/.gitignore
new file mode 100644
index 0000000000..4ec178818e
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2-utils-name-alias/expected/demo-project/.gitignore
@@ -0,0 +1,25 @@
+
+# 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
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2-utils-name-alias/expected/demo-project/README.md b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2-utils-name-alias/expected/demo-project/README.md
new file mode 100644
index 0000000000..6d9dd75215
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2-utils-name-alias/expected/demo-project/README.md
@@ -0,0 +1 @@
+This project is generated by lowcode-code-generator & lowcode-solution-icejs3.
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2-utils-name-alias/expected/demo-project/ice.config.mts b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2-utils-name-alias/expected/demo-project/ice.config.mts
new file mode 100644
index 0000000000..e1d8a28141
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2-utils-name-alias/expected/demo-project/ice.config.mts
@@ -0,0 +1,90 @@
+import { join } from 'path';
+import { defineConfig } from '@ice/app';
+import _ from 'lodash';
+import fusion from '@ice/plugin-fusion';
+import locales from '@ice/plugin-moment-locales';
+import type { Plugin } from '@ice/app/esm/types';
+
+interface PluginOptions {
+ id: string;
+}
+
+const plugin: Plugin = (options) => ({
+ // name 可选,插件名称
+ name: 'plugin-name',
+ // setup 必选,用于定制工程构建配置
+ setup: ({ onGetConfig, modifyUserConfig }) => {
+ modifyUserConfig('codeSplitting', 'page');
+
+ onGetConfig((config) => {
+ config.entry = {
+ web: join(process.cwd(), '.ice/entry.client.tsx'),
+ };
+
+ config.cssFilename = '[name].css';
+
+ config.configureWebpack = config.configureWebpack || [];
+ config.configureWebpack?.push((webpackConfig) => {
+ if (webpackConfig.output) {
+ webpackConfig.output.filename = '[name].js';
+ webpackConfig.output.chunkFilename = '[name].js';
+ }
+ return webpackConfig;
+ });
+
+ config.swcOptions = _.merge(config.swcOptions, {
+ compilationConfig: {
+ jsc: {
+ transform: {
+ react: {
+ runtime: 'classic',
+ },
+ },
+ },
+ },
+ });
+
+ // 解决 webpack publicPath 问题
+ config.transforms = config.transforms || [];
+ config.transforms.push((source: string, id: string) => {
+ if (id.includes('.ice/entry.client.tsx')) {
+ let code = `
+ if (!__webpack_public_path__?.startsWith('http') && document.currentScript) {
+ // @ts-ignore
+ __webpack_public_path__ = document.currentScript.src.replace(/^(.*\\/)[^/]+$/, '$1');
+ window.__ICE_ASSETS_MANIFEST__ = window.__ICE_ASSETS_MANIFEST__ || {};
+ window.__ICE_ASSETS_MANIFEST__.publicPath = __webpack_public_path__;
+ }
+ `;
+ code += source;
+ return { code };
+ }
+ });
+ });
+ },
+});
+
+// The project config, see https://v3.ice.work/docs/guide/basic/config
+const minify = process.env.NODE_ENV === 'production' ? 'swc' : false;
+export default defineConfig(() => ({
+ ssr: false,
+ ssg: false,
+ minify,
+
+ externals: {
+ react: 'React',
+ 'react-dom': 'ReactDOM',
+ 'react-dom/client': 'ReactDOM',
+ '@alifd/next': 'Next',
+ lodash: 'var window._',
+ '@alilc/lowcode-engine': 'var window.AliLowCodeEngine',
+ },
+ plugins: [
+ fusion({
+ importStyle: 'sass',
+ }),
+ locales(),
+ plugin(),
+ ],
+}));
+
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2-utils-name-alias/expected/demo-project/package.json b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2-utils-name-alias/expected/demo-project/package.json
new file mode 100644
index 0000000000..3a0015c9bd
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2-utils-name-alias/expected/demo-project/package.json
@@ -0,0 +1,48 @@
+{
+ "name": "icejs3-demo-app",
+ "version": "0.1.5",
+ "description": "icejs 3 轻量级模板,使用 JavaScript,仅包含基础的 Layout。",
+ "dependencies": {
+ "moment": "^2.24.0",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
+ "react-router": "^6.9.0",
+ "react-router-dom": "^6.9.0",
+ "intl-messageformat": "^9.3.6",
+ "@alifd/next": "1.26.15",
+ "@ice/runtime": "~1.1.0",
+ "@alilc/lowcode-datasource-engine": "^1.0.0",
+ "@alilc/lowcode-datasource-url-params-handler": "^1.0.0",
+ "@alilc/b6-page": "^0.1.0",
+ "@alilc/b6-text": "^0.1.0",
+ "@alilc/b6-compact-legao-builtin": "1.x",
+ "antd": "3.x",
+ "@alilc/b6-util-mocks": "1.x"
+ },
+ "devDependencies": {
+ "@ice/app": "~3.1.0",
+ "@types/react": "^18.0.0",
+ "@types/react-dom": "^18.0.0",
+ "@types/node": "^18.11.17",
+ "@ice/plugin-fusion": "^1.0.1",
+ "@ice/plugin-moment-locales": "^1.0.0",
+ "eslint": "^6.0.1",
+ "stylelint": "^13.2.0"
+ },
+ "scripts": {
+ "start": "ice start",
+ "build": "ice build",
+ "lint": "npm run eslint && npm run stylelint",
+ "eslint": "eslint --cache --ext .js,.jsx ./",
+ "stylelint": "stylelint ./**/*.scss"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "repository": {
+ "type": "git",
+ "url": "http://gitlab.xxx.com/msd/leak-scan/tree/master"
+ },
+ "private": true,
+ "originTemplate": "@alifd/scaffold-lite-js"
+}
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2-utils-name-alias/expected/demo-project/src/app.ts b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2-utils-name-alias/expected/demo-project/src/app.ts
new file mode 100644
index 0000000000..6d5856292d
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2-utils-name-alias/expected/demo-project/src/app.ts
@@ -0,0 +1,13 @@
+import { defineAppConfig } from 'ice';
+
+// App config, see https://v3.ice.work/docs/guide/basic/app
+export default defineAppConfig(() => ({
+ // Set your configs here.
+ app: {
+ rootId: 'App',
+ },
+ router: {
+ type: 'browser',
+ basename: '/',
+ },
+}));
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2-utils-name-alias/expected/demo-project/src/constants.js b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2-utils-name-alias/expected/demo-project/src/constants.js
new file mode 100644
index 0000000000..ea766c9da3
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2-utils-name-alias/expected/demo-project/src/constants.js
@@ -0,0 +1,3 @@
+const __$$constants = {};
+
+export default __$$constants;
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2-utils-name-alias/expected/demo-project/src/document.tsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2-utils-name-alias/expected/demo-project/src/document.tsx
new file mode 100644
index 0000000000..aff0231d95
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2-utils-name-alias/expected/demo-project/src/document.tsx
@@ -0,0 +1,29 @@
+import React from 'react';
+import { Meta, Title, Links, Main, Scripts } from 'ice';
+
+export default function Document() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2-utils-name-alias/expected/demo-project/src/global.scss b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2-utils-name-alias/expected/demo-project/src/global.scss
new file mode 100644
index 0000000000..82ca3eac73
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2-utils-name-alias/expected/demo-project/src/global.scss
@@ -0,0 +1,6 @@
+// 引入默认全局样式
+@import '@alifd/next/reset.scss';
+
+body {
+ -webkit-font-smoothing: antialiased;
+}
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2-utils-name-alias/expected/demo-project/src/i18n.js b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2-utils-name-alias/expected/demo-project/src/i18n.js
new file mode 100644
index 0000000000..1334d2502b
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2-utils-name-alias/expected/demo-project/src/i18n.js
@@ -0,0 +1,77 @@
+const i18nConfig = {};
+
+let locale =
+ typeof navigator === 'object' && typeof navigator.language === 'string'
+ ? navigator.language
+ : 'zh-CN';
+
+const getLocale = () => locale;
+
+const setLocale = (target) => {
+ locale = target;
+};
+
+const isEmptyVariables = (variables) =>
+ (Array.isArray(variables) && variables.length === 0) ||
+ (typeof variables === 'object' &&
+ (!variables || Object.keys(variables).length === 0));
+
+// 按低代码规范里面的要求进行变量替换
+const format = (msg, variables) =>
+ typeof msg === 'string'
+ ? msg.replace(/\$?\{(\w+)\}/g, (match, key) => variables?.[key] ?? '')
+ : msg;
+
+const i18nFormat = ({ id, defaultMessage, fallback }, variables) => {
+ const msg =
+ i18nConfig[locale]?.[id] ??
+ i18nConfig[locale.replace('-', '_')]?.[id] ??
+ defaultMessage;
+ if (msg == null) {
+ console.warn('[i18n]: unknown message id: %o (locale=%o)', id, locale);
+ return fallback === undefined ? `${id}` : fallback;
+ }
+
+ return format(msg, variables);
+};
+
+const i18n = (id, params) => {
+ return i18nFormat({ id }, params);
+};
+
+// 将国际化的一些方法注入到目标对象&上下文中
+const _inject2 = (target) => {
+ target.i18n = i18n;
+ target.getLocale = getLocale;
+ target.setLocale = (locale) => {
+ setLocale(locale);
+ target.forceUpdate();
+ };
+ target._i18nText = (t) => {
+ // 优先取直接传过来的语料
+ const localMsg = t[locale] ?? t[String(locale).replace('-', '_')];
+ if (localMsg != null) {
+ return format(localMsg, t.params);
+ }
+
+ // 其次用项目级别的
+ const projectMsg = i18nFormat({ id: t.key, fallback: null }, t.params);
+ if (projectMsg != null) {
+ return projectMsg;
+ }
+
+ // 兜底用 use 指定的或默认语言的
+ return format(t[t.use || 'zh-CN'] ?? t.en_US, t.params);
+ };
+
+ // 注入到上下文中去
+ if (target._context && target._context !== target) {
+ Object.assign(target._context, {
+ i18n,
+ getLocale,
+ setLocale: target.setLocale,
+ });
+ }
+};
+
+export { getLocale, setLocale, i18n, i18nFormat, _inject2 };
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2-utils-name-alias/expected/demo-project/src/layouts/BasicLayout/components/Footer/index.jsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2-utils-name-alias/expected/demo-project/src/layouts/BasicLayout/components/Footer/index.jsx
new file mode 100644
index 0000000000..cc70d53bea
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2-utils-name-alias/expected/demo-project/src/layouts/BasicLayout/components/Footer/index.jsx
@@ -0,0 +1,14 @@
+
+import React from 'react';
+import styles from './index.module.scss';
+
+export default function Footer() {
+ return (
+
+ Alibaba Fusion
+
+ © 2019-现在 Alibaba Fusion & ICE
+
+ );
+}
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2-utils-name-alias/expected/demo-project/src/layouts/BasicLayout/components/Footer/index.module.scss b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2-utils-name-alias/expected/demo-project/src/layouts/BasicLayout/components/Footer/index.module.scss
new file mode 100644
index 0000000000..81e77fda5f
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2-utils-name-alias/expected/demo-project/src/layouts/BasicLayout/components/Footer/index.module.scss
@@ -0,0 +1,15 @@
+
+.footer {
+ line-height: 20px;
+ text-align: center;
+}
+
+.logo {
+ font-weight: bold;
+ font-size: 16px;
+}
+
+.copyright {
+ font-size: 12px;
+}
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2-utils-name-alias/expected/demo-project/src/layouts/BasicLayout/components/Logo/index.jsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2-utils-name-alias/expected/demo-project/src/layouts/BasicLayout/components/Logo/index.jsx
new file mode 100644
index 0000000000..265bfdaa07
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2-utils-name-alias/expected/demo-project/src/layouts/BasicLayout/components/Logo/index.jsx
@@ -0,0 +1,16 @@
+
+import React from 'react';
+import { Link } from 'ice';
+import styles from './index.module.scss';
+
+export default function Logo({ image, text, url }) {
+ return (
+
+
+ {image &&

}
+
{text}
+
+
+ );
+}
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2-utils-name-alias/expected/demo-project/src/layouts/BasicLayout/components/Logo/index.module.scss b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2-utils-name-alias/expected/demo-project/src/layouts/BasicLayout/components/Logo/index.module.scss
new file mode 100644
index 0000000000..dad05a263f
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2-utils-name-alias/expected/demo-project/src/layouts/BasicLayout/components/Logo/index.module.scss
@@ -0,0 +1,20 @@
+
+.logo{
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: #FF7300;
+ font-weight: bold;
+ font-size: 14px;
+ line-height: 22px;
+
+ &:visited, &:link {
+ color: #FF7300;
+ }
+
+ img {
+ height: 24px;
+ margin-right: 10px;
+ }
+}
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2-utils-name-alias/expected/demo-project/src/layouts/BasicLayout/components/PageNav/index.jsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2-utils-name-alias/expected/demo-project/src/layouts/BasicLayout/components/PageNav/index.jsx
new file mode 100644
index 0000000000..911998b0d3
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2-utils-name-alias/expected/demo-project/src/layouts/BasicLayout/components/PageNav/index.jsx
@@ -0,0 +1,68 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { Link, useLocation } from 'ice';
+import { Nav } from '@alifd/next';
+import { asideMenuConfig } from '../../menuConfig';
+
+const { SubNav } = Nav;
+const NavItem = Nav.Item;
+
+function getNavMenuItems(menusData) {
+ if (!menusData) {
+ return [];
+ }
+
+ return menusData
+ .filter(item => item.name && !item.hideInMenu)
+ .map((item, index) => getSubMenuOrItem(item, index));
+}
+
+function getSubMenuOrItem(item, index) {
+ if (item.children && item.children.some(child => child.name)) {
+ const childrenItems = getNavMenuItems(item.children);
+
+ if (childrenItems && childrenItems.length > 0) {
+ const subNav = (
+
+ {childrenItems}
+
+ );
+ return subNav;
+ }
+
+ return null;
+ }
+
+ const navItem = (
+
+ {item.name}
+
+ );
+ return navItem;
+}
+
+const Navigation = (props, context) => {
+ const location = useLocation();
+ const { pathname } = location;
+ const { isCollapse } = context;
+ return (
+
+ );
+};
+
+Navigation.contextTypes = {
+ isCollapse: PropTypes.bool,
+};
+export default Navigation;
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2-utils-name-alias/expected/demo-project/src/layouts/BasicLayout/index.jsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2-utils-name-alias/expected/demo-project/src/layouts/BasicLayout/index.jsx
new file mode 100644
index 0000000000..18db44df5e
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2-utils-name-alias/expected/demo-project/src/layouts/BasicLayout/index.jsx
@@ -0,0 +1,81 @@
+
+import React, { useState } from 'react';
+import { Shell, ConfigProvider } from '@alifd/next';
+import PageNav from './components/PageNav';
+import Logo from './components/Logo';
+import Footer from './components/Footer';
+
+(function() {
+ const throttle = function(type, name, obj = window) {
+ let running = false;
+
+ const func = () => {
+ if (running) {
+ return;
+ }
+
+ running = true;
+ requestAnimationFrame(() => {
+ obj.dispatchEvent(new CustomEvent(name));
+ running = false;
+ });
+ };
+
+ obj.addEventListener(type, func);
+ };
+
+ throttle('resize', 'optimizedResize');
+})();
+
+export default function BasicLayout({ children }) {
+ const getDevice = width => {
+ const isPhone =
+ typeof navigator !== 'undefined' && navigator && navigator.userAgent.match(/phone/gi);
+
+ if (width < 680 || isPhone) {
+ return 'phone';
+ }
+ if (width < 1280 && width > 680) {
+ return 'tablet';
+ }
+ return 'desktop';
+ };
+
+ const [device, setDevice] = useState(getDevice(NaN));
+ window.addEventListener('optimizedResize', e => {
+ setDevice(getDevice(e && e.target && e.target.innerWidth));
+ });
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ {children}
+
+
+
+
+
+ );
+}
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2-utils-name-alias/expected/demo-project/src/layouts/BasicLayout/menuConfig.js b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2-utils-name-alias/expected/demo-project/src/layouts/BasicLayout/menuConfig.js
new file mode 100644
index 0000000000..5332202be4
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2-utils-name-alias/expected/demo-project/src/layouts/BasicLayout/menuConfig.js
@@ -0,0 +1,11 @@
+
+const headerMenuConfig = [];
+const asideMenuConfig = [
+ {
+ name: 'Dashboard',
+ path: '/',
+ icon: 'smile',
+ },
+];
+export { headerMenuConfig, asideMenuConfig };
+
\ No newline at end of file
diff --git a/packages/rax-renderer/demo/miniapp/pages/index.acss b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2-utils-name-alias/expected/demo-project/src/pages/Aaaa/index.css
similarity index 100%
rename from packages/rax-renderer/demo/miniapp/pages/index.acss
rename to modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2-utils-name-alias/expected/demo-project/src/pages/Aaaa/index.css
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2-utils-name-alias/expected/demo-project/src/pages/Aaaa/index.jsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2-utils-name-alias/expected/demo-project/src/pages/Aaaa/index.jsx
new file mode 100644
index 0000000000..2945a9d8f5
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2-utils-name-alias/expected/demo-project/src/pages/Aaaa/index.jsx
@@ -0,0 +1,118 @@
+// 注意: 出码引擎注入的临时变量默认都以 "__$$" 开头,禁止在搭建的代码中直接访问。
+// 例外:react 框架的导出名和各种组件名除外。
+import React from 'react';
+
+import { Page } from '@alilc/b6-page';
+
+import { Text } from '@alilc/b6-text';
+
+import { createUrlParamsHandler as __$$createUrlParamsRequestHandler } from '@alilc/lowcode-datasource-url-params-handler';
+
+import { create as __$$createDataSourceEngine } from '@alilc/lowcode-datasource-engine/runtime';
+
+import utils from '../../utils';
+
+import * as __$$i18n from '../../i18n';
+
+import __$$constants from '../../constants';
+
+import './index.css';
+
+class Aaaa$$Page extends React.Component {
+ _context = this;
+
+ _dataSourceConfig = this._defineDataSourceConfig();
+ _dataSourceEngine = __$$createDataSourceEngine(this._dataSourceConfig, this, {
+ runtimeConfig: true,
+ requestHandlersMap: {
+ urlParams: __$$createUrlParamsRequestHandler(window.location.search),
+ },
+ });
+
+ get dataSourceMap() {
+ return this._dataSourceEngine.dataSourceMap || {};
+ }
+
+ reloadDataSource = async () => {
+ await this._dataSourceEngine.reloadDataSource();
+ };
+
+ get constants() {
+ return __$$constants || {};
+ }
+
+ constructor(props, context) {
+ super(props);
+
+ this.utils = utils;
+
+ __$$i18n._inject2(this);
+
+ this.state = {};
+ }
+
+ $ = () => null;
+
+ $$ = () => [];
+
+ _defineDataSourceConfig() {
+ const _this = this;
+ return {
+ list: [
+ {
+ id: 'urlParams',
+ type: 'urlParams',
+ description: 'URL参数',
+ options: function () {
+ return {
+ uri: '',
+ };
+ }.bind(_this),
+ isInit: function () {
+ return undefined;
+ }.bind(_this),
+ },
+ ],
+ };
+ }
+
+ componentDidMount() {
+ this._dataSourceEngine.reloadDataSource();
+ }
+
+ render() {
+ const __$$context = this._context || this;
+ const { state } = __$$context;
+ return (
+
+
+
+ );
+ }
+}
+
+export default Aaaa$$Page;
+
+function __$$eval(expr) {
+ try {
+ return expr();
+ } catch (error) {}
+}
+
+function __$$evalArray(expr) {
+ const res = __$$eval(expr);
+ return Array.isArray(res) ? res : [];
+}
+
+function __$$createChildContext(oldContext, ext) {
+ const childContext = {
+ ...oldContext,
+ ...ext,
+ };
+ childContext.__proto__ = oldContext;
+ return childContext;
+}
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2-utils-name-alias/expected/demo-project/src/pages/layout.jsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2-utils-name-alias/expected/demo-project/src/pages/layout.jsx
new file mode 100644
index 0000000000..50fbb2d1f1
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2-utils-name-alias/expected/demo-project/src/pages/layout.jsx
@@ -0,0 +1,10 @@
+import { Outlet } from 'ice';
+import BasicLayout from '@/layouts/BasicLayout';
+
+export default function Layout() {
+ return (
+
+
+
+ );
+}
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2-utils-name-alias/expected/demo-project/src/typings.d.ts b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2-utils-name-alias/expected/demo-project/src/typings.d.ts
new file mode 100644
index 0000000000..a9f8de7ceb
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2-utils-name-alias/expected/demo-project/src/typings.d.ts
@@ -0,0 +1,9 @@
+///
+
+export {};
+declare global {
+ interface Window {
+ g_config: Record;
+ }
+}
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2-utils-name-alias/expected/demo-project/src/utils.js b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2-utils-name-alias/expected/demo-project/src/utils.js
new file mode 100644
index 0000000000..868d471106
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2-utils-name-alias/expected/demo-project/src/utils.js
@@ -0,0 +1,61 @@
+import legaoBuiltin from '@alilc/b6-compact-legao-builtin';
+
+import { message, Modal as modal } from 'antd';
+
+import { mocks } from '@alilc/b6-util-mocks';
+
+import { createRef } from 'react';
+
+export class RefsManager {
+ constructor() {
+ this.refInsStore = {};
+ }
+
+ clearNullRefs() {
+ Object.keys(this.refInsStore).forEach((refName) => {
+ const filteredInsList = this.refInsStore[refName].filter(
+ (insRef) => !!insRef.current
+ );
+ if (filteredInsList.length > 0) {
+ this.refInsStore[refName] = filteredInsList;
+ } else {
+ delete this.refInsStore[refName];
+ }
+ });
+ }
+
+ get(refName) {
+ this.clearNullRefs();
+ if (this.refInsStore[refName] && this.refInsStore[refName].length > 0) {
+ return this.refInsStore[refName][0].current;
+ }
+
+ return null;
+ }
+
+ getAll(refName) {
+ this.clearNullRefs();
+ if (this.refInsStore[refName] && this.refInsStore[refName].length > 0) {
+ return this.refInsStore[refName].map((i) => i.current);
+ }
+
+ return [];
+ }
+
+ linkRef(refName) {
+ const refIns = createRef();
+ this.refInsStore[refName] = this.refInsStore[refName] || [];
+ this.refInsStore[refName].push(refIns);
+ return refIns;
+ }
+}
+
+export default {
+ legaoBuiltin,
+
+ message,
+
+ mocks,
+
+ modal,
+};
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2-utils-name-alias/schema.json5 b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2-utils-name-alias/schema.json5
new file mode 100644
index 0000000000..76e8248cbb
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2-utils-name-alias/schema.json5
@@ -0,0 +1,123 @@
+{
+ version: '1.0.0',
+ componentsMap: [
+ {
+ package: '@alilc/b6-page',
+ version: '^0.1.0',
+ componentName: 'Page',
+ destructuring: true,
+ exportName: 'Page',
+ },
+ {
+ package: '@alilc/b6-text',
+ version: '^0.1.0',
+ componentName: 'Text',
+ destructuring: true,
+ exportName: 'Text',
+ },
+ ],
+ componentsTree: [
+ {
+ componentName: 'Page',
+ id: 'node_ockp6ci0hm1',
+ props: {
+ title: '',
+ backgroundColor: '#fff',
+ textColor: '#333',
+ style: {},
+ },
+ fileName: 'aaaa',
+ dataSource: {
+ list: [
+ {
+ id: 'urlParams',
+ type: 'urlParams',
+ description: 'URL参数',
+ options: {
+ uri: '',
+ },
+ },
+ ],
+ },
+ children: [
+ {
+ componentName: 'Text',
+ id: 'node_ockp6ci0hm2',
+ props: {
+ content: '欢迎使用 BuildSuccess!sadsad',
+ style: {},
+ fieldId: 'text_kp6ci11t',
+ },
+ },
+ ],
+ meta: {
+ router: '/aaaa',
+ },
+ methodsModule: {
+ type: 'JSModule',
+ compiled: '"use strict";\n\nObject.defineProperty(exports, "__esModule", {\n value: true\n});\nexports.helloPage = helloPage;\n\n/**\n * Private, and can be re-used functions\n * Actions panel help documentation:\n * @see https://yuque.antfin.com/docs/share/89ca7965-6387-4e3a-9964-81929ed48f1e\n */\nfunction printLog(obj) {\n console.info(obj);\n}\n/**\n * page function\n */\n\n\nfunction helloPage() {\n console.log(\'hello page\');\n}',
+ source: "/**\n * Private, and can be re-used functions\n * Actions panel help documentation:\n * @see https://yuque.antfin.com/docs/share/89ca7965-6387-4e3a-9964-81929ed48f1e\n */\nfunction printLog(obj) {\n console.info(obj);\n}\n\n/**\n * page function\n */\nexport function helloPage() {\n console.log('hello page');\n}",
+ },
+ },
+ ],
+ i18n: {},
+ utils: [
+ {
+ name: 'legaoBuiltin',
+ type: 'npm',
+ content: {
+ exportName: 'legaoBuiltin',
+ package: '@alilc/b6-compact-legao-builtin',
+ version: '1.x',
+ },
+ },
+ {
+ name: 'message',
+ type: 'npm',
+ content: {
+ package: 'antd',
+ version: '3.x',
+ destructuring: true,
+ exportName: 'message',
+ },
+ },
+ {
+ name: 'mocks',
+ type: 'npm',
+ content: {
+ package: '@alilc/b6-util-mocks',
+ version: '1.x',
+ exportName: 'mocks',
+ destructuring: true,
+ },
+ },
+ {
+ name: 'modal',
+ type: 'npm',
+ content: {
+ package: 'antd',
+ version: '3.x',
+ destructuring: true,
+ exportName: 'Modal',
+ },
+ },
+ ],
+ constants: {},
+ dataSource: {
+ list: [],
+ },
+ config: {
+ sdkVersion: '1.0.3',
+ historyMode: 'hash',
+ targetRootID: 'root',
+ miniAppBuildType: 'runtime',
+ },
+ meta: {
+ name: 'jinyuan-test2',
+ git_group: 'b6',
+ project_name: 'jinyuan-test2',
+ description: '瑾源测试',
+ spma: 'spmademo',
+ creator: '张三',
+ },
+}
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2/expected/demo-project/.browserslistrc b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2/expected/demo-project/.browserslistrc
new file mode 100644
index 0000000000..55a130413d
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2/expected/demo-project/.browserslistrc
@@ -0,0 +1,3 @@
+defaults
+ios_saf 9
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2/expected/demo-project/.gitignore b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2/expected/demo-project/.gitignore
new file mode 100644
index 0000000000..4ec178818e
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2/expected/demo-project/.gitignore
@@ -0,0 +1,25 @@
+
+# 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
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2/expected/demo-project/README.md b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2/expected/demo-project/README.md
new file mode 100644
index 0000000000..6d9dd75215
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2/expected/demo-project/README.md
@@ -0,0 +1 @@
+This project is generated by lowcode-code-generator & lowcode-solution-icejs3.
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2/expected/demo-project/ice.config.mts b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2/expected/demo-project/ice.config.mts
new file mode 100644
index 0000000000..e1d8a28141
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2/expected/demo-project/ice.config.mts
@@ -0,0 +1,90 @@
+import { join } from 'path';
+import { defineConfig } from '@ice/app';
+import _ from 'lodash';
+import fusion from '@ice/plugin-fusion';
+import locales from '@ice/plugin-moment-locales';
+import type { Plugin } from '@ice/app/esm/types';
+
+interface PluginOptions {
+ id: string;
+}
+
+const plugin: Plugin = (options) => ({
+ // name 可选,插件名称
+ name: 'plugin-name',
+ // setup 必选,用于定制工程构建配置
+ setup: ({ onGetConfig, modifyUserConfig }) => {
+ modifyUserConfig('codeSplitting', 'page');
+
+ onGetConfig((config) => {
+ config.entry = {
+ web: join(process.cwd(), '.ice/entry.client.tsx'),
+ };
+
+ config.cssFilename = '[name].css';
+
+ config.configureWebpack = config.configureWebpack || [];
+ config.configureWebpack?.push((webpackConfig) => {
+ if (webpackConfig.output) {
+ webpackConfig.output.filename = '[name].js';
+ webpackConfig.output.chunkFilename = '[name].js';
+ }
+ return webpackConfig;
+ });
+
+ config.swcOptions = _.merge(config.swcOptions, {
+ compilationConfig: {
+ jsc: {
+ transform: {
+ react: {
+ runtime: 'classic',
+ },
+ },
+ },
+ },
+ });
+
+ // 解决 webpack publicPath 问题
+ config.transforms = config.transforms || [];
+ config.transforms.push((source: string, id: string) => {
+ if (id.includes('.ice/entry.client.tsx')) {
+ let code = `
+ if (!__webpack_public_path__?.startsWith('http') && document.currentScript) {
+ // @ts-ignore
+ __webpack_public_path__ = document.currentScript.src.replace(/^(.*\\/)[^/]+$/, '$1');
+ window.__ICE_ASSETS_MANIFEST__ = window.__ICE_ASSETS_MANIFEST__ || {};
+ window.__ICE_ASSETS_MANIFEST__.publicPath = __webpack_public_path__;
+ }
+ `;
+ code += source;
+ return { code };
+ }
+ });
+ });
+ },
+});
+
+// The project config, see https://v3.ice.work/docs/guide/basic/config
+const minify = process.env.NODE_ENV === 'production' ? 'swc' : false;
+export default defineConfig(() => ({
+ ssr: false,
+ ssg: false,
+ minify,
+
+ externals: {
+ react: 'React',
+ 'react-dom': 'ReactDOM',
+ 'react-dom/client': 'ReactDOM',
+ '@alifd/next': 'Next',
+ lodash: 'var window._',
+ '@alilc/lowcode-engine': 'var window.AliLowCodeEngine',
+ },
+ plugins: [
+ fusion({
+ importStyle: 'sass',
+ }),
+ locales(),
+ plugin(),
+ ],
+}));
+
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2/expected/demo-project/package.json b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2/expected/demo-project/package.json
new file mode 100644
index 0000000000..8f02ff1c7e
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2/expected/demo-project/package.json
@@ -0,0 +1,42 @@
+{
+ "name": "icejs3-demo-app",
+ "version": "0.1.5",
+ "description": "icejs 3 轻量级模板,使用 JavaScript,仅包含基础的 Layout。",
+ "dependencies": {
+ "moment": "^2.24.0",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
+ "react-router": "^6.9.0",
+ "react-router-dom": "^6.9.0",
+ "intl-messageformat": "^9.3.6",
+ "@alifd/next": "1.19.18",
+ "@ice/runtime": "~1.1.0",
+ "@alilc/lowcode-datasource-engine": "^1.0.0"
+ },
+ "devDependencies": {
+ "@ice/app": "~3.1.0",
+ "@types/react": "^18.0.0",
+ "@types/react-dom": "^18.0.0",
+ "@types/node": "^18.11.17",
+ "@ice/plugin-fusion": "^1.0.1",
+ "@ice/plugin-moment-locales": "^1.0.0",
+ "eslint": "^6.0.1",
+ "stylelint": "^13.2.0"
+ },
+ "scripts": {
+ "start": "ice start",
+ "build": "ice build",
+ "lint": "npm run eslint && npm run stylelint",
+ "eslint": "eslint --cache --ext .js,.jsx ./",
+ "stylelint": "stylelint ./**/*.scss"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "repository": {
+ "type": "git",
+ "url": "http://gitlab.xxx.com/msd/leak-scan/tree/master"
+ },
+ "private": true,
+ "originTemplate": "@alifd/scaffold-lite-js"
+}
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2/expected/demo-project/src/app.ts b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2/expected/demo-project/src/app.ts
new file mode 100644
index 0000000000..6d5856292d
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2/expected/demo-project/src/app.ts
@@ -0,0 +1,13 @@
+import { defineAppConfig } from 'ice';
+
+// App config, see https://v3.ice.work/docs/guide/basic/app
+export default defineAppConfig(() => ({
+ // Set your configs here.
+ app: {
+ rootId: 'App',
+ },
+ router: {
+ type: 'browser',
+ basename: '/',
+ },
+}));
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2/expected/demo-project/src/constants.js b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2/expected/demo-project/src/constants.js
new file mode 100644
index 0000000000..91198f9044
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2/expected/demo-project/src/constants.js
@@ -0,0 +1,3 @@
+const __$$constants = { ENV: 'prod', DOMAIN: 'xxx.xxx.com' };
+
+export default __$$constants;
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2/expected/demo-project/src/document.tsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2/expected/demo-project/src/document.tsx
new file mode 100644
index 0000000000..aff0231d95
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2/expected/demo-project/src/document.tsx
@@ -0,0 +1,29 @@
+import React from 'react';
+import { Meta, Title, Links, Main, Scripts } from 'ice';
+
+export default function Document() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2/expected/demo-project/src/global.scss b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2/expected/demo-project/src/global.scss
new file mode 100644
index 0000000000..ed7204b4a3
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2/expected/demo-project/src/global.scss
@@ -0,0 +1,13 @@
+// 引入默认全局样式
+@import '@alifd/next/reset.scss';
+
+body {
+ -webkit-font-smoothing: antialiased;
+}
+
+body {
+ font-size: 12px;
+}
+.table {
+ width: 100px;
+}
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2/expected/demo-project/src/i18n.js b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2/expected/demo-project/src/i18n.js
new file mode 100644
index 0000000000..e8cb58e640
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2/expected/demo-project/src/i18n.js
@@ -0,0 +1,86 @@
+const i18nConfig = {
+ 'zh-CN': {
+ 'i18n-jwg27yo4': '你好',
+ 'i18n-jwg27yo3': '中国',
+ },
+ 'en-US': {
+ 'i18n-jwg27yo4': 'Hello',
+ 'i18n-jwg27yo3': 'China',
+ },
+};
+
+let locale =
+ typeof navigator === 'object' && typeof navigator.language === 'string'
+ ? navigator.language
+ : 'zh-CN';
+
+const getLocale = () => locale;
+
+const setLocale = (target) => {
+ locale = target;
+};
+
+const isEmptyVariables = (variables) =>
+ (Array.isArray(variables) && variables.length === 0) ||
+ (typeof variables === 'object' &&
+ (!variables || Object.keys(variables).length === 0));
+
+// 按低代码规范里面的要求进行变量替换
+const format = (msg, variables) =>
+ typeof msg === 'string'
+ ? msg.replace(/\$?\{(\w+)\}/g, (match, key) => variables?.[key] ?? '')
+ : msg;
+
+const i18nFormat = ({ id, defaultMessage, fallback }, variables) => {
+ const msg =
+ i18nConfig[locale]?.[id] ??
+ i18nConfig[locale.replace('-', '_')]?.[id] ??
+ defaultMessage;
+ if (msg == null) {
+ console.warn('[i18n]: unknown message id: %o (locale=%o)', id, locale);
+ return fallback === undefined ? `${id}` : fallback;
+ }
+
+ return format(msg, variables);
+};
+
+const i18n = (id, params) => {
+ return i18nFormat({ id }, params);
+};
+
+// 将国际化的一些方法注入到目标对象&上下文中
+const _inject2 = (target) => {
+ target.i18n = i18n;
+ target.getLocale = getLocale;
+ target.setLocale = (locale) => {
+ setLocale(locale);
+ target.forceUpdate();
+ };
+ target._i18nText = (t) => {
+ // 优先取直接传过来的语料
+ const localMsg = t[locale] ?? t[String(locale).replace('-', '_')];
+ if (localMsg != null) {
+ return format(localMsg, t.params);
+ }
+
+ // 其次用项目级别的
+ const projectMsg = i18nFormat({ id: t.key, fallback: null }, t.params);
+ if (projectMsg != null) {
+ return projectMsg;
+ }
+
+ // 兜底用 use 指定的或默认语言的
+ return format(t[t.use || 'zh-CN'] ?? t.en_US, t.params);
+ };
+
+ // 注入到上下文中去
+ if (target._context && target._context !== target) {
+ Object.assign(target._context, {
+ i18n,
+ getLocale,
+ setLocale: target.setLocale,
+ });
+ }
+};
+
+export { getLocale, setLocale, i18n, i18nFormat, _inject2 };
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2/expected/demo-project/src/layouts/BasicLayout/components/Footer/index.jsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2/expected/demo-project/src/layouts/BasicLayout/components/Footer/index.jsx
new file mode 100644
index 0000000000..cc70d53bea
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2/expected/demo-project/src/layouts/BasicLayout/components/Footer/index.jsx
@@ -0,0 +1,14 @@
+
+import React from 'react';
+import styles from './index.module.scss';
+
+export default function Footer() {
+ return (
+
+ Alibaba Fusion
+
+ © 2019-现在 Alibaba Fusion & ICE
+
+ );
+}
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2/expected/demo-project/src/layouts/BasicLayout/components/Footer/index.module.scss b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2/expected/demo-project/src/layouts/BasicLayout/components/Footer/index.module.scss
new file mode 100644
index 0000000000..81e77fda5f
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2/expected/demo-project/src/layouts/BasicLayout/components/Footer/index.module.scss
@@ -0,0 +1,15 @@
+
+.footer {
+ line-height: 20px;
+ text-align: center;
+}
+
+.logo {
+ font-weight: bold;
+ font-size: 16px;
+}
+
+.copyright {
+ font-size: 12px;
+}
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2/expected/demo-project/src/layouts/BasicLayout/components/Logo/index.jsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2/expected/demo-project/src/layouts/BasicLayout/components/Logo/index.jsx
new file mode 100644
index 0000000000..265bfdaa07
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2/expected/demo-project/src/layouts/BasicLayout/components/Logo/index.jsx
@@ -0,0 +1,16 @@
+
+import React from 'react';
+import { Link } from 'ice';
+import styles from './index.module.scss';
+
+export default function Logo({ image, text, url }) {
+ return (
+
+
+ {image &&

}
+
{text}
+
+
+ );
+}
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2/expected/demo-project/src/layouts/BasicLayout/components/Logo/index.module.scss b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2/expected/demo-project/src/layouts/BasicLayout/components/Logo/index.module.scss
new file mode 100644
index 0000000000..dad05a263f
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2/expected/demo-project/src/layouts/BasicLayout/components/Logo/index.module.scss
@@ -0,0 +1,20 @@
+
+.logo{
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: #FF7300;
+ font-weight: bold;
+ font-size: 14px;
+ line-height: 22px;
+
+ &:visited, &:link {
+ color: #FF7300;
+ }
+
+ img {
+ height: 24px;
+ margin-right: 10px;
+ }
+}
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2/expected/demo-project/src/layouts/BasicLayout/components/PageNav/index.jsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2/expected/demo-project/src/layouts/BasicLayout/components/PageNav/index.jsx
new file mode 100644
index 0000000000..911998b0d3
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2/expected/demo-project/src/layouts/BasicLayout/components/PageNav/index.jsx
@@ -0,0 +1,68 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { Link, useLocation } from 'ice';
+import { Nav } from '@alifd/next';
+import { asideMenuConfig } from '../../menuConfig';
+
+const { SubNav } = Nav;
+const NavItem = Nav.Item;
+
+function getNavMenuItems(menusData) {
+ if (!menusData) {
+ return [];
+ }
+
+ return menusData
+ .filter(item => item.name && !item.hideInMenu)
+ .map((item, index) => getSubMenuOrItem(item, index));
+}
+
+function getSubMenuOrItem(item, index) {
+ if (item.children && item.children.some(child => child.name)) {
+ const childrenItems = getNavMenuItems(item.children);
+
+ if (childrenItems && childrenItems.length > 0) {
+ const subNav = (
+
+ {childrenItems}
+
+ );
+ return subNav;
+ }
+
+ return null;
+ }
+
+ const navItem = (
+
+ {item.name}
+
+ );
+ return navItem;
+}
+
+const Navigation = (props, context) => {
+ const location = useLocation();
+ const { pathname } = location;
+ const { isCollapse } = context;
+ return (
+
+ );
+};
+
+Navigation.contextTypes = {
+ isCollapse: PropTypes.bool,
+};
+export default Navigation;
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2/expected/demo-project/src/layouts/BasicLayout/index.jsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2/expected/demo-project/src/layouts/BasicLayout/index.jsx
new file mode 100644
index 0000000000..18db44df5e
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2/expected/demo-project/src/layouts/BasicLayout/index.jsx
@@ -0,0 +1,81 @@
+
+import React, { useState } from 'react';
+import { Shell, ConfigProvider } from '@alifd/next';
+import PageNav from './components/PageNav';
+import Logo from './components/Logo';
+import Footer from './components/Footer';
+
+(function() {
+ const throttle = function(type, name, obj = window) {
+ let running = false;
+
+ const func = () => {
+ if (running) {
+ return;
+ }
+
+ running = true;
+ requestAnimationFrame(() => {
+ obj.dispatchEvent(new CustomEvent(name));
+ running = false;
+ });
+ };
+
+ obj.addEventListener(type, func);
+ };
+
+ throttle('resize', 'optimizedResize');
+})();
+
+export default function BasicLayout({ children }) {
+ const getDevice = width => {
+ const isPhone =
+ typeof navigator !== 'undefined' && navigator && navigator.userAgent.match(/phone/gi);
+
+ if (width < 680 || isPhone) {
+ return 'phone';
+ }
+ if (width < 1280 && width > 680) {
+ return 'tablet';
+ }
+ return 'desktop';
+ };
+
+ const [device, setDevice] = useState(getDevice(NaN));
+ window.addEventListener('optimizedResize', e => {
+ setDevice(getDevice(e && e.target && e.target.innerWidth));
+ });
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ {children}
+
+
+
+
+
+ );
+}
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2/expected/demo-project/src/layouts/BasicLayout/menuConfig.js b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2/expected/demo-project/src/layouts/BasicLayout/menuConfig.js
new file mode 100644
index 0000000000..5332202be4
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2/expected/demo-project/src/layouts/BasicLayout/menuConfig.js
@@ -0,0 +1,11 @@
+
+const headerMenuConfig = [];
+const asideMenuConfig = [
+ {
+ name: 'Dashboard',
+ path: '/',
+ icon: 'smile',
+ },
+];
+export { headerMenuConfig, asideMenuConfig };
+
\ No newline at end of file
diff --git a/packages/rax-renderer/demo/wechat-miniprogram/pages/index.wxss b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2/expected/demo-project/src/pages/Test/index.css
similarity index 100%
rename from packages/rax-renderer/demo/wechat-miniprogram/pages/index.wxss
rename to modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2/expected/demo-project/src/pages/Test/index.css
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2/expected/demo-project/src/pages/Test/index.jsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2/expected/demo-project/src/pages/Test/index.jsx
new file mode 100644
index 0000000000..080c4245b6
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2/expected/demo-project/src/pages/Test/index.jsx
@@ -0,0 +1,129 @@
+// 注意: 出码引擎注入的临时变量默认都以 "__$$" 开头,禁止在搭建的代码中直接访问。
+// 例外:react 框架的导出名和各种组件名除外。
+import React from 'react';
+
+import { Form, Input, NumberPicker, Select, Button } from '@alifd/next';
+
+import '@alifd/next/lib/form/style';
+
+import '@alifd/next/lib/input/style';
+
+import '@alifd/next/lib/number-picker/style';
+
+import '@alifd/next/lib/select/style';
+
+import '@alifd/next/lib/button/style';
+
+import utils, { RefsManager } from '../../utils';
+
+import * as __$$i18n from '../../i18n';
+
+import __$$constants from '../../constants';
+
+import './index.css';
+
+class Test$$Page extends React.Component {
+ _context = this;
+
+ get constants() {
+ return __$$constants || {};
+ }
+
+ constructor(props, context) {
+ super(props);
+
+ this.utils = utils;
+
+ this._refsManager = new RefsManager();
+
+ __$$i18n._inject2(this);
+
+ this.state = { text: 'outter' };
+ }
+
+ $ = (refName) => {
+ return this._refsManager.get(refName);
+ };
+
+ $$ = (refName) => {
+ return this._refsManager.getAll(refName);
+ };
+
+ componentDidMount() {
+ console.log('componentDidMount');
+ }
+
+ render() {
+ const __$$context = this._context || this;
+ const { state } = __$$context;
+ return (
+
+
this.i18n('i18n-jwg27yo4'))}
+ name="name"
+ initValue="李雷"
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+}
+
+export default Test$$Page;
+
+function __$$eval(expr) {
+ try {
+ return expr();
+ } catch (error) {}
+}
+
+function __$$evalArray(expr) {
+ const res = __$$eval(expr);
+ return Array.isArray(res) ? res : [];
+}
+
+function __$$createChildContext(oldContext, ext) {
+ const childContext = {
+ ...oldContext,
+ ...ext,
+ };
+ childContext.__proto__ = oldContext;
+ return childContext;
+}
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2/expected/demo-project/src/pages/layout.jsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2/expected/demo-project/src/pages/layout.jsx
new file mode 100644
index 0000000000..50fbb2d1f1
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2/expected/demo-project/src/pages/layout.jsx
@@ -0,0 +1,10 @@
+import { Outlet } from 'ice';
+import BasicLayout from '@/layouts/BasicLayout';
+
+export default function Layout() {
+ return (
+
+
+
+ );
+}
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2/expected/demo-project/src/typings.d.ts b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2/expected/demo-project/src/typings.d.ts
new file mode 100644
index 0000000000..a9f8de7ceb
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2/expected/demo-project/src/typings.d.ts
@@ -0,0 +1,9 @@
+///
+
+export {};
+declare global {
+ interface Window {
+ g_config: Record;
+ }
+}
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2/expected/demo-project/src/utils.js b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2/expected/demo-project/src/utils.js
new file mode 100644
index 0000000000..1190717924
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2/expected/demo-project/src/utils.js
@@ -0,0 +1,47 @@
+import { createRef } from 'react';
+
+export class RefsManager {
+ constructor() {
+ this.refInsStore = {};
+ }
+
+ clearNullRefs() {
+ Object.keys(this.refInsStore).forEach((refName) => {
+ const filteredInsList = this.refInsStore[refName].filter(
+ (insRef) => !!insRef.current
+ );
+ if (filteredInsList.length > 0) {
+ this.refInsStore[refName] = filteredInsList;
+ } else {
+ delete this.refInsStore[refName];
+ }
+ });
+ }
+
+ get(refName) {
+ this.clearNullRefs();
+ if (this.refInsStore[refName] && this.refInsStore[refName].length > 0) {
+ return this.refInsStore[refName][0].current;
+ }
+
+ return null;
+ }
+
+ getAll(refName) {
+ this.clearNullRefs();
+ if (this.refInsStore[refName] && this.refInsStore[refName].length > 0) {
+ return this.refInsStore[refName].map((i) => i.current);
+ }
+
+ return [];
+ }
+
+ linkRef(refName) {
+ const refIns = createRef();
+ this.refInsStore[refName] = this.refInsStore[refName] || [];
+ this.refInsStore[refName].push(refIns);
+ return refIns;
+ }
+}
+
+export default {};
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2/schema.json5 b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2/schema.json5
new file mode 100644
index 0000000000..2228212067
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo2/schema.json5
@@ -0,0 +1,256 @@
+{
+ "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": "JSFunction",
+ "value": "function() { console.log('componentDidMount'); }"
+ }
+ },
+ "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": {
+ type: 'JSExpression',
+ value: 'this.i18n("i18n-jwg27yo4")',
+ },
+ "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",
+ "props": {
+ "type": "primary",
+ "style": {
+ "margin": "0 5px 0 5px"
+ },
+ "htmlType": "submit"
+ },
+ "children": [
+ "提交"
+ ]
+ },
+ {
+ "componentName": "Button",
+ "id": "node$d",
+ "props": {
+ "type": "normal",
+ "style": {
+ "margin": "0 5px 0 5px"
+ },
+ "htmlType": "reset"
+ },
+ "children": [
+ "重置"
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "constants": {
+ "ENV": "prod",
+ "DOMAIN": "xxx.xxx.com"
+ },
+ "i18n": {
+ "zh-CN": {
+ "i18n-jwg27yo4": "你好",
+ "i18n-jwg27yo3": "中国"
+ },
+ "en-US": {
+ "i18n-jwg27yo4": "Hello",
+ "i18n-jwg27yo3": "China"
+ }
+ },
+ "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": "月飞"
+ }
+}
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo3/expected/demo-project/.browserslistrc b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo3/expected/demo-project/.browserslistrc
new file mode 100644
index 0000000000..55a130413d
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo3/expected/demo-project/.browserslistrc
@@ -0,0 +1,3 @@
+defaults
+ios_saf 9
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo3/expected/demo-project/.gitignore b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo3/expected/demo-project/.gitignore
new file mode 100644
index 0000000000..4ec178818e
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo3/expected/demo-project/.gitignore
@@ -0,0 +1,25 @@
+
+# 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
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo3/expected/demo-project/README.md b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo3/expected/demo-project/README.md
new file mode 100644
index 0000000000..6d9dd75215
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo3/expected/demo-project/README.md
@@ -0,0 +1 @@
+This project is generated by lowcode-code-generator & lowcode-solution-icejs3.
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo3/expected/demo-project/ice.config.mts b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo3/expected/demo-project/ice.config.mts
new file mode 100644
index 0000000000..e1d8a28141
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo3/expected/demo-project/ice.config.mts
@@ -0,0 +1,90 @@
+import { join } from 'path';
+import { defineConfig } from '@ice/app';
+import _ from 'lodash';
+import fusion from '@ice/plugin-fusion';
+import locales from '@ice/plugin-moment-locales';
+import type { Plugin } from '@ice/app/esm/types';
+
+interface PluginOptions {
+ id: string;
+}
+
+const plugin: Plugin = (options) => ({
+ // name 可选,插件名称
+ name: 'plugin-name',
+ // setup 必选,用于定制工程构建配置
+ setup: ({ onGetConfig, modifyUserConfig }) => {
+ modifyUserConfig('codeSplitting', 'page');
+
+ onGetConfig((config) => {
+ config.entry = {
+ web: join(process.cwd(), '.ice/entry.client.tsx'),
+ };
+
+ config.cssFilename = '[name].css';
+
+ config.configureWebpack = config.configureWebpack || [];
+ config.configureWebpack?.push((webpackConfig) => {
+ if (webpackConfig.output) {
+ webpackConfig.output.filename = '[name].js';
+ webpackConfig.output.chunkFilename = '[name].js';
+ }
+ return webpackConfig;
+ });
+
+ config.swcOptions = _.merge(config.swcOptions, {
+ compilationConfig: {
+ jsc: {
+ transform: {
+ react: {
+ runtime: 'classic',
+ },
+ },
+ },
+ },
+ });
+
+ // 解决 webpack publicPath 问题
+ config.transforms = config.transforms || [];
+ config.transforms.push((source: string, id: string) => {
+ if (id.includes('.ice/entry.client.tsx')) {
+ let code = `
+ if (!__webpack_public_path__?.startsWith('http') && document.currentScript) {
+ // @ts-ignore
+ __webpack_public_path__ = document.currentScript.src.replace(/^(.*\\/)[^/]+$/, '$1');
+ window.__ICE_ASSETS_MANIFEST__ = window.__ICE_ASSETS_MANIFEST__ || {};
+ window.__ICE_ASSETS_MANIFEST__.publicPath = __webpack_public_path__;
+ }
+ `;
+ code += source;
+ return { code };
+ }
+ });
+ });
+ },
+});
+
+// The project config, see https://v3.ice.work/docs/guide/basic/config
+const minify = process.env.NODE_ENV === 'production' ? 'swc' : false;
+export default defineConfig(() => ({
+ ssr: false,
+ ssg: false,
+ minify,
+
+ externals: {
+ react: 'React',
+ 'react-dom': 'ReactDOM',
+ 'react-dom/client': 'ReactDOM',
+ '@alifd/next': 'Next',
+ lodash: 'var window._',
+ '@alilc/lowcode-engine': 'var window.AliLowCodeEngine',
+ },
+ plugins: [
+ fusion({
+ importStyle: 'sass',
+ }),
+ locales(),
+ plugin(),
+ ],
+}));
+
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo3/expected/demo-project/package.json b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo3/expected/demo-project/package.json
new file mode 100644
index 0000000000..8f02ff1c7e
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo3/expected/demo-project/package.json
@@ -0,0 +1,42 @@
+{
+ "name": "icejs3-demo-app",
+ "version": "0.1.5",
+ "description": "icejs 3 轻量级模板,使用 JavaScript,仅包含基础的 Layout。",
+ "dependencies": {
+ "moment": "^2.24.0",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
+ "react-router": "^6.9.0",
+ "react-router-dom": "^6.9.0",
+ "intl-messageformat": "^9.3.6",
+ "@alifd/next": "1.19.18",
+ "@ice/runtime": "~1.1.0",
+ "@alilc/lowcode-datasource-engine": "^1.0.0"
+ },
+ "devDependencies": {
+ "@ice/app": "~3.1.0",
+ "@types/react": "^18.0.0",
+ "@types/react-dom": "^18.0.0",
+ "@types/node": "^18.11.17",
+ "@ice/plugin-fusion": "^1.0.1",
+ "@ice/plugin-moment-locales": "^1.0.0",
+ "eslint": "^6.0.1",
+ "stylelint": "^13.2.0"
+ },
+ "scripts": {
+ "start": "ice start",
+ "build": "ice build",
+ "lint": "npm run eslint && npm run stylelint",
+ "eslint": "eslint --cache --ext .js,.jsx ./",
+ "stylelint": "stylelint ./**/*.scss"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "repository": {
+ "type": "git",
+ "url": "http://gitlab.xxx.com/msd/leak-scan/tree/master"
+ },
+ "private": true,
+ "originTemplate": "@alifd/scaffold-lite-js"
+}
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo3/expected/demo-project/src/app.ts b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo3/expected/demo-project/src/app.ts
new file mode 100644
index 0000000000..6d5856292d
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo3/expected/demo-project/src/app.ts
@@ -0,0 +1,13 @@
+import { defineAppConfig } from 'ice';
+
+// App config, see https://v3.ice.work/docs/guide/basic/app
+export default defineAppConfig(() => ({
+ // Set your configs here.
+ app: {
+ rootId: 'App',
+ },
+ router: {
+ type: 'browser',
+ basename: '/',
+ },
+}));
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo3/expected/demo-project/src/constants.js b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo3/expected/demo-project/src/constants.js
new file mode 100644
index 0000000000..91198f9044
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo3/expected/demo-project/src/constants.js
@@ -0,0 +1,3 @@
+const __$$constants = { ENV: 'prod', DOMAIN: 'xxx.xxx.com' };
+
+export default __$$constants;
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo3/expected/demo-project/src/document.tsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo3/expected/demo-project/src/document.tsx
new file mode 100644
index 0000000000..aff0231d95
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo3/expected/demo-project/src/document.tsx
@@ -0,0 +1,29 @@
+import React from 'react';
+import { Meta, Title, Links, Main, Scripts } from 'ice';
+
+export default function Document() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo3/expected/demo-project/src/global.scss b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo3/expected/demo-project/src/global.scss
new file mode 100644
index 0000000000..ed7204b4a3
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo3/expected/demo-project/src/global.scss
@@ -0,0 +1,13 @@
+// 引入默认全局样式
+@import '@alifd/next/reset.scss';
+
+body {
+ -webkit-font-smoothing: antialiased;
+}
+
+body {
+ font-size: 12px;
+}
+.table {
+ width: 100px;
+}
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo3/expected/demo-project/src/i18n.js b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo3/expected/demo-project/src/i18n.js
new file mode 100644
index 0000000000..e8cb58e640
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo3/expected/demo-project/src/i18n.js
@@ -0,0 +1,86 @@
+const i18nConfig = {
+ 'zh-CN': {
+ 'i18n-jwg27yo4': '你好',
+ 'i18n-jwg27yo3': '中国',
+ },
+ 'en-US': {
+ 'i18n-jwg27yo4': 'Hello',
+ 'i18n-jwg27yo3': 'China',
+ },
+};
+
+let locale =
+ typeof navigator === 'object' && typeof navigator.language === 'string'
+ ? navigator.language
+ : 'zh-CN';
+
+const getLocale = () => locale;
+
+const setLocale = (target) => {
+ locale = target;
+};
+
+const isEmptyVariables = (variables) =>
+ (Array.isArray(variables) && variables.length === 0) ||
+ (typeof variables === 'object' &&
+ (!variables || Object.keys(variables).length === 0));
+
+// 按低代码规范里面的要求进行变量替换
+const format = (msg, variables) =>
+ typeof msg === 'string'
+ ? msg.replace(/\$?\{(\w+)\}/g, (match, key) => variables?.[key] ?? '')
+ : msg;
+
+const i18nFormat = ({ id, defaultMessage, fallback }, variables) => {
+ const msg =
+ i18nConfig[locale]?.[id] ??
+ i18nConfig[locale.replace('-', '_')]?.[id] ??
+ defaultMessage;
+ if (msg == null) {
+ console.warn('[i18n]: unknown message id: %o (locale=%o)', id, locale);
+ return fallback === undefined ? `${id}` : fallback;
+ }
+
+ return format(msg, variables);
+};
+
+const i18n = (id, params) => {
+ return i18nFormat({ id }, params);
+};
+
+// 将国际化的一些方法注入到目标对象&上下文中
+const _inject2 = (target) => {
+ target.i18n = i18n;
+ target.getLocale = getLocale;
+ target.setLocale = (locale) => {
+ setLocale(locale);
+ target.forceUpdate();
+ };
+ target._i18nText = (t) => {
+ // 优先取直接传过来的语料
+ const localMsg = t[locale] ?? t[String(locale).replace('-', '_')];
+ if (localMsg != null) {
+ return format(localMsg, t.params);
+ }
+
+ // 其次用项目级别的
+ const projectMsg = i18nFormat({ id: t.key, fallback: null }, t.params);
+ if (projectMsg != null) {
+ return projectMsg;
+ }
+
+ // 兜底用 use 指定的或默认语言的
+ return format(t[t.use || 'zh-CN'] ?? t.en_US, t.params);
+ };
+
+ // 注入到上下文中去
+ if (target._context && target._context !== target) {
+ Object.assign(target._context, {
+ i18n,
+ getLocale,
+ setLocale: target.setLocale,
+ });
+ }
+};
+
+export { getLocale, setLocale, i18n, i18nFormat, _inject2 };
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo3/expected/demo-project/src/layouts/BasicLayout/components/Footer/index.jsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo3/expected/demo-project/src/layouts/BasicLayout/components/Footer/index.jsx
new file mode 100644
index 0000000000..cc70d53bea
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo3/expected/demo-project/src/layouts/BasicLayout/components/Footer/index.jsx
@@ -0,0 +1,14 @@
+
+import React from 'react';
+import styles from './index.module.scss';
+
+export default function Footer() {
+ return (
+
+ Alibaba Fusion
+
+ © 2019-现在 Alibaba Fusion & ICE
+
+ );
+}
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo3/expected/demo-project/src/layouts/BasicLayout/components/Footer/index.module.scss b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo3/expected/demo-project/src/layouts/BasicLayout/components/Footer/index.module.scss
new file mode 100644
index 0000000000..81e77fda5f
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo3/expected/demo-project/src/layouts/BasicLayout/components/Footer/index.module.scss
@@ -0,0 +1,15 @@
+
+.footer {
+ line-height: 20px;
+ text-align: center;
+}
+
+.logo {
+ font-weight: bold;
+ font-size: 16px;
+}
+
+.copyright {
+ font-size: 12px;
+}
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo3/expected/demo-project/src/layouts/BasicLayout/components/Logo/index.jsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo3/expected/demo-project/src/layouts/BasicLayout/components/Logo/index.jsx
new file mode 100644
index 0000000000..265bfdaa07
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo3/expected/demo-project/src/layouts/BasicLayout/components/Logo/index.jsx
@@ -0,0 +1,16 @@
+
+import React from 'react';
+import { Link } from 'ice';
+import styles from './index.module.scss';
+
+export default function Logo({ image, text, url }) {
+ return (
+
+
+ {image &&

}
+
{text}
+
+
+ );
+}
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo3/expected/demo-project/src/layouts/BasicLayout/components/Logo/index.module.scss b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo3/expected/demo-project/src/layouts/BasicLayout/components/Logo/index.module.scss
new file mode 100644
index 0000000000..dad05a263f
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo3/expected/demo-project/src/layouts/BasicLayout/components/Logo/index.module.scss
@@ -0,0 +1,20 @@
+
+.logo{
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: #FF7300;
+ font-weight: bold;
+ font-size: 14px;
+ line-height: 22px;
+
+ &:visited, &:link {
+ color: #FF7300;
+ }
+
+ img {
+ height: 24px;
+ margin-right: 10px;
+ }
+}
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo3/expected/demo-project/src/layouts/BasicLayout/components/PageNav/index.jsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo3/expected/demo-project/src/layouts/BasicLayout/components/PageNav/index.jsx
new file mode 100644
index 0000000000..911998b0d3
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo3/expected/demo-project/src/layouts/BasicLayout/components/PageNav/index.jsx
@@ -0,0 +1,68 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { Link, useLocation } from 'ice';
+import { Nav } from '@alifd/next';
+import { asideMenuConfig } from '../../menuConfig';
+
+const { SubNav } = Nav;
+const NavItem = Nav.Item;
+
+function getNavMenuItems(menusData) {
+ if (!menusData) {
+ return [];
+ }
+
+ return menusData
+ .filter(item => item.name && !item.hideInMenu)
+ .map((item, index) => getSubMenuOrItem(item, index));
+}
+
+function getSubMenuOrItem(item, index) {
+ if (item.children && item.children.some(child => child.name)) {
+ const childrenItems = getNavMenuItems(item.children);
+
+ if (childrenItems && childrenItems.length > 0) {
+ const subNav = (
+
+ {childrenItems}
+
+ );
+ return subNav;
+ }
+
+ return null;
+ }
+
+ const navItem = (
+
+ {item.name}
+
+ );
+ return navItem;
+}
+
+const Navigation = (props, context) => {
+ const location = useLocation();
+ const { pathname } = location;
+ const { isCollapse } = context;
+ return (
+
+ );
+};
+
+Navigation.contextTypes = {
+ isCollapse: PropTypes.bool,
+};
+export default Navigation;
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo3/expected/demo-project/src/layouts/BasicLayout/index.jsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo3/expected/demo-project/src/layouts/BasicLayout/index.jsx
new file mode 100644
index 0000000000..18db44df5e
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo3/expected/demo-project/src/layouts/BasicLayout/index.jsx
@@ -0,0 +1,81 @@
+
+import React, { useState } from 'react';
+import { Shell, ConfigProvider } from '@alifd/next';
+import PageNav from './components/PageNav';
+import Logo from './components/Logo';
+import Footer from './components/Footer';
+
+(function() {
+ const throttle = function(type, name, obj = window) {
+ let running = false;
+
+ const func = () => {
+ if (running) {
+ return;
+ }
+
+ running = true;
+ requestAnimationFrame(() => {
+ obj.dispatchEvent(new CustomEvent(name));
+ running = false;
+ });
+ };
+
+ obj.addEventListener(type, func);
+ };
+
+ throttle('resize', 'optimizedResize');
+})();
+
+export default function BasicLayout({ children }) {
+ const getDevice = width => {
+ const isPhone =
+ typeof navigator !== 'undefined' && navigator && navigator.userAgent.match(/phone/gi);
+
+ if (width < 680 || isPhone) {
+ return 'phone';
+ }
+ if (width < 1280 && width > 680) {
+ return 'tablet';
+ }
+ return 'desktop';
+ };
+
+ const [device, setDevice] = useState(getDevice(NaN));
+ window.addEventListener('optimizedResize', e => {
+ setDevice(getDevice(e && e.target && e.target.innerWidth));
+ });
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ {children}
+
+
+
+
+
+ );
+}
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo3/expected/demo-project/src/layouts/BasicLayout/menuConfig.js b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo3/expected/demo-project/src/layouts/BasicLayout/menuConfig.js
new file mode 100644
index 0000000000..5332202be4
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo3/expected/demo-project/src/layouts/BasicLayout/menuConfig.js
@@ -0,0 +1,11 @@
+
+const headerMenuConfig = [];
+const asideMenuConfig = [
+ {
+ name: 'Dashboard',
+ path: '/',
+ icon: 'smile',
+ },
+];
+export { headerMenuConfig, asideMenuConfig };
+
\ No newline at end of file
diff --git a/packages/rax-simulator-renderer/src/builtin-components/UnusualComponent/index.less b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo3/expected/demo-project/src/pages/Test/index.css
similarity index 100%
rename from packages/rax-simulator-renderer/src/builtin-components/UnusualComponent/index.less
rename to modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo3/expected/demo-project/src/pages/Test/index.css
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo3/expected/demo-project/src/pages/Test/index.jsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo3/expected/demo-project/src/pages/Test/index.jsx
new file mode 100644
index 0000000000..2f0a6efa9a
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo3/expected/demo-project/src/pages/Test/index.jsx
@@ -0,0 +1,107 @@
+// 注意: 出码引擎注入的临时变量默认都以 "__$$" 开头,禁止在搭建的代码中直接访问。
+// 例外:react 框架的导出名和各种组件名除外。
+import React from 'react';
+
+import Super, {
+ Button,
+ Input as CustomInput,
+ Form,
+ NumberPicker,
+ Select,
+ SearchTable as SearchTableExport,
+} from '@alifd/next';
+
+import SuperOther from '@alifd/next';
+
+import '@alifd/next/lib/super/style';
+
+import '@alifd/next/lib/button/style';
+
+import '@alifd/next/lib/input/style';
+
+import '@alifd/next/lib/form/style';
+
+import '@alifd/next/lib/number-picker/style';
+
+import '@alifd/next/lib/select/style';
+
+import '@alifd/next/lib/search-table/style';
+
+import utils from '../../utils';
+
+import * as __$$i18n from '../../i18n';
+
+import __$$constants from '../../constants';
+
+import './index.css';
+
+const SuperSub = Super.Sub;
+
+const SelectOption = Select.Option;
+
+const SearchTable = SearchTableExport.default;
+
+class Test$$Page extends React.Component {
+ _context = this;
+
+ get constants() {
+ return __$$constants || {};
+ }
+
+ constructor(props, context) {
+ super(props);
+
+ this.utils = utils;
+
+ __$$i18n._inject2(this);
+
+ this.state = {};
+ }
+
+ $ = () => null;
+
+ $$ = () => [];
+
+ componentDidMount() {}
+
+ render() {
+ const __$$context = this._context || this;
+ const { state } = __$$context;
+ return (
+
+ this.state.title)} />
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+}
+
+export default Test$$Page;
+
+function __$$eval(expr) {
+ try {
+ return expr();
+ } catch (error) {}
+}
+
+function __$$evalArray(expr) {
+ const res = __$$eval(expr);
+ return Array.isArray(res) ? res : [];
+}
+
+function __$$createChildContext(oldContext, ext) {
+ const childContext = {
+ ...oldContext,
+ ...ext,
+ };
+ childContext.__proto__ = oldContext;
+ return childContext;
+}
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo3/expected/demo-project/src/pages/layout.jsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo3/expected/demo-project/src/pages/layout.jsx
new file mode 100644
index 0000000000..50fbb2d1f1
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo3/expected/demo-project/src/pages/layout.jsx
@@ -0,0 +1,10 @@
+import { Outlet } from 'ice';
+import BasicLayout from '@/layouts/BasicLayout';
+
+export default function Layout() {
+ return (
+
+
+
+ );
+}
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo3/expected/demo-project/src/typings.d.ts b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo3/expected/demo-project/src/typings.d.ts
new file mode 100644
index 0000000000..a9f8de7ceb
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo3/expected/demo-project/src/typings.d.ts
@@ -0,0 +1,9 @@
+///
+
+export {};
+declare global {
+ interface Window {
+ g_config: Record;
+ }
+}
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo3/expected/demo-project/src/utils.js b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo3/expected/demo-project/src/utils.js
new file mode 100644
index 0000000000..1190717924
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo3/expected/demo-project/src/utils.js
@@ -0,0 +1,47 @@
+import { createRef } from 'react';
+
+export class RefsManager {
+ constructor() {
+ this.refInsStore = {};
+ }
+
+ clearNullRefs() {
+ Object.keys(this.refInsStore).forEach((refName) => {
+ const filteredInsList = this.refInsStore[refName].filter(
+ (insRef) => !!insRef.current
+ );
+ if (filteredInsList.length > 0) {
+ this.refInsStore[refName] = filteredInsList;
+ } else {
+ delete this.refInsStore[refName];
+ }
+ });
+ }
+
+ get(refName) {
+ this.clearNullRefs();
+ if (this.refInsStore[refName] && this.refInsStore[refName].length > 0) {
+ return this.refInsStore[refName][0].current;
+ }
+
+ return null;
+ }
+
+ getAll(refName) {
+ this.clearNullRefs();
+ if (this.refInsStore[refName] && this.refInsStore[refName].length > 0) {
+ return this.refInsStore[refName].map((i) => i.current);
+ }
+
+ return [];
+ }
+
+ linkRef(refName) {
+ const refIns = createRef();
+ this.refInsStore[refName] = this.refInsStore[refName] || [];
+ this.refInsStore[refName].push(refIns);
+ return refIns;
+ }
+}
+
+export default {};
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo3/schema.json5 b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo3/schema.json5
new file mode 100644
index 0000000000..173b793e13
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo3/schema.json5
@@ -0,0 +1,159 @@
+{
+ "version": "1.0.0",
+ "componentsMap": [
+ {
+ "componentName": "Super",
+ "package": "@alifd/next",
+ "version": "1.19.18",
+ "destructuring": false,
+ "exportName": "Super"
+ },
+ {
+ "componentName": "SuperOther",
+ "package": "@alifd/next",
+ "version": "1.19.18",
+ "destructuring": false,
+ "exportName": "Super"
+ },
+ {
+ "componentName": "SuperSub",
+ "package": "@alifd/next",
+ "version": "1.19.18",
+ "destructuring": false,
+ "exportName": "Super",
+ "subName": "Sub",
+ },
+ {
+ "componentName": "Button",
+ "package": "@alifd/next",
+ "version": "1.19.18",
+ "destructuring": true,
+ "exportName": "Button"
+ },
+ {
+ "componentName": "SearchTable",
+ "package": "@alifd/next",
+ "version": "1.19.18",
+ "destructuring": true,
+ "exportName": "SearchTable",
+ "subName": "default",
+ },
+ {
+ "componentName": "Button.Group",
+ "package": "@alifd/next",
+ "version": "1.19.18",
+ "destructuring": true,
+ "exportName": "Button",
+ "subName": "Group"
+ },
+ {
+ "componentName": "CustomInput",
+ "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": "SelectOption",
+ "package": "@alifd/next",
+ "version": "1.19.18",
+ "destructuring": true,
+ "exportName": "Select",
+ "subName": "Option"
+ }
+ ],
+ "componentsTree": [
+ {
+ "componentName": "Page",
+ "id": "node$1",
+ "meta": {
+ "title": "测试",
+ "router": "/"
+ },
+ "fileName": "test",
+ "children": [
+ {
+ "componentName": "Super",
+ "props": {
+ "title": {
+ "type":"variable",
+ "value":"标题",
+ "variable":"this.state.title"
+ }
+ }
+ },
+ { "componentName": "SuperSub" },
+ { "componentName": "SuperOther" },
+ { "componentName": "Button" },
+ { "componentName": "Button.Group" },
+ { "componentName": "CustomInput" },
+ { "componentName": "Form.Item" },
+ { "componentName": "NumberPicker" },
+ { "componentName": "SelectOption" },
+ { "componentName": "SearchTable" },
+ ]
+ }
+ ],
+ "constants": {
+ "ENV": "prod",
+ "DOMAIN": "xxx.xxx.com"
+ },
+ "i18n": {
+ "zh-CN": {
+ "i18n-jwg27yo4": "你好",
+ "i18n-jwg27yo3": "中国"
+ },
+ "en-US": {
+ "i18n-jwg27yo4": "Hello",
+ "i18n-jwg27yo3": "China"
+ }
+ },
+ "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": "月飞"
+ }
+}
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo4/expected/demo-project/.browserslistrc b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo4/expected/demo-project/.browserslistrc
new file mode 100644
index 0000000000..55a130413d
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo4/expected/demo-project/.browserslistrc
@@ -0,0 +1,3 @@
+defaults
+ios_saf 9
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo4/expected/demo-project/.gitignore b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo4/expected/demo-project/.gitignore
new file mode 100644
index 0000000000..4ec178818e
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo4/expected/demo-project/.gitignore
@@ -0,0 +1,25 @@
+
+# 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
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo4/expected/demo-project/README.md b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo4/expected/demo-project/README.md
new file mode 100644
index 0000000000..6d9dd75215
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo4/expected/demo-project/README.md
@@ -0,0 +1 @@
+This project is generated by lowcode-code-generator & lowcode-solution-icejs3.
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo4/expected/demo-project/ice.config.mts b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo4/expected/demo-project/ice.config.mts
new file mode 100644
index 0000000000..e1d8a28141
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo4/expected/demo-project/ice.config.mts
@@ -0,0 +1,90 @@
+import { join } from 'path';
+import { defineConfig } from '@ice/app';
+import _ from 'lodash';
+import fusion from '@ice/plugin-fusion';
+import locales from '@ice/plugin-moment-locales';
+import type { Plugin } from '@ice/app/esm/types';
+
+interface PluginOptions {
+ id: string;
+}
+
+const plugin: Plugin = (options) => ({
+ // name 可选,插件名称
+ name: 'plugin-name',
+ // setup 必选,用于定制工程构建配置
+ setup: ({ onGetConfig, modifyUserConfig }) => {
+ modifyUserConfig('codeSplitting', 'page');
+
+ onGetConfig((config) => {
+ config.entry = {
+ web: join(process.cwd(), '.ice/entry.client.tsx'),
+ };
+
+ config.cssFilename = '[name].css';
+
+ config.configureWebpack = config.configureWebpack || [];
+ config.configureWebpack?.push((webpackConfig) => {
+ if (webpackConfig.output) {
+ webpackConfig.output.filename = '[name].js';
+ webpackConfig.output.chunkFilename = '[name].js';
+ }
+ return webpackConfig;
+ });
+
+ config.swcOptions = _.merge(config.swcOptions, {
+ compilationConfig: {
+ jsc: {
+ transform: {
+ react: {
+ runtime: 'classic',
+ },
+ },
+ },
+ },
+ });
+
+ // 解决 webpack publicPath 问题
+ config.transforms = config.transforms || [];
+ config.transforms.push((source: string, id: string) => {
+ if (id.includes('.ice/entry.client.tsx')) {
+ let code = `
+ if (!__webpack_public_path__?.startsWith('http') && document.currentScript) {
+ // @ts-ignore
+ __webpack_public_path__ = document.currentScript.src.replace(/^(.*\\/)[^/]+$/, '$1');
+ window.__ICE_ASSETS_MANIFEST__ = window.__ICE_ASSETS_MANIFEST__ || {};
+ window.__ICE_ASSETS_MANIFEST__.publicPath = __webpack_public_path__;
+ }
+ `;
+ code += source;
+ return { code };
+ }
+ });
+ });
+ },
+});
+
+// The project config, see https://v3.ice.work/docs/guide/basic/config
+const minify = process.env.NODE_ENV === 'production' ? 'swc' : false;
+export default defineConfig(() => ({
+ ssr: false,
+ ssg: false,
+ minify,
+
+ externals: {
+ react: 'React',
+ 'react-dom': 'ReactDOM',
+ 'react-dom/client': 'ReactDOM',
+ '@alifd/next': 'Next',
+ lodash: 'var window._',
+ '@alilc/lowcode-engine': 'var window.AliLowCodeEngine',
+ },
+ plugins: [
+ fusion({
+ importStyle: 'sass',
+ }),
+ locales(),
+ plugin(),
+ ],
+}));
+
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo4/expected/demo-project/package.json b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo4/expected/demo-project/package.json
new file mode 100644
index 0000000000..393be4663e
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo4/expected/demo-project/package.json
@@ -0,0 +1,45 @@
+{
+ "name": "icejs3-demo-app",
+ "version": "0.1.5",
+ "description": "icejs 3 轻量级模板,使用 JavaScript,仅包含基础的 Layout。",
+ "dependencies": {
+ "moment": "^2.24.0",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
+ "react-router": "^6.9.0",
+ "react-router-dom": "^6.9.0",
+ "intl-messageformat": "^9.3.6",
+ "@alifd/next": "1.26.15",
+ "@ice/runtime": "~1.1.0",
+ "@alilc/lowcode-datasource-engine": "^1.0.0",
+ "@alilc/lowcode-datasource-fetch-handler": "^1.0.0",
+ "@alife/container": "^1.0.0",
+ "@alife/mc-assets-1935": "0.1.9"
+ },
+ "devDependencies": {
+ "@ice/app": "~3.1.0",
+ "@types/react": "^18.0.0",
+ "@types/react-dom": "^18.0.0",
+ "@types/node": "^18.11.17",
+ "@ice/plugin-fusion": "^1.0.1",
+ "@ice/plugin-moment-locales": "^1.0.0",
+ "eslint": "^6.0.1",
+ "stylelint": "^13.2.0"
+ },
+ "scripts": {
+ "start": "ice start",
+ "build": "ice build",
+ "lint": "npm run eslint && npm run stylelint",
+ "eslint": "eslint --cache --ext .js,.jsx ./",
+ "stylelint": "stylelint ./**/*.scss"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "repository": {
+ "type": "git",
+ "url": "http://gitlab.xxx.com/msd/leak-scan/tree/master"
+ },
+ "private": true,
+ "originTemplate": "@alifd/scaffold-lite-js"
+}
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo4/expected/demo-project/src/app.ts b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo4/expected/demo-project/src/app.ts
new file mode 100644
index 0000000000..6d5856292d
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo4/expected/demo-project/src/app.ts
@@ -0,0 +1,13 @@
+import { defineAppConfig } from 'ice';
+
+// App config, see https://v3.ice.work/docs/guide/basic/app
+export default defineAppConfig(() => ({
+ // Set your configs here.
+ app: {
+ rootId: 'App',
+ },
+ router: {
+ type: 'browser',
+ basename: '/',
+ },
+}));
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo4/expected/demo-project/src/constants.js b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo4/expected/demo-project/src/constants.js
new file mode 100644
index 0000000000..ea766c9da3
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo4/expected/demo-project/src/constants.js
@@ -0,0 +1,3 @@
+const __$$constants = {};
+
+export default __$$constants;
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo4/expected/demo-project/src/document.tsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo4/expected/demo-project/src/document.tsx
new file mode 100644
index 0000000000..aff0231d95
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo4/expected/demo-project/src/document.tsx
@@ -0,0 +1,29 @@
+import React from 'react';
+import { Meta, Title, Links, Main, Scripts } from 'ice';
+
+export default function Document() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo4/expected/demo-project/src/global.scss b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo4/expected/demo-project/src/global.scss
new file mode 100644
index 0000000000..82ca3eac73
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo4/expected/demo-project/src/global.scss
@@ -0,0 +1,6 @@
+// 引入默认全局样式
+@import '@alifd/next/reset.scss';
+
+body {
+ -webkit-font-smoothing: antialiased;
+}
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo4/expected/demo-project/src/i18n.js b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo4/expected/demo-project/src/i18n.js
new file mode 100644
index 0000000000..1334d2502b
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo4/expected/demo-project/src/i18n.js
@@ -0,0 +1,77 @@
+const i18nConfig = {};
+
+let locale =
+ typeof navigator === 'object' && typeof navigator.language === 'string'
+ ? navigator.language
+ : 'zh-CN';
+
+const getLocale = () => locale;
+
+const setLocale = (target) => {
+ locale = target;
+};
+
+const isEmptyVariables = (variables) =>
+ (Array.isArray(variables) && variables.length === 0) ||
+ (typeof variables === 'object' &&
+ (!variables || Object.keys(variables).length === 0));
+
+// 按低代码规范里面的要求进行变量替换
+const format = (msg, variables) =>
+ typeof msg === 'string'
+ ? msg.replace(/\$?\{(\w+)\}/g, (match, key) => variables?.[key] ?? '')
+ : msg;
+
+const i18nFormat = ({ id, defaultMessage, fallback }, variables) => {
+ const msg =
+ i18nConfig[locale]?.[id] ??
+ i18nConfig[locale.replace('-', '_')]?.[id] ??
+ defaultMessage;
+ if (msg == null) {
+ console.warn('[i18n]: unknown message id: %o (locale=%o)', id, locale);
+ return fallback === undefined ? `${id}` : fallback;
+ }
+
+ return format(msg, variables);
+};
+
+const i18n = (id, params) => {
+ return i18nFormat({ id }, params);
+};
+
+// 将国际化的一些方法注入到目标对象&上下文中
+const _inject2 = (target) => {
+ target.i18n = i18n;
+ target.getLocale = getLocale;
+ target.setLocale = (locale) => {
+ setLocale(locale);
+ target.forceUpdate();
+ };
+ target._i18nText = (t) => {
+ // 优先取直接传过来的语料
+ const localMsg = t[locale] ?? t[String(locale).replace('-', '_')];
+ if (localMsg != null) {
+ return format(localMsg, t.params);
+ }
+
+ // 其次用项目级别的
+ const projectMsg = i18nFormat({ id: t.key, fallback: null }, t.params);
+ if (projectMsg != null) {
+ return projectMsg;
+ }
+
+ // 兜底用 use 指定的或默认语言的
+ return format(t[t.use || 'zh-CN'] ?? t.en_US, t.params);
+ };
+
+ // 注入到上下文中去
+ if (target._context && target._context !== target) {
+ Object.assign(target._context, {
+ i18n,
+ getLocale,
+ setLocale: target.setLocale,
+ });
+ }
+};
+
+export { getLocale, setLocale, i18n, i18nFormat, _inject2 };
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo4/expected/demo-project/src/layouts/BasicLayout/components/Footer/index.jsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo4/expected/demo-project/src/layouts/BasicLayout/components/Footer/index.jsx
new file mode 100644
index 0000000000..cc70d53bea
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo4/expected/demo-project/src/layouts/BasicLayout/components/Footer/index.jsx
@@ -0,0 +1,14 @@
+
+import React from 'react';
+import styles from './index.module.scss';
+
+export default function Footer() {
+ return (
+
+ Alibaba Fusion
+
+ © 2019-现在 Alibaba Fusion & ICE
+
+ );
+}
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo4/expected/demo-project/src/layouts/BasicLayout/components/Footer/index.module.scss b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo4/expected/demo-project/src/layouts/BasicLayout/components/Footer/index.module.scss
new file mode 100644
index 0000000000..81e77fda5f
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo4/expected/demo-project/src/layouts/BasicLayout/components/Footer/index.module.scss
@@ -0,0 +1,15 @@
+
+.footer {
+ line-height: 20px;
+ text-align: center;
+}
+
+.logo {
+ font-weight: bold;
+ font-size: 16px;
+}
+
+.copyright {
+ font-size: 12px;
+}
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo4/expected/demo-project/src/layouts/BasicLayout/components/Logo/index.jsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo4/expected/demo-project/src/layouts/BasicLayout/components/Logo/index.jsx
new file mode 100644
index 0000000000..265bfdaa07
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo4/expected/demo-project/src/layouts/BasicLayout/components/Logo/index.jsx
@@ -0,0 +1,16 @@
+
+import React from 'react';
+import { Link } from 'ice';
+import styles from './index.module.scss';
+
+export default function Logo({ image, text, url }) {
+ return (
+
+
+ {image &&

}
+
{text}
+
+
+ );
+}
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo4/expected/demo-project/src/layouts/BasicLayout/components/Logo/index.module.scss b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo4/expected/demo-project/src/layouts/BasicLayout/components/Logo/index.module.scss
new file mode 100644
index 0000000000..dad05a263f
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo4/expected/demo-project/src/layouts/BasicLayout/components/Logo/index.module.scss
@@ -0,0 +1,20 @@
+
+.logo{
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: #FF7300;
+ font-weight: bold;
+ font-size: 14px;
+ line-height: 22px;
+
+ &:visited, &:link {
+ color: #FF7300;
+ }
+
+ img {
+ height: 24px;
+ margin-right: 10px;
+ }
+}
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo4/expected/demo-project/src/layouts/BasicLayout/components/PageNav/index.jsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo4/expected/demo-project/src/layouts/BasicLayout/components/PageNav/index.jsx
new file mode 100644
index 0000000000..911998b0d3
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo4/expected/demo-project/src/layouts/BasicLayout/components/PageNav/index.jsx
@@ -0,0 +1,68 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { Link, useLocation } from 'ice';
+import { Nav } from '@alifd/next';
+import { asideMenuConfig } from '../../menuConfig';
+
+const { SubNav } = Nav;
+const NavItem = Nav.Item;
+
+function getNavMenuItems(menusData) {
+ if (!menusData) {
+ return [];
+ }
+
+ return menusData
+ .filter(item => item.name && !item.hideInMenu)
+ .map((item, index) => getSubMenuOrItem(item, index));
+}
+
+function getSubMenuOrItem(item, index) {
+ if (item.children && item.children.some(child => child.name)) {
+ const childrenItems = getNavMenuItems(item.children);
+
+ if (childrenItems && childrenItems.length > 0) {
+ const subNav = (
+
+ {childrenItems}
+
+ );
+ return subNav;
+ }
+
+ return null;
+ }
+
+ const navItem = (
+
+ {item.name}
+
+ );
+ return navItem;
+}
+
+const Navigation = (props, context) => {
+ const location = useLocation();
+ const { pathname } = location;
+ const { isCollapse } = context;
+ return (
+
+ );
+};
+
+Navigation.contextTypes = {
+ isCollapse: PropTypes.bool,
+};
+export default Navigation;
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo4/expected/demo-project/src/layouts/BasicLayout/index.jsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo4/expected/demo-project/src/layouts/BasicLayout/index.jsx
new file mode 100644
index 0000000000..18db44df5e
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo4/expected/demo-project/src/layouts/BasicLayout/index.jsx
@@ -0,0 +1,81 @@
+
+import React, { useState } from 'react';
+import { Shell, ConfigProvider } from '@alifd/next';
+import PageNav from './components/PageNav';
+import Logo from './components/Logo';
+import Footer from './components/Footer';
+
+(function() {
+ const throttle = function(type, name, obj = window) {
+ let running = false;
+
+ const func = () => {
+ if (running) {
+ return;
+ }
+
+ running = true;
+ requestAnimationFrame(() => {
+ obj.dispatchEvent(new CustomEvent(name));
+ running = false;
+ });
+ };
+
+ obj.addEventListener(type, func);
+ };
+
+ throttle('resize', 'optimizedResize');
+})();
+
+export default function BasicLayout({ children }) {
+ const getDevice = width => {
+ const isPhone =
+ typeof navigator !== 'undefined' && navigator && navigator.userAgent.match(/phone/gi);
+
+ if (width < 680 || isPhone) {
+ return 'phone';
+ }
+ if (width < 1280 && width > 680) {
+ return 'tablet';
+ }
+ return 'desktop';
+ };
+
+ const [device, setDevice] = useState(getDevice(NaN));
+ window.addEventListener('optimizedResize', e => {
+ setDevice(getDevice(e && e.target && e.target.innerWidth));
+ });
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ {children}
+
+
+
+
+
+ );
+}
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo4/expected/demo-project/src/layouts/BasicLayout/menuConfig.js b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo4/expected/demo-project/src/layouts/BasicLayout/menuConfig.js
new file mode 100644
index 0000000000..5332202be4
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo4/expected/demo-project/src/layouts/BasicLayout/menuConfig.js
@@ -0,0 +1,11 @@
+
+const headerMenuConfig = [];
+const asideMenuConfig = [
+ {
+ name: 'Dashboard',
+ path: '/',
+ icon: 'smile',
+ },
+];
+export { headerMenuConfig, asideMenuConfig };
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo4/expected/demo-project/src/pages/Test/index.css b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo4/expected/demo-project/src/pages/Test/index.css
new file mode 100644
index 0000000000..066114aeeb
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo4/expected/demo-project/src/pages/Test/index.css
@@ -0,0 +1,8 @@
+body {
+ font-size: 12px;
+}
+
+.botton {
+ width: 100px;
+ color: #ff00ff;
+}
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo4/expected/demo-project/src/pages/Test/index.jsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo4/expected/demo-project/src/pages/Test/index.jsx
new file mode 100644
index 0000000000..6400d7445b
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo4/expected/demo-project/src/pages/Test/index.jsx
@@ -0,0 +1,292 @@
+// 注意: 出码引擎注入的临时变量默认都以 "__$$" 开头,禁止在搭建的代码中直接访问。
+// 例外:react 框架的导出名和各种组件名除外。
+import React from 'react';
+
+import {
+ Page as NextPage,
+ Block as NextBlock,
+ P as NextP,
+ Text as NextText,
+} from '@alife/container/lib/index.js';
+
+import { AliSearchTable as AliSearchTableExport } from '@alife/mc-assets-1935/build/lowcode/index.js';
+
+import { createFetchHandler as __$$createFetchRequestHandler } from '@alilc/lowcode-datasource-fetch-handler';
+
+import { create as __$$createDataSourceEngine } from '@alilc/lowcode-datasource-engine/runtime';
+
+import utils, { RefsManager } from '../../utils';
+
+import * as __$$i18n from '../../i18n';
+
+import __$$constants from '../../constants';
+
+import './index.css';
+
+const NextBlockCell = NextBlock.Cell;
+
+const AliSearchTable = AliSearchTableExport.default;
+
+class Test$$Page extends React.Component {
+ _context = this;
+
+ _dataSourceConfig = this._defineDataSourceConfig();
+ _dataSourceEngine = __$$createDataSourceEngine(this._dataSourceConfig, this, {
+ runtimeConfig: true,
+ requestHandlersMap: { fetch: __$$createFetchRequestHandler() },
+ });
+
+ get dataSourceMap() {
+ return this._dataSourceEngine.dataSourceMap || {};
+ }
+
+ reloadDataSource = async () => {
+ await this._dataSourceEngine.reloadDataSource();
+ };
+
+ get constants() {
+ return __$$constants || {};
+ }
+
+ constructor(props, context) {
+ super(props);
+
+ this.utils = utils;
+
+ this._refsManager = new RefsManager();
+
+ __$$i18n._inject2(this);
+
+ this.state = { text: 'outter', isShowDialog: false };
+ }
+
+ $ = (refName) => {
+ return this._refsManager.get(refName);
+ };
+
+ $$ = (refName) => {
+ return this._refsManager.getAll(refName);
+ };
+
+ _defineDataSourceConfig() {
+ const _this = this;
+ return {
+ list: [
+ {
+ type: 'fetch',
+ isInit: function () {
+ return true;
+ }.bind(_this),
+ options: function () {
+ return {
+ params: {},
+ method: 'GET',
+ isCors: true,
+ timeout: 5000,
+ headers: {},
+ uri: 'https://mocks.xxx.com/mock/jjpin/user/list',
+ };
+ }.bind(_this),
+ id: 'users',
+ },
+ ],
+ };
+ }
+
+ componentWillUnmount() {
+ console.log('will umount');
+ }
+
+ componentDidUpdate(prevProps, prevState, snapshot) {
+ console.log(this.state);
+ }
+
+ testFunc() {
+ console.log('test func');
+ }
+
+ onClick() {
+ this.setState({
+ isShowDialog: true,
+ });
+ }
+
+ closeDialog() {
+ this.setState({
+ isShowDialog: false,
+ });
+ }
+
+ onSearch(values) {
+ console.log('search form:', values);
+ console.log(this.dataSourceMap);
+ this.dataSourceMap['users'].load(values);
+ }
+
+ onClear() {
+ console.log('form reset');
+ this.setState({
+ isShowDialog: true,
+ });
+ }
+
+ onPageChange(page, pageSize) {
+ console.log(`page: ${page}, pageSize: ${pageSize}`);
+ }
+
+ componentDidMount() {
+ this._dataSourceEngine.reloadDataSource();
+
+ console.log('did mount');
+ }
+
+ render() {
+ const __$$context = this._context || this;
+ const { state } = __$$context;
+ return (
+
+
+ 员工列表
+
+ }
+ headerTest={[]}
+ headerProps={{ background: 'surface' }}
+ footer={null}
+ minHeight="100vh"
+ >
+
+
+
+ this.state.users.data)}
+ rowKey="workid"
+ columns={[
+ { title: '花名', dataIndex: 'cname' },
+ { title: 'user_id', dataIndex: 'workid' },
+ { title: '部门', dataIndex: 'dep' },
+ ]}
+ searchItems={[
+ { label: '姓名', name: 'cname' },
+ { label: '部门', name: 'dep' },
+ ]}
+ onSearch={function () {
+ return this.onSearch.apply(
+ this,
+ Array.prototype.slice.call(arguments).concat([])
+ );
+ }.bind(this)}
+ onClear={function () {
+ return this.onClear.apply(
+ this,
+ Array.prototype.slice.call(arguments).concat([])
+ );
+ }.bind(this)}
+ pagination={{
+ defaultPageSize: '',
+ onPageChange: function () {
+ return this.onPageChange.apply(
+ this,
+ Array.prototype.slice.call(arguments).concat([])
+ );
+ }.bind(this),
+ showSizeChanger: true,
+ }}
+ />
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+}
+
+export default Test$$Page;
+
+function __$$eval(expr) {
+ try {
+ return expr();
+ } catch (error) {}
+}
+
+function __$$evalArray(expr) {
+ const res = __$$eval(expr);
+ return Array.isArray(res) ? res : [];
+}
+
+function __$$createChildContext(oldContext, ext) {
+ const childContext = {
+ ...oldContext,
+ ...ext,
+ };
+ childContext.__proto__ = oldContext;
+ return childContext;
+}
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo4/expected/demo-project/src/pages/layout.jsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo4/expected/demo-project/src/pages/layout.jsx
new file mode 100644
index 0000000000..50fbb2d1f1
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo4/expected/demo-project/src/pages/layout.jsx
@@ -0,0 +1,10 @@
+import { Outlet } from 'ice';
+import BasicLayout from '@/layouts/BasicLayout';
+
+export default function Layout() {
+ return (
+
+
+
+ );
+}
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo4/expected/demo-project/src/typings.d.ts b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo4/expected/demo-project/src/typings.d.ts
new file mode 100644
index 0000000000..a9f8de7ceb
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo4/expected/demo-project/src/typings.d.ts
@@ -0,0 +1,9 @@
+///
+
+export {};
+declare global {
+ interface Window {
+ g_config: Record;
+ }
+}
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo4/expected/demo-project/src/utils.js b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo4/expected/demo-project/src/utils.js
new file mode 100644
index 0000000000..1190717924
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo4/expected/demo-project/src/utils.js
@@ -0,0 +1,47 @@
+import { createRef } from 'react';
+
+export class RefsManager {
+ constructor() {
+ this.refInsStore = {};
+ }
+
+ clearNullRefs() {
+ Object.keys(this.refInsStore).forEach((refName) => {
+ const filteredInsList = this.refInsStore[refName].filter(
+ (insRef) => !!insRef.current
+ );
+ if (filteredInsList.length > 0) {
+ this.refInsStore[refName] = filteredInsList;
+ } else {
+ delete this.refInsStore[refName];
+ }
+ });
+ }
+
+ get(refName) {
+ this.clearNullRefs();
+ if (this.refInsStore[refName] && this.refInsStore[refName].length > 0) {
+ return this.refInsStore[refName][0].current;
+ }
+
+ return null;
+ }
+
+ getAll(refName) {
+ this.clearNullRefs();
+ if (this.refInsStore[refName] && this.refInsStore[refName].length > 0) {
+ return this.refInsStore[refName].map((i) => i.current);
+ }
+
+ return [];
+ }
+
+ linkRef(refName) {
+ const refIns = createRef();
+ this.refInsStore[refName] = this.refInsStore[refName] || [];
+ this.refInsStore[refName].push(refIns);
+ return refIns;
+ }
+}
+
+export default {};
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo4/schema.json5 b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo4/schema.json5
new file mode 100644
index 0000000000..ca97204e9c
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo4/schema.json5
@@ -0,0 +1,353 @@
+{
+ "version": "1.0.0",
+ "componentsMap": [
+ {
+ "package": "@alife/mc-assets-1935",
+ "version": "0.1.9",
+ "exportName": "AliSearchTable",
+ "main": "build/lowcode/index.js",
+ "subName": "default",
+ "destructuring": true,
+ "componentName": "AliSearchTable"
+ },
+ {
+ "package": "@alife/container",
+ "version": "^1.0.0",
+ "exportName": "P",
+ "main": "lib/index.js",
+ "destructuring": true,
+ "subName": "",
+ "componentName": "NextP"
+ },
+ {
+ "package": "@alife/container",
+ "version": "^1.0.0",
+ "exportName": "Block",
+ "main": "lib/index.js",
+ "destructuring": true,
+ "subName": "Cell",
+ "componentName": "NextBlockCell"
+ },
+ {
+ "package": "@alife/container",
+ "version": "^1.0.0",
+ "exportName": "Block",
+ "main": "lib/index.js",
+ "destructuring": true,
+ "subName": "",
+ "componentName": "NextBlock"
+ },
+ {
+ "package": "@alife/container",
+ "version": "^1.0.0",
+ "exportName": "Text",
+ "main": "lib/index.js",
+ "destructuring": true,
+ "subName": "",
+ "componentName": "NextText"
+ },
+ {
+ "package": "@alife/container",
+ "version": "^1.0.0",
+ "exportName": "Page",
+ "main": "lib/index.js",
+ "destructuring": true,
+ "subName": "",
+ "componentName": "NextPage"
+ }
+ ],
+ "componentsTree": [
+ {
+ "componentName": "Page",
+ "id": "node_dockcviv8fo1",
+ "props": {
+ "ref": "outterView",
+ "style": {
+ "height": "100%"
+ }
+ },
+ "fileName": "test",
+ "dataSource": {
+ "list": [
+ {
+ "type": "fetch",
+ "isInit": true,
+ "options": {
+ "params": {},
+ "method": "GET",
+ "isCors": true,
+ "timeout": 5000,
+ "headers": {},
+ "uri": "https://mocks.xxx.com/mock/jjpin/user/list"
+ },
+ "id": "users"
+ }
+ ]
+ },
+ "css": "body {\n font-size: 12px;\n}\n\n.botton {\n width: 100px;\n color: #ff00ff\n}",
+ "lifeCycles": {
+ "componentDidMount": {
+ "type": "JSFunction",
+ "value": "function() {\n console.log('did mount');\n }"
+ },
+ "componentWillUnmount": {
+ "type": "JSFunction",
+ "value": "function() {\n console.log('will umount');\n }"
+ },
+ "componentDidUpdate": {
+ "type": "JSFunction",
+ "value": "function(prevProps, prevState, snapshot) {\n console.log(this.state);\n }"
+ }
+ },
+ "methods": {
+ "testFunc": {
+ "type": "JSFunction",
+ "value": "function() {\n console.log('test func');\n }"
+ },
+ "onClick": {
+ "type": "JSFunction",
+ "value": "function() {\n this.setState({\n isShowDialog: true\n })\n }"
+ },
+ "closeDialog": {
+ "type": "JSFunction",
+ "value": "function() {\n this.setState({\n isShowDialog: false\n })\n }"
+ },
+ "onSearch": {
+ "type": "JSFunction",
+ "value": "function(values) {\n console.log('search form:', values)\n console.log(this.dataSourceMap);\n this.dataSourceMap['users'].load(values)\n }"
+ },
+ "onClear": {
+ "type": "JSFunction",
+ "value": "function() {\n console.log('form reset')\n this.setState({\n isShowDialog: true\n })\n }"
+ },
+ "onPageChange": {
+ "type": "JSFunction",
+ "value": "function(page, pageSize) {\n console.log(`page: ${page}, pageSize: ${pageSize}`)\n }"
+ }
+ },
+ "state": {
+ "text": "outter",
+ "isShowDialog": false
+ },
+ "children": [
+ {
+ "componentName": "NextPage",
+ "id": "node_ockkgjwi8z1",
+ "props": {
+ "columns": 12,
+ "placeholderStyle": {
+ "gridRowEnd": "span 1",
+ "gridColumnEnd": "span 12"
+ },
+ "placeholder": "页面主体内容:拖拽Block布局组件到这里",
+ "header": {
+ "type": "JSSlot",
+ "value": [
+ {
+ "componentName": "NextP",
+ "id": "node_ockkgjwi8zn",
+ "props": {
+ "wrap": true,
+ "type": "body2",
+ "verAlign": "middle",
+ "textSpacing": true,
+ "align": "left",
+ "flex": true
+ },
+ "children": [
+ {
+ "componentName": "NextText",
+ "id": "node_ockkgjwi8zo",
+ "props": {
+ "type": "h5",
+ "children": "员工列表"
+ }
+ }
+ ]
+ }
+ ],
+ "title": "header"
+ },
+ "headerTest": {
+ "type": "JSSlot",
+ "value": [],
+ "title": "header"
+ },
+ "headerProps": {
+ "background": "surface"
+ },
+ "footer": {
+ "type": "JSSlot",
+ "title": "footer"
+ },
+ "minHeight": "100vh"
+ },
+ "children": [
+ {
+ "componentName": "NextBlock",
+ "id": "node_ockkgjwi8z2",
+ "props": {
+ "prefix": "next-",
+ "placeholderStyle": {
+ "height": "100%"
+ },
+ "noPadding": false,
+ "noBorder": false,
+ "background": "surface",
+ "colSpan": 12,
+ "rowSpan": 1,
+ "childTotalColumns": "1fr"
+ },
+ "title": "分区",
+ "children": [
+ {
+ "componentName": "NextBlockCell",
+ "id": "node_ockkgjwi8z3",
+ "props": {
+ "title": "",
+ "primaryKey": "732",
+ "prefix": "next-",
+ "placeholderStyle": {
+ "height": "100%"
+ },
+ "colSpan": 1,
+ "rowSpan": 1
+ },
+ "children": [
+ {
+ "componentName": "NextP",
+ "id": "node_ockkgjwi8zu",
+ "props": {
+ "wrap": true,
+ "type": "body2",
+ "textSpacing": true,
+ "verAlign": "center",
+ "align": "flex-start",
+ "flex": true
+ },
+ "children": [
+ {
+ "componentName": "AliSearchTable",
+ "id": "node_ockkgjwi8zv",
+ "props": {
+ "dataSource": {
+ "type": "JSExpression",
+ "value": "this.state.users.data"
+ },
+ "rowKey": "workid",
+ "columns": [
+ {
+ "title": "花名",
+ "dataIndex": "cname"
+ },
+ {
+ "title": "user_id",
+ "dataIndex": "workid"
+ },
+ {
+ "title": "部门",
+ "dataIndex": "dep"
+ }
+ ],
+ "searchItems": [
+ {
+ "label": "姓名",
+ "name": "cname"
+ },
+ {
+ "label": "部门",
+ "name": "dep"
+ }
+ ],
+ "onSearch": {
+ "type": "JSFunction",
+ "value": "function(){ return this.onSearch.apply(this,Array.prototype.slice.call(arguments).concat([])) }"
+ },
+ "onClear": {
+ "type": "JSFunction",
+ "value": "function(){ return this.onClear.apply(this,Array.prototype.slice.call(arguments).concat([])) }"
+ },
+ "pagination": {
+ "defaultPageSize": "",
+ "onPageChange": {
+ "type": "JSFunction",
+ "value": "function(){ return this.onPageChange.apply(this,Array.prototype.slice.call(arguments).concat([])) }"
+ },
+ "showSizeChanger": true
+ }
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "componentName": "NextPage",
+ "id": "node_ockm4jxd6313",
+ "props": {
+ "columns": 12,
+ "headerDivider": true,
+ "placeholderStyle": {
+ "gridRowEnd": "span 1",
+ "gridColumnEnd": "span 12"
+ },
+ "placeholder": "页面主体内容:拖拽Block布局组件到这里",
+ "header": {
+ "type": "JSSlot",
+ "title": "header"
+ },
+ "headerProps": {
+ "background": "surface"
+ },
+ "footer": {
+ "type": "JSSlot",
+ "title": "footer"
+ },
+ "minHeight": "100vh"
+ },
+ "title": "页面",
+ "children": [
+ {
+ "componentName": "NextBlock",
+ "id": "node_ockm4jxd6314",
+ "props": {
+ "prefix": "next-",
+ "placeholderStyle": {
+ "height": "100%"
+ },
+ "noPadding": false,
+ "noBorder": false,
+ "background": "surface",
+ "colSpan": 12,
+ "rowSpan": 1,
+ "childTotalColumns": 1
+ },
+ "title": "区块",
+ "children": [
+ {
+ "componentName": "NextBlockCell",
+ "id": "node_ockm4jxd6315",
+ "props": {
+ "title": "",
+ "primaryKey": "472",
+ "prefix": "next-",
+ "placeholderStyle": {
+ "height": "100%"
+ },
+ "colSpan": 1,
+ "rowSpan": 1
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "i18n": {}
+}
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo5/expected/demo-project/.browserslistrc b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo5/expected/demo-project/.browserslistrc
new file mode 100644
index 0000000000..55a130413d
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo5/expected/demo-project/.browserslistrc
@@ -0,0 +1,3 @@
+defaults
+ios_saf 9
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo5/expected/demo-project/.gitignore b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo5/expected/demo-project/.gitignore
new file mode 100644
index 0000000000..4ec178818e
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo5/expected/demo-project/.gitignore
@@ -0,0 +1,25 @@
+
+# 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
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo5/expected/demo-project/README.md b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo5/expected/demo-project/README.md
new file mode 100644
index 0000000000..6d9dd75215
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo5/expected/demo-project/README.md
@@ -0,0 +1 @@
+This project is generated by lowcode-code-generator & lowcode-solution-icejs3.
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo5/expected/demo-project/ice.config.mts b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo5/expected/demo-project/ice.config.mts
new file mode 100644
index 0000000000..e1d8a28141
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo5/expected/demo-project/ice.config.mts
@@ -0,0 +1,90 @@
+import { join } from 'path';
+import { defineConfig } from '@ice/app';
+import _ from 'lodash';
+import fusion from '@ice/plugin-fusion';
+import locales from '@ice/plugin-moment-locales';
+import type { Plugin } from '@ice/app/esm/types';
+
+interface PluginOptions {
+ id: string;
+}
+
+const plugin: Plugin = (options) => ({
+ // name 可选,插件名称
+ name: 'plugin-name',
+ // setup 必选,用于定制工程构建配置
+ setup: ({ onGetConfig, modifyUserConfig }) => {
+ modifyUserConfig('codeSplitting', 'page');
+
+ onGetConfig((config) => {
+ config.entry = {
+ web: join(process.cwd(), '.ice/entry.client.tsx'),
+ };
+
+ config.cssFilename = '[name].css';
+
+ config.configureWebpack = config.configureWebpack || [];
+ config.configureWebpack?.push((webpackConfig) => {
+ if (webpackConfig.output) {
+ webpackConfig.output.filename = '[name].js';
+ webpackConfig.output.chunkFilename = '[name].js';
+ }
+ return webpackConfig;
+ });
+
+ config.swcOptions = _.merge(config.swcOptions, {
+ compilationConfig: {
+ jsc: {
+ transform: {
+ react: {
+ runtime: 'classic',
+ },
+ },
+ },
+ },
+ });
+
+ // 解决 webpack publicPath 问题
+ config.transforms = config.transforms || [];
+ config.transforms.push((source: string, id: string) => {
+ if (id.includes('.ice/entry.client.tsx')) {
+ let code = `
+ if (!__webpack_public_path__?.startsWith('http') && document.currentScript) {
+ // @ts-ignore
+ __webpack_public_path__ = document.currentScript.src.replace(/^(.*\\/)[^/]+$/, '$1');
+ window.__ICE_ASSETS_MANIFEST__ = window.__ICE_ASSETS_MANIFEST__ || {};
+ window.__ICE_ASSETS_MANIFEST__.publicPath = __webpack_public_path__;
+ }
+ `;
+ code += source;
+ return { code };
+ }
+ });
+ });
+ },
+});
+
+// The project config, see https://v3.ice.work/docs/guide/basic/config
+const minify = process.env.NODE_ENV === 'production' ? 'swc' : false;
+export default defineConfig(() => ({
+ ssr: false,
+ ssg: false,
+ minify,
+
+ externals: {
+ react: 'React',
+ 'react-dom': 'ReactDOM',
+ 'react-dom/client': 'ReactDOM',
+ '@alifd/next': 'Next',
+ lodash: 'var window._',
+ '@alilc/lowcode-engine': 'var window.AliLowCodeEngine',
+ },
+ plugins: [
+ fusion({
+ importStyle: 'sass',
+ }),
+ locales(),
+ plugin(),
+ ],
+}));
+
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo5/expected/demo-project/package.json b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo5/expected/demo-project/package.json
new file mode 100644
index 0000000000..d007025a60
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo5/expected/demo-project/package.json
@@ -0,0 +1,46 @@
+{
+ "name": "icejs3-demo-app",
+ "version": "0.1.5",
+ "description": "icejs 3 轻量级模板,使用 JavaScript,仅包含基础的 Layout。",
+ "dependencies": {
+ "moment": "^2.24.0",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
+ "react-router": "^6.9.0",
+ "react-router-dom": "^6.9.0",
+ "intl-messageformat": "^9.3.6",
+ "@alifd/next": "1.26.15",
+ "@ice/runtime": "~1.1.0",
+ "@alilc/lowcode-datasource-engine": "^1.0.0",
+ "undefined": "*",
+ "@alife/container": "0.3.7",
+ "@alilc/antd-lowcode": "0.5.4",
+ "@alife/mc-assets-1935": "0.1.16"
+ },
+ "devDependencies": {
+ "@ice/app": "~3.1.0",
+ "@types/react": "^18.0.0",
+ "@types/react-dom": "^18.0.0",
+ "@types/node": "^18.11.17",
+ "@ice/plugin-fusion": "^1.0.1",
+ "@ice/plugin-moment-locales": "^1.0.0",
+ "eslint": "^6.0.1",
+ "stylelint": "^13.2.0"
+ },
+ "scripts": {
+ "start": "ice start",
+ "build": "ice build",
+ "lint": "npm run eslint && npm run stylelint",
+ "eslint": "eslint --cache --ext .js,.jsx ./",
+ "stylelint": "stylelint ./**/*.scss"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "repository": {
+ "type": "git",
+ "url": "http://gitlab.xxx.com/msd/leak-scan/tree/master"
+ },
+ "private": true,
+ "originTemplate": "@alifd/scaffold-lite-js"
+}
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo5/expected/demo-project/src/app.ts b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo5/expected/demo-project/src/app.ts
new file mode 100644
index 0000000000..6d5856292d
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo5/expected/demo-project/src/app.ts
@@ -0,0 +1,13 @@
+import { defineAppConfig } from 'ice';
+
+// App config, see https://v3.ice.work/docs/guide/basic/app
+export default defineAppConfig(() => ({
+ // Set your configs here.
+ app: {
+ rootId: 'App',
+ },
+ router: {
+ type: 'browser',
+ basename: '/',
+ },
+}));
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo5/expected/demo-project/src/constants.js b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo5/expected/demo-project/src/constants.js
new file mode 100644
index 0000000000..ea766c9da3
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo5/expected/demo-project/src/constants.js
@@ -0,0 +1,3 @@
+const __$$constants = {};
+
+export default __$$constants;
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo5/expected/demo-project/src/document.tsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo5/expected/demo-project/src/document.tsx
new file mode 100644
index 0000000000..aff0231d95
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo5/expected/demo-project/src/document.tsx
@@ -0,0 +1,29 @@
+import React from 'react';
+import { Meta, Title, Links, Main, Scripts } from 'ice';
+
+export default function Document() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo5/expected/demo-project/src/global.scss b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo5/expected/demo-project/src/global.scss
new file mode 100644
index 0000000000..82ca3eac73
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo5/expected/demo-project/src/global.scss
@@ -0,0 +1,6 @@
+// 引入默认全局样式
+@import '@alifd/next/reset.scss';
+
+body {
+ -webkit-font-smoothing: antialiased;
+}
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo5/expected/demo-project/src/i18n.js b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo5/expected/demo-project/src/i18n.js
new file mode 100644
index 0000000000..1334d2502b
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo5/expected/demo-project/src/i18n.js
@@ -0,0 +1,77 @@
+const i18nConfig = {};
+
+let locale =
+ typeof navigator === 'object' && typeof navigator.language === 'string'
+ ? navigator.language
+ : 'zh-CN';
+
+const getLocale = () => locale;
+
+const setLocale = (target) => {
+ locale = target;
+};
+
+const isEmptyVariables = (variables) =>
+ (Array.isArray(variables) && variables.length === 0) ||
+ (typeof variables === 'object' &&
+ (!variables || Object.keys(variables).length === 0));
+
+// 按低代码规范里面的要求进行变量替换
+const format = (msg, variables) =>
+ typeof msg === 'string'
+ ? msg.replace(/\$?\{(\w+)\}/g, (match, key) => variables?.[key] ?? '')
+ : msg;
+
+const i18nFormat = ({ id, defaultMessage, fallback }, variables) => {
+ const msg =
+ i18nConfig[locale]?.[id] ??
+ i18nConfig[locale.replace('-', '_')]?.[id] ??
+ defaultMessage;
+ if (msg == null) {
+ console.warn('[i18n]: unknown message id: %o (locale=%o)', id, locale);
+ return fallback === undefined ? `${id}` : fallback;
+ }
+
+ return format(msg, variables);
+};
+
+const i18n = (id, params) => {
+ return i18nFormat({ id }, params);
+};
+
+// 将国际化的一些方法注入到目标对象&上下文中
+const _inject2 = (target) => {
+ target.i18n = i18n;
+ target.getLocale = getLocale;
+ target.setLocale = (locale) => {
+ setLocale(locale);
+ target.forceUpdate();
+ };
+ target._i18nText = (t) => {
+ // 优先取直接传过来的语料
+ const localMsg = t[locale] ?? t[String(locale).replace('-', '_')];
+ if (localMsg != null) {
+ return format(localMsg, t.params);
+ }
+
+ // 其次用项目级别的
+ const projectMsg = i18nFormat({ id: t.key, fallback: null }, t.params);
+ if (projectMsg != null) {
+ return projectMsg;
+ }
+
+ // 兜底用 use 指定的或默认语言的
+ return format(t[t.use || 'zh-CN'] ?? t.en_US, t.params);
+ };
+
+ // 注入到上下文中去
+ if (target._context && target._context !== target) {
+ Object.assign(target._context, {
+ i18n,
+ getLocale,
+ setLocale: target.setLocale,
+ });
+ }
+};
+
+export { getLocale, setLocale, i18n, i18nFormat, _inject2 };
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo5/expected/demo-project/src/layouts/BasicLayout/components/Footer/index.jsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo5/expected/demo-project/src/layouts/BasicLayout/components/Footer/index.jsx
new file mode 100644
index 0000000000..cc70d53bea
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo5/expected/demo-project/src/layouts/BasicLayout/components/Footer/index.jsx
@@ -0,0 +1,14 @@
+
+import React from 'react';
+import styles from './index.module.scss';
+
+export default function Footer() {
+ return (
+
+ Alibaba Fusion
+
+ © 2019-现在 Alibaba Fusion & ICE
+
+ );
+}
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo5/expected/demo-project/src/layouts/BasicLayout/components/Footer/index.module.scss b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo5/expected/demo-project/src/layouts/BasicLayout/components/Footer/index.module.scss
new file mode 100644
index 0000000000..81e77fda5f
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo5/expected/demo-project/src/layouts/BasicLayout/components/Footer/index.module.scss
@@ -0,0 +1,15 @@
+
+.footer {
+ line-height: 20px;
+ text-align: center;
+}
+
+.logo {
+ font-weight: bold;
+ font-size: 16px;
+}
+
+.copyright {
+ font-size: 12px;
+}
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo5/expected/demo-project/src/layouts/BasicLayout/components/Logo/index.jsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo5/expected/demo-project/src/layouts/BasicLayout/components/Logo/index.jsx
new file mode 100644
index 0000000000..265bfdaa07
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo5/expected/demo-project/src/layouts/BasicLayout/components/Logo/index.jsx
@@ -0,0 +1,16 @@
+
+import React from 'react';
+import { Link } from 'ice';
+import styles from './index.module.scss';
+
+export default function Logo({ image, text, url }) {
+ return (
+
+
+ {image &&

}
+
{text}
+
+
+ );
+}
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo5/expected/demo-project/src/layouts/BasicLayout/components/Logo/index.module.scss b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo5/expected/demo-project/src/layouts/BasicLayout/components/Logo/index.module.scss
new file mode 100644
index 0000000000..dad05a263f
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo5/expected/demo-project/src/layouts/BasicLayout/components/Logo/index.module.scss
@@ -0,0 +1,20 @@
+
+.logo{
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: #FF7300;
+ font-weight: bold;
+ font-size: 14px;
+ line-height: 22px;
+
+ &:visited, &:link {
+ color: #FF7300;
+ }
+
+ img {
+ height: 24px;
+ margin-right: 10px;
+ }
+}
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo5/expected/demo-project/src/layouts/BasicLayout/components/PageNav/index.jsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo5/expected/demo-project/src/layouts/BasicLayout/components/PageNav/index.jsx
new file mode 100644
index 0000000000..911998b0d3
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo5/expected/demo-project/src/layouts/BasicLayout/components/PageNav/index.jsx
@@ -0,0 +1,68 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { Link, useLocation } from 'ice';
+import { Nav } from '@alifd/next';
+import { asideMenuConfig } from '../../menuConfig';
+
+const { SubNav } = Nav;
+const NavItem = Nav.Item;
+
+function getNavMenuItems(menusData) {
+ if (!menusData) {
+ return [];
+ }
+
+ return menusData
+ .filter(item => item.name && !item.hideInMenu)
+ .map((item, index) => getSubMenuOrItem(item, index));
+}
+
+function getSubMenuOrItem(item, index) {
+ if (item.children && item.children.some(child => child.name)) {
+ const childrenItems = getNavMenuItems(item.children);
+
+ if (childrenItems && childrenItems.length > 0) {
+ const subNav = (
+
+ {childrenItems}
+
+ );
+ return subNav;
+ }
+
+ return null;
+ }
+
+ const navItem = (
+
+ {item.name}
+
+ );
+ return navItem;
+}
+
+const Navigation = (props, context) => {
+ const location = useLocation();
+ const { pathname } = location;
+ const { isCollapse } = context;
+ return (
+
+ );
+};
+
+Navigation.contextTypes = {
+ isCollapse: PropTypes.bool,
+};
+export default Navigation;
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo5/expected/demo-project/src/layouts/BasicLayout/index.jsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo5/expected/demo-project/src/layouts/BasicLayout/index.jsx
new file mode 100644
index 0000000000..18db44df5e
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo5/expected/demo-project/src/layouts/BasicLayout/index.jsx
@@ -0,0 +1,81 @@
+
+import React, { useState } from 'react';
+import { Shell, ConfigProvider } from '@alifd/next';
+import PageNav from './components/PageNav';
+import Logo from './components/Logo';
+import Footer from './components/Footer';
+
+(function() {
+ const throttle = function(type, name, obj = window) {
+ let running = false;
+
+ const func = () => {
+ if (running) {
+ return;
+ }
+
+ running = true;
+ requestAnimationFrame(() => {
+ obj.dispatchEvent(new CustomEvent(name));
+ running = false;
+ });
+ };
+
+ obj.addEventListener(type, func);
+ };
+
+ throttle('resize', 'optimizedResize');
+})();
+
+export default function BasicLayout({ children }) {
+ const getDevice = width => {
+ const isPhone =
+ typeof navigator !== 'undefined' && navigator && navigator.userAgent.match(/phone/gi);
+
+ if (width < 680 || isPhone) {
+ return 'phone';
+ }
+ if (width < 1280 && width > 680) {
+ return 'tablet';
+ }
+ return 'desktop';
+ };
+
+ const [device, setDevice] = useState(getDevice(NaN));
+ window.addEventListener('optimizedResize', e => {
+ setDevice(getDevice(e && e.target && e.target.innerWidth));
+ });
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ {children}
+
+
+
+
+
+ );
+}
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo5/expected/demo-project/src/layouts/BasicLayout/menuConfig.js b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo5/expected/demo-project/src/layouts/BasicLayout/menuConfig.js
new file mode 100644
index 0000000000..5332202be4
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo5/expected/demo-project/src/layouts/BasicLayout/menuConfig.js
@@ -0,0 +1,11 @@
+
+const headerMenuConfig = [];
+const asideMenuConfig = [
+ {
+ name: 'Dashboard',
+ path: '/',
+ icon: 'smile',
+ },
+];
+export { headerMenuConfig, asideMenuConfig };
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo5/expected/demo-project/src/pages/Test/index.css b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo5/expected/demo-project/src/pages/Test/index.css
new file mode 100644
index 0000000000..066114aeeb
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo5/expected/demo-project/src/pages/Test/index.css
@@ -0,0 +1,8 @@
+body {
+ font-size: 12px;
+}
+
+.botton {
+ width: 100px;
+ color: #ff00ff;
+}
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo5/expected/demo-project/src/pages/Test/index.jsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo5/expected/demo-project/src/pages/Test/index.jsx
new file mode 100644
index 0000000000..7427f164d9
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo5/expected/demo-project/src/pages/Test/index.jsx
@@ -0,0 +1,389 @@
+// 注意: 出码引擎注入的临时变量默认都以 "__$$" 开头,禁止在搭建的代码中直接访问。
+// 例外:react 框架的导出名和各种组件名除外。
+import React from 'react';
+
+import {
+ Page as NextPage,
+ Block as NextBlock,
+ P as NextP,
+} from '@alife/container/lib/index.js';
+
+import {
+ Card,
+ Space,
+ Typography,
+ Select,
+ Button,
+ Modal,
+ Form,
+ InputNumber,
+ Input,
+} from '@alilc/antd-lowcode/dist/antd-lowcode.esm.js';
+
+import { AliAutoSearchTable } from '@alife/mc-assets-1935/build/lowcode/index.js';
+
+import utils, { RefsManager } from '../../utils';
+
+import * as __$$i18n from '../../i18n';
+
+import __$$constants from '../../constants';
+
+import './index.css';
+
+const NextBlockCell = NextBlock.Cell;
+
+const AliAutoSearchTableDefault = AliAutoSearchTable.default;
+
+class Test$$Page extends React.Component {
+ _context = this;
+
+ get constants() {
+ return __$$constants || {};
+ }
+
+ constructor(props, context) {
+ super(props);
+
+ this.utils = utils;
+
+ this._refsManager = new RefsManager();
+
+ __$$i18n._inject2(this);
+
+ this.state = {
+ name: 'nongzhou',
+ gateways: [],
+ selectedGateway: null,
+ records: [],
+ modalVisible: false,
+ };
+ }
+
+ $ = (refName) => {
+ return this._refsManager.get(refName);
+ };
+
+ $$ = (refName) => {
+ return this._refsManager.getAll(refName);
+ };
+
+ componentWillUnmount() {
+ /* ... */
+ }
+
+ componentDidUpdate() {
+ /* ... */
+ }
+
+ onChange() {
+ /* ... */
+ }
+
+ getActions() {
+ /* ... */
+ }
+
+ onCreateOrder() {
+ /* ... */
+ }
+
+ onCancelModal() {
+ /* ... */
+ }
+
+ onConfirmCreateOrder() {
+ /* ... */
+ }
+
+ componentDidMount() {}
+
+ render() {
+ const __$$context = this._context || this;
+ const { state } = __$$context;
+ return (
+
+
+
+
+
+
+
+ 所在网关:
+
+
+ this.state.modalVisible)}
+ footer=""
+ __events={{
+ eventDataList: [
+ {
+ type: 'componentEvent',
+ name: 'onCancel',
+ relatedEventName: 'onCancelModal',
+ },
+ ],
+ eventList: [
+ { name: 'onCancel', disabled: true },
+ { name: 'onOk', disabled: false },
+ ],
+ }}
+ onCancel={function () {
+ this.onCancelModal.apply(
+ this,
+ Array.prototype.slice.call(arguments).concat([])
+ );
+ }.bind(this)}
+ zIndex={2000}
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ this.state.records)}
+ columns={[
+ {
+ title: '发布名称',
+ dataIndex: 'order_name',
+ key: 'name',
+ },
+ {
+ title: '类型',
+ dataIndex: 'order_type_desc',
+ key: 'age',
+ },
+ {
+ title: '发布状态',
+ dataIndex: 'order_status_desc',
+ key: 'address',
+ },
+ { title: '发布人', dataIndex: 'creator_name' },
+ { title: '当前批次/总批次', dataIndex: 'cur_batch_no' },
+ {
+ title: '发布机器/总机器',
+ dataIndex: 'pubblish_ip_finish_num',
+ },
+ { title: '发布时间', dataIndex: 'publish_id' },
+ ]}
+ actions={__$$eval(() => this.actions || [])}
+ getActions={function () {
+ return this.getActions.apply(
+ this,
+ Array.prototype.slice.call(arguments).concat([])
+ );
+ }.bind(this)}
+ />
+
+
+
+
+
+
+ );
+ }
+}
+
+export default Test$$Page;
+
+function __$$eval(expr) {
+ try {
+ return expr();
+ } catch (error) {}
+}
+
+function __$$evalArray(expr) {
+ const res = __$$eval(expr);
+ return Array.isArray(res) ? res : [];
+}
+
+function __$$createChildContext(oldContext, ext) {
+ const childContext = {
+ ...oldContext,
+ ...ext,
+ };
+ childContext.__proto__ = oldContext;
+ return childContext;
+}
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo5/expected/demo-project/src/pages/layout.jsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo5/expected/demo-project/src/pages/layout.jsx
new file mode 100644
index 0000000000..50fbb2d1f1
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo5/expected/demo-project/src/pages/layout.jsx
@@ -0,0 +1,10 @@
+import { Outlet } from 'ice';
+import BasicLayout from '@/layouts/BasicLayout';
+
+export default function Layout() {
+ return (
+
+
+
+ );
+}
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo5/expected/demo-project/src/typings.d.ts b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo5/expected/demo-project/src/typings.d.ts
new file mode 100644
index 0000000000..a9f8de7ceb
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo5/expected/demo-project/src/typings.d.ts
@@ -0,0 +1,9 @@
+///
+
+export {};
+declare global {
+ interface Window {
+ g_config: Record;
+ }
+}
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo5/expected/demo-project/src/utils.js b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo5/expected/demo-project/src/utils.js
new file mode 100644
index 0000000000..1190717924
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo5/expected/demo-project/src/utils.js
@@ -0,0 +1,47 @@
+import { createRef } from 'react';
+
+export class RefsManager {
+ constructor() {
+ this.refInsStore = {};
+ }
+
+ clearNullRefs() {
+ Object.keys(this.refInsStore).forEach((refName) => {
+ const filteredInsList = this.refInsStore[refName].filter(
+ (insRef) => !!insRef.current
+ );
+ if (filteredInsList.length > 0) {
+ this.refInsStore[refName] = filteredInsList;
+ } else {
+ delete this.refInsStore[refName];
+ }
+ });
+ }
+
+ get(refName) {
+ this.clearNullRefs();
+ if (this.refInsStore[refName] && this.refInsStore[refName].length > 0) {
+ return this.refInsStore[refName][0].current;
+ }
+
+ return null;
+ }
+
+ getAll(refName) {
+ this.clearNullRefs();
+ if (this.refInsStore[refName] && this.refInsStore[refName].length > 0) {
+ return this.refInsStore[refName].map((i) => i.current);
+ }
+
+ return [];
+ }
+
+ linkRef(refName) {
+ const refIns = createRef();
+ this.refInsStore[refName] = this.refInsStore[refName] || [];
+ this.refInsStore[refName].push(refIns);
+ return refIns;
+ }
+}
+
+export default {};
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo5/schema.json5 b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo5/schema.json5
new file mode 100644
index 0000000000..b59b97e763
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo5/schema.json5
@@ -0,0 +1,677 @@
+{
+ version: '1.0.0',
+ componentsMap: [
+ {
+ package: '@alilc/antd-lowcode',
+ version: '0.5.4',
+ exportName: 'Typography',
+ main: 'dist/antd-lowcode.esm.js',
+ destructuring: true,
+ subName: 'Text',
+ componentName: 'Typography.Text',
+ },
+ {
+ package: '@alilc/antd-lowcode',
+ version: '0.5.4',
+ exportName: 'Select',
+ main: 'dist/antd-lowcode.esm.js',
+ destructuring: true,
+ componentName: 'Select',
+ },
+ {
+ package: '@alilc/antd-lowcode',
+ version: '0.5.4',
+ exportName: 'Space',
+ main: 'dist/antd-lowcode.esm.js',
+ destructuring: true,
+ componentName: 'Space',
+ },
+ {
+ package: '@alilc/antd-lowcode',
+ version: '0.5.4',
+ exportName: 'Button',
+ main: 'dist/antd-lowcode.esm.js',
+ destructuring: true,
+ componentName: 'Button',
+ },
+ {
+ package: '@alilc/antd-lowcode',
+ version: '0.5.4',
+ exportName: 'InputNumber',
+ main: 'dist/antd-lowcode.esm.js',
+ destructuring: true,
+ componentName: 'InputNumber',
+ },
+ {
+ package: '@alilc/antd-lowcode',
+ version: '0.5.4',
+ exportName: 'Form',
+ main: 'dist/antd-lowcode.esm.js',
+ destructuring: true,
+ subName: 'Item',
+ componentName: 'Form.Item',
+ },
+ {
+ package: '@alilc/antd-lowcode',
+ version: '0.5.4',
+ exportName: 'Input',
+ main: 'dist/antd-lowcode.esm.js',
+ destructuring: true,
+ subName: 'TextArea',
+ componentName: 'Input.TextArea',
+ },
+ {
+ package: '@alilc/antd-lowcode',
+ version: '0.5.4',
+ exportName: 'Form',
+ main: 'dist/antd-lowcode.esm.js',
+ destructuring: true,
+ componentName: 'Form',
+ },
+ {
+ package: '@alilc/antd-lowcode',
+ version: '0.5.4',
+ exportName: 'Modal',
+ main: 'dist/antd-lowcode.esm.js',
+ destructuring: true,
+ componentName: 'Modal',
+ },
+ {
+ package: '@alife/mc-assets-1935',
+ version: '0.1.16',
+ exportName: 'AliAutoSearchTable',
+ main: 'build/lowcode/index.js',
+ destructuring: true,
+ subName: 'default',
+ componentName: 'AliAutoSearchTableDefault',
+ },
+ {
+ package: '@alilc/antd-lowcode',
+ version: '0.5.4',
+ exportName: 'Card',
+ main: 'dist/antd-lowcode.esm.js',
+ destructuring: true,
+ componentName: 'Card',
+ },
+ {
+ package: '@alife/container',
+ version: '0.3.7',
+ exportName: 'P',
+ main: 'lib/index.js',
+ destructuring: true,
+ subName: '',
+ componentName: 'NextP',
+ },
+ {
+ package: '@alife/container',
+ version: '0.3.7',
+ exportName: 'Block',
+ main: 'lib/index.js',
+ destructuring: true,
+ subName: 'Cell',
+ componentName: 'NextBlockCell',
+ },
+ {
+ package: '@alife/container',
+ version: '0.3.7',
+ exportName: 'Block',
+ main: 'lib/index.js',
+ destructuring: true,
+ subName: '',
+ componentName: 'NextBlock',
+ },
+ {
+ devMode: 'lowcode',
+ componentName: 'Slot',
+ },
+ {
+ package: '@alife/container',
+ version: '0.3.7',
+ exportName: 'Page',
+ main: 'lib/index.js',
+ destructuring: true,
+ subName: '',
+ componentName: 'NextPage',
+ },
+ {
+ devMode: 'lowcode',
+ componentName: 'Page',
+ },
+ ],
+ componentsTree: [
+ {
+ componentName: 'Page',
+ id: 'node_dockcviv8fo1',
+ props: {
+ ref: 'outterView',
+ style: {
+ height: '100%',
+ },
+ },
+ fileName: 'test',
+ dataSource: {
+ list: [],
+ },
+ css: 'body {\n font-size: 12px;\n}\n\n.botton {\n width: 100px;\n color: #ff00ff\n}',
+ lifeCycles: {
+ componentDidMount: {
+ type: 'JSFunction',
+ value: 'function() { /* ... */ }',
+ },
+ componentWillUnmount: {
+ type: 'JSFunction',
+ value: 'function() { /* ... */ }',
+ },
+ componentDidUpdate: {
+ type: 'JSFunction',
+ value: 'function() { /* ... */ }',
+ },
+ },
+ methods: {
+ onChange: {
+ type: 'JSFunction',
+ value: 'function() { /* ... */ }',
+ },
+ getActions: {
+ type: 'JSFunction',
+ value: 'function() { /* ... */ }',
+ },
+ onCreateOrder: {
+ type: 'JSFunction',
+ value: 'function() { /* ... */ }',
+ },
+ onCancelModal: {
+ type: 'JSFunction',
+ value: 'function() { /* ... */ }',
+ },
+ onConfirmCreateOrder: {
+ type: 'JSFunction',
+ value: 'function() { /* ... */ }',
+ },
+ },
+ state: {
+ name: 'nongzhou',
+ gateways: [],
+ selectedGateway: null,
+ records: [],
+ modalVisible: false,
+ },
+ children: [
+ {
+ componentName: 'NextPage',
+ id: 'node_ocknqx3esma',
+ props: {
+ columns: 12,
+ headerDivider: true,
+ placeholderStyle: {
+ gridRowEnd: 'span 1',
+ gridColumnEnd: 'span 12',
+ },
+ placeholder: '页面主体内容:拖拽Block布局组件到这里',
+ header: {
+ type: 'JSSlot',
+ title: 'header',
+ },
+ headerProps: {
+ background: 'surface',
+ },
+ footer: {
+ type: 'JSSlot',
+ title: 'footer',
+ },
+ minHeight: '100vh',
+ style: {
+ cursor: 'pointer',
+ },
+ },
+ title: '页面',
+ children: [
+ {
+ componentName: 'NextBlock',
+ id: 'node_ocknqx3esmb',
+ props: {
+ prefix: 'next-',
+ placeholderStyle: {
+ height: '100%',
+ },
+ noPadding: false,
+ noBorder: false,
+ background: 'surface',
+ layoutmode: 'O',
+ colSpan: 12,
+ rowSpan: 1,
+ childTotalColumns: 12,
+ },
+ title: '区块',
+ children: [
+ {
+ componentName: 'NextBlockCell',
+ id: 'node_ocknqx3esmc',
+ props: {
+ title: '',
+ prefix: 'next-',
+ placeholderStyle: {
+ height: '100%',
+ },
+ layoutmode: 'O',
+ childTotalColumns: 12,
+ isAutoContainer: true,
+ colSpan: 12,
+ rowSpan: 1,
+ },
+ children: [
+ {
+ componentName: 'NextP',
+ id: 'node_ocknqx3esm1j',
+ props: {
+ wrap: false,
+ type: 'body2',
+ verAlign: 'middle',
+ textSpacing: true,
+ align: 'left',
+ full: true,
+ flex: true,
+ },
+ title: '段落',
+ children: [
+ {
+ componentName: 'Card',
+ id: 'node_ocknqx3esm1k',
+ props: {
+ title: '',
+ },
+ children: [
+ {
+ componentName: 'Space',
+ id: 'node_ocknqx3esm1n',
+ props: {
+ size: 0,
+ align: 'center',
+ direction: 'horizontal',
+ },
+ children: [
+ {
+ componentName: 'Typography.Text',
+ id: 'node_ocknqx3esm1l',
+ props: {
+ children: '所在网关:',
+ },
+ },
+ {
+ componentName: 'Select',
+ id: 'node_ocknqx3esm1m',
+ props: {
+ style: {
+ marginTop: '16px',
+ marginRight: '16px',
+ marginBottom: '16px',
+ marginLeft: '16px',
+ width: '400px',
+ display: 'inline-block',
+ },
+ options: {
+ type: 'JSExpression',
+ value: 'this.state.gateways',
+ },
+ mode: 'single',
+ defaultValue: ['auto-edd-uniproxy'],
+ labelInValue: true,
+ showSearch: true,
+ allowClear: false,
+ placeholder: '请选取网关',
+ showArrow: true,
+ loading: false,
+ tokenSeparators: [],
+ __events: {
+ eventDataList: [
+ {
+ type: 'componentEvent',
+ name: 'onChange',
+ relatedEventName: 'onChange',
+ },
+ ],
+ eventList: [
+ {
+ name: 'onBlur',
+ disabled: false,
+ },
+ {
+ name: 'onChange',
+ disabled: true,
+ },
+ {
+ name: 'onDeselect',
+ disabled: false,
+ },
+ {
+ name: 'onFocus',
+ disabled: false,
+ },
+ {
+ name: 'onInputKeyDown',
+ disabled: false,
+ },
+ {
+ name: 'onMouseEnter',
+ disabled: false,
+ },
+ {
+ name: 'onMouseLeave',
+ disabled: false,
+ },
+ {
+ name: 'onPopupScroll',
+ disabled: false,
+ },
+ {
+ name: 'onSearch',
+ disabled: false,
+ },
+ {
+ name: 'onSelect',
+ disabled: false,
+ },
+ {
+ name: 'onDropdownVisibleChange',
+ disabled: false,
+ },
+ ],
+ },
+ onChange: {
+ type: 'JSFunction',
+ value: 'function(){this.onChange.apply(this,Array.prototype.slice.call(arguments).concat([])) }',
+ },
+ },
+ },
+ ],
+ },
+ {
+ componentName: 'Button',
+ id: 'node_ockntwgdsn7',
+ props: {
+ type: 'primary',
+ children: '创建发布单',
+ style: {
+ display: 'block',
+ marginTop: '20px',
+ marginBottom: '20px',
+ },
+ __events: {
+ eventDataList: [
+ {
+ type: 'componentEvent',
+ name: 'onClick',
+ relatedEventName: 'onCreateOrder',
+ },
+ ],
+ eventList: [
+ {
+ name: 'onClick',
+ disabled: true,
+ },
+ ],
+ },
+ onClick: {
+ type: 'JSFunction',
+ value: 'function(){this.onCreateOrder.apply(this,Array.prototype.slice.call(arguments).concat([])) }',
+ },
+ },
+ },
+ {
+ componentName: 'Modal',
+ id: 'node_ockntx4eo9p',
+ props: {
+ title: '创建发布单',
+ visible: {
+ type: 'JSExpression',
+ value: 'this.state.modalVisible',
+ },
+ footer: '',
+ __events: {
+ eventDataList: [
+ {
+ type: 'componentEvent',
+ name: 'onCancel',
+ relatedEventName: 'onCancelModal',
+ },
+ ],
+ eventList: [
+ {
+ name: 'onCancel',
+ disabled: true,
+ },
+ {
+ name: 'onOk',
+ disabled: false,
+ },
+ ],
+ },
+ onCancel: {
+ type: 'JSFunction',
+ value: 'function(){this.onCancelModal.apply(this,Array.prototype.slice.call(arguments).concat([])) }',
+ },
+ zIndex: 2000,
+ },
+ hidden: true,
+ children: [
+ {
+ componentName: 'Form',
+ id: 'node_ockntx4eo9s',
+ props: {
+ labelCol: {
+ span: 6,
+ },
+ wrapperCol: {
+ span: 14,
+ },
+ onFinish: {
+ type: 'JSFunction',
+ value: 'function(){this.onConfirmCreateOrder.apply(this,Array.prototype.slice.call(arguments).concat([])) }',
+ },
+ name: 'basic',
+ __events: {
+ eventDataList: [
+ {
+ type: 'componentEvent',
+ name: 'onFinish',
+ relatedEventName: 'onConfirmCreateOrder',
+ },
+ ],
+ eventList: [
+ {
+ name: 'onFinish',
+ disabled: true,
+ },
+ {
+ name: 'onFinishFailed',
+ disabled: false,
+ },
+ {
+ name: 'onFieldsChange',
+ disabled: false,
+ },
+ {
+ name: 'onValuesChange',
+ disabled: false,
+ },
+ ],
+ },
+ },
+ children: [
+ {
+ componentName: 'Form.Item',
+ id: 'node_ockntx4eo91k',
+ props: {
+ label: '发布批次',
+ },
+ children: [
+ {
+ componentName: 'InputNumber',
+ id: 'node_ockntx4eo91l',
+ props: {
+ value: 3,
+ min: 1,
+ },
+ },
+ ],
+ },
+ {
+ componentName: 'Form.Item',
+ id: 'node_ockntx4eo91r',
+ props: {
+ label: '批次间隔时间',
+ },
+ children: [
+ {
+ componentName: 'InputNumber',
+ id: 'node_ockntx4eo91s',
+ props: {
+ value: 3,
+ },
+ },
+ ],
+ },
+ {
+ componentName: 'Form.Item',
+ id: 'node_ockntx4eo91y',
+ props: {
+ label: '备注 ',
+ },
+ children: [
+ {
+ componentName: 'Input.TextArea',
+ id: 'node_ockntx4eo91z',
+ props: {
+ rows: 3,
+ placeholder: '请输入',
+ },
+ },
+ ],
+ },
+ {
+ componentName: 'Form.Item',
+ id: 'node_ockntx4eo9v',
+ props: {
+ wrapperCol: {
+ offset: 6,
+ },
+ style: {
+ flexDirection: 'row',
+ alignItems: 'flex-end',
+ justifyContent: 'center',
+ display: 'flex',
+ },
+ labelAlign: 'right',
+ },
+ children: [
+ {
+ componentName: 'Button',
+ id: 'node_ockntx4eo9w',
+ props: {
+ type: 'primary',
+ children: '提交',
+ htmlType: 'submit',
+ },
+ },
+ {
+ componentName: 'Button',
+ id: 'node_ockntx4eo9x',
+ props: {
+ style: {
+ marginLeft: 20,
+ },
+ children: '取消',
+ __events: {
+ eventDataList: [
+ {
+ type: 'componentEvent',
+ name: 'onClick',
+ relatedEventName: 'onCancelModal',
+ },
+ ],
+ eventList: [
+ {
+ name: 'onClick',
+ disabled: true,
+ },
+ ],
+ },
+ onClick: {
+ type: 'JSFunction',
+ value: 'function(){this.onCancelModal.apply(this,Array.prototype.slice.call(arguments).concat([])) }',
+ },
+ },
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ {
+ componentName: 'AliAutoSearchTableDefault',
+ id: 'node_ocknqx3esm1q',
+ props: {
+ rowKey: 'key',
+ dataSource: {
+ type: 'JSExpression',
+ value: 'this.state.records',
+ },
+ columns: [
+ {
+ title: '发布名称',
+ dataIndex: 'order_name',
+ key: 'name',
+ },
+ {
+ title: '类型',
+ dataIndex: 'order_type_desc',
+ key: 'age',
+ },
+ {
+ title: '发布状态',
+ dataIndex: 'order_status_desc',
+ key: 'address',
+ },
+ {
+ title: '发布人',
+ dataIndex: 'creator_name',
+ },
+ {
+ title: '当前批次/总批次',
+ dataIndex: 'cur_batch_no',
+ },
+ {
+ title: '发布机器/总机器',
+ dataIndex: 'pubblish_ip_finish_num',
+ },
+ {
+ title: '发布时间',
+ dataIndex: 'publish_id',
+ },
+ ],
+ actions: {
+ type: 'JSExpression',
+ value: 'this.actions || []',
+ },
+ getActions: {
+ type: 'JSFunction',
+ value: 'function(){ return this.getActions.apply(this,Array.prototype.slice.call(arguments).concat([])) }',
+ },
+ },
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ i18n: {},
+}
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo6-literal-condition/expected/demo-project/.browserslistrc b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo6-literal-condition/expected/demo-project/.browserslistrc
new file mode 100644
index 0000000000..55a130413d
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo6-literal-condition/expected/demo-project/.browserslistrc
@@ -0,0 +1,3 @@
+defaults
+ios_saf 9
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo6-literal-condition/expected/demo-project/.gitignore b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo6-literal-condition/expected/demo-project/.gitignore
new file mode 100644
index 0000000000..4ec178818e
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo6-literal-condition/expected/demo-project/.gitignore
@@ -0,0 +1,25 @@
+
+# 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
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo6-literal-condition/expected/demo-project/README.md b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo6-literal-condition/expected/demo-project/README.md
new file mode 100644
index 0000000000..6d9dd75215
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo6-literal-condition/expected/demo-project/README.md
@@ -0,0 +1 @@
+This project is generated by lowcode-code-generator & lowcode-solution-icejs3.
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo6-literal-condition/expected/demo-project/ice.config.mts b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo6-literal-condition/expected/demo-project/ice.config.mts
new file mode 100644
index 0000000000..e1d8a28141
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo6-literal-condition/expected/demo-project/ice.config.mts
@@ -0,0 +1,90 @@
+import { join } from 'path';
+import { defineConfig } from '@ice/app';
+import _ from 'lodash';
+import fusion from '@ice/plugin-fusion';
+import locales from '@ice/plugin-moment-locales';
+import type { Plugin } from '@ice/app/esm/types';
+
+interface PluginOptions {
+ id: string;
+}
+
+const plugin: Plugin = (options) => ({
+ // name 可选,插件名称
+ name: 'plugin-name',
+ // setup 必选,用于定制工程构建配置
+ setup: ({ onGetConfig, modifyUserConfig }) => {
+ modifyUserConfig('codeSplitting', 'page');
+
+ onGetConfig((config) => {
+ config.entry = {
+ web: join(process.cwd(), '.ice/entry.client.tsx'),
+ };
+
+ config.cssFilename = '[name].css';
+
+ config.configureWebpack = config.configureWebpack || [];
+ config.configureWebpack?.push((webpackConfig) => {
+ if (webpackConfig.output) {
+ webpackConfig.output.filename = '[name].js';
+ webpackConfig.output.chunkFilename = '[name].js';
+ }
+ return webpackConfig;
+ });
+
+ config.swcOptions = _.merge(config.swcOptions, {
+ compilationConfig: {
+ jsc: {
+ transform: {
+ react: {
+ runtime: 'classic',
+ },
+ },
+ },
+ },
+ });
+
+ // 解决 webpack publicPath 问题
+ config.transforms = config.transforms || [];
+ config.transforms.push((source: string, id: string) => {
+ if (id.includes('.ice/entry.client.tsx')) {
+ let code = `
+ if (!__webpack_public_path__?.startsWith('http') && document.currentScript) {
+ // @ts-ignore
+ __webpack_public_path__ = document.currentScript.src.replace(/^(.*\\/)[^/]+$/, '$1');
+ window.__ICE_ASSETS_MANIFEST__ = window.__ICE_ASSETS_MANIFEST__ || {};
+ window.__ICE_ASSETS_MANIFEST__.publicPath = __webpack_public_path__;
+ }
+ `;
+ code += source;
+ return { code };
+ }
+ });
+ });
+ },
+});
+
+// The project config, see https://v3.ice.work/docs/guide/basic/config
+const minify = process.env.NODE_ENV === 'production' ? 'swc' : false;
+export default defineConfig(() => ({
+ ssr: false,
+ ssg: false,
+ minify,
+
+ externals: {
+ react: 'React',
+ 'react-dom': 'ReactDOM',
+ 'react-dom/client': 'ReactDOM',
+ '@alifd/next': 'Next',
+ lodash: 'var window._',
+ '@alilc/lowcode-engine': 'var window.AliLowCodeEngine',
+ },
+ plugins: [
+ fusion({
+ importStyle: 'sass',
+ }),
+ locales(),
+ plugin(),
+ ],
+}));
+
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo6-literal-condition/expected/demo-project/package.json b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo6-literal-condition/expected/demo-project/package.json
new file mode 100644
index 0000000000..38f24df0b1
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo6-literal-condition/expected/demo-project/package.json
@@ -0,0 +1,44 @@
+{
+ "name": "icejs3-demo-app",
+ "version": "0.1.5",
+ "description": "icejs 3 轻量级模板,使用 JavaScript,仅包含基础的 Layout。",
+ "dependencies": {
+ "moment": "^2.24.0",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
+ "react-router": "^6.9.0",
+ "react-router-dom": "^6.9.0",
+ "intl-messageformat": "^9.3.6",
+ "@alifd/next": "1.19.18",
+ "@ice/runtime": "~1.1.0",
+ "@alilc/lowcode-datasource-engine": "^1.0.0",
+ "@alilc/lowcode-datasource-url-params-handler": "^1.0.0",
+ "@alilc/lowcode-datasource-fetch-handler": "^1.0.0"
+ },
+ "devDependencies": {
+ "@ice/app": "~3.1.0",
+ "@types/react": "^18.0.0",
+ "@types/react-dom": "^18.0.0",
+ "@types/node": "^18.11.17",
+ "@ice/plugin-fusion": "^1.0.1",
+ "@ice/plugin-moment-locales": "^1.0.0",
+ "eslint": "^6.0.1",
+ "stylelint": "^13.2.0"
+ },
+ "scripts": {
+ "start": "ice start",
+ "build": "ice build",
+ "lint": "npm run eslint && npm run stylelint",
+ "eslint": "eslint --cache --ext .js,.jsx ./",
+ "stylelint": "stylelint ./**/*.scss"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "repository": {
+ "type": "git",
+ "url": "http://gitlab.xxx.com/msd/leak-scan/tree/master"
+ },
+ "private": true,
+ "originTemplate": "@alifd/scaffold-lite-js"
+}
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo6-literal-condition/expected/demo-project/src/app.ts b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo6-literal-condition/expected/demo-project/src/app.ts
new file mode 100644
index 0000000000..6d5856292d
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo6-literal-condition/expected/demo-project/src/app.ts
@@ -0,0 +1,13 @@
+import { defineAppConfig } from 'ice';
+
+// App config, see https://v3.ice.work/docs/guide/basic/app
+export default defineAppConfig(() => ({
+ // Set your configs here.
+ app: {
+ rootId: 'App',
+ },
+ router: {
+ type: 'browser',
+ basename: '/',
+ },
+}));
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo6-literal-condition/expected/demo-project/src/constants.js b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo6-literal-condition/expected/demo-project/src/constants.js
new file mode 100644
index 0000000000..91198f9044
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo6-literal-condition/expected/demo-project/src/constants.js
@@ -0,0 +1,3 @@
+const __$$constants = { ENV: 'prod', DOMAIN: 'xxx.xxx.com' };
+
+export default __$$constants;
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo6-literal-condition/expected/demo-project/src/document.tsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo6-literal-condition/expected/demo-project/src/document.tsx
new file mode 100644
index 0000000000..aff0231d95
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo6-literal-condition/expected/demo-project/src/document.tsx
@@ -0,0 +1,29 @@
+import React from 'react';
+import { Meta, Title, Links, Main, Scripts } from 'ice';
+
+export default function Document() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo6-literal-condition/expected/demo-project/src/global.scss b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo6-literal-condition/expected/demo-project/src/global.scss
new file mode 100644
index 0000000000..ed7204b4a3
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo6-literal-condition/expected/demo-project/src/global.scss
@@ -0,0 +1,13 @@
+// 引入默认全局样式
+@import '@alifd/next/reset.scss';
+
+body {
+ -webkit-font-smoothing: antialiased;
+}
+
+body {
+ font-size: 12px;
+}
+.table {
+ width: 100px;
+}
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo6-literal-condition/expected/demo-project/src/i18n.js b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo6-literal-condition/expected/demo-project/src/i18n.js
new file mode 100644
index 0000000000..1334d2502b
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo6-literal-condition/expected/demo-project/src/i18n.js
@@ -0,0 +1,77 @@
+const i18nConfig = {};
+
+let locale =
+ typeof navigator === 'object' && typeof navigator.language === 'string'
+ ? navigator.language
+ : 'zh-CN';
+
+const getLocale = () => locale;
+
+const setLocale = (target) => {
+ locale = target;
+};
+
+const isEmptyVariables = (variables) =>
+ (Array.isArray(variables) && variables.length === 0) ||
+ (typeof variables === 'object' &&
+ (!variables || Object.keys(variables).length === 0));
+
+// 按低代码规范里面的要求进行变量替换
+const format = (msg, variables) =>
+ typeof msg === 'string'
+ ? msg.replace(/\$?\{(\w+)\}/g, (match, key) => variables?.[key] ?? '')
+ : msg;
+
+const i18nFormat = ({ id, defaultMessage, fallback }, variables) => {
+ const msg =
+ i18nConfig[locale]?.[id] ??
+ i18nConfig[locale.replace('-', '_')]?.[id] ??
+ defaultMessage;
+ if (msg == null) {
+ console.warn('[i18n]: unknown message id: %o (locale=%o)', id, locale);
+ return fallback === undefined ? `${id}` : fallback;
+ }
+
+ return format(msg, variables);
+};
+
+const i18n = (id, params) => {
+ return i18nFormat({ id }, params);
+};
+
+// 将国际化的一些方法注入到目标对象&上下文中
+const _inject2 = (target) => {
+ target.i18n = i18n;
+ target.getLocale = getLocale;
+ target.setLocale = (locale) => {
+ setLocale(locale);
+ target.forceUpdate();
+ };
+ target._i18nText = (t) => {
+ // 优先取直接传过来的语料
+ const localMsg = t[locale] ?? t[String(locale).replace('-', '_')];
+ if (localMsg != null) {
+ return format(localMsg, t.params);
+ }
+
+ // 其次用项目级别的
+ const projectMsg = i18nFormat({ id: t.key, fallback: null }, t.params);
+ if (projectMsg != null) {
+ return projectMsg;
+ }
+
+ // 兜底用 use 指定的或默认语言的
+ return format(t[t.use || 'zh-CN'] ?? t.en_US, t.params);
+ };
+
+ // 注入到上下文中去
+ if (target._context && target._context !== target) {
+ Object.assign(target._context, {
+ i18n,
+ getLocale,
+ setLocale: target.setLocale,
+ });
+ }
+};
+
+export { getLocale, setLocale, i18n, i18nFormat, _inject2 };
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo6-literal-condition/expected/demo-project/src/layouts/BasicLayout/components/Footer/index.jsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo6-literal-condition/expected/demo-project/src/layouts/BasicLayout/components/Footer/index.jsx
new file mode 100644
index 0000000000..cc70d53bea
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo6-literal-condition/expected/demo-project/src/layouts/BasicLayout/components/Footer/index.jsx
@@ -0,0 +1,14 @@
+
+import React from 'react';
+import styles from './index.module.scss';
+
+export default function Footer() {
+ return (
+
+ Alibaba Fusion
+
+ © 2019-现在 Alibaba Fusion & ICE
+
+ );
+}
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo6-literal-condition/expected/demo-project/src/layouts/BasicLayout/components/Footer/index.module.scss b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo6-literal-condition/expected/demo-project/src/layouts/BasicLayout/components/Footer/index.module.scss
new file mode 100644
index 0000000000..81e77fda5f
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo6-literal-condition/expected/demo-project/src/layouts/BasicLayout/components/Footer/index.module.scss
@@ -0,0 +1,15 @@
+
+.footer {
+ line-height: 20px;
+ text-align: center;
+}
+
+.logo {
+ font-weight: bold;
+ font-size: 16px;
+}
+
+.copyright {
+ font-size: 12px;
+}
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo6-literal-condition/expected/demo-project/src/layouts/BasicLayout/components/Logo/index.jsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo6-literal-condition/expected/demo-project/src/layouts/BasicLayout/components/Logo/index.jsx
new file mode 100644
index 0000000000..265bfdaa07
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo6-literal-condition/expected/demo-project/src/layouts/BasicLayout/components/Logo/index.jsx
@@ -0,0 +1,16 @@
+
+import React from 'react';
+import { Link } from 'ice';
+import styles from './index.module.scss';
+
+export default function Logo({ image, text, url }) {
+ return (
+
+
+ {image &&

}
+
{text}
+
+
+ );
+}
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo6-literal-condition/expected/demo-project/src/layouts/BasicLayout/components/Logo/index.module.scss b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo6-literal-condition/expected/demo-project/src/layouts/BasicLayout/components/Logo/index.module.scss
new file mode 100644
index 0000000000..dad05a263f
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo6-literal-condition/expected/demo-project/src/layouts/BasicLayout/components/Logo/index.module.scss
@@ -0,0 +1,20 @@
+
+.logo{
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: #FF7300;
+ font-weight: bold;
+ font-size: 14px;
+ line-height: 22px;
+
+ &:visited, &:link {
+ color: #FF7300;
+ }
+
+ img {
+ height: 24px;
+ margin-right: 10px;
+ }
+}
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo6-literal-condition/expected/demo-project/src/layouts/BasicLayout/components/PageNav/index.jsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo6-literal-condition/expected/demo-project/src/layouts/BasicLayout/components/PageNav/index.jsx
new file mode 100644
index 0000000000..911998b0d3
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo6-literal-condition/expected/demo-project/src/layouts/BasicLayout/components/PageNav/index.jsx
@@ -0,0 +1,68 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { Link, useLocation } from 'ice';
+import { Nav } from '@alifd/next';
+import { asideMenuConfig } from '../../menuConfig';
+
+const { SubNav } = Nav;
+const NavItem = Nav.Item;
+
+function getNavMenuItems(menusData) {
+ if (!menusData) {
+ return [];
+ }
+
+ return menusData
+ .filter(item => item.name && !item.hideInMenu)
+ .map((item, index) => getSubMenuOrItem(item, index));
+}
+
+function getSubMenuOrItem(item, index) {
+ if (item.children && item.children.some(child => child.name)) {
+ const childrenItems = getNavMenuItems(item.children);
+
+ if (childrenItems && childrenItems.length > 0) {
+ const subNav = (
+
+ {childrenItems}
+
+ );
+ return subNav;
+ }
+
+ return null;
+ }
+
+ const navItem = (
+
+ {item.name}
+
+ );
+ return navItem;
+}
+
+const Navigation = (props, context) => {
+ const location = useLocation();
+ const { pathname } = location;
+ const { isCollapse } = context;
+ return (
+
+ );
+};
+
+Navigation.contextTypes = {
+ isCollapse: PropTypes.bool,
+};
+export default Navigation;
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo6-literal-condition/expected/demo-project/src/layouts/BasicLayout/index.jsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo6-literal-condition/expected/demo-project/src/layouts/BasicLayout/index.jsx
new file mode 100644
index 0000000000..18db44df5e
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo6-literal-condition/expected/demo-project/src/layouts/BasicLayout/index.jsx
@@ -0,0 +1,81 @@
+
+import React, { useState } from 'react';
+import { Shell, ConfigProvider } from '@alifd/next';
+import PageNav from './components/PageNav';
+import Logo from './components/Logo';
+import Footer from './components/Footer';
+
+(function() {
+ const throttle = function(type, name, obj = window) {
+ let running = false;
+
+ const func = () => {
+ if (running) {
+ return;
+ }
+
+ running = true;
+ requestAnimationFrame(() => {
+ obj.dispatchEvent(new CustomEvent(name));
+ running = false;
+ });
+ };
+
+ obj.addEventListener(type, func);
+ };
+
+ throttle('resize', 'optimizedResize');
+})();
+
+export default function BasicLayout({ children }) {
+ const getDevice = width => {
+ const isPhone =
+ typeof navigator !== 'undefined' && navigator && navigator.userAgent.match(/phone/gi);
+
+ if (width < 680 || isPhone) {
+ return 'phone';
+ }
+ if (width < 1280 && width > 680) {
+ return 'tablet';
+ }
+ return 'desktop';
+ };
+
+ const [device, setDevice] = useState(getDevice(NaN));
+ window.addEventListener('optimizedResize', e => {
+ setDevice(getDevice(e && e.target && e.target.innerWidth));
+ });
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ {children}
+
+
+
+
+
+ );
+}
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo6-literal-condition/expected/demo-project/src/layouts/BasicLayout/menuConfig.js b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo6-literal-condition/expected/demo-project/src/layouts/BasicLayout/menuConfig.js
new file mode 100644
index 0000000000..5332202be4
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo6-literal-condition/expected/demo-project/src/layouts/BasicLayout/menuConfig.js
@@ -0,0 +1,11 @@
+
+const headerMenuConfig = [];
+const asideMenuConfig = [
+ {
+ name: 'Dashboard',
+ path: '/',
+ icon: 'smile',
+ },
+];
+export { headerMenuConfig, asideMenuConfig };
+
\ No newline at end of file
diff --git a/packages/rax-simulator-renderer/src/utils/get-closest-node-instance.ts b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo6-literal-condition/expected/demo-project/src/pages/Test/index.css
similarity index 100%
rename from packages/rax-simulator-renderer/src/utils/get-closest-node-instance.ts
rename to modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo6-literal-condition/expected/demo-project/src/pages/Test/index.css
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo6-literal-condition/expected/demo-project/src/pages/Test/index.jsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo6-literal-condition/expected/demo-project/src/pages/Test/index.jsx
new file mode 100644
index 0000000000..515940c334
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo6-literal-condition/expected/demo-project/src/pages/Test/index.jsx
@@ -0,0 +1,205 @@
+// 注意: 出码引擎注入的临时变量默认都以 "__$$" 开头,禁止在搭建的代码中直接访问。
+// 例外:react 框架的导出名和各种组件名除外。
+import React from 'react';
+
+import { Form, Input, NumberPicker, Select, Button } from '@alifd/next';
+
+import { createUrlParamsHandler as __$$createUrlParamsRequestHandler } from '@alilc/lowcode-datasource-url-params-handler';
+
+import { createFetchHandler as __$$createFetchRequestHandler } from '@alilc/lowcode-datasource-fetch-handler';
+
+import { create as __$$createDataSourceEngine } from '@alilc/lowcode-datasource-engine/runtime';
+
+import '@alifd/next/lib/form/style';
+
+import '@alifd/next/lib/input/style';
+
+import '@alifd/next/lib/number-picker/style';
+
+import '@alifd/next/lib/select/style';
+
+import '@alifd/next/lib/button/style';
+
+import utils, { RefsManager } from '../../utils';
+
+import * as __$$i18n from '../../i18n';
+
+import __$$constants from '../../constants';
+
+import './index.css';
+
+class Test$$Page extends React.Component {
+ _context = this;
+
+ _dataSourceConfig = this._defineDataSourceConfig();
+ _dataSourceEngine = __$$createDataSourceEngine(this._dataSourceConfig, this, {
+ runtimeConfig: true,
+ requestHandlersMap: {
+ urlParams: __$$createUrlParamsRequestHandler(window.location.search),
+ fetch: __$$createFetchRequestHandler(),
+ },
+ });
+
+ get dataSourceMap() {
+ return this._dataSourceEngine.dataSourceMap || {};
+ }
+
+ reloadDataSource = async () => {
+ await this._dataSourceEngine.reloadDataSource();
+ };
+
+ get constants() {
+ return __$$constants || {};
+ }
+
+ constructor(props, context) {
+ super(props);
+
+ this.utils = utils;
+
+ this._refsManager = new RefsManager();
+
+ __$$i18n._inject2(this);
+
+ this.state = { text: 'outter' };
+ }
+
+ $ = (refName) => {
+ return this._refsManager.get(refName);
+ };
+
+ $$ = (refName) => {
+ return this._refsManager.getAll(refName);
+ };
+
+ _defineDataSourceConfig() {
+ const _this = this;
+ return {
+ list: [
+ {
+ id: 'urlParams',
+ type: 'urlParams',
+ isInit: function () {
+ return undefined;
+ }.bind(_this),
+ options: function () {
+ return undefined;
+ }.bind(_this),
+ },
+ {
+ id: 'user',
+ type: 'fetch',
+ options: function () {
+ return {
+ method: 'GET',
+ uri: 'https://shs.xxx.com/mock/1458/demo/user',
+ isSync: true,
+ };
+ }.bind(_this),
+ dataHandler: function (response) {
+ if (!response.data.success) {
+ throw new Error(response.data.message);
+ }
+ return response.data.data;
+ },
+ isInit: function () {
+ return undefined;
+ }.bind(_this),
+ },
+ {
+ id: 'orders',
+ type: 'fetch',
+ options: function () {
+ return {
+ method: 'GET',
+ uri: 'https://shs.xxx.com/mock/1458/demo/orders',
+ isSync: true,
+ };
+ }.bind(_this),
+ dataHandler: function (response) {
+ if (!response.data.success) {
+ throw new Error(response.data.message);
+ }
+ return response.data.data.result;
+ },
+ isInit: function () {
+ return undefined;
+ }.bind(_this),
+ },
+ ],
+ dataHandler: function (dataMap) {
+ console.info('All datasources loaded:', dataMap);
+ },
+ };
+ }
+
+ componentDidMount() {
+ this._dataSourceEngine.reloadDataSource();
+
+ console.log('componentDidMount');
+ }
+
+ render() {
+ const __$$context = this._context || this;
+ const { state } = __$$context;
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+ {__$$evalArray(() => ['a', 'b', 'c']).map((item, index) =>
+ ((__$$context) =>
+ !!false && (
+
+ ))(__$$createChildContext(__$$context, { item, index }))
+ )}
+
+
+
+
+ );
+ }
+}
+
+export default Test$$Page;
+
+function __$$eval(expr) {
+ try {
+ return expr();
+ } catch (error) {}
+}
+
+function __$$evalArray(expr) {
+ const res = __$$eval(expr);
+ return Array.isArray(res) ? res : [];
+}
+
+function __$$createChildContext(oldContext, ext) {
+ const childContext = {
+ ...oldContext,
+ ...ext,
+ };
+ childContext.__proto__ = oldContext;
+ return childContext;
+}
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo6-literal-condition/expected/demo-project/src/pages/layout.jsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo6-literal-condition/expected/demo-project/src/pages/layout.jsx
new file mode 100644
index 0000000000..50fbb2d1f1
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo6-literal-condition/expected/demo-project/src/pages/layout.jsx
@@ -0,0 +1,10 @@
+import { Outlet } from 'ice';
+import BasicLayout from '@/layouts/BasicLayout';
+
+export default function Layout() {
+ return (
+
+
+
+ );
+}
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo6-literal-condition/expected/demo-project/src/typings.d.ts b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo6-literal-condition/expected/demo-project/src/typings.d.ts
new file mode 100644
index 0000000000..a9f8de7ceb
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo6-literal-condition/expected/demo-project/src/typings.d.ts
@@ -0,0 +1,9 @@
+///
+
+export {};
+declare global {
+ interface Window {
+ g_config: Record;
+ }
+}
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo6-literal-condition/expected/demo-project/src/utils.js b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo6-literal-condition/expected/demo-project/src/utils.js
new file mode 100644
index 0000000000..1190717924
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo6-literal-condition/expected/demo-project/src/utils.js
@@ -0,0 +1,47 @@
+import { createRef } from 'react';
+
+export class RefsManager {
+ constructor() {
+ this.refInsStore = {};
+ }
+
+ clearNullRefs() {
+ Object.keys(this.refInsStore).forEach((refName) => {
+ const filteredInsList = this.refInsStore[refName].filter(
+ (insRef) => !!insRef.current
+ );
+ if (filteredInsList.length > 0) {
+ this.refInsStore[refName] = filteredInsList;
+ } else {
+ delete this.refInsStore[refName];
+ }
+ });
+ }
+
+ get(refName) {
+ this.clearNullRefs();
+ if (this.refInsStore[refName] && this.refInsStore[refName].length > 0) {
+ return this.refInsStore[refName][0].current;
+ }
+
+ return null;
+ }
+
+ getAll(refName) {
+ this.clearNullRefs();
+ if (this.refInsStore[refName] && this.refInsStore[refName].length > 0) {
+ return this.refInsStore[refName].map((i) => i.current);
+ }
+
+ return [];
+ }
+
+ linkRef(refName) {
+ const refIns = createRef();
+ this.refInsStore[refName] = this.refInsStore[refName] || [];
+ this.refInsStore[refName].push(refIns);
+ return refIns;
+ }
+}
+
+export default {};
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo6-literal-condition/schema.json5 b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo6-literal-condition/schema.json5
new file mode 100644
index 0000000000..5b6776c1ee
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo6-literal-condition/schema.json5
@@ -0,0 +1,273 @@
+{
+ 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: 'JSFunction',
+ value: "function() { console.log('componentDidMount'); }",
+ },
+ },
+ dataSource: {
+ list: [
+ {
+ id: 'urlParams',
+ type: 'urlParams',
+ },
+ // 示例数据源:https://shs.xxx.com/mock/1458/demo/user
+ {
+ id: 'user',
+ type: 'fetch',
+ options: {
+ method: 'GET',
+ uri: 'https://shs.xxx.com/mock/1458/demo/user',
+ isSync: true,
+ },
+ dataHandler: {
+ type: 'JSFunction',
+ value: 'function (response) {\nif (!response.data.success){\n throw new Error(response.data.message);\n }\n return response.data.data;\n}',
+ },
+ },
+ // 示例数据源:https://shs.xxx.com/mock/1458/demo/orders
+ {
+ id: 'orders',
+ type: 'fetch',
+ options: {
+ method: 'GET',
+ uri: 'https://shs.xxx.com/mock/1458/demo/orders',
+ isSync: true,
+ },
+ dataHandler: {
+ type: 'JSFunction',
+ value: 'function (response) {\nif (!response.data.success){\n throw new Error(response.data.message);\n }\n return response.data.data.result;\n}',
+ },
+ },
+ ],
+ dataHandler: {
+ type: 'JSFunction',
+ 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: false,
+ loop: ['a', 'b', 'c'],
+ props: {
+ type: 'primary',
+ style: {
+ margin: '0 5px 0 5px',
+ },
+ },
+ children: [
+ {
+ type: 'JSExpression',
+ value: 'this.item',
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ constants: {
+ ENV: 'prod',
+ DOMAIN: 'xxx.xxx.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: '月飞',
+ },
+}
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo7-literal-condition2/expected/demo-project/.browserslistrc b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo7-literal-condition2/expected/demo-project/.browserslistrc
new file mode 100644
index 0000000000..55a130413d
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo7-literal-condition2/expected/demo-project/.browserslistrc
@@ -0,0 +1,3 @@
+defaults
+ios_saf 9
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo7-literal-condition2/expected/demo-project/.gitignore b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo7-literal-condition2/expected/demo-project/.gitignore
new file mode 100644
index 0000000000..4ec178818e
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo7-literal-condition2/expected/demo-project/.gitignore
@@ -0,0 +1,25 @@
+
+# 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
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo7-literal-condition2/expected/demo-project/README.md b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo7-literal-condition2/expected/demo-project/README.md
new file mode 100644
index 0000000000..6d9dd75215
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo7-literal-condition2/expected/demo-project/README.md
@@ -0,0 +1 @@
+This project is generated by lowcode-code-generator & lowcode-solution-icejs3.
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo7-literal-condition2/expected/demo-project/ice.config.mts b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo7-literal-condition2/expected/demo-project/ice.config.mts
new file mode 100644
index 0000000000..e1d8a28141
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo7-literal-condition2/expected/demo-project/ice.config.mts
@@ -0,0 +1,90 @@
+import { join } from 'path';
+import { defineConfig } from '@ice/app';
+import _ from 'lodash';
+import fusion from '@ice/plugin-fusion';
+import locales from '@ice/plugin-moment-locales';
+import type { Plugin } from '@ice/app/esm/types';
+
+interface PluginOptions {
+ id: string;
+}
+
+const plugin: Plugin = (options) => ({
+ // name 可选,插件名称
+ name: 'plugin-name',
+ // setup 必选,用于定制工程构建配置
+ setup: ({ onGetConfig, modifyUserConfig }) => {
+ modifyUserConfig('codeSplitting', 'page');
+
+ onGetConfig((config) => {
+ config.entry = {
+ web: join(process.cwd(), '.ice/entry.client.tsx'),
+ };
+
+ config.cssFilename = '[name].css';
+
+ config.configureWebpack = config.configureWebpack || [];
+ config.configureWebpack?.push((webpackConfig) => {
+ if (webpackConfig.output) {
+ webpackConfig.output.filename = '[name].js';
+ webpackConfig.output.chunkFilename = '[name].js';
+ }
+ return webpackConfig;
+ });
+
+ config.swcOptions = _.merge(config.swcOptions, {
+ compilationConfig: {
+ jsc: {
+ transform: {
+ react: {
+ runtime: 'classic',
+ },
+ },
+ },
+ },
+ });
+
+ // 解决 webpack publicPath 问题
+ config.transforms = config.transforms || [];
+ config.transforms.push((source: string, id: string) => {
+ if (id.includes('.ice/entry.client.tsx')) {
+ let code = `
+ if (!__webpack_public_path__?.startsWith('http') && document.currentScript) {
+ // @ts-ignore
+ __webpack_public_path__ = document.currentScript.src.replace(/^(.*\\/)[^/]+$/, '$1');
+ window.__ICE_ASSETS_MANIFEST__ = window.__ICE_ASSETS_MANIFEST__ || {};
+ window.__ICE_ASSETS_MANIFEST__.publicPath = __webpack_public_path__;
+ }
+ `;
+ code += source;
+ return { code };
+ }
+ });
+ });
+ },
+});
+
+// The project config, see https://v3.ice.work/docs/guide/basic/config
+const minify = process.env.NODE_ENV === 'production' ? 'swc' : false;
+export default defineConfig(() => ({
+ ssr: false,
+ ssg: false,
+ minify,
+
+ externals: {
+ react: 'React',
+ 'react-dom': 'ReactDOM',
+ 'react-dom/client': 'ReactDOM',
+ '@alifd/next': 'Next',
+ lodash: 'var window._',
+ '@alilc/lowcode-engine': 'var window.AliLowCodeEngine',
+ },
+ plugins: [
+ fusion({
+ importStyle: 'sass',
+ }),
+ locales(),
+ plugin(),
+ ],
+}));
+
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo7-literal-condition2/expected/demo-project/package.json b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo7-literal-condition2/expected/demo-project/package.json
new file mode 100644
index 0000000000..fcdfc7bd4b
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo7-literal-condition2/expected/demo-project/package.json
@@ -0,0 +1,45 @@
+{
+ "name": "icejs3-demo-app",
+ "version": "0.1.5",
+ "description": "icejs 3 轻量级模板,使用 JavaScript,仅包含基础的 Layout。",
+ "dependencies": {
+ "moment": "^2.24.0",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
+ "react-router": "^6.9.0",
+ "react-router-dom": "^6.9.0",
+ "intl-messageformat": "^9.3.6",
+ "@alifd/next": "1.26.15",
+ "@ice/runtime": "~1.1.0",
+ "@alilc/lowcode-datasource-engine": "^1.0.0",
+ "undefined": "*",
+ "@alilc/antd-lowcode": "0.8.0",
+ "@alife/container": "0.3.7"
+ },
+ "devDependencies": {
+ "@ice/app": "~3.1.0",
+ "@types/react": "^18.0.0",
+ "@types/react-dom": "^18.0.0",
+ "@types/node": "^18.11.17",
+ "@ice/plugin-fusion": "^1.0.1",
+ "@ice/plugin-moment-locales": "^1.0.0",
+ "eslint": "^6.0.1",
+ "stylelint": "^13.2.0"
+ },
+ "scripts": {
+ "start": "ice start",
+ "build": "ice build",
+ "lint": "npm run eslint && npm run stylelint",
+ "eslint": "eslint --cache --ext .js,.jsx ./",
+ "stylelint": "stylelint ./**/*.scss"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "repository": {
+ "type": "git",
+ "url": "http://gitlab.xxx.com/msd/leak-scan/tree/master"
+ },
+ "private": true,
+ "originTemplate": "@alifd/scaffold-lite-js"
+}
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo7-literal-condition2/expected/demo-project/src/app.ts b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo7-literal-condition2/expected/demo-project/src/app.ts
new file mode 100644
index 0000000000..6d5856292d
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo7-literal-condition2/expected/demo-project/src/app.ts
@@ -0,0 +1,13 @@
+import { defineAppConfig } from 'ice';
+
+// App config, see https://v3.ice.work/docs/guide/basic/app
+export default defineAppConfig(() => ({
+ // Set your configs here.
+ app: {
+ rootId: 'App',
+ },
+ router: {
+ type: 'browser',
+ basename: '/',
+ },
+}));
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo7-literal-condition2/expected/demo-project/src/constants.js b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo7-literal-condition2/expected/demo-project/src/constants.js
new file mode 100644
index 0000000000..ea766c9da3
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo7-literal-condition2/expected/demo-project/src/constants.js
@@ -0,0 +1,3 @@
+const __$$constants = {};
+
+export default __$$constants;
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo7-literal-condition2/expected/demo-project/src/document.tsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo7-literal-condition2/expected/demo-project/src/document.tsx
new file mode 100644
index 0000000000..aff0231d95
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo7-literal-condition2/expected/demo-project/src/document.tsx
@@ -0,0 +1,29 @@
+import React from 'react';
+import { Meta, Title, Links, Main, Scripts } from 'ice';
+
+export default function Document() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo7-literal-condition2/expected/demo-project/src/global.scss b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo7-literal-condition2/expected/demo-project/src/global.scss
new file mode 100644
index 0000000000..82ca3eac73
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo7-literal-condition2/expected/demo-project/src/global.scss
@@ -0,0 +1,6 @@
+// 引入默认全局样式
+@import '@alifd/next/reset.scss';
+
+body {
+ -webkit-font-smoothing: antialiased;
+}
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo7-literal-condition2/expected/demo-project/src/i18n.js b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo7-literal-condition2/expected/demo-project/src/i18n.js
new file mode 100644
index 0000000000..1334d2502b
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo7-literal-condition2/expected/demo-project/src/i18n.js
@@ -0,0 +1,77 @@
+const i18nConfig = {};
+
+let locale =
+ typeof navigator === 'object' && typeof navigator.language === 'string'
+ ? navigator.language
+ : 'zh-CN';
+
+const getLocale = () => locale;
+
+const setLocale = (target) => {
+ locale = target;
+};
+
+const isEmptyVariables = (variables) =>
+ (Array.isArray(variables) && variables.length === 0) ||
+ (typeof variables === 'object' &&
+ (!variables || Object.keys(variables).length === 0));
+
+// 按低代码规范里面的要求进行变量替换
+const format = (msg, variables) =>
+ typeof msg === 'string'
+ ? msg.replace(/\$?\{(\w+)\}/g, (match, key) => variables?.[key] ?? '')
+ : msg;
+
+const i18nFormat = ({ id, defaultMessage, fallback }, variables) => {
+ const msg =
+ i18nConfig[locale]?.[id] ??
+ i18nConfig[locale.replace('-', '_')]?.[id] ??
+ defaultMessage;
+ if (msg == null) {
+ console.warn('[i18n]: unknown message id: %o (locale=%o)', id, locale);
+ return fallback === undefined ? `${id}` : fallback;
+ }
+
+ return format(msg, variables);
+};
+
+const i18n = (id, params) => {
+ return i18nFormat({ id }, params);
+};
+
+// 将国际化的一些方法注入到目标对象&上下文中
+const _inject2 = (target) => {
+ target.i18n = i18n;
+ target.getLocale = getLocale;
+ target.setLocale = (locale) => {
+ setLocale(locale);
+ target.forceUpdate();
+ };
+ target._i18nText = (t) => {
+ // 优先取直接传过来的语料
+ const localMsg = t[locale] ?? t[String(locale).replace('-', '_')];
+ if (localMsg != null) {
+ return format(localMsg, t.params);
+ }
+
+ // 其次用项目级别的
+ const projectMsg = i18nFormat({ id: t.key, fallback: null }, t.params);
+ if (projectMsg != null) {
+ return projectMsg;
+ }
+
+ // 兜底用 use 指定的或默认语言的
+ return format(t[t.use || 'zh-CN'] ?? t.en_US, t.params);
+ };
+
+ // 注入到上下文中去
+ if (target._context && target._context !== target) {
+ Object.assign(target._context, {
+ i18n,
+ getLocale,
+ setLocale: target.setLocale,
+ });
+ }
+};
+
+export { getLocale, setLocale, i18n, i18nFormat, _inject2 };
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo7-literal-condition2/expected/demo-project/src/layouts/BasicLayout/components/Footer/index.jsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo7-literal-condition2/expected/demo-project/src/layouts/BasicLayout/components/Footer/index.jsx
new file mode 100644
index 0000000000..cc70d53bea
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo7-literal-condition2/expected/demo-project/src/layouts/BasicLayout/components/Footer/index.jsx
@@ -0,0 +1,14 @@
+
+import React from 'react';
+import styles from './index.module.scss';
+
+export default function Footer() {
+ return (
+
+ Alibaba Fusion
+
+ © 2019-现在 Alibaba Fusion & ICE
+
+ );
+}
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo7-literal-condition2/expected/demo-project/src/layouts/BasicLayout/components/Footer/index.module.scss b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo7-literal-condition2/expected/demo-project/src/layouts/BasicLayout/components/Footer/index.module.scss
new file mode 100644
index 0000000000..81e77fda5f
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo7-literal-condition2/expected/demo-project/src/layouts/BasicLayout/components/Footer/index.module.scss
@@ -0,0 +1,15 @@
+
+.footer {
+ line-height: 20px;
+ text-align: center;
+}
+
+.logo {
+ font-weight: bold;
+ font-size: 16px;
+}
+
+.copyright {
+ font-size: 12px;
+}
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo7-literal-condition2/expected/demo-project/src/layouts/BasicLayout/components/Logo/index.jsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo7-literal-condition2/expected/demo-project/src/layouts/BasicLayout/components/Logo/index.jsx
new file mode 100644
index 0000000000..265bfdaa07
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo7-literal-condition2/expected/demo-project/src/layouts/BasicLayout/components/Logo/index.jsx
@@ -0,0 +1,16 @@
+
+import React from 'react';
+import { Link } from 'ice';
+import styles from './index.module.scss';
+
+export default function Logo({ image, text, url }) {
+ return (
+
+
+ {image &&

}
+
{text}
+
+
+ );
+}
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo7-literal-condition2/expected/demo-project/src/layouts/BasicLayout/components/Logo/index.module.scss b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo7-literal-condition2/expected/demo-project/src/layouts/BasicLayout/components/Logo/index.module.scss
new file mode 100644
index 0000000000..dad05a263f
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo7-literal-condition2/expected/demo-project/src/layouts/BasicLayout/components/Logo/index.module.scss
@@ -0,0 +1,20 @@
+
+.logo{
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: #FF7300;
+ font-weight: bold;
+ font-size: 14px;
+ line-height: 22px;
+
+ &:visited, &:link {
+ color: #FF7300;
+ }
+
+ img {
+ height: 24px;
+ margin-right: 10px;
+ }
+}
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo7-literal-condition2/expected/demo-project/src/layouts/BasicLayout/components/PageNav/index.jsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo7-literal-condition2/expected/demo-project/src/layouts/BasicLayout/components/PageNav/index.jsx
new file mode 100644
index 0000000000..911998b0d3
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo7-literal-condition2/expected/demo-project/src/layouts/BasicLayout/components/PageNav/index.jsx
@@ -0,0 +1,68 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { Link, useLocation } from 'ice';
+import { Nav } from '@alifd/next';
+import { asideMenuConfig } from '../../menuConfig';
+
+const { SubNav } = Nav;
+const NavItem = Nav.Item;
+
+function getNavMenuItems(menusData) {
+ if (!menusData) {
+ return [];
+ }
+
+ return menusData
+ .filter(item => item.name && !item.hideInMenu)
+ .map((item, index) => getSubMenuOrItem(item, index));
+}
+
+function getSubMenuOrItem(item, index) {
+ if (item.children && item.children.some(child => child.name)) {
+ const childrenItems = getNavMenuItems(item.children);
+
+ if (childrenItems && childrenItems.length > 0) {
+ const subNav = (
+
+ {childrenItems}
+
+ );
+ return subNav;
+ }
+
+ return null;
+ }
+
+ const navItem = (
+
+ {item.name}
+
+ );
+ return navItem;
+}
+
+const Navigation = (props, context) => {
+ const location = useLocation();
+ const { pathname } = location;
+ const { isCollapse } = context;
+ return (
+
+ );
+};
+
+Navigation.contextTypes = {
+ isCollapse: PropTypes.bool,
+};
+export default Navigation;
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo7-literal-condition2/expected/demo-project/src/layouts/BasicLayout/index.jsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo7-literal-condition2/expected/demo-project/src/layouts/BasicLayout/index.jsx
new file mode 100644
index 0000000000..18db44df5e
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo7-literal-condition2/expected/demo-project/src/layouts/BasicLayout/index.jsx
@@ -0,0 +1,81 @@
+
+import React, { useState } from 'react';
+import { Shell, ConfigProvider } from '@alifd/next';
+import PageNav from './components/PageNav';
+import Logo from './components/Logo';
+import Footer from './components/Footer';
+
+(function() {
+ const throttle = function(type, name, obj = window) {
+ let running = false;
+
+ const func = () => {
+ if (running) {
+ return;
+ }
+
+ running = true;
+ requestAnimationFrame(() => {
+ obj.dispatchEvent(new CustomEvent(name));
+ running = false;
+ });
+ };
+
+ obj.addEventListener(type, func);
+ };
+
+ throttle('resize', 'optimizedResize');
+})();
+
+export default function BasicLayout({ children }) {
+ const getDevice = width => {
+ const isPhone =
+ typeof navigator !== 'undefined' && navigator && navigator.userAgent.match(/phone/gi);
+
+ if (width < 680 || isPhone) {
+ return 'phone';
+ }
+ if (width < 1280 && width > 680) {
+ return 'tablet';
+ }
+ return 'desktop';
+ };
+
+ const [device, setDevice] = useState(getDevice(NaN));
+ window.addEventListener('optimizedResize', e => {
+ setDevice(getDevice(e && e.target && e.target.innerWidth));
+ });
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ {children}
+
+
+
+
+
+ );
+}
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo7-literal-condition2/expected/demo-project/src/layouts/BasicLayout/menuConfig.js b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo7-literal-condition2/expected/demo-project/src/layouts/BasicLayout/menuConfig.js
new file mode 100644
index 0000000000..5332202be4
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo7-literal-condition2/expected/demo-project/src/layouts/BasicLayout/menuConfig.js
@@ -0,0 +1,11 @@
+
+const headerMenuConfig = [];
+const asideMenuConfig = [
+ {
+ name: 'Dashboard',
+ path: '/',
+ icon: 'smile',
+ },
+];
+export { headerMenuConfig, asideMenuConfig };
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo7-literal-condition2/expected/demo-project/src/pages/Test/index.css b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo7-literal-condition2/expected/demo-project/src/pages/Test/index.css
new file mode 100644
index 0000000000..066114aeeb
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo7-literal-condition2/expected/demo-project/src/pages/Test/index.css
@@ -0,0 +1,8 @@
+body {
+ font-size: 12px;
+}
+
+.botton {
+ width: 100px;
+ color: #ff00ff;
+}
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo7-literal-condition2/expected/demo-project/src/pages/Test/index.jsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo7-literal-condition2/expected/demo-project/src/pages/Test/index.jsx
new file mode 100644
index 0000000000..9e93a3ff6f
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo7-literal-condition2/expected/demo-project/src/pages/Test/index.jsx
@@ -0,0 +1,1076 @@
+// 注意: 出码引擎注入的临时变量默认都以 "__$$" 开头,禁止在搭建的代码中直接访问。
+// 例外:react 框架的导出名和各种组件名除外。
+import React from 'react';
+
+import {
+ Modal,
+ Steps,
+ Form,
+ Input,
+ Checkbox,
+ Select,
+ DatePicker,
+ InputNumber,
+ Button,
+} from '@alilc/antd-lowcode/dist/antd-lowcode.esm.js';
+
+import {
+ Text as NextText,
+ Page as NextPage,
+ Block as NextBlock,
+ P as NextP,
+} from '@alife/container/lib/index.js';
+
+import utils, { RefsManager } from '../../utils';
+
+import * as __$$i18n from '../../i18n';
+
+import __$$constants from '../../constants';
+
+import './index.css';
+
+const NextBlockCell = NextBlock.Cell;
+
+class Test$$Page extends React.Component {
+ _context = this;
+
+ get constants() {
+ return __$$constants || {};
+ }
+
+ constructor(props, context) {
+ super(props);
+
+ this.utils = utils;
+
+ this._refsManager = new RefsManager();
+
+ __$$i18n._inject2(this);
+
+ this.state = {
+ books: [],
+ currentStep: 0,
+ isModifyDialogVisible: false,
+ isModifyStatus: false,
+ secondCommitText: '完成并提交',
+ thirdAuditText: '审核中',
+ thirdButtonText: '修改',
+ customerProjectInfo: {
+ id: null,
+ systemProjectName: null,
+ projectVersionTypeArray: null,
+ projectVersionType: null,
+ versionLine: 2,
+ expectedTime: null,
+ expectedNum: null,
+ projectModal: null,
+ displayWidth: null,
+ displayHeight: null,
+ displayInch: null,
+ displayDpi: null,
+ mainSoc: null,
+ cpuCoreNum: null,
+ instructions: null,
+ osVersion: null,
+ status: null,
+ },
+ versionLinesArray: [
+ { label: 'AmapAuto 485', value: 1 },
+ { label: 'AmapAuto 505', value: 2 },
+ ],
+ projectModalsArray: [
+ { label: '车机', value: 1 },
+ { label: '车镜', value: 2 },
+ { label: '记录仪', value: 3 },
+ { label: '其他', value: 4 },
+ ],
+ osVersionsArray: [
+ { label: '安卓5', value: 1 },
+ { label: '安卓6', value: 2 },
+ { label: '安卓7', value: 3 },
+ { label: '安卓8', value: 4 },
+ { label: '安卓9', value: 5 },
+ { label: '安卓10', value: 6 },
+ ],
+ instructionsArray: [
+ { label: 'ARM64-V8', value: 'ARM64-V8' },
+ { label: 'ARM32-V7', value: 'ARM32-V7' },
+ { label: 'X86', value: 'X86' },
+ { label: 'X64', value: 'X64' },
+ ],
+ };
+ }
+
+ $ = (refName) => {
+ return this._refsManager.get(refName);
+ };
+
+ $$ = (refName) => {
+ return this._refsManager.getAll(refName);
+ };
+
+ componentDidUpdate(prevProps, prevState, snapshot) {}
+
+ componentWillUnmount() {}
+
+ __jp__init() {
+ /*...*/
+ }
+
+ __jp__initRouter() {
+ /*...*/
+ }
+
+ __jp__initDataSource() {
+ /*...*/
+ }
+
+ __jp__initEnv() {
+ /*...*/
+ }
+
+ __jp__initUtils() {
+ /*...*/
+ }
+
+ onFinishFirst() {
+ /*...*/
+ }
+
+ onClickPreSecond() {
+ /*...*/
+ }
+
+ onFinishSecond() {
+ /*...*/
+ }
+
+ onClickModifyThird() {
+ /*...*/
+ }
+
+ onOkModifyDialogThird() {
+ //第三步 修改 对话框 确定
+
+ this.setState({
+ currentStep: 0,
+ isModifyDialogVisible: false,
+ });
+ }
+
+ onCancelModifyDialogThird() {
+ //第三步 修改 对话框 取消
+
+ this.setState({
+ isModifyDialogVisible: false,
+ });
+ }
+
+ onFinishFailed() {}
+
+ onClickPreThird() {
+ // 第三步 上一步
+ this.setState({
+ currentStep: 1,
+ });
+ }
+
+ onClickFirstBack() {
+ // 第一步 返回按钮
+ this.$router.push('/myProjectList');
+ }
+
+ onClickSecondBack() {
+ // 第二步 返回按钮
+ this.$router.push('/myProjectList');
+ }
+
+ onClickThirdBack() {
+ // 第三步 返回按钮
+ this.$router.push('/myProjectList');
+ }
+
+ onValuesChange(_, values) {
+ this.setState({
+ customerProjectInfo: {
+ ...this.state.customerProjectInfo,
+ ...values,
+ },
+ });
+ }
+
+ componentDidMount() {}
+
+ render() {
+ const __$$context = this._context || this;
+ const { state } = __$$context;
+ return (
+
+ this.state.isModifyDialogVisible)}
+ okText="确认"
+ okType=""
+ forceRender={false}
+ cancelText="取消"
+ zIndex={2000}
+ destroyOnClose={false}
+ confirmLoading={false}
+ __events={{
+ eventDataList: [
+ {
+ type: 'componentEvent',
+ name: 'onOk',
+ relatedEventName: 'onOkModifyDialogThird',
+ },
+ {
+ type: 'componentEvent',
+ name: 'onCancel',
+ relatedEventName: 'onCancelModifyDialogThird',
+ },
+ ],
+ eventList: [
+ { name: 'onCancel', disabled: true },
+ { name: 'onOk', disabled: true },
+ ],
+ }}
+ onOk={function () {
+ this.onOkModifyDialogThird.apply(
+ this,
+ Array.prototype.slice.call(arguments).concat([])
+ );
+ }.bind(this)}
+ onCancel={function () {
+ this.onCancelModifyDialogThird.apply(
+ this,
+ Array.prototype.slice.call(arguments).concat([])
+ );
+ }.bind(this)}
+ >
+
+ 修改将撤回此前填写的信息
+
+
+
+
+
+
+ this.state.currentStep)}>
+
+
+
+
+
+ {!!__$$eval(() => this.state.currentStep === 0) && (
+
+
+
+
+ )}
+
+
+ this.state.customerProjectInfo.id > 0 &&
+ !this.state.isModifyStatus
+ )}
+ />
+
+
+
+
+
+ this.state.customerProjectInfo.id > 0 &&
+ !this.state.isModifyStatus
+ )}
+ />
+
+
+
+ this.state.customerProjectInfo.id > 0 &&
+ !this.state.isModifyStatus
+ )}
+ />
+
+
+
+ this.state.customerProjectInfo.id > 0 &&
+ !this.state.isModifyStatus
+ )}
+ min={0}
+ size="middle"
+ />
+
+
+
+
+
+
+
+ )}
+ {!!__$$eval(() => this.state.currentStep === 1) && (
+
+
+
+
+
+ this.state.customerProjectInfo.id > 0 &&
+ !this.state.isModifyStatus
+ )}
+ min={0}
+ />
+
+
+
+ this.state.customerProjectInfo.id > 0 &&
+ !this.state.isModifyStatus
+ )}
+ min={0}
+ />
+
+
+
+ this.state.customerProjectInfo.id > 0 &&
+ !this.state.isModifyStatus
+ )}
+ min={0}
+ />
+
+
+
+ this.state.customerProjectInfo.id > 0 &&
+ !this.state.isModifyStatus
+ )}
+ min={0}
+ />
+
+
+
+ this.state.customerProjectInfo.id > 0 &&
+ !this.state.isModifyStatus
+ )}
+ />
+
+
+
+ this.state.customerProjectInfo.id > 0 &&
+ !this.state.isModifyStatus
+ )}
+ defaultValue=""
+ min={0}
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+ )}
+ {!!__$$eval(() => this.state.currentStep === 2) && (
+
+
+
+
+ this.state.thirdAuditText)}
+ subTitle=""
+ description=""
+ style={{ width: '200px' }}
+ />
+
+
+
+
+
+ {!!__$$eval(
+ () => this.state.customerProjectInfo.status > 2
+ ) && (
+
+ )}
+
+
+
+ )}
+
+
+
+
+ );
+ }
+}
+
+export default Test$$Page;
+
+function __$$eval(expr) {
+ try {
+ return expr();
+ } catch (error) {}
+}
+
+function __$$evalArray(expr) {
+ const res = __$$eval(expr);
+ return Array.isArray(res) ? res : [];
+}
+
+function __$$createChildContext(oldContext, ext) {
+ const childContext = {
+ ...oldContext,
+ ...ext,
+ };
+ childContext.__proto__ = oldContext;
+ return childContext;
+}
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo7-literal-condition2/expected/demo-project/src/pages/layout.jsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo7-literal-condition2/expected/demo-project/src/pages/layout.jsx
new file mode 100644
index 0000000000..50fbb2d1f1
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo7-literal-condition2/expected/demo-project/src/pages/layout.jsx
@@ -0,0 +1,10 @@
+import { Outlet } from 'ice';
+import BasicLayout from '@/layouts/BasicLayout';
+
+export default function Layout() {
+ return (
+
+
+
+ );
+}
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo7-literal-condition2/expected/demo-project/src/typings.d.ts b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo7-literal-condition2/expected/demo-project/src/typings.d.ts
new file mode 100644
index 0000000000..a9f8de7ceb
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo7-literal-condition2/expected/demo-project/src/typings.d.ts
@@ -0,0 +1,9 @@
+///
+
+export {};
+declare global {
+ interface Window {
+ g_config: Record;
+ }
+}
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo7-literal-condition2/expected/demo-project/src/utils.js b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo7-literal-condition2/expected/demo-project/src/utils.js
new file mode 100644
index 0000000000..1190717924
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo7-literal-condition2/expected/demo-project/src/utils.js
@@ -0,0 +1,47 @@
+import { createRef } from 'react';
+
+export class RefsManager {
+ constructor() {
+ this.refInsStore = {};
+ }
+
+ clearNullRefs() {
+ Object.keys(this.refInsStore).forEach((refName) => {
+ const filteredInsList = this.refInsStore[refName].filter(
+ (insRef) => !!insRef.current
+ );
+ if (filteredInsList.length > 0) {
+ this.refInsStore[refName] = filteredInsList;
+ } else {
+ delete this.refInsStore[refName];
+ }
+ });
+ }
+
+ get(refName) {
+ this.clearNullRefs();
+ if (this.refInsStore[refName] && this.refInsStore[refName].length > 0) {
+ return this.refInsStore[refName][0].current;
+ }
+
+ return null;
+ }
+
+ getAll(refName) {
+ this.clearNullRefs();
+ if (this.refInsStore[refName] && this.refInsStore[refName].length > 0) {
+ return this.refInsStore[refName].map((i) => i.current);
+ }
+
+ return [];
+ }
+
+ linkRef(refName) {
+ const refIns = createRef();
+ this.refInsStore[refName] = this.refInsStore[refName] || [];
+ this.refInsStore[refName].push(refIns);
+ return refIns;
+ }
+}
+
+export default {};
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo7-literal-condition2/schema.json5 b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo7-literal-condition2/schema.json5
new file mode 100644
index 0000000000..4c829cfab1
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo7-literal-condition2/schema.json5
@@ -0,0 +1,1703 @@
+{
+ version: '1.0.0',
+ componentsMap: [
+ {
+ package: '@alife/container',
+ version: '0.3.7',
+ exportName: 'Text',
+ main: 'lib/index.js',
+ destructuring: true,
+ subName: '',
+ componentName: 'NextText',
+ },
+ {
+ package: '@alilc/antd-lowcode',
+ version: '0.8.0',
+ exportName: 'Modal',
+ main: 'dist/antd-lowcode.esm.js',
+ destructuring: true,
+ componentName: 'Modal',
+ },
+ {
+ package: '@alilc/antd-lowcode',
+ version: '0.8.0',
+ exportName: 'Steps',
+ main: 'dist/antd-lowcode.esm.js',
+ destructuring: true,
+ subName: 'Step',
+ componentName: 'Steps.Step',
+ },
+ {
+ package: '@alilc/antd-lowcode',
+ version: '0.8.0',
+ exportName: 'Steps',
+ main: 'dist/antd-lowcode.esm.js',
+ destructuring: true,
+ componentName: 'Steps',
+ },
+ {
+ package: '@alife/container',
+ version: '0.3.7',
+ exportName: 'P',
+ main: 'lib/index.js',
+ destructuring: true,
+ subName: '',
+ componentName: 'NextP',
+ },
+ {
+ package: '@alilc/antd-lowcode',
+ version: '0.8.0',
+ exportName: 'Input',
+ main: 'dist/antd-lowcode.esm.js',
+ destructuring: true,
+ componentName: 'Input',
+ },
+ {
+ package: '@alilc/antd-lowcode',
+ version: '0.8.0',
+ exportName: 'Form',
+ main: 'dist/antd-lowcode.esm.js',
+ destructuring: true,
+ subName: 'Item',
+ componentName: 'Form.Item',
+ },
+ {
+ package: '@alilc/antd-lowcode',
+ version: '0.8.0',
+ exportName: 'Checkbox',
+ main: 'dist/antd-lowcode.esm.js',
+ destructuring: true,
+ subName: 'Group',
+ componentName: 'Checkbox.Group',
+ },
+ {
+ package: '@alilc/antd-lowcode',
+ version: '0.8.0',
+ exportName: 'Select',
+ main: 'dist/antd-lowcode.esm.js',
+ destructuring: true,
+ componentName: 'Select',
+ },
+ {
+ package: '@alilc/antd-lowcode',
+ version: '0.8.0',
+ exportName: 'DatePicker',
+ main: 'dist/antd-lowcode.esm.js',
+ destructuring: true,
+ componentName: 'DatePicker',
+ },
+ {
+ package: '@alilc/antd-lowcode',
+ version: '0.8.0',
+ exportName: 'InputNumber',
+ main: 'dist/antd-lowcode.esm.js',
+ destructuring: true,
+ componentName: 'InputNumber',
+ },
+ {
+ package: '@alilc/antd-lowcode',
+ version: '0.8.0',
+ exportName: 'Button',
+ main: 'dist/antd-lowcode.esm.js',
+ destructuring: true,
+ componentName: 'Button',
+ },
+ {
+ package: '@alilc/antd-lowcode',
+ version: '0.8.0',
+ exportName: 'Form',
+ main: 'dist/antd-lowcode.esm.js',
+ destructuring: true,
+ componentName: 'Form',
+ },
+ {
+ package: '@alife/container',
+ version: '0.3.7',
+ exportName: 'Block',
+ main: 'lib/index.js',
+ destructuring: true,
+ subName: 'Cell',
+ componentName: 'NextBlockCell',
+ },
+ {
+ package: '@alife/container',
+ version: '0.3.7',
+ exportName: 'Block',
+ main: 'lib/index.js',
+ destructuring: true,
+ subName: '',
+ componentName: 'NextBlock',
+ },
+ {
+ devMode: 'lowcode',
+ componentName: 'Slot',
+ },
+ {
+ package: '@alife/container',
+ version: '0.3.7',
+ exportName: 'Page',
+ main: 'lib/index.js',
+ destructuring: true,
+ subName: '',
+ componentName: 'NextPage',
+ },
+ {
+ devMode: 'lowcode',
+ componentName: 'Page',
+ },
+ ],
+ componentsTree: [
+ {
+ componentName: 'Page',
+ id: 'node_dockcviv8fo1',
+ props: {
+ ref: 'outterView',
+ style: {
+ height: '100%',
+ },
+ },
+ fileName: 'test',
+ dataSource: {
+ list: [],
+ },
+ css: 'body {\n font-size: 12px;\n}\n\n.botton {\n width: 100px;\n color: #ff00ff\n}',
+ lifeCycles: {
+ constructor: {
+ type: 'JSFunction',
+ value: "function() { /*...*/ }",
+ },
+ componentDidMount: {
+ type: 'JSFunction',
+ value: 'function() {}',
+ },
+ componentDidUpdate: {
+ type: 'JSFunction',
+ value: 'function(prevProps, prevState, snapshot) {}',
+ },
+ componentWillUnmount: {
+ type: 'JSFunction',
+ value: 'function() {}',
+ },
+ },
+ methods: {
+ __jp__init: {
+ type: 'JSFunction',
+ value: "function() { /*...*/ }",
+ },
+ __jp__initRouter: {
+ type: 'JSFunction',
+ value: "function() { /*...*/ }",
+ },
+ __jp__initDataSource: {
+ type: 'JSFunction',
+ value: "function() { /*...*/ }",
+ },
+ __jp__initEnv: {
+ type: 'JSFunction',
+ value: "function() { /*...*/ }",
+ },
+ __jp__initUtils: {
+ type: 'JSFunction',
+ value: "function() { /*...*/ }",
+ },
+ onFinishFirst: {
+ type: 'JSFunction',
+ value: "function() { /*...*/ }",
+ },
+ onClickPreSecond: {
+ type: 'JSFunction',
+ value: "function() { /*...*/ }",
+ },
+ onFinishSecond: {
+ type: 'JSFunction',
+ value: "function() { /*...*/ }",
+ },
+ onClickModifyThird: {
+ type: 'JSFunction',
+ value: "function() { /*...*/ }",
+ },
+ onOkModifyDialogThird: {
+ type: 'JSFunction',
+ value: 'function() {\n //第三步 修改 对话框 确定\n\n this.setState({\n currentStep: 0,\n isModifyDialogVisible: false,\n });\n }',
+ },
+ onCancelModifyDialogThird: {
+ type: 'JSFunction',
+ value: 'function() {\n //第三步 修改 对话框 取消\n\n this.setState({\n isModifyDialogVisible: false,\n });\n }',
+ },
+ onFinishFailed: {
+ type: 'JSFunction',
+ value: 'function() {}',
+ },
+ onClickPreThird: {
+ type: 'JSFunction',
+ value: 'function() {\n // 第三步 上一步\n this.setState({\n currentStep: 1,\n });\n }',
+ },
+ onClickFirstBack: {
+ type: 'JSFunction',
+ value: "function() {\n // 第一步 返回按钮\n this.$router.push('/myProjectList');\n }",
+ },
+ onClickSecondBack: {
+ type: 'JSFunction',
+ value: "function() {\n // 第二步 返回按钮\n this.$router.push('/myProjectList');\n }",
+ },
+ onClickThirdBack: {
+ type: 'JSFunction',
+ value: "function() {\n // 第三步 返回按钮\n this.$router.push('/myProjectList');\n }",
+ },
+ onValuesChange: {
+ type: 'JSFunction',
+ value: 'function(_, values) {\n this.setState({\n customerProjectInfo: {\n ...this.state.customerProjectInfo,\n ...values,\n },\n });\n }',
+ },
+ },
+ state: {
+ books: [],
+ currentStep: 0,
+ isModifyDialogVisible: false,
+ isModifyStatus: false,
+ secondCommitText: '完成并提交',
+ thirdAuditText: '审核中',
+ thirdButtonText: '修改',
+ customerProjectInfo: {
+ id: null,
+ systemProjectName: null,
+ projectVersionTypeArray: null,
+ projectVersionType: null,
+ versionLine: 2,
+ expectedTime: null,
+ expectedNum: null,
+ projectModal: null,
+ displayWidth: null,
+ displayHeight: null,
+ displayInch: null,
+ displayDpi: null,
+ mainSoc: null,
+ cpuCoreNum: null,
+ instructions: null,
+ osVersion: null,
+ status: null,
+ },
+ versionLinesArray: [
+ {
+ label: 'AmapAuto 485',
+ value: 1,
+ },
+ {
+ label: 'AmapAuto 505',
+ value: 2,
+ },
+ ],
+ projectModalsArray: [
+ {
+ label: '车机',
+ value: 1,
+ },
+ {
+ label: '车镜',
+ value: 2,
+ },
+ {
+ label: '记录仪',
+ value: 3,
+ },
+ {
+ label: '其他',
+ value: 4,
+ },
+ ],
+ osVersionsArray: [
+ {
+ label: '安卓5',
+ value: 1,
+ },
+ {
+ label: '安卓6',
+ value: 2,
+ },
+ {
+ label: '安卓7',
+ value: 3,
+ },
+ {
+ label: '安卓8',
+ value: 4,
+ },
+ {
+ label: '安卓9',
+ value: 5,
+ },
+ {
+ label: '安卓10',
+ value: 6,
+ },
+ ],
+ instructionsArray: [
+ {
+ label: 'ARM64-V8',
+ value: 'ARM64-V8',
+ },
+ {
+ label: 'ARM32-V7',
+ value: 'ARM32-V7',
+ },
+ {
+ label: 'X86',
+ value: 'X86',
+ },
+ {
+ label: 'X64',
+ value: 'X64',
+ },
+ ],
+ },
+ children: [
+ {
+ componentName: 'Modal',
+ id: 'node_ockodngwu940',
+ props: {
+ title: '是否修改',
+ visible: {
+ type: 'JSExpression',
+ value: 'this.state.isModifyDialogVisible',
+ },
+ okText: '确认',
+ okType: '',
+ forceRender: false,
+ cancelText: '取消',
+ zIndex: 2000,
+ destroyOnClose: false,
+ confirmLoading: false,
+ __events: {
+ eventDataList: [
+ {
+ type: 'componentEvent',
+ name: 'onOk',
+ relatedEventName: 'onOkModifyDialogThird',
+ },
+ {
+ type: 'componentEvent',
+ name: 'onCancel',
+ relatedEventName: 'onCancelModifyDialogThird',
+ },
+ ],
+ eventList: [
+ {
+ name: 'onCancel',
+ disabled: true,
+ },
+ {
+ name: 'onOk',
+ disabled: true,
+ },
+ ],
+ },
+ onOk: {
+ type: 'JSFunction',
+ value: 'function(){this.onOkModifyDialogThird.apply(this,Array.prototype.slice.call(arguments).concat([])) }',
+ },
+ onCancel: {
+ type: 'JSFunction',
+ value: 'function(){this.onCancelModifyDialogThird.apply(this,Array.prototype.slice.call(arguments).concat([])) }',
+ },
+ },
+ hidden: true,
+ children: [
+ {
+ componentName: 'NextText',
+ id: 'node_ockodngwu946',
+ props: {
+ type: 'inherit',
+ children: '修改将撤回此前填写的信息',
+ style: {
+ fontStyle: 'normal',
+ textAlign: 'left',
+ display: 'block',
+ fontFamily: 'arial, helvetica, microsoft yahei',
+ fontWeight: 'normal',
+ },
+ },
+ },
+ ],
+ },
+ {
+ componentName: 'NextPage',
+ id: 'node_ocko19zplh1',
+ props: {
+ columns: 12,
+ headerDivider: true,
+ placeholderStyle: {
+ gridRowEnd: 'span 1',
+ gridColumnEnd: 'span 12',
+ },
+ placeholder: '页面主体内容:拖拽Block布局组件到这里',
+ header: {
+ type: 'JSSlot',
+ title: 'header',
+ },
+ headerProps: {
+ background: 'surface',
+ },
+ footer: {
+ type: 'JSSlot',
+ title: 'footer',
+ },
+ minHeight: '100vh',
+ style: {},
+ },
+ title: '页面',
+ children: [
+ {
+ componentName: 'NextBlock',
+ id: 'node_ocko19zplh2',
+ props: {
+ prefix: 'next-',
+ placeholderStyle: {
+ height: '100%',
+ },
+ noPadding: false,
+ noBorder: false,
+ background: 'surface',
+ layoutmode: 'O',
+ colSpan: 12,
+ rowSpan: 1,
+ childTotalColumns: 12,
+ },
+ title: '区块',
+ children: [
+ {
+ componentName: 'NextBlockCell',
+ id: 'node_ocko19zplh3',
+ props: {
+ title: '',
+ prefix: 'next-',
+ placeholderStyle: {
+ height: '100%',
+ },
+ layoutmode: 'O',
+ childTotalColumns: 12,
+ isAutoContainer: true,
+ colSpan: 12,
+ rowSpan: 1,
+ },
+ children: [
+ {
+ componentName: 'NextP',
+ id: 'node_ockoco6icv1w',
+ props: {
+ wrap: false,
+ type: 'body2',
+ verAlign: 'middle',
+ textSpacing: true,
+ align: 'left',
+ flex: true,
+ style: {
+ marginBottom: '24px',
+ },
+ },
+ title: '段落',
+ children: [
+ {
+ componentName: 'Steps',
+ id: 'node_ockoco6icv1x',
+ props: {
+ current: {
+ type: 'JSExpression',
+ value: 'this.state.currentStep',
+ },
+ },
+ children: [
+ {
+ componentName: 'Steps.Step',
+ id: 'node_ockoco6icv1y',
+ props: {
+ title: '版本申请',
+ description: '',
+ },
+ },
+ {
+ componentName: 'Steps.Step',
+ id: 'node_ockoco6icv1z',
+ props: {
+ title: '机器配置',
+ subTitle: '',
+ description: '',
+ },
+ },
+ {
+ componentName: 'Steps.Step',
+ id: 'node_ockoco6icv20',
+ props: {
+ title: '项目审批',
+ description: '',
+ },
+ },
+ ],
+ },
+ ],
+ },
+ {
+ componentName: 'NextP',
+ id: 'node_ockoco6icv12w',
+ props: {
+ wrap: false,
+ type: 'body2',
+ verAlign: 'middle',
+ textSpacing: true,
+ align: 'left',
+ full: true,
+ flex: true,
+ style: {
+ display: 'flex',
+ justifyContent: 'center',
+ },
+ },
+ title: '段落',
+ condition: {
+ type: 'JSExpression',
+ value: 'this.state.currentStep === 0',
+ },
+ children: [
+ {
+ componentName: 'Form',
+ id: 'node_ockoco6icv12x',
+ props: {
+ labelCol: {
+ span: 10,
+ },
+ wrapperCol: {
+ span: 10,
+ },
+ onFinish: {
+ type: 'JSFunction',
+ value: 'function(){this.onFinishFirst.apply(this,Array.prototype.slice.call(arguments).concat([])) }',
+ },
+ name: 'basic',
+ style: {
+ display: 'flex',
+ flexDirection: 'column',
+ width: '600px',
+ justifyContent: 'center',
+ },
+ layout: 'vertical',
+ __events: {
+ eventDataList: [
+ {
+ type: 'componentEvent',
+ name: 'onFinish',
+ relatedEventName: 'onFinishFirst',
+ },
+ {
+ type: 'componentEvent',
+ name: 'onValuesChange',
+ relatedEventName: 'onValuesChange',
+ },
+ ],
+ eventList: [
+ {
+ name: 'onFinish',
+ disabled: true,
+ },
+ {
+ name: 'onFinishFailed',
+ disabled: false,
+ },
+ {
+ name: 'onFieldsChange',
+ disabled: false,
+ },
+ {
+ name: 'onValuesChange',
+ disabled: true,
+ },
+ ],
+ },
+ initialValues: {
+ type: 'JSExpression',
+ value: 'this.state.customerProjectInfo',
+ },
+ onValuesChange: {
+ type: 'JSFunction',
+ value: 'function(){this.onValuesChange.apply(this,Array.prototype.slice.call(arguments).concat([])) }',
+ },
+ },
+ children: [
+ {
+ componentName: 'Form.Item',
+ id: 'node_ockojhvrkn2u',
+ props: {
+ label: '',
+ style: {
+ width: '600px',
+ },
+ colon: false,
+ name: 'id',
+ },
+ condition: false,
+ children: [
+ {
+ componentName: 'Input',
+ id: 'node_ockojhvrkn2v',
+ props: {
+ placeholder: '',
+ style: {
+ width: '600px',
+ },
+ bordered: false,
+ disabled: true,
+ },
+ },
+ ],
+ },
+ {
+ componentName: 'Form.Item',
+ id: 'node_ockoco6icv12y',
+ props: {
+ label: '版本类型选择',
+ name: 'projectVersionTypeArray',
+ initialValue: '',
+ labelAlign: 'left',
+ colon: false,
+ required: true,
+ style: {
+ flexDirection: 'column',
+ width: '600px',
+ },
+ requiredobj: {
+ required: true,
+ message: '请选择版本类型',
+ },
+ },
+ children: [
+ {
+ componentName: 'Checkbox.Group',
+ id: 'node_ockoco6icv12z',
+ props: {
+ options: [
+ {
+ label: '基础版本',
+ value: '3',
+ },
+ {
+ label: 'AR导航',
+ value: '1',
+ },
+ {
+ label: '货车导航',
+ value: '2',
+ },
+ {
+ label: 'UI定制',
+ value: '4',
+ disabled: false,
+ },
+ ],
+ style: {
+ width: '600px',
+ },
+ disabled: {
+ type: 'JSExpression',
+ value: 'this.state.customerProjectInfo.id > 0 && ! this.state.isModifyStatus',
+ },
+ },
+ },
+ ],
+ },
+ {
+ componentName: 'Form.Item',
+ id: 'node_ockoco6icv13a',
+ props: {
+ label: '版本线选择',
+ labelAlign: 'left',
+ colon: false,
+ required: true,
+ style: {
+ width: '600px',
+ },
+ name: 'versionLine',
+ requiredobj: {
+ required: true,
+ message: '请选择版本线',
+ },
+ extra: '',
+ },
+ children: [
+ {
+ componentName: 'Select',
+ id: 'node_ockoco6icv13b',
+ props: {
+ style: {
+ width: '600px',
+ },
+ options: {
+ type: 'JSExpression',
+ value: 'this.state.versionLinesArray',
+ },
+ disabled: {
+ type: 'JSExpression',
+ value: 'this.state.customerProjectInfo.id > 0 && ! this.state.isModifyStatus',
+ },
+ placeholder: '请选择版本线',
+ },
+ },
+ ],
+ },
+ {
+ componentName: 'Form.Item',
+ id: 'node_ockoco6icv13o',
+ props: {
+ label: '项目名称',
+ colon: false,
+ required: true,
+ style: {
+ display: 'flex',
+ },
+ labelAlign: 'left',
+ extra: '',
+ name: 'systemProjectName',
+ requiredobj: {
+ required: true,
+ message: '请按格式填写项目名称',
+ },
+ typeobj: {
+ type: 'string',
+ message: '请输入项目名称,格式:公司简称-产品名称-版本类型',
+ },
+ lenobj: {
+ max: 100,
+ message: '项目名称不能超过100个字符',
+ },
+ },
+ children: [
+ {
+ componentName: 'Input',
+ id: 'node_ockoco6icv13p',
+ props: {
+ placeholder: '公司简称-产品名称-版本类型',
+ style: {
+ width: '600px',
+ },
+ disabled: {
+ type: 'JSExpression',
+ value: 'this.state.customerProjectInfo.id > 0 && ! this.state.isModifyStatus',
+ },
+ },
+ },
+ ],
+ },
+ {
+ componentName: 'Form.Item',
+ id: 'node_ockoco6icv13v',
+ props: {
+ label: '预期交付时间',
+ style: {
+ width: '600px',
+ },
+ colon: false,
+ required: true,
+ name: 'expectedTime',
+ labelAlign: 'left',
+ requiredobj: {
+ required: true,
+ message: '请填写预期交付时间',
+ },
+ },
+ children: [
+ {
+ componentName: 'DatePicker',
+ id: 'node_ockoco6icv13w',
+ props: {
+ style: {
+ width: '600px',
+ },
+ disabled: {
+ type: 'JSExpression',
+ value: 'this.state.customerProjectInfo.id > 0 && ! this.state.isModifyStatus',
+ },
+ },
+ },
+ ],
+ },
+ {
+ componentName: 'Form.Item',
+ id: 'node_ockpmbs0bv8',
+ props: {
+ label: '预期出货量',
+ style: {
+ width: '600px',
+ },
+ required: true,
+ requiredobj: {
+ required: true,
+ message: '请填写预期出货量',
+ },
+ name: 'expectedNum',
+ labelAlign: 'left',
+ colon: false,
+ },
+ children: [
+ {
+ componentName: 'InputNumber',
+ id: 'node_ockpmbs0bv9',
+ props: {
+ value: 3,
+ style: {
+ width: '600px',
+ },
+ placeholder: '单位(台)使用该版本的机器数量+预计出货量,请如实填写',
+ disabled: {
+ type: 'JSExpression',
+ value: 'this.state.customerProjectInfo.id > 0 && ! this.state.isModifyStatus',
+ },
+ min: 0,
+ size: 'middle',
+ },
+ },
+ ],
+ },
+ {
+ componentName: 'Form.Item',
+ id: 'node_ockoco6icv130',
+ props: {
+ wrapperCol: {
+ offset: '',
+ },
+ style: {
+ flexDirection: 'row',
+ alignItems: 'baseline',
+ justifyContent: 'space-between',
+ width: '600px',
+ display: 'block',
+ },
+ labelAlign: 'left',
+ colon: false,
+ },
+ children: [
+ {
+ componentName: 'Button',
+ id: 'node_ockoco6icv132',
+ props: {
+ style: {
+ margin: '0px',
+ },
+ children: '返回',
+ __events: {
+ eventDataList: [
+ {
+ type: 'componentEvent',
+ name: 'onClick',
+ relatedEventName: 'onClickFirstBack',
+ },
+ ],
+ eventList: [
+ {
+ name: 'onClick',
+ disabled: true,
+ },
+ ],
+ },
+ onClick: {
+ type: 'JSFunction',
+ value: 'function(){this.onClickFirstBack.apply(this,Array.prototype.slice.call(arguments).concat([])) }',
+ },
+ },
+ },
+ {
+ componentName: 'Button',
+ id: 'node_ockoco6icv131',
+ props: {
+ type: 'primary',
+ children: '下一步',
+ htmlType: 'submit',
+ style: {
+ boxShadow: 'rgba(31, 56, 88, 0.2) 0px 0px 0px 0px',
+ float: 'right',
+ },
+ __events: {
+ eventDataList: [],
+ eventList: [
+ {
+ name: 'onClick',
+ disabled: false,
+ },
+ ],
+ },
+ },
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ {
+ componentName: 'NextP',
+ id: 'node_ockoco6icv1ue',
+ props: {
+ wrap: false,
+ type: 'body2',
+ verAlign: 'middle',
+ textSpacing: true,
+ align: 'left',
+ full: true,
+ flex: true,
+ style: {
+ display: 'flex',
+ justifyContent: 'center',
+ },
+ },
+ title: '段落',
+ condition: {
+ type: 'JSExpression',
+ value: 'this.state.currentStep === 1',
+ },
+ children: [
+ {
+ componentName: 'Form',
+ id: 'node_ockoco6icv1uf',
+ props: {
+ labelCol: {
+ span: 10,
+ },
+ wrapperCol: {
+ span: 10,
+ },
+ onFinish: {
+ type: 'JSFunction',
+ value: 'function(){this.onFinishSecond.apply(this,Array.prototype.slice.call(arguments).concat([])) }',
+ },
+ name: 'basic',
+ style: {
+ display: 'flex',
+ flexDirection: 'column',
+ width: '600px',
+ justifyContent: 'center',
+ height: '800px',
+ },
+ layout: 'vertical',
+ __events: {
+ eventDataList: [
+ {
+ type: 'componentEvent',
+ name: 'onFinish',
+ relatedEventName: 'onFinishSecond',
+ },
+ {
+ type: 'componentEvent',
+ name: 'onValuesChange',
+ relatedEventName: 'onValuesChange',
+ },
+ ],
+ eventList: [
+ {
+ name: 'onFinish',
+ disabled: true,
+ },
+ {
+ name: 'onFinishFailed',
+ disabled: false,
+ },
+ {
+ name: 'onFieldsChange',
+ disabled: false,
+ },
+ {
+ name: 'onValuesChange',
+ disabled: true,
+ },
+ ],
+ },
+ initialValues: {
+ type: 'JSExpression',
+ value: 'this.state.customerProjectInfo',
+ },
+ onValuesChange: {
+ type: 'JSFunction',
+ value: 'function(){this.onValuesChange.apply(this,Array.prototype.slice.call(arguments).concat([])) }',
+ },
+ },
+ children: [
+ {
+ componentName: 'Form.Item',
+ id: 'node_ockoco6icv1ui',
+ props: {
+ label: '设备类型选择',
+ labelAlign: 'left',
+ colon: false,
+ required: true,
+ style: {
+ width: '600px',
+ },
+ name: 'projectModal',
+ requiredobj: {
+ required: true,
+ message: '请选择设备类型',
+ },
+ },
+ children: [
+ {
+ componentName: 'Select',
+ id: 'node_ockoco6icv1uj',
+ props: {
+ style: {
+ width: '600px',
+ },
+ options: {
+ type: 'JSExpression',
+ value: 'this.state.projectModalsArray',
+ },
+ disabled: {
+ type: 'JSExpression',
+ value: 'this.state.customerProjectInfo.id > 0 && ! this.state.isModifyStatus',
+ },
+ placeholder: '请选择设备类型',
+ },
+ },
+ ],
+ },
+ {
+ componentName: 'Form.Item',
+ id: 'node_ockpmbs0bv17',
+ props: {
+ label: '屏幕分辨率宽',
+ style: {
+ width: '600px',
+ },
+ name: 'displayWidth',
+ colon: false,
+ required: true,
+ requiredobj: {
+ required: true,
+ message: '请输入屏幕分辨率宽',
+ },
+ labelAlign: 'left',
+ },
+ children: [
+ {
+ componentName: 'InputNumber',
+ id: 'node_ockpmbs0bv18',
+ props: {
+ value: 3,
+ style: {
+ width: '600px',
+ },
+ placeholder: '例如1280',
+ disabled: {
+ type: 'JSExpression',
+ value: 'this.state.customerProjectInfo.id > 0 && ! this.state.isModifyStatus',
+ },
+ min: 0,
+ },
+ },
+ ],
+ },
+ {
+ componentName: 'Form.Item',
+ id: 'node_ockpmbs0bv10',
+ props: {
+ label: '屏幕分辨率高',
+ style: {
+ width: '600px',
+ },
+ labelAlign: 'left',
+ colon: false,
+ name: 'displayHeight',
+ required: true,
+ requiredobj: {
+ required: true,
+ message: '请输入屏幕分辨率高',
+ },
+ },
+ children: [
+ {
+ componentName: 'InputNumber',
+ id: 'node_ockpmbs0bv11',
+ props: {
+ value: 3,
+ style: {
+ width: '600px',
+ },
+ placeholder: '例如720',
+ disabled: {
+ type: 'JSExpression',
+ value: 'this.state.customerProjectInfo.id > 0 && ! this.state.isModifyStatus',
+ },
+ min: 0,
+ },
+ },
+ ],
+ },
+ {
+ componentName: 'Form.Item',
+ id: 'node_ockpmbs0bvt',
+ props: {
+ label: '屏幕尺寸(inch)',
+ style: {
+ width: '600px',
+ },
+ name: 'displayInch',
+ labelAlign: 'left',
+ required: true,
+ colon: false,
+ requiredobj: {
+ required: true,
+ message: '请输入屏幕尺寸',
+ },
+ },
+ children: [
+ {
+ componentName: 'InputNumber',
+ id: 'node_ockpmbs0bvu',
+ props: {
+ value: 3,
+ style: {
+ width: '600px',
+ },
+ placeholder: '请输入尺寸',
+ disabled: {
+ type: 'JSExpression',
+ value: 'this.state.customerProjectInfo.id > 0 && ! this.state.isModifyStatus',
+ },
+ min: 0,
+ },
+ },
+ ],
+ },
+ {
+ componentName: 'Form.Item',
+ id: 'node_ockpmbs0bvm',
+ props: {
+ label: '屏幕DPI',
+ style: {
+ width: '600px',
+ },
+ labelAlign: 'left',
+ colon: false,
+ required: false,
+ name: 'displayDpi',
+ },
+ children: [
+ {
+ componentName: 'InputNumber',
+ id: 'node_ockpmbs0bvn',
+ props: {
+ value: 3,
+ style: {
+ width: '600px',
+ },
+ placeholder: 'UI定制项目必填',
+ disabled: {
+ type: 'JSExpression',
+ value: 'this.state.customerProjectInfo.id > 0 && ! this.state.isModifyStatus',
+ },
+ min: 0,
+ },
+ },
+ ],
+ },
+ {
+ componentName: 'Form.Item',
+ id: 'node_ockoco6icv1v3',
+ props: {
+ label: '芯片名称',
+ colon: false,
+ required: true,
+ style: {
+ display: 'flex',
+ },
+ labelAlign: 'left',
+ extra: '',
+ name: 'mainSoc',
+ requiredobj: {
+ required: true,
+ message: '请输入芯片名称',
+ },
+ lenobj: {
+ max: 50,
+ message: '芯片名称不能超过50个字符',
+ },
+ },
+ children: [
+ {
+ componentName: 'Input',
+ id: 'node_ockoco6icv1v4',
+ props: {
+ placeholder: '请输入芯片名称',
+ style: {
+ width: '600px',
+ },
+ disabled: {
+ type: 'JSExpression',
+ value: 'this.state.customerProjectInfo.id > 0 && ! this.state.isModifyStatus',
+ },
+ },
+ },
+ ],
+ },
+ {
+ componentName: 'Form.Item',
+ id: 'node_ockpmbs0bvf',
+ props: {
+ label: '芯片核数',
+ style: {
+ width: '600px',
+ },
+ required: true,
+ requiredobj: {
+ required: true,
+ message: '请输入芯片核数',
+ },
+ name: 'cpuCoreNum',
+ labelAlign: 'left',
+ colon: false,
+ },
+ children: [
+ {
+ componentName: 'InputNumber',
+ id: 'node_ockpmbs0bvg',
+ props: {
+ value: 3,
+ style: {
+ width: '600px',
+ },
+ placeholder: '请输入芯片核数',
+ disabled: {
+ type: 'JSExpression',
+ value: 'this.state.customerProjectInfo.id > 0 && ! this.state.isModifyStatus',
+ },
+ defaultValue: '',
+ min: 0,
+ },
+ },
+ ],
+ },
+ {
+ componentName: 'Form.Item',
+ id: 'node_ockpxba11aa',
+ props: {
+ label: '指令集',
+ style: {
+ width: '600px',
+ },
+ required: true,
+ requiredobj: {
+ required: true,
+ message: '请选择指令集',
+ },
+ name: 'instructions',
+ colon: false,
+ },
+ children: [
+ {
+ componentName: 'Select',
+ id: 'node_ockpxba11ab',
+ props: {
+ style: {
+ width: '600px',
+ },
+ options: {
+ type: 'JSExpression',
+ value: 'this.state.instructionsArray',
+ },
+ disabled: {
+ type: 'JSExpression',
+ value: 'this.state.customerProjectInfo.id > 0 && ! this.state.isModifyStatus',
+ },
+ },
+ },
+ ],
+ },
+ {
+ componentName: 'Form.Item',
+ id: 'node_ockodz1kiqh',
+ props: {
+ label: '系统版本',
+ labelAlign: 'left',
+ colon: false,
+ required: true,
+ style: {
+ width: '600px',
+ },
+ name: 'osVersion',
+ requiredobj: {
+ required: true,
+ message: '请选择系统版本',
+ },
+ },
+ children: [
+ {
+ componentName: 'Select',
+ id: 'node_ockodz1kiqi',
+ props: {
+ style: {
+ width: '600px',
+ },
+ options: {
+ type: 'JSExpression',
+ value: 'this.state.osVersionsArray',
+ },
+ disabled: {
+ type: 'JSExpression',
+ value: 'this.state.customerProjectInfo.id > 0 && ! this.state.isModifyStatus',
+ },
+ placeholder: '请选择系统版本',
+ },
+ },
+ ],
+ },
+ {
+ componentName: 'Form.Item',
+ id: 'node_ockoco6icv1uq',
+ props: {
+ wrapperCol: {
+ offset: '',
+ },
+ style: {
+ flexDirection: 'row',
+ width: '600px',
+ display: 'flex',
+ },
+ },
+ children: [
+ {
+ componentName: 'Button',
+ id: 'node_ockoco6icv1ur',
+ props: {
+ style: {
+ marginLeft: '0',
+ },
+ children: '返回',
+ __events: {
+ eventDataList: [
+ {
+ type: 'componentEvent',
+ name: 'onClick',
+ relatedEventName: 'onClickSecondBack',
+ },
+ ],
+ eventList: [
+ {
+ name: 'onClick',
+ disabled: true,
+ },
+ ],
+ },
+ onClick: {
+ type: 'JSFunction',
+ value: 'function(){this.onClickSecondBack.apply(this,Array.prototype.slice.call(arguments).concat([])) }',
+ },
+ },
+ },
+ {
+ componentName: 'Button',
+ id: 'node_ockoco6icv1vb',
+ props: {
+ type: 'primary',
+ children: {
+ type: 'JSExpression',
+ value: 'this.state.secondCommitText',
+ },
+ htmlType: 'submit',
+ style: {
+ float: 'right',
+ marginLeft: '20px',
+ },
+ loading: {
+ type: 'JSExpression',
+ value: 'this.state.LOADING_ADD_OR_UPDATE_CUSTOMER_PROJECT',
+ },
+ },
+ },
+ {
+ componentName: 'Button',
+ id: 'node_ockoco6icv1us',
+ props: {
+ type: 'primary',
+ children: '上一步',
+ htmlType: 'submit',
+ style: {
+ marginLeft: '0px',
+ float: 'right',
+ },
+ __events: {
+ eventDataList: [
+ {
+ type: 'componentEvent',
+ name: 'onClick',
+ relatedEventName: 'onClickPreSecond',
+ },
+ ],
+ eventList: [
+ {
+ name: 'onClick',
+ disabled: true,
+ },
+ ],
+ },
+ onClick: {
+ type: 'JSFunction',
+ value: 'function(){this.onClickPreSecond.apply(this,Array.prototype.slice.call(arguments).concat([])) }',
+ },
+ },
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ {
+ componentName: 'NextP',
+ id: 'node_ockodngwu9m',
+ props: {
+ wrap: false,
+ type: 'body2',
+ verAlign: 'middle',
+ textSpacing: true,
+ align: 'left',
+ full: true,
+ flex: true,
+ style: {
+ display: 'flex',
+ justifyContent: 'center',
+ },
+ },
+ title: '段落',
+ condition: {
+ type: 'JSExpression',
+ value: 'this.state.currentStep === 2',
+ },
+ children: [
+ {
+ componentName: 'Form',
+ id: 'node_ockodngwu9n',
+ props: {
+ labelCol: {
+ span: 10,
+ },
+ wrapperCol: {
+ span: 10,
+ },
+ onFinishFailed: {
+ type: 'JSFunction',
+ value: 'function(){this.onFinishFailed.apply(this,Array.prototype.slice.call(arguments).concat([])) }',
+ },
+ name: 'basic',
+ style: {
+ display: 'flex',
+ flexDirection: 'column',
+ width: '600px',
+ justifyContent: 'center',
+ },
+ layout: 'vertical',
+ __events: {
+ eventDataList: [
+ {
+ type: 'componentEvent',
+ name: 'onFinishFailed',
+ relatedEventName: 'onFinishFailed',
+ },
+ ],
+ eventList: [
+ {
+ name: 'onFinish',
+ disabled: false,
+ },
+ {
+ name: 'onFinishFailed',
+ disabled: true,
+ },
+ {
+ name: 'onFieldsChange',
+ disabled: false,
+ },
+ {
+ name: 'onValuesChange',
+ disabled: false,
+ },
+ ],
+ },
+ },
+ children: [
+ {
+ componentName: 'Form.Item',
+ id: 'node_ockodngwu91m',
+ props: {
+ label: '',
+ },
+ children: [
+ {
+ componentName: 'Steps',
+ id: 'node_ockodngwu91n',
+ props: {
+ current: 1,
+ style: {
+ width: '600px',
+ display: 'flex',
+ justifyContent: 'space-around',
+ alignItems: 'center',
+ height: '300px',
+ },
+ labelPlacement: 'horizontal',
+ direction: 'vertical',
+ },
+ children: [
+ {
+ componentName: 'Steps.Step',
+ id: 'node_ockodngwu91o',
+ props: {
+ title: '提交完成',
+ description: '',
+ style: {
+ width: '200px',
+ },
+ },
+ },
+ {
+ componentName: 'Steps.Step',
+ id: 'node_ockodngwu91p',
+ props: {
+ title: {
+ type: 'JSExpression',
+ value: 'this.state.thirdAuditText',
+ },
+ subTitle: '',
+ description: '',
+ style: {
+ width: '200px',
+ },
+ },
+ },
+ ],
+ },
+ ],
+ },
+ {
+ componentName: 'Form.Item',
+ id: 'node_ockodngwu914',
+ props: {
+ wrapperCol: {
+ offset: '',
+ },
+ style: {
+ flexDirection: 'row',
+ width: '600px',
+ display: 'flex',
+ },
+ },
+ children: [
+ {
+ componentName: 'Button',
+ id: 'node_ockodngwu915',
+ props: {
+ style: {
+ marginLeft: '0',
+ },
+ children: '返回',
+ __events: {
+ eventDataList: [
+ {
+ type: 'componentEvent',
+ name: 'onClick',
+ relatedEventName: 'onClickThirdBack',
+ },
+ ],
+ eventList: [
+ {
+ name: 'onClick',
+ disabled: true,
+ },
+ ],
+ },
+ onClick: {
+ type: 'JSFunction',
+ value: 'function(){this.onClickThirdBack.apply(this,Array.prototype.slice.call(arguments).concat([])) }',
+ },
+ },
+ },
+ {
+ componentName: 'Button',
+ id: 'node_ockodngwu916',
+ props: {
+ type: 'primary',
+ children: {
+ type: 'JSExpression',
+ value: 'this.state.thirdButtonText',
+ },
+ htmlType: 'submit',
+ style: {
+ float: 'right',
+ marginLeft: '20px',
+ },
+ __events: {
+ eventDataList: [
+ {
+ type: 'componentEvent',
+ name: 'onClick',
+ relatedEventName: 'onClickModifyThird',
+ },
+ ],
+ eventList: [
+ {
+ name: 'onClick',
+ disabled: true,
+ },
+ ],
+ },
+ onClick: {
+ type: 'JSFunction',
+ value: 'function(){this.onClickModifyThird.apply(this,Array.prototype.slice.call(arguments).concat([])) }',
+ },
+ },
+ },
+ {
+ componentName: 'Button',
+ id: 'node_ockosjrkvr1d',
+ props: {
+ type: 'primary',
+ children: '上一步',
+ htmlType: 'submit',
+ style: {
+ marginLeft: '0px',
+ float: 'right',
+ },
+ __events: {
+ eventDataList: [
+ {
+ type: 'componentEvent',
+ name: 'onClick',
+ relatedEventName: 'onClickPreThird',
+ },
+ ],
+ eventList: [
+ {
+ name: 'onClick',
+ disabled: true,
+ },
+ ],
+ },
+ onClick: {
+ type: 'JSFunction',
+ value: 'function(){this.onClickPreThird.apply(this,Array.prototype.slice.call(arguments).concat([])) }',
+ },
+ },
+ condition: {
+ type: 'JSExpression',
+ value: 'this.state.customerProjectInfo.status > 2',
+ },
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ i18n: {},
+}
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo8-datasource-prop/expected/demo-project/.browserslistrc b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo8-datasource-prop/expected/demo-project/.browserslistrc
new file mode 100644
index 0000000000..55a130413d
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo8-datasource-prop/expected/demo-project/.browserslistrc
@@ -0,0 +1,3 @@
+defaults
+ios_saf 9
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo8-datasource-prop/expected/demo-project/.gitignore b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo8-datasource-prop/expected/demo-project/.gitignore
new file mode 100644
index 0000000000..4ec178818e
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo8-datasource-prop/expected/demo-project/.gitignore
@@ -0,0 +1,25 @@
+
+# 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
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo8-datasource-prop/expected/demo-project/README.md b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo8-datasource-prop/expected/demo-project/README.md
new file mode 100644
index 0000000000..6d9dd75215
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo8-datasource-prop/expected/demo-project/README.md
@@ -0,0 +1 @@
+This project is generated by lowcode-code-generator & lowcode-solution-icejs3.
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo8-datasource-prop/expected/demo-project/ice.config.mts b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo8-datasource-prop/expected/demo-project/ice.config.mts
new file mode 100644
index 0000000000..e1d8a28141
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo8-datasource-prop/expected/demo-project/ice.config.mts
@@ -0,0 +1,90 @@
+import { join } from 'path';
+import { defineConfig } from '@ice/app';
+import _ from 'lodash';
+import fusion from '@ice/plugin-fusion';
+import locales from '@ice/plugin-moment-locales';
+import type { Plugin } from '@ice/app/esm/types';
+
+interface PluginOptions {
+ id: string;
+}
+
+const plugin: Plugin = (options) => ({
+ // name 可选,插件名称
+ name: 'plugin-name',
+ // setup 必选,用于定制工程构建配置
+ setup: ({ onGetConfig, modifyUserConfig }) => {
+ modifyUserConfig('codeSplitting', 'page');
+
+ onGetConfig((config) => {
+ config.entry = {
+ web: join(process.cwd(), '.ice/entry.client.tsx'),
+ };
+
+ config.cssFilename = '[name].css';
+
+ config.configureWebpack = config.configureWebpack || [];
+ config.configureWebpack?.push((webpackConfig) => {
+ if (webpackConfig.output) {
+ webpackConfig.output.filename = '[name].js';
+ webpackConfig.output.chunkFilename = '[name].js';
+ }
+ return webpackConfig;
+ });
+
+ config.swcOptions = _.merge(config.swcOptions, {
+ compilationConfig: {
+ jsc: {
+ transform: {
+ react: {
+ runtime: 'classic',
+ },
+ },
+ },
+ },
+ });
+
+ // 解决 webpack publicPath 问题
+ config.transforms = config.transforms || [];
+ config.transforms.push((source: string, id: string) => {
+ if (id.includes('.ice/entry.client.tsx')) {
+ let code = `
+ if (!__webpack_public_path__?.startsWith('http') && document.currentScript) {
+ // @ts-ignore
+ __webpack_public_path__ = document.currentScript.src.replace(/^(.*\\/)[^/]+$/, '$1');
+ window.__ICE_ASSETS_MANIFEST__ = window.__ICE_ASSETS_MANIFEST__ || {};
+ window.__ICE_ASSETS_MANIFEST__.publicPath = __webpack_public_path__;
+ }
+ `;
+ code += source;
+ return { code };
+ }
+ });
+ });
+ },
+});
+
+// The project config, see https://v3.ice.work/docs/guide/basic/config
+const minify = process.env.NODE_ENV === 'production' ? 'swc' : false;
+export default defineConfig(() => ({
+ ssr: false,
+ ssg: false,
+ minify,
+
+ externals: {
+ react: 'React',
+ 'react-dom': 'ReactDOM',
+ 'react-dom/client': 'ReactDOM',
+ '@alifd/next': 'Next',
+ lodash: 'var window._',
+ '@alilc/lowcode-engine': 'var window.AliLowCodeEngine',
+ },
+ plugins: [
+ fusion({
+ importStyle: 'sass',
+ }),
+ locales(),
+ plugin(),
+ ],
+}));
+
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo8-datasource-prop/expected/demo-project/package.json b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo8-datasource-prop/expected/demo-project/package.json
new file mode 100644
index 0000000000..342a5a0774
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo8-datasource-prop/expected/demo-project/package.json
@@ -0,0 +1,44 @@
+{
+ "name": "icejs3-demo-app",
+ "version": "0.1.5",
+ "description": "icejs 3 轻量级模板,使用 JavaScript,仅包含基础的 Layout。",
+ "dependencies": {
+ "moment": "^2.24.0",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
+ "react-router": "^6.9.0",
+ "react-router-dom": "^6.9.0",
+ "intl-messageformat": "^9.3.6",
+ "@alifd/next": "1.26.15",
+ "@ice/runtime": "~1.1.0",
+ "@alilc/lowcode-datasource-engine": "^1.0.0",
+ "@alilc/lowcode-datasource-http-handler": "^1.0.0",
+ "@alilc/lowcode-components": "^1.0.0"
+ },
+ "devDependencies": {
+ "@ice/app": "~3.1.0",
+ "@types/react": "^18.0.0",
+ "@types/react-dom": "^18.0.0",
+ "@types/node": "^18.11.17",
+ "@ice/plugin-fusion": "^1.0.1",
+ "@ice/plugin-moment-locales": "^1.0.0",
+ "eslint": "^6.0.1",
+ "stylelint": "^13.2.0"
+ },
+ "scripts": {
+ "start": "ice start",
+ "build": "ice build",
+ "lint": "npm run eslint && npm run stylelint",
+ "eslint": "eslint --cache --ext .js,.jsx ./",
+ "stylelint": "stylelint ./**/*.scss"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "repository": {
+ "type": "git",
+ "url": "http://gitlab.xxx.com/msd/leak-scan/tree/master"
+ },
+ "private": true,
+ "originTemplate": "@alifd/scaffold-lite-js"
+}
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo8-datasource-prop/expected/demo-project/src/app.ts b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo8-datasource-prop/expected/demo-project/src/app.ts
new file mode 100644
index 0000000000..6d5856292d
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo8-datasource-prop/expected/demo-project/src/app.ts
@@ -0,0 +1,13 @@
+import { defineAppConfig } from 'ice';
+
+// App config, see https://v3.ice.work/docs/guide/basic/app
+export default defineAppConfig(() => ({
+ // Set your configs here.
+ app: {
+ rootId: 'App',
+ },
+ router: {
+ type: 'browser',
+ basename: '/',
+ },
+}));
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo8-datasource-prop/expected/demo-project/src/constants.js b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo8-datasource-prop/expected/demo-project/src/constants.js
new file mode 100644
index 0000000000..ea766c9da3
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo8-datasource-prop/expected/demo-project/src/constants.js
@@ -0,0 +1,3 @@
+const __$$constants = {};
+
+export default __$$constants;
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo8-datasource-prop/expected/demo-project/src/document.tsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo8-datasource-prop/expected/demo-project/src/document.tsx
new file mode 100644
index 0000000000..aff0231d95
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo8-datasource-prop/expected/demo-project/src/document.tsx
@@ -0,0 +1,29 @@
+import React from 'react';
+import { Meta, Title, Links, Main, Scripts } from 'ice';
+
+export default function Document() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo8-datasource-prop/expected/demo-project/src/global.scss b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo8-datasource-prop/expected/demo-project/src/global.scss
new file mode 100644
index 0000000000..82ca3eac73
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo8-datasource-prop/expected/demo-project/src/global.scss
@@ -0,0 +1,6 @@
+// 引入默认全局样式
+@import '@alifd/next/reset.scss';
+
+body {
+ -webkit-font-smoothing: antialiased;
+}
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo8-datasource-prop/expected/demo-project/src/i18n.js b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo8-datasource-prop/expected/demo-project/src/i18n.js
new file mode 100644
index 0000000000..1334d2502b
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo8-datasource-prop/expected/demo-project/src/i18n.js
@@ -0,0 +1,77 @@
+const i18nConfig = {};
+
+let locale =
+ typeof navigator === 'object' && typeof navigator.language === 'string'
+ ? navigator.language
+ : 'zh-CN';
+
+const getLocale = () => locale;
+
+const setLocale = (target) => {
+ locale = target;
+};
+
+const isEmptyVariables = (variables) =>
+ (Array.isArray(variables) && variables.length === 0) ||
+ (typeof variables === 'object' &&
+ (!variables || Object.keys(variables).length === 0));
+
+// 按低代码规范里面的要求进行变量替换
+const format = (msg, variables) =>
+ typeof msg === 'string'
+ ? msg.replace(/\$?\{(\w+)\}/g, (match, key) => variables?.[key] ?? '')
+ : msg;
+
+const i18nFormat = ({ id, defaultMessage, fallback }, variables) => {
+ const msg =
+ i18nConfig[locale]?.[id] ??
+ i18nConfig[locale.replace('-', '_')]?.[id] ??
+ defaultMessage;
+ if (msg == null) {
+ console.warn('[i18n]: unknown message id: %o (locale=%o)', id, locale);
+ return fallback === undefined ? `${id}` : fallback;
+ }
+
+ return format(msg, variables);
+};
+
+const i18n = (id, params) => {
+ return i18nFormat({ id }, params);
+};
+
+// 将国际化的一些方法注入到目标对象&上下文中
+const _inject2 = (target) => {
+ target.i18n = i18n;
+ target.getLocale = getLocale;
+ target.setLocale = (locale) => {
+ setLocale(locale);
+ target.forceUpdate();
+ };
+ target._i18nText = (t) => {
+ // 优先取直接传过来的语料
+ const localMsg = t[locale] ?? t[String(locale).replace('-', '_')];
+ if (localMsg != null) {
+ return format(localMsg, t.params);
+ }
+
+ // 其次用项目级别的
+ const projectMsg = i18nFormat({ id: t.key, fallback: null }, t.params);
+ if (projectMsg != null) {
+ return projectMsg;
+ }
+
+ // 兜底用 use 指定的或默认语言的
+ return format(t[t.use || 'zh-CN'] ?? t.en_US, t.params);
+ };
+
+ // 注入到上下文中去
+ if (target._context && target._context !== target) {
+ Object.assign(target._context, {
+ i18n,
+ getLocale,
+ setLocale: target.setLocale,
+ });
+ }
+};
+
+export { getLocale, setLocale, i18n, i18nFormat, _inject2 };
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo8-datasource-prop/expected/demo-project/src/layouts/BasicLayout/components/Footer/index.jsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo8-datasource-prop/expected/demo-project/src/layouts/BasicLayout/components/Footer/index.jsx
new file mode 100644
index 0000000000..cc70d53bea
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo8-datasource-prop/expected/demo-project/src/layouts/BasicLayout/components/Footer/index.jsx
@@ -0,0 +1,14 @@
+
+import React from 'react';
+import styles from './index.module.scss';
+
+export default function Footer() {
+ return (
+
+ Alibaba Fusion
+
+ © 2019-现在 Alibaba Fusion & ICE
+
+ );
+}
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo8-datasource-prop/expected/demo-project/src/layouts/BasicLayout/components/Footer/index.module.scss b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo8-datasource-prop/expected/demo-project/src/layouts/BasicLayout/components/Footer/index.module.scss
new file mode 100644
index 0000000000..81e77fda5f
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo8-datasource-prop/expected/demo-project/src/layouts/BasicLayout/components/Footer/index.module.scss
@@ -0,0 +1,15 @@
+
+.footer {
+ line-height: 20px;
+ text-align: center;
+}
+
+.logo {
+ font-weight: bold;
+ font-size: 16px;
+}
+
+.copyright {
+ font-size: 12px;
+}
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo8-datasource-prop/expected/demo-project/src/layouts/BasicLayout/components/Logo/index.jsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo8-datasource-prop/expected/demo-project/src/layouts/BasicLayout/components/Logo/index.jsx
new file mode 100644
index 0000000000..265bfdaa07
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo8-datasource-prop/expected/demo-project/src/layouts/BasicLayout/components/Logo/index.jsx
@@ -0,0 +1,16 @@
+
+import React from 'react';
+import { Link } from 'ice';
+import styles from './index.module.scss';
+
+export default function Logo({ image, text, url }) {
+ return (
+
+
+ {image &&

}
+
{text}
+
+
+ );
+}
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo8-datasource-prop/expected/demo-project/src/layouts/BasicLayout/components/Logo/index.module.scss b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo8-datasource-prop/expected/demo-project/src/layouts/BasicLayout/components/Logo/index.module.scss
new file mode 100644
index 0000000000..dad05a263f
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo8-datasource-prop/expected/demo-project/src/layouts/BasicLayout/components/Logo/index.module.scss
@@ -0,0 +1,20 @@
+
+.logo{
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: #FF7300;
+ font-weight: bold;
+ font-size: 14px;
+ line-height: 22px;
+
+ &:visited, &:link {
+ color: #FF7300;
+ }
+
+ img {
+ height: 24px;
+ margin-right: 10px;
+ }
+}
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo8-datasource-prop/expected/demo-project/src/layouts/BasicLayout/components/PageNav/index.jsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo8-datasource-prop/expected/demo-project/src/layouts/BasicLayout/components/PageNav/index.jsx
new file mode 100644
index 0000000000..911998b0d3
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo8-datasource-prop/expected/demo-project/src/layouts/BasicLayout/components/PageNav/index.jsx
@@ -0,0 +1,68 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { Link, useLocation } from 'ice';
+import { Nav } from '@alifd/next';
+import { asideMenuConfig } from '../../menuConfig';
+
+const { SubNav } = Nav;
+const NavItem = Nav.Item;
+
+function getNavMenuItems(menusData) {
+ if (!menusData) {
+ return [];
+ }
+
+ return menusData
+ .filter(item => item.name && !item.hideInMenu)
+ .map((item, index) => getSubMenuOrItem(item, index));
+}
+
+function getSubMenuOrItem(item, index) {
+ if (item.children && item.children.some(child => child.name)) {
+ const childrenItems = getNavMenuItems(item.children);
+
+ if (childrenItems && childrenItems.length > 0) {
+ const subNav = (
+
+ {childrenItems}
+
+ );
+ return subNav;
+ }
+
+ return null;
+ }
+
+ const navItem = (
+
+ {item.name}
+
+ );
+ return navItem;
+}
+
+const Navigation = (props, context) => {
+ const location = useLocation();
+ const { pathname } = location;
+ const { isCollapse } = context;
+ return (
+
+ );
+};
+
+Navigation.contextTypes = {
+ isCollapse: PropTypes.bool,
+};
+export default Navigation;
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo8-datasource-prop/expected/demo-project/src/layouts/BasicLayout/index.jsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo8-datasource-prop/expected/demo-project/src/layouts/BasicLayout/index.jsx
new file mode 100644
index 0000000000..18db44df5e
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo8-datasource-prop/expected/demo-project/src/layouts/BasicLayout/index.jsx
@@ -0,0 +1,81 @@
+
+import React, { useState } from 'react';
+import { Shell, ConfigProvider } from '@alifd/next';
+import PageNav from './components/PageNav';
+import Logo from './components/Logo';
+import Footer from './components/Footer';
+
+(function() {
+ const throttle = function(type, name, obj = window) {
+ let running = false;
+
+ const func = () => {
+ if (running) {
+ return;
+ }
+
+ running = true;
+ requestAnimationFrame(() => {
+ obj.dispatchEvent(new CustomEvent(name));
+ running = false;
+ });
+ };
+
+ obj.addEventListener(type, func);
+ };
+
+ throttle('resize', 'optimizedResize');
+})();
+
+export default function BasicLayout({ children }) {
+ const getDevice = width => {
+ const isPhone =
+ typeof navigator !== 'undefined' && navigator && navigator.userAgent.match(/phone/gi);
+
+ if (width < 680 || isPhone) {
+ return 'phone';
+ }
+ if (width < 1280 && width > 680) {
+ return 'tablet';
+ }
+ return 'desktop';
+ };
+
+ const [device, setDevice] = useState(getDevice(NaN));
+ window.addEventListener('optimizedResize', e => {
+ setDevice(getDevice(e && e.target && e.target.innerWidth));
+ });
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ {children}
+
+
+
+
+
+ );
+}
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo8-datasource-prop/expected/demo-project/src/layouts/BasicLayout/menuConfig.js b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo8-datasource-prop/expected/demo-project/src/layouts/BasicLayout/menuConfig.js
new file mode 100644
index 0000000000..5332202be4
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo8-datasource-prop/expected/demo-project/src/layouts/BasicLayout/menuConfig.js
@@ -0,0 +1,11 @@
+
+const headerMenuConfig = [];
+const asideMenuConfig = [
+ {
+ name: 'Dashboard',
+ path: '/',
+ icon: 'smile',
+ },
+];
+export { headerMenuConfig, asideMenuConfig };
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo8-datasource-prop/expected/demo-project/src/pages/Example/index.css b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo8-datasource-prop/expected/demo-project/src/pages/Example/index.css
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo8-datasource-prop/expected/demo-project/src/pages/Example/index.jsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo8-datasource-prop/expected/demo-project/src/pages/Example/index.jsx
new file mode 100644
index 0000000000..9a661ad753
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo8-datasource-prop/expected/demo-project/src/pages/Example/index.jsx
@@ -0,0 +1,116 @@
+// 注意: 出码引擎注入的临时变量默认都以 "__$$" 开头,禁止在搭建的代码中直接访问。
+// 例外:react 框架的导出名和各种组件名除外。
+import React from 'react';
+
+import { Page, Table } from '@alilc/lowcode-components';
+
+import { createHttpHandler as __$$createHttpRequestHandler } from '@alilc/lowcode-datasource-http-handler';
+
+import { create as __$$createDataSourceEngine } from '@alilc/lowcode-datasource-engine/runtime';
+
+import utils from '../../utils';
+
+import * as __$$i18n from '../../i18n';
+
+import __$$constants from '../../constants';
+
+import './index.css';
+
+class Example$$Page extends React.Component {
+ _context = this;
+
+ _dataSourceConfig = this._defineDataSourceConfig();
+ _dataSourceEngine = __$$createDataSourceEngine(this._dataSourceConfig, this, {
+ runtimeConfig: true,
+ requestHandlersMap: { http: __$$createHttpRequestHandler() },
+ });
+
+ get dataSourceMap() {
+ return this._dataSourceEngine.dataSourceMap || {};
+ }
+
+ reloadDataSource = async () => {
+ await this._dataSourceEngine.reloadDataSource();
+ };
+
+ get constants() {
+ return __$$constants || {};
+ }
+
+ constructor(props, context) {
+ super(props);
+
+ this.utils = utils;
+
+ __$$i18n._inject2(this);
+
+ this.state = {};
+ }
+
+ $ = () => null;
+
+ $$ = () => [];
+
+ _defineDataSourceConfig() {
+ const _this = this;
+ return {
+ list: [
+ {
+ id: 'userList',
+ type: 'http',
+ description: '用户列表',
+ options: function () {
+ return {
+ uri: 'https://api.example.com/user/list',
+ };
+ }.bind(_this),
+ isInit: function () {
+ return undefined;
+ }.bind(_this),
+ },
+ ],
+ };
+ }
+
+ componentDidMount() {
+ this._dataSourceEngine.reloadDataSource();
+ }
+
+ render() {
+ const __$$context = this._context || this;
+ const { state } = __$$context;
+ return (
+
+
this.dataSourceMap['userList'])}
+ columns={[
+ { dataIndex: 'name', title: '姓名' },
+ { dataIndex: 'age', title: '年龄' },
+ ]}
+ />
+
+ );
+ }
+}
+
+export default Example$$Page;
+
+function __$$eval(expr) {
+ try {
+ return expr();
+ } catch (error) {}
+}
+
+function __$$evalArray(expr) {
+ const res = __$$eval(expr);
+ return Array.isArray(res) ? res : [];
+}
+
+function __$$createChildContext(oldContext, ext) {
+ const childContext = {
+ ...oldContext,
+ ...ext,
+ };
+ childContext.__proto__ = oldContext;
+ return childContext;
+}
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo8-datasource-prop/expected/demo-project/src/pages/layout.jsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo8-datasource-prop/expected/demo-project/src/pages/layout.jsx
new file mode 100644
index 0000000000..50fbb2d1f1
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo8-datasource-prop/expected/demo-project/src/pages/layout.jsx
@@ -0,0 +1,10 @@
+import { Outlet } from 'ice';
+import BasicLayout from '@/layouts/BasicLayout';
+
+export default function Layout() {
+ return (
+
+
+
+ );
+}
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo8-datasource-prop/expected/demo-project/src/typings.d.ts b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo8-datasource-prop/expected/demo-project/src/typings.d.ts
new file mode 100644
index 0000000000..a9f8de7ceb
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo8-datasource-prop/expected/demo-project/src/typings.d.ts
@@ -0,0 +1,9 @@
+///
+
+export {};
+declare global {
+ interface Window {
+ g_config: Record;
+ }
+}
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo8-datasource-prop/expected/demo-project/src/utils.js b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo8-datasource-prop/expected/demo-project/src/utils.js
new file mode 100644
index 0000000000..1190717924
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo8-datasource-prop/expected/demo-project/src/utils.js
@@ -0,0 +1,47 @@
+import { createRef } from 'react';
+
+export class RefsManager {
+ constructor() {
+ this.refInsStore = {};
+ }
+
+ clearNullRefs() {
+ Object.keys(this.refInsStore).forEach((refName) => {
+ const filteredInsList = this.refInsStore[refName].filter(
+ (insRef) => !!insRef.current
+ );
+ if (filteredInsList.length > 0) {
+ this.refInsStore[refName] = filteredInsList;
+ } else {
+ delete this.refInsStore[refName];
+ }
+ });
+ }
+
+ get(refName) {
+ this.clearNullRefs();
+ if (this.refInsStore[refName] && this.refInsStore[refName].length > 0) {
+ return this.refInsStore[refName][0].current;
+ }
+
+ return null;
+ }
+
+ getAll(refName) {
+ this.clearNullRefs();
+ if (this.refInsStore[refName] && this.refInsStore[refName].length > 0) {
+ return this.refInsStore[refName].map((i) => i.current);
+ }
+
+ return [];
+ }
+
+ linkRef(refName) {
+ const refIns = createRef();
+ this.refInsStore[refName] = this.refInsStore[refName] || [];
+ this.refInsStore[refName].push(refIns);
+ return refIns;
+ }
+}
+
+export default {};
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo8-datasource-prop/schema.json5 b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo8-datasource-prop/schema.json5
new file mode 100644
index 0000000000..1e61996cf5
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo8-datasource-prop/schema.json5
@@ -0,0 +1,65 @@
+{
+ version: '1.0.0',
+ componentsMap: [
+ {
+ package: '@alilc/lowcode-components',
+ version: '^1.0.0',
+ componentName: 'Page',
+ destructuring: true,
+ exportName: 'Page',
+ },
+ {
+ package: '@alilc/lowcode-components',
+ version: '^1.0.0',
+ componentName: 'Table',
+ destructuring: true,
+ exportName: 'Table',
+ },
+ ],
+ componentsTree: [
+ {
+ componentName: 'Page',
+ id: 'node_ockp6ci0hm1',
+ props: {},
+ fileName: 'example',
+ dataSource: {
+ list: [
+ {
+ id: 'userList',
+ type: 'http',
+ description: '用户列表',
+ options: {
+ uri: 'https://api.example.com/user/list',
+ },
+ },
+ ],
+ },
+ children: [
+ {
+ componentName: 'Table',
+ id: 'node_ockp6ci0hm22',
+ props: {
+ dataSource: {
+ type: 'DataSource',
+ id: 'userList',
+ },
+ columns: [
+ {
+ dataIndex: 'name',
+ title: '姓名',
+ },
+ {
+ dataIndex: 'age',
+ title: '年龄',
+ },
+ ],
+ },
+ },
+ ],
+ },
+ ],
+ meta: {
+ name: 'example',
+ description: 'Example',
+ },
+}
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo9-datasource-engine/expected/demo-project/.browserslistrc b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo9-datasource-engine/expected/demo-project/.browserslistrc
new file mode 100644
index 0000000000..55a130413d
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo9-datasource-engine/expected/demo-project/.browserslistrc
@@ -0,0 +1,3 @@
+defaults
+ios_saf 9
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo9-datasource-engine/expected/demo-project/.gitignore b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo9-datasource-engine/expected/demo-project/.gitignore
new file mode 100644
index 0000000000..4ec178818e
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo9-datasource-engine/expected/demo-project/.gitignore
@@ -0,0 +1,25 @@
+
+# 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
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo9-datasource-engine/expected/demo-project/README.md b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo9-datasource-engine/expected/demo-project/README.md
new file mode 100644
index 0000000000..6d9dd75215
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo9-datasource-engine/expected/demo-project/README.md
@@ -0,0 +1 @@
+This project is generated by lowcode-code-generator & lowcode-solution-icejs3.
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo9-datasource-engine/expected/demo-project/ice.config.mts b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo9-datasource-engine/expected/demo-project/ice.config.mts
new file mode 100644
index 0000000000..e1d8a28141
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo9-datasource-engine/expected/demo-project/ice.config.mts
@@ -0,0 +1,90 @@
+import { join } from 'path';
+import { defineConfig } from '@ice/app';
+import _ from 'lodash';
+import fusion from '@ice/plugin-fusion';
+import locales from '@ice/plugin-moment-locales';
+import type { Plugin } from '@ice/app/esm/types';
+
+interface PluginOptions {
+ id: string;
+}
+
+const plugin: Plugin = (options) => ({
+ // name 可选,插件名称
+ name: 'plugin-name',
+ // setup 必选,用于定制工程构建配置
+ setup: ({ onGetConfig, modifyUserConfig }) => {
+ modifyUserConfig('codeSplitting', 'page');
+
+ onGetConfig((config) => {
+ config.entry = {
+ web: join(process.cwd(), '.ice/entry.client.tsx'),
+ };
+
+ config.cssFilename = '[name].css';
+
+ config.configureWebpack = config.configureWebpack || [];
+ config.configureWebpack?.push((webpackConfig) => {
+ if (webpackConfig.output) {
+ webpackConfig.output.filename = '[name].js';
+ webpackConfig.output.chunkFilename = '[name].js';
+ }
+ return webpackConfig;
+ });
+
+ config.swcOptions = _.merge(config.swcOptions, {
+ compilationConfig: {
+ jsc: {
+ transform: {
+ react: {
+ runtime: 'classic',
+ },
+ },
+ },
+ },
+ });
+
+ // 解决 webpack publicPath 问题
+ config.transforms = config.transforms || [];
+ config.transforms.push((source: string, id: string) => {
+ if (id.includes('.ice/entry.client.tsx')) {
+ let code = `
+ if (!__webpack_public_path__?.startsWith('http') && document.currentScript) {
+ // @ts-ignore
+ __webpack_public_path__ = document.currentScript.src.replace(/^(.*\\/)[^/]+$/, '$1');
+ window.__ICE_ASSETS_MANIFEST__ = window.__ICE_ASSETS_MANIFEST__ || {};
+ window.__ICE_ASSETS_MANIFEST__.publicPath = __webpack_public_path__;
+ }
+ `;
+ code += source;
+ return { code };
+ }
+ });
+ });
+ },
+});
+
+// The project config, see https://v3.ice.work/docs/guide/basic/config
+const minify = process.env.NODE_ENV === 'production' ? 'swc' : false;
+export default defineConfig(() => ({
+ ssr: false,
+ ssg: false,
+ minify,
+
+ externals: {
+ react: 'React',
+ 'react-dom': 'ReactDOM',
+ 'react-dom/client': 'ReactDOM',
+ '@alifd/next': 'Next',
+ lodash: 'var window._',
+ '@alilc/lowcode-engine': 'var window.AliLowCodeEngine',
+ },
+ plugins: [
+ fusion({
+ importStyle: 'sass',
+ }),
+ locales(),
+ plugin(),
+ ],
+}));
+
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo9-datasource-engine/expected/demo-project/package.json b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo9-datasource-engine/expected/demo-project/package.json
new file mode 100644
index 0000000000..d7d07dc6ed
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo9-datasource-engine/expected/demo-project/package.json
@@ -0,0 +1,43 @@
+{
+ "name": "icejs3-demo-app",
+ "version": "0.1.5",
+ "description": "icejs 3 轻量级模板,使用 JavaScript,仅包含基础的 Layout。",
+ "dependencies": {
+ "moment": "^2.24.0",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
+ "react-router": "^6.9.0",
+ "react-router-dom": "^6.9.0",
+ "intl-messageformat": "^9.3.6",
+ "@alifd/next": "1.19.18",
+ "@ice/runtime": "~1.1.0",
+ "@alilc/lowcode-datasource-engine": "^1.0.0",
+ "@alilc/lowcode-datasource-jsonp-handler": "^1.0.0"
+ },
+ "devDependencies": {
+ "@ice/app": "~3.1.0",
+ "@types/react": "^18.0.0",
+ "@types/react-dom": "^18.0.0",
+ "@types/node": "^18.11.17",
+ "@ice/plugin-fusion": "^1.0.1",
+ "@ice/plugin-moment-locales": "^1.0.0",
+ "eslint": "^6.0.1",
+ "stylelint": "^13.2.0"
+ },
+ "scripts": {
+ "start": "ice start",
+ "build": "ice build",
+ "lint": "npm run eslint && npm run stylelint",
+ "eslint": "eslint --cache --ext .js,.jsx ./",
+ "stylelint": "stylelint ./**/*.scss"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "repository": {
+ "type": "git",
+ "url": "http://gitlab.xxx.com/msd/leak-scan/tree/master"
+ },
+ "private": true,
+ "originTemplate": "@alifd/scaffold-lite-js"
+}
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo9-datasource-engine/expected/demo-project/src/app.ts b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo9-datasource-engine/expected/demo-project/src/app.ts
new file mode 100644
index 0000000000..6d5856292d
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo9-datasource-engine/expected/demo-project/src/app.ts
@@ -0,0 +1,13 @@
+import { defineAppConfig } from 'ice';
+
+// App config, see https://v3.ice.work/docs/guide/basic/app
+export default defineAppConfig(() => ({
+ // Set your configs here.
+ app: {
+ rootId: 'App',
+ },
+ router: {
+ type: 'browser',
+ basename: '/',
+ },
+}));
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo9-datasource-engine/expected/demo-project/src/constants.js b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo9-datasource-engine/expected/demo-project/src/constants.js
new file mode 100644
index 0000000000..ea766c9da3
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo9-datasource-engine/expected/demo-project/src/constants.js
@@ -0,0 +1,3 @@
+const __$$constants = {};
+
+export default __$$constants;
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo9-datasource-engine/expected/demo-project/src/document.tsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo9-datasource-engine/expected/demo-project/src/document.tsx
new file mode 100644
index 0000000000..aff0231d95
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo9-datasource-engine/expected/demo-project/src/document.tsx
@@ -0,0 +1,29 @@
+import React from 'react';
+import { Meta, Title, Links, Main, Scripts } from 'ice';
+
+export default function Document() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo9-datasource-engine/expected/demo-project/src/global.scss b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo9-datasource-engine/expected/demo-project/src/global.scss
new file mode 100644
index 0000000000..82ca3eac73
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo9-datasource-engine/expected/demo-project/src/global.scss
@@ -0,0 +1,6 @@
+// 引入默认全局样式
+@import '@alifd/next/reset.scss';
+
+body {
+ -webkit-font-smoothing: antialiased;
+}
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo9-datasource-engine/expected/demo-project/src/i18n.js b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo9-datasource-engine/expected/demo-project/src/i18n.js
new file mode 100644
index 0000000000..1334d2502b
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo9-datasource-engine/expected/demo-project/src/i18n.js
@@ -0,0 +1,77 @@
+const i18nConfig = {};
+
+let locale =
+ typeof navigator === 'object' && typeof navigator.language === 'string'
+ ? navigator.language
+ : 'zh-CN';
+
+const getLocale = () => locale;
+
+const setLocale = (target) => {
+ locale = target;
+};
+
+const isEmptyVariables = (variables) =>
+ (Array.isArray(variables) && variables.length === 0) ||
+ (typeof variables === 'object' &&
+ (!variables || Object.keys(variables).length === 0));
+
+// 按低代码规范里面的要求进行变量替换
+const format = (msg, variables) =>
+ typeof msg === 'string'
+ ? msg.replace(/\$?\{(\w+)\}/g, (match, key) => variables?.[key] ?? '')
+ : msg;
+
+const i18nFormat = ({ id, defaultMessage, fallback }, variables) => {
+ const msg =
+ i18nConfig[locale]?.[id] ??
+ i18nConfig[locale.replace('-', '_')]?.[id] ??
+ defaultMessage;
+ if (msg == null) {
+ console.warn('[i18n]: unknown message id: %o (locale=%o)', id, locale);
+ return fallback === undefined ? `${id}` : fallback;
+ }
+
+ return format(msg, variables);
+};
+
+const i18n = (id, params) => {
+ return i18nFormat({ id }, params);
+};
+
+// 将国际化的一些方法注入到目标对象&上下文中
+const _inject2 = (target) => {
+ target.i18n = i18n;
+ target.getLocale = getLocale;
+ target.setLocale = (locale) => {
+ setLocale(locale);
+ target.forceUpdate();
+ };
+ target._i18nText = (t) => {
+ // 优先取直接传过来的语料
+ const localMsg = t[locale] ?? t[String(locale).replace('-', '_')];
+ if (localMsg != null) {
+ return format(localMsg, t.params);
+ }
+
+ // 其次用项目级别的
+ const projectMsg = i18nFormat({ id: t.key, fallback: null }, t.params);
+ if (projectMsg != null) {
+ return projectMsg;
+ }
+
+ // 兜底用 use 指定的或默认语言的
+ return format(t[t.use || 'zh-CN'] ?? t.en_US, t.params);
+ };
+
+ // 注入到上下文中去
+ if (target._context && target._context !== target) {
+ Object.assign(target._context, {
+ i18n,
+ getLocale,
+ setLocale: target.setLocale,
+ });
+ }
+};
+
+export { getLocale, setLocale, i18n, i18nFormat, _inject2 };
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo9-datasource-engine/expected/demo-project/src/layouts/BasicLayout/components/Footer/index.jsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo9-datasource-engine/expected/demo-project/src/layouts/BasicLayout/components/Footer/index.jsx
new file mode 100644
index 0000000000..cc70d53bea
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo9-datasource-engine/expected/demo-project/src/layouts/BasicLayout/components/Footer/index.jsx
@@ -0,0 +1,14 @@
+
+import React from 'react';
+import styles from './index.module.scss';
+
+export default function Footer() {
+ return (
+
+ Alibaba Fusion
+
+ © 2019-现在 Alibaba Fusion & ICE
+
+ );
+}
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo9-datasource-engine/expected/demo-project/src/layouts/BasicLayout/components/Footer/index.module.scss b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo9-datasource-engine/expected/demo-project/src/layouts/BasicLayout/components/Footer/index.module.scss
new file mode 100644
index 0000000000..81e77fda5f
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo9-datasource-engine/expected/demo-project/src/layouts/BasicLayout/components/Footer/index.module.scss
@@ -0,0 +1,15 @@
+
+.footer {
+ line-height: 20px;
+ text-align: center;
+}
+
+.logo {
+ font-weight: bold;
+ font-size: 16px;
+}
+
+.copyright {
+ font-size: 12px;
+}
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo9-datasource-engine/expected/demo-project/src/layouts/BasicLayout/components/Logo/index.jsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo9-datasource-engine/expected/demo-project/src/layouts/BasicLayout/components/Logo/index.jsx
new file mode 100644
index 0000000000..265bfdaa07
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo9-datasource-engine/expected/demo-project/src/layouts/BasicLayout/components/Logo/index.jsx
@@ -0,0 +1,16 @@
+
+import React from 'react';
+import { Link } from 'ice';
+import styles from './index.module.scss';
+
+export default function Logo({ image, text, url }) {
+ return (
+
+
+ {image &&

}
+
{text}
+
+
+ );
+}
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo9-datasource-engine/expected/demo-project/src/layouts/BasicLayout/components/Logo/index.module.scss b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo9-datasource-engine/expected/demo-project/src/layouts/BasicLayout/components/Logo/index.module.scss
new file mode 100644
index 0000000000..dad05a263f
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo9-datasource-engine/expected/demo-project/src/layouts/BasicLayout/components/Logo/index.module.scss
@@ -0,0 +1,20 @@
+
+.logo{
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: #FF7300;
+ font-weight: bold;
+ font-size: 14px;
+ line-height: 22px;
+
+ &:visited, &:link {
+ color: #FF7300;
+ }
+
+ img {
+ height: 24px;
+ margin-right: 10px;
+ }
+}
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo9-datasource-engine/expected/demo-project/src/layouts/BasicLayout/components/PageNav/index.jsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo9-datasource-engine/expected/demo-project/src/layouts/BasicLayout/components/PageNav/index.jsx
new file mode 100644
index 0000000000..911998b0d3
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo9-datasource-engine/expected/demo-project/src/layouts/BasicLayout/components/PageNav/index.jsx
@@ -0,0 +1,68 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { Link, useLocation } from 'ice';
+import { Nav } from '@alifd/next';
+import { asideMenuConfig } from '../../menuConfig';
+
+const { SubNav } = Nav;
+const NavItem = Nav.Item;
+
+function getNavMenuItems(menusData) {
+ if (!menusData) {
+ return [];
+ }
+
+ return menusData
+ .filter(item => item.name && !item.hideInMenu)
+ .map((item, index) => getSubMenuOrItem(item, index));
+}
+
+function getSubMenuOrItem(item, index) {
+ if (item.children && item.children.some(child => child.name)) {
+ const childrenItems = getNavMenuItems(item.children);
+
+ if (childrenItems && childrenItems.length > 0) {
+ const subNav = (
+
+ {childrenItems}
+
+ );
+ return subNav;
+ }
+
+ return null;
+ }
+
+ const navItem = (
+
+ {item.name}
+
+ );
+ return navItem;
+}
+
+const Navigation = (props, context) => {
+ const location = useLocation();
+ const { pathname } = location;
+ const { isCollapse } = context;
+ return (
+
+ );
+};
+
+Navigation.contextTypes = {
+ isCollapse: PropTypes.bool,
+};
+export default Navigation;
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo9-datasource-engine/expected/demo-project/src/layouts/BasicLayout/index.jsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo9-datasource-engine/expected/demo-project/src/layouts/BasicLayout/index.jsx
new file mode 100644
index 0000000000..18db44df5e
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo9-datasource-engine/expected/demo-project/src/layouts/BasicLayout/index.jsx
@@ -0,0 +1,81 @@
+
+import React, { useState } from 'react';
+import { Shell, ConfigProvider } from '@alifd/next';
+import PageNav from './components/PageNav';
+import Logo from './components/Logo';
+import Footer from './components/Footer';
+
+(function() {
+ const throttle = function(type, name, obj = window) {
+ let running = false;
+
+ const func = () => {
+ if (running) {
+ return;
+ }
+
+ running = true;
+ requestAnimationFrame(() => {
+ obj.dispatchEvent(new CustomEvent(name));
+ running = false;
+ });
+ };
+
+ obj.addEventListener(type, func);
+ };
+
+ throttle('resize', 'optimizedResize');
+})();
+
+export default function BasicLayout({ children }) {
+ const getDevice = width => {
+ const isPhone =
+ typeof navigator !== 'undefined' && navigator && navigator.userAgent.match(/phone/gi);
+
+ if (width < 680 || isPhone) {
+ return 'phone';
+ }
+ if (width < 1280 && width > 680) {
+ return 'tablet';
+ }
+ return 'desktop';
+ };
+
+ const [device, setDevice] = useState(getDevice(NaN));
+ window.addEventListener('optimizedResize', e => {
+ setDevice(getDevice(e && e.target && e.target.innerWidth));
+ });
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ {children}
+
+
+
+
+
+ );
+}
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo9-datasource-engine/expected/demo-project/src/layouts/BasicLayout/menuConfig.js b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo9-datasource-engine/expected/demo-project/src/layouts/BasicLayout/menuConfig.js
new file mode 100644
index 0000000000..5332202be4
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo9-datasource-engine/expected/demo-project/src/layouts/BasicLayout/menuConfig.js
@@ -0,0 +1,11 @@
+
+const headerMenuConfig = [];
+const asideMenuConfig = [
+ {
+ name: 'Dashboard',
+ path: '/',
+ icon: 'smile',
+ },
+];
+export { headerMenuConfig, asideMenuConfig };
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo9-datasource-engine/expected/demo-project/src/pages/$/index.css b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo9-datasource-engine/expected/demo-project/src/pages/$/index.css
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/modules/code-generator/tests/fixtures/test-cases/react-app/demo9-datasource-engine/expected/demo-project/src/components/Index/index.jsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo9-datasource-engine/expected/demo-project/src/pages/$/index.jsx
similarity index 61%
rename from modules/code-generator/tests/fixtures/test-cases/react-app/demo9-datasource-engine/expected/demo-project/src/components/Index/index.jsx
rename to modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo9-datasource-engine/expected/demo-project/src/pages/$/index.jsx
index 47bca5a63e..799ca0d28f 100644
--- a/modules/code-generator/tests/fixtures/test-cases/react-app/demo9-datasource-engine/expected/demo-project/src/components/Index/index.jsx
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo9-datasource-engine/expected/demo-project/src/pages/$/index.jsx
@@ -1,20 +1,24 @@
// 注意: 出码引擎注入的临时变量默认都以 "__$$" 开头,禁止在搭建的代码中直接访问。
// 例外:react 框架的导出名和各种组件名除外。
-import React from "react";
+import React from 'react';
-import { Switch } from "@alifd/next";
+import { Switch } from '@alifd/next';
-import { createJsonpHandler as __$$createJsonpRequestHandler } from "@alilc/lowcode-datasource-jsonp-handler";
+import { createJsonpHandler as __$$createJsonpRequestHandler } from '@alilc/lowcode-datasource-jsonp-handler';
-import { create as __$$createDataSourceEngine } from "@alilc/lowcode-datasource-engine/runtime";
+import { create as __$$createDataSourceEngine } from '@alilc/lowcode-datasource-engine/runtime';
-import utils from "../../utils";
+import '@alifd/next/lib/switch/style';
-import * as __$$i18n from "../../i18n";
+import utils from '../../utils';
-import "./index.css";
+import * as __$$i18n from '../../i18n';
-class Index$$Page extends React.Component {
+import __$$constants from '../../constants';
+
+import './index.css';
+
+class $$Page extends React.Component {
_context = this;
_dataSourceConfig = this._defineDataSourceConfig();
@@ -31,6 +35,10 @@ class Index$$Page extends React.Component {
await this._dataSourceEngine.reloadDataSource();
};
+ get constants() {
+ return __$$constants || {};
+ }
+
constructor(props, context) {
super(props);
@@ -50,17 +58,17 @@ class Index$$Page extends React.Component {
return {
list: [
{
- id: "todos",
+ id: 'todos',
isInit: function () {
return true;
- },
- type: "jsonp",
+ }.bind(_this),
+ type: 'jsonp',
options: function () {
return {
- method: "GET",
- uri: "https://a0ee9135-6a7f-4c0f-a215-f0f247ad907d.mock.pstmn.io",
+ method: 'GET',
+ uri: 'https://a0ee9135-6a7f-4c0f-a215-f0f247ad907d.mock.pstmn.io',
};
- },
+ }.bind(_this),
dataHandler: function dataHandler(data) {
return data.data;
},
@@ -78,26 +86,23 @@ class Index$$Page extends React.Component {
const { state } = __$$context;
return (
-
- {__$$evalArray(() => this.dataSourceMap.todos.data).map(
- (item, index) =>
- ((__$$context) => (
-
- item.done)}
- />
-
- ))(__$$createChildContext(__$$context, { item, index }))
- )}
-
+ {__$$evalArray(() => this.dataSourceMap.todos.data).map((item, index) =>
+ ((__$$context) => (
+
+ item.done)}
+ />
+
+ ))(__$$createChildContext(__$$context, { item, index }))
+ )}
);
}
}
-export default Index$$Page;
+export default $$Page;
function __$$eval(expr) {
try {
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo9-datasource-engine/expected/demo-project/src/pages/layout.jsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo9-datasource-engine/expected/demo-project/src/pages/layout.jsx
new file mode 100644
index 0000000000..50fbb2d1f1
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo9-datasource-engine/expected/demo-project/src/pages/layout.jsx
@@ -0,0 +1,10 @@
+import { Outlet } from 'ice';
+import BasicLayout from '@/layouts/BasicLayout';
+
+export default function Layout() {
+ return (
+
+
+
+ );
+}
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo9-datasource-engine/expected/demo-project/src/typings.d.ts b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo9-datasource-engine/expected/demo-project/src/typings.d.ts
new file mode 100644
index 0000000000..a9f8de7ceb
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo9-datasource-engine/expected/demo-project/src/typings.d.ts
@@ -0,0 +1,9 @@
+///
+
+export {};
+declare global {
+ interface Window {
+ g_config: Record;
+ }
+}
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo9-datasource-engine/expected/demo-project/src/utils.js b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo9-datasource-engine/expected/demo-project/src/utils.js
new file mode 100644
index 0000000000..1190717924
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo9-datasource-engine/expected/demo-project/src/utils.js
@@ -0,0 +1,47 @@
+import { createRef } from 'react';
+
+export class RefsManager {
+ constructor() {
+ this.refInsStore = {};
+ }
+
+ clearNullRefs() {
+ Object.keys(this.refInsStore).forEach((refName) => {
+ const filteredInsList = this.refInsStore[refName].filter(
+ (insRef) => !!insRef.current
+ );
+ if (filteredInsList.length > 0) {
+ this.refInsStore[refName] = filteredInsList;
+ } else {
+ delete this.refInsStore[refName];
+ }
+ });
+ }
+
+ get(refName) {
+ this.clearNullRefs();
+ if (this.refInsStore[refName] && this.refInsStore[refName].length > 0) {
+ return this.refInsStore[refName][0].current;
+ }
+
+ return null;
+ }
+
+ getAll(refName) {
+ this.clearNullRefs();
+ if (this.refInsStore[refName] && this.refInsStore[refName].length > 0) {
+ return this.refInsStore[refName].map((i) => i.current);
+ }
+
+ return [];
+ }
+
+ linkRef(refName) {
+ const refIns = createRef();
+ this.refInsStore[refName] = this.refInsStore[refName] || [];
+ this.refInsStore[refName].push(refIns);
+ return refIns;
+ }
+}
+
+export default {};
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo9-datasource-engine/schema.json5 b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo9-datasource-engine/schema.json5
new file mode 100644
index 0000000000..f91f132ad2
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo9-datasource-engine/schema.json5
@@ -0,0 +1,59 @@
+{
+ version: '1.0.0',
+ componentsMap: [
+ {
+ componentName: 'Switch',
+ package: '@alifd/next',
+ version: '1.19.18',
+ exportName: 'Switch',
+ destructuring: true,
+ subName: '',
+ },
+ ],
+ componentsTree: [
+ {
+ componentName: 'Page',
+ props: {},
+ children: [
+ {
+ componentName: 'Div',
+ props: {},
+ children: [
+ {
+ componentName: 'Switch',
+ props: {
+ checkedChildren: '开',
+ unCheckedChildren: '关',
+ checked: {
+ type: 'JSExpression',
+ value: 'this.item.done',
+ },
+ },
+ },
+ ],
+ loop: {
+ type: 'JSExpression',
+ value: 'this.dataSourceMap.todos.data',
+ },
+ },
+ ],
+ dataSource: {
+ list: [
+ {
+ id: 'todos',
+ isInit: true,
+ type: 'jsonp',
+ options: {
+ method: 'GET',
+ uri: 'https://a0ee9135-6a7f-4c0f-a215-f0f247ad907d.mock.pstmn.io',
+ },
+ dataHandler: {
+ type: 'JSFunction',
+ value: 'function dataHandler(data) {return data.data;}',
+ },
+ },
+ ],
+ },
+ },
+ ],
+}
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_10-jsslot/expected/demo-project/.browserslistrc b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_10-jsslot/expected/demo-project/.browserslistrc
new file mode 100644
index 0000000000..55a130413d
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_10-jsslot/expected/demo-project/.browserslistrc
@@ -0,0 +1,3 @@
+defaults
+ios_saf 9
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_10-jsslot/expected/demo-project/.gitignore b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_10-jsslot/expected/demo-project/.gitignore
new file mode 100644
index 0000000000..4ec178818e
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_10-jsslot/expected/demo-project/.gitignore
@@ -0,0 +1,25 @@
+
+# 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
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_10-jsslot/expected/demo-project/README.md b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_10-jsslot/expected/demo-project/README.md
new file mode 100644
index 0000000000..6d9dd75215
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_10-jsslot/expected/demo-project/README.md
@@ -0,0 +1 @@
+This project is generated by lowcode-code-generator & lowcode-solution-icejs3.
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_10-jsslot/expected/demo-project/ice.config.mts b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_10-jsslot/expected/demo-project/ice.config.mts
new file mode 100644
index 0000000000..e1d8a28141
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_10-jsslot/expected/demo-project/ice.config.mts
@@ -0,0 +1,90 @@
+import { join } from 'path';
+import { defineConfig } from '@ice/app';
+import _ from 'lodash';
+import fusion from '@ice/plugin-fusion';
+import locales from '@ice/plugin-moment-locales';
+import type { Plugin } from '@ice/app/esm/types';
+
+interface PluginOptions {
+ id: string;
+}
+
+const plugin: Plugin = (options) => ({
+ // name 可选,插件名称
+ name: 'plugin-name',
+ // setup 必选,用于定制工程构建配置
+ setup: ({ onGetConfig, modifyUserConfig }) => {
+ modifyUserConfig('codeSplitting', 'page');
+
+ onGetConfig((config) => {
+ config.entry = {
+ web: join(process.cwd(), '.ice/entry.client.tsx'),
+ };
+
+ config.cssFilename = '[name].css';
+
+ config.configureWebpack = config.configureWebpack || [];
+ config.configureWebpack?.push((webpackConfig) => {
+ if (webpackConfig.output) {
+ webpackConfig.output.filename = '[name].js';
+ webpackConfig.output.chunkFilename = '[name].js';
+ }
+ return webpackConfig;
+ });
+
+ config.swcOptions = _.merge(config.swcOptions, {
+ compilationConfig: {
+ jsc: {
+ transform: {
+ react: {
+ runtime: 'classic',
+ },
+ },
+ },
+ },
+ });
+
+ // 解决 webpack publicPath 问题
+ config.transforms = config.transforms || [];
+ config.transforms.push((source: string, id: string) => {
+ if (id.includes('.ice/entry.client.tsx')) {
+ let code = `
+ if (!__webpack_public_path__?.startsWith('http') && document.currentScript) {
+ // @ts-ignore
+ __webpack_public_path__ = document.currentScript.src.replace(/^(.*\\/)[^/]+$/, '$1');
+ window.__ICE_ASSETS_MANIFEST__ = window.__ICE_ASSETS_MANIFEST__ || {};
+ window.__ICE_ASSETS_MANIFEST__.publicPath = __webpack_public_path__;
+ }
+ `;
+ code += source;
+ return { code };
+ }
+ });
+ });
+ },
+});
+
+// The project config, see https://v3.ice.work/docs/guide/basic/config
+const minify = process.env.NODE_ENV === 'production' ? 'swc' : false;
+export default defineConfig(() => ({
+ ssr: false,
+ ssg: false,
+ minify,
+
+ externals: {
+ react: 'React',
+ 'react-dom': 'ReactDOM',
+ 'react-dom/client': 'ReactDOM',
+ '@alifd/next': 'Next',
+ lodash: 'var window._',
+ '@alilc/lowcode-engine': 'var window.AliLowCodeEngine',
+ },
+ plugins: [
+ fusion({
+ importStyle: 'sass',
+ }),
+ locales(),
+ plugin(),
+ ],
+}));
+
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_10-jsslot/expected/demo-project/package.json b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_10-jsslot/expected/demo-project/package.json
new file mode 100644
index 0000000000..fb35f3cf46
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_10-jsslot/expected/demo-project/package.json
@@ -0,0 +1,46 @@
+{
+ "name": "icejs3-demo-app",
+ "version": "0.1.5",
+ "description": "icejs 3 轻量级模板,使用 JavaScript,仅包含基础的 Layout。",
+ "dependencies": {
+ "moment": "^2.24.0",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
+ "react-router": "^6.9.0",
+ "react-router-dom": "^6.9.0",
+ "intl-messageformat": "^9.3.6",
+ "@alifd/next": "1.26.15",
+ "@ice/runtime": "~1.1.0",
+ "@alilc/lowcode-datasource-engine": "^1.0.0",
+ "undefined": "*",
+ "@alilc/antd-lowcode-materials": "0.9.4",
+ "@alife/mc-assets-1935": "0.1.42",
+ "@alife/container": "0.3.7"
+ },
+ "devDependencies": {
+ "@ice/app": "~3.1.0",
+ "@types/react": "^18.0.0",
+ "@types/react-dom": "^18.0.0",
+ "@types/node": "^18.11.17",
+ "@ice/plugin-fusion": "^1.0.1",
+ "@ice/plugin-moment-locales": "^1.0.0",
+ "eslint": "^6.0.1",
+ "stylelint": "^13.2.0"
+ },
+ "scripts": {
+ "start": "ice start",
+ "build": "ice build",
+ "lint": "npm run eslint && npm run stylelint",
+ "eslint": "eslint --cache --ext .js,.jsx ./",
+ "stylelint": "stylelint ./**/*.scss"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "repository": {
+ "type": "git",
+ "url": "http://gitlab.xxx.com/msd/leak-scan/tree/master"
+ },
+ "private": true,
+ "originTemplate": "@alifd/scaffold-lite-js"
+}
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_10-jsslot/expected/demo-project/src/app.ts b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_10-jsslot/expected/demo-project/src/app.ts
new file mode 100644
index 0000000000..6d5856292d
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_10-jsslot/expected/demo-project/src/app.ts
@@ -0,0 +1,13 @@
+import { defineAppConfig } from 'ice';
+
+// App config, see https://v3.ice.work/docs/guide/basic/app
+export default defineAppConfig(() => ({
+ // Set your configs here.
+ app: {
+ rootId: 'App',
+ },
+ router: {
+ type: 'browser',
+ basename: '/',
+ },
+}));
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_10-jsslot/expected/demo-project/src/constants.js b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_10-jsslot/expected/demo-project/src/constants.js
new file mode 100644
index 0000000000..ea766c9da3
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_10-jsslot/expected/demo-project/src/constants.js
@@ -0,0 +1,3 @@
+const __$$constants = {};
+
+export default __$$constants;
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_10-jsslot/expected/demo-project/src/document.tsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_10-jsslot/expected/demo-project/src/document.tsx
new file mode 100644
index 0000000000..aff0231d95
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_10-jsslot/expected/demo-project/src/document.tsx
@@ -0,0 +1,29 @@
+import React from 'react';
+import { Meta, Title, Links, Main, Scripts } from 'ice';
+
+export default function Document() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_10-jsslot/expected/demo-project/src/global.scss b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_10-jsslot/expected/demo-project/src/global.scss
new file mode 100644
index 0000000000..82ca3eac73
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_10-jsslot/expected/demo-project/src/global.scss
@@ -0,0 +1,6 @@
+// 引入默认全局样式
+@import '@alifd/next/reset.scss';
+
+body {
+ -webkit-font-smoothing: antialiased;
+}
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_10-jsslot/expected/demo-project/src/i18n.js b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_10-jsslot/expected/demo-project/src/i18n.js
new file mode 100644
index 0000000000..1334d2502b
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_10-jsslot/expected/demo-project/src/i18n.js
@@ -0,0 +1,77 @@
+const i18nConfig = {};
+
+let locale =
+ typeof navigator === 'object' && typeof navigator.language === 'string'
+ ? navigator.language
+ : 'zh-CN';
+
+const getLocale = () => locale;
+
+const setLocale = (target) => {
+ locale = target;
+};
+
+const isEmptyVariables = (variables) =>
+ (Array.isArray(variables) && variables.length === 0) ||
+ (typeof variables === 'object' &&
+ (!variables || Object.keys(variables).length === 0));
+
+// 按低代码规范里面的要求进行变量替换
+const format = (msg, variables) =>
+ typeof msg === 'string'
+ ? msg.replace(/\$?\{(\w+)\}/g, (match, key) => variables?.[key] ?? '')
+ : msg;
+
+const i18nFormat = ({ id, defaultMessage, fallback }, variables) => {
+ const msg =
+ i18nConfig[locale]?.[id] ??
+ i18nConfig[locale.replace('-', '_')]?.[id] ??
+ defaultMessage;
+ if (msg == null) {
+ console.warn('[i18n]: unknown message id: %o (locale=%o)', id, locale);
+ return fallback === undefined ? `${id}` : fallback;
+ }
+
+ return format(msg, variables);
+};
+
+const i18n = (id, params) => {
+ return i18nFormat({ id }, params);
+};
+
+// 将国际化的一些方法注入到目标对象&上下文中
+const _inject2 = (target) => {
+ target.i18n = i18n;
+ target.getLocale = getLocale;
+ target.setLocale = (locale) => {
+ setLocale(locale);
+ target.forceUpdate();
+ };
+ target._i18nText = (t) => {
+ // 优先取直接传过来的语料
+ const localMsg = t[locale] ?? t[String(locale).replace('-', '_')];
+ if (localMsg != null) {
+ return format(localMsg, t.params);
+ }
+
+ // 其次用项目级别的
+ const projectMsg = i18nFormat({ id: t.key, fallback: null }, t.params);
+ if (projectMsg != null) {
+ return projectMsg;
+ }
+
+ // 兜底用 use 指定的或默认语言的
+ return format(t[t.use || 'zh-CN'] ?? t.en_US, t.params);
+ };
+
+ // 注入到上下文中去
+ if (target._context && target._context !== target) {
+ Object.assign(target._context, {
+ i18n,
+ getLocale,
+ setLocale: target.setLocale,
+ });
+ }
+};
+
+export { getLocale, setLocale, i18n, i18nFormat, _inject2 };
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_10-jsslot/expected/demo-project/src/layouts/BasicLayout/components/Footer/index.jsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_10-jsslot/expected/demo-project/src/layouts/BasicLayout/components/Footer/index.jsx
new file mode 100644
index 0000000000..cc70d53bea
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_10-jsslot/expected/demo-project/src/layouts/BasicLayout/components/Footer/index.jsx
@@ -0,0 +1,14 @@
+
+import React from 'react';
+import styles from './index.module.scss';
+
+export default function Footer() {
+ return (
+
+ Alibaba Fusion
+
+ © 2019-现在 Alibaba Fusion & ICE
+
+ );
+}
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_10-jsslot/expected/demo-project/src/layouts/BasicLayout/components/Footer/index.module.scss b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_10-jsslot/expected/demo-project/src/layouts/BasicLayout/components/Footer/index.module.scss
new file mode 100644
index 0000000000..81e77fda5f
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_10-jsslot/expected/demo-project/src/layouts/BasicLayout/components/Footer/index.module.scss
@@ -0,0 +1,15 @@
+
+.footer {
+ line-height: 20px;
+ text-align: center;
+}
+
+.logo {
+ font-weight: bold;
+ font-size: 16px;
+}
+
+.copyright {
+ font-size: 12px;
+}
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_10-jsslot/expected/demo-project/src/layouts/BasicLayout/components/Logo/index.jsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_10-jsslot/expected/demo-project/src/layouts/BasicLayout/components/Logo/index.jsx
new file mode 100644
index 0000000000..265bfdaa07
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_10-jsslot/expected/demo-project/src/layouts/BasicLayout/components/Logo/index.jsx
@@ -0,0 +1,16 @@
+
+import React from 'react';
+import { Link } from 'ice';
+import styles from './index.module.scss';
+
+export default function Logo({ image, text, url }) {
+ return (
+
+
+ {image &&

}
+
{text}
+
+
+ );
+}
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_10-jsslot/expected/demo-project/src/layouts/BasicLayout/components/Logo/index.module.scss b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_10-jsslot/expected/demo-project/src/layouts/BasicLayout/components/Logo/index.module.scss
new file mode 100644
index 0000000000..dad05a263f
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_10-jsslot/expected/demo-project/src/layouts/BasicLayout/components/Logo/index.module.scss
@@ -0,0 +1,20 @@
+
+.logo{
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: #FF7300;
+ font-weight: bold;
+ font-size: 14px;
+ line-height: 22px;
+
+ &:visited, &:link {
+ color: #FF7300;
+ }
+
+ img {
+ height: 24px;
+ margin-right: 10px;
+ }
+}
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_10-jsslot/expected/demo-project/src/layouts/BasicLayout/components/PageNav/index.jsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_10-jsslot/expected/demo-project/src/layouts/BasicLayout/components/PageNav/index.jsx
new file mode 100644
index 0000000000..911998b0d3
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_10-jsslot/expected/demo-project/src/layouts/BasicLayout/components/PageNav/index.jsx
@@ -0,0 +1,68 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { Link, useLocation } from 'ice';
+import { Nav } from '@alifd/next';
+import { asideMenuConfig } from '../../menuConfig';
+
+const { SubNav } = Nav;
+const NavItem = Nav.Item;
+
+function getNavMenuItems(menusData) {
+ if (!menusData) {
+ return [];
+ }
+
+ return menusData
+ .filter(item => item.name && !item.hideInMenu)
+ .map((item, index) => getSubMenuOrItem(item, index));
+}
+
+function getSubMenuOrItem(item, index) {
+ if (item.children && item.children.some(child => child.name)) {
+ const childrenItems = getNavMenuItems(item.children);
+
+ if (childrenItems && childrenItems.length > 0) {
+ const subNav = (
+
+ {childrenItems}
+
+ );
+ return subNav;
+ }
+
+ return null;
+ }
+
+ const navItem = (
+
+ {item.name}
+
+ );
+ return navItem;
+}
+
+const Navigation = (props, context) => {
+ const location = useLocation();
+ const { pathname } = location;
+ const { isCollapse } = context;
+ return (
+
+ );
+};
+
+Navigation.contextTypes = {
+ isCollapse: PropTypes.bool,
+};
+export default Navigation;
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_10-jsslot/expected/demo-project/src/layouts/BasicLayout/index.jsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_10-jsslot/expected/demo-project/src/layouts/BasicLayout/index.jsx
new file mode 100644
index 0000000000..18db44df5e
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_10-jsslot/expected/demo-project/src/layouts/BasicLayout/index.jsx
@@ -0,0 +1,81 @@
+
+import React, { useState } from 'react';
+import { Shell, ConfigProvider } from '@alifd/next';
+import PageNav from './components/PageNav';
+import Logo from './components/Logo';
+import Footer from './components/Footer';
+
+(function() {
+ const throttle = function(type, name, obj = window) {
+ let running = false;
+
+ const func = () => {
+ if (running) {
+ return;
+ }
+
+ running = true;
+ requestAnimationFrame(() => {
+ obj.dispatchEvent(new CustomEvent(name));
+ running = false;
+ });
+ };
+
+ obj.addEventListener(type, func);
+ };
+
+ throttle('resize', 'optimizedResize');
+})();
+
+export default function BasicLayout({ children }) {
+ const getDevice = width => {
+ const isPhone =
+ typeof navigator !== 'undefined' && navigator && navigator.userAgent.match(/phone/gi);
+
+ if (width < 680 || isPhone) {
+ return 'phone';
+ }
+ if (width < 1280 && width > 680) {
+ return 'tablet';
+ }
+ return 'desktop';
+ };
+
+ const [device, setDevice] = useState(getDevice(NaN));
+ window.addEventListener('optimizedResize', e => {
+ setDevice(getDevice(e && e.target && e.target.innerWidth));
+ });
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ {children}
+
+
+
+
+
+ );
+}
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_10-jsslot/expected/demo-project/src/layouts/BasicLayout/menuConfig.js b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_10-jsslot/expected/demo-project/src/layouts/BasicLayout/menuConfig.js
new file mode 100644
index 0000000000..5332202be4
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_10-jsslot/expected/demo-project/src/layouts/BasicLayout/menuConfig.js
@@ -0,0 +1,11 @@
+
+const headerMenuConfig = [];
+const asideMenuConfig = [
+ {
+ name: 'Dashboard',
+ path: '/',
+ icon: 'smile',
+ },
+];
+export { headerMenuConfig, asideMenuConfig };
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_10-jsslot/expected/demo-project/src/pages/Test/index.css b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_10-jsslot/expected/demo-project/src/pages/Test/index.css
new file mode 100644
index 0000000000..066114aeeb
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_10-jsslot/expected/demo-project/src/pages/Test/index.css
@@ -0,0 +1,8 @@
+body {
+ font-size: 12px;
+}
+
+.botton {
+ width: 100px;
+ color: #ff00ff;
+}
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_10-jsslot/expected/demo-project/src/pages/Test/index.jsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_10-jsslot/expected/demo-project/src/pages/Test/index.jsx
new file mode 100644
index 0000000000..922ad47ad8
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_10-jsslot/expected/demo-project/src/pages/Test/index.jsx
@@ -0,0 +1,822 @@
+// 注意: 出码引擎注入的临时变量默认都以 "__$$" 开头,禁止在搭建的代码中直接访问。
+// 例外:react 框架的导出名和各种组件名除外。
+import React from 'react';
+
+import {
+ Modal,
+ Button,
+ Typography,
+ Form,
+ Select,
+ Input,
+ ConfigProvider,
+ Tooltip,
+ Empty,
+} from '@alilc/antd-lowcode-materials/dist/antd-lowcode.esm.js';
+
+import {
+ AliAutoDiv,
+ AliAutoSearchTable,
+} from '@alife/mc-assets-1935/build/lowcode/index.js';
+
+import {
+ Page as NextPage,
+ Block as NextBlock,
+ P as NextP,
+} from '@alife/container/lib/index.js';
+
+import utils, { RefsManager } from '../../utils';
+
+import * as __$$i18n from '../../i18n';
+
+import __$$constants from '../../constants';
+
+import './index.css';
+
+const AliAutoDivDefault = AliAutoDiv.default;
+
+const AliAutoSearchTableDefault = AliAutoSearchTable.default;
+
+const NextBlockCell = NextBlock.Cell;
+
+class Test$$Page extends React.Component {
+ _context = this;
+
+ get constants() {
+ return __$$constants || {};
+ }
+
+ constructor(props, context) {
+ super(props);
+
+ this.utils = utils;
+
+ this._refsManager = new RefsManager();
+
+ __$$i18n._inject2(this);
+
+ this.state = {
+ pkgs: [],
+ total: 0,
+ isSearch: false,
+ projects: [],
+ results: [],
+ resultVisible: false,
+ };
+
+ this.__jp__init();
+ this.statusDesc = {
+ 0: '失败',
+ 1: '成功',
+ 2: '构建中',
+ 3: '构建超时',
+ };
+ this.pageParams = {};
+ }
+
+ $ = (refName) => {
+ return this._refsManager.get(refName);
+ };
+
+ $$ = (refName) => {
+ return this._refsManager.getAll(refName);
+ };
+
+ componentDidUpdate(prevProps, prevState, snapshot) {}
+
+ componentWillUnmount() {}
+
+ __jp__init() {
+ /*...*/
+ }
+
+ __jp__initRouter() {
+ if (window.arsenal) {
+ this.$router = new window.jianpin.ArsenalRouter({
+ app: this.props.microApp,
+ });
+ } else {
+ this.$router = new window.jianpin.ArsenalRouter();
+ }
+ }
+
+ __jp__initDataSource() {
+ /*...*/
+ }
+
+ __jp__initEnv() {
+ /*...*/
+ }
+
+ __jp__initConfig() {
+ /*...*/
+ }
+
+ __jp__initUtils() {
+ this.$utils = {
+ message: window.jianpin.utils.message,
+ axios: window.jianpin.utils.axios,
+ moment: window.jianpin.utils.moment,
+ };
+ }
+
+ fetchPkgs() {
+ /*...*/
+ }
+
+ onPageChange(pageIndex, pageSize) {
+ this.pageParams = {
+ pageIndex,
+ pageSize,
+ };
+ this.fetchPkgs();
+ }
+
+ renderTime(time) {
+ return this.$utils.moment(time).format('YYYY-MM-DD HH:mm');
+ }
+
+ renderUserName(user) {
+ return user.user_name;
+ }
+
+ reload() {
+ /*...*/
+ }
+
+ handleResult() {
+ /*...*/
+ }
+
+ handleDetail() {
+ // 跳转详情页面 TODO
+ }
+
+ onResultCancel() {
+ this.setState({
+ resultVisible: false,
+ });
+ }
+
+ formatResult(item) {
+ if (!item) {
+ return '暂无结果';
+ }
+ const { channel, plat, version, status } = item;
+ return [channel, plat, version, status].join('-');
+ }
+
+ handleDownload() {
+ /*...*/
+ }
+
+ onFinish() {
+ /*...*/
+ }
+
+ componentDidMount() {
+ this.$ds.resolve('PROJECTS', {
+ params: {
+ size: 5000,
+ },
+ });
+ // if (this.state.init === false) {
+ // this.setState({
+ // init: true,
+ // });
+ // }
+ }
+
+ render() {
+ const __$$context = this._context || this;
+ const { state } = __$$context;
+ return (
+
+
this.state.resultVisible)}
+ footer={
+
+ }
+ __events={{
+ eventDataList: [
+ {
+ type: 'componentEvent',
+ name: 'onCancel',
+ relatedEventName: 'onResultCancel',
+ },
+ ],
+ eventList: [
+ { name: 'onCancel', disabled: true },
+ { name: 'onOk', disabled: false },
+ ],
+ }}
+ onCancel={function () {
+ this.onResultCancel.apply(
+ this,
+ Array.prototype.slice.call(arguments).concat([])
+ );
+ }.bind(this)}
+ width="720px"
+ centered={true}
+ >
+ {__$$evalArray(() => this.state.results).map((item, index) =>
+ ((__$$context) => (
+
+ {!!__$$eval(
+ () =>
+ __$$context.state.results &&
+ __$$context.state.results.length > 0
+ ) && (
+
+
+
+ )}
+
+ {__$$eval(() => __$$context.formatResult(item))}
+
+ {!!__$$eval(() => item.download_link) && (
+ item.download_link)}
+ target="_blank"
+ >
+ {' '}
+ - 点击下载
+
+ )}
+ {!!__$$eval(() => item.release_notes) && (
+ item.release_notes)}
+ target="_blank"
+ >
+ {' '}
+ - 跳转发布节点
+
+ )}
+
+ ))(__$$createChildContext(__$$context, { item, index }))
+ )}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {!!__$$eval(
+ () =>
+ !this.state.isSearch ||
+ (this.state.isSearch && this.state.pkgs.length > 0)
+ ) && (
+ this.state.pkgs)}
+ columns={[
+ {
+ title: 'ID',
+ dataIndex: 'id',
+ key: 'name',
+ width: 80,
+ },
+ {
+ title: '渠道号',
+ dataIndex: 'channels',
+ key: 'age',
+ width: 142,
+ render: (text, record, index) =>
+ ((__$$context) =>
+ __$$evalArray(() => text.split(',')).map(
+ (item, index) =>
+ ((__$$context) => (
+
+ {__$$eval(() => item)}
+
+ ))(
+ __$$createChildContext(__$$context, {
+ item,
+ index,
+ })
+ )
+ ))(
+ __$$createChildContext(__$$context, {
+ text,
+ record,
+ index,
+ })
+ ),
+ },
+ {
+ title: '版本号',
+ dataIndex: 'dic_version',
+ key: 'address',
+ render: (text, record, index) =>
+ ((__$$context) => (
+ text || []).map(
+ (item, index) =>
+ ((__$$context) => (
+
+ {__$$eval(
+ () =>
+ item.channelId +
+ ' / ' +
+ item.version
+ )}
+
+ ))(
+ __$$createChildContext(__$$context, {
+ item,
+ index,
+ })
+ )
+ )}
+ >
+
+ {__$$eval(() => text[0].version)}
+
+
+ ))(
+ __$$createChildContext(__$$context, {
+ text,
+ record,
+ index,
+ })
+ ),
+ width: 120,
+ },
+ { title: '构建Job', dataIndex: 'job_name', width: 180 },
+ {
+ title: '构建类型',
+ dataIndex: 'packaging_type',
+ width: 94,
+ },
+ {
+ title: '构建状态',
+ dataIndex: 'status',
+ render: (text, record, index) =>
+ ((__$$context) => [
+
+ {__$$eval(() => __$$context.statusDesc[text])}
+ ,
+ !!__$$eval(() => text === 2) && (
+
+ ),
+ ])(
+ __$$createChildContext(__$$context, {
+ text,
+ record,
+ index,
+ })
+ ),
+ width: 100,
+ },
+ {
+ title: '构建时间',
+ dataIndex: 'start_time',
+ render: function () {
+ return this.renderTime.apply(
+ this,
+ Array.prototype.slice.call(arguments).concat([])
+ );
+ }.bind(this),
+ width: 148,
+ },
+ {
+ title: '构建人',
+ dataIndex: 'user',
+ render: function () {
+ return this.renderUserName.apply(
+ this,
+ Array.prototype.slice.call(arguments).concat([])
+ );
+ }.bind(this),
+ width: 80,
+ },
+ {
+ title: 'Jenkins 链接',
+ dataIndex: 'jenkins_link',
+ render: (text, record, index) =>
+ ((__$$context) => [
+ !!__$$eval(() => text) && (
+ text)}
+ target="_blank"
+ >
+ 查看
+
+ ),
+ !!__$$eval(() => !text) && (
+ 暂无
+ ),
+ ])(
+ __$$createChildContext(__$$context, {
+ text,
+ record,
+ index,
+ })
+ ),
+ width: 120,
+ },
+ {
+ title: '测试平台链接',
+ dataIndex: 'is_run_testing',
+ width: 120,
+ render: (text, record, index) =>
+ ((__$$context) => [
+ !!__$$eval(() => text) && (
+
+ 查看
+
+ ),
+ !!__$$eval(() => !text) && (
+ 暂无
+ ),
+ ])(
+ __$$createChildContext(__$$context, {
+ text,
+ record,
+ index,
+ })
+ ),
+ },
+ { title: '触发源', dataIndex: 'source', width: 120 },
+ {
+ title: '详情',
+ dataIndex: 'id',
+ render: (text, record, index) =>
+ ((__$$context) => (
+
+ ))(
+ __$$createChildContext(__$$context, {
+ text,
+ record,
+ index,
+ })
+ ),
+ width: 80,
+ fixed: 'right',
+ },
+ {
+ title: '结果',
+ dataIndex: 'id',
+ render: (text, record, index) =>
+ ((__$$context) => (
+
+ ))(
+ __$$createChildContext(__$$context, {
+ text,
+ record,
+ index,
+ })
+ ),
+ width: 80,
+ fixed: 'right',
+ },
+ {
+ title: '重新执行',
+ dataIndex: 'id',
+ width: 92,
+ render: (text, record, index) =>
+ ((__$$context) => (
+
+ }
+ shape="circle"
+ __events={{
+ eventDataList: [
+ {
+ type: 'componentEvent',
+ name: 'onClick',
+ relatedEventName: 'reload',
+ },
+ ],
+ eventList: [
+ { name: 'onClick', disabled: true },
+ ],
+ }}
+ onClick={function () {
+ this.reload.apply(
+ this,
+ Array.prototype.slice
+ .call(arguments)
+ .concat([])
+ );
+ }.bind(__$$context)}
+ />
+ ))(
+ __$$createChildContext(__$$context, {
+ text,
+ record,
+ index,
+ })
+ ),
+ fixed: 'right',
+ },
+ ]}
+ actions={[]}
+ pagination={{
+ total: __$$eval(() => this.state.total),
+ defaultPageSize: 8,
+ onPageChange: function () {
+ return this.onPageChange.apply(
+ this,
+ Array.prototype.slice.call(arguments).concat([])
+ );
+ }.bind(this),
+ }}
+ scrollX={1200}
+ />
+ )}
+
+
+
+
+
+
+
+ {!!__$$eval(
+ () => this.state.pkgs.length < 1 && this.state.isSearch
+ ) && }
+
+
+
+
+
+ );
+ }
+}
+
+export default Test$$Page;
+
+function __$$eval(expr) {
+ try {
+ return expr();
+ } catch (error) {}
+}
+
+function __$$evalArray(expr) {
+ const res = __$$eval(expr);
+ return Array.isArray(res) ? res : [];
+}
+
+function __$$createChildContext(oldContext, ext) {
+ const childContext = {
+ ...oldContext,
+ ...ext,
+ };
+ childContext.__proto__ = oldContext;
+ return childContext;
+}
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_10-jsslot/expected/demo-project/src/pages/layout.jsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_10-jsslot/expected/demo-project/src/pages/layout.jsx
new file mode 100644
index 0000000000..50fbb2d1f1
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_10-jsslot/expected/demo-project/src/pages/layout.jsx
@@ -0,0 +1,10 @@
+import { Outlet } from 'ice';
+import BasicLayout from '@/layouts/BasicLayout';
+
+export default function Layout() {
+ return (
+
+
+
+ );
+}
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_10-jsslot/expected/demo-project/src/typings.d.ts b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_10-jsslot/expected/demo-project/src/typings.d.ts
new file mode 100644
index 0000000000..a9f8de7ceb
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_10-jsslot/expected/demo-project/src/typings.d.ts
@@ -0,0 +1,9 @@
+///
+
+export {};
+declare global {
+ interface Window {
+ g_config: Record;
+ }
+}
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_10-jsslot/expected/demo-project/src/utils.js b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_10-jsslot/expected/demo-project/src/utils.js
new file mode 100644
index 0000000000..1190717924
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_10-jsslot/expected/demo-project/src/utils.js
@@ -0,0 +1,47 @@
+import { createRef } from 'react';
+
+export class RefsManager {
+ constructor() {
+ this.refInsStore = {};
+ }
+
+ clearNullRefs() {
+ Object.keys(this.refInsStore).forEach((refName) => {
+ const filteredInsList = this.refInsStore[refName].filter(
+ (insRef) => !!insRef.current
+ );
+ if (filteredInsList.length > 0) {
+ this.refInsStore[refName] = filteredInsList;
+ } else {
+ delete this.refInsStore[refName];
+ }
+ });
+ }
+
+ get(refName) {
+ this.clearNullRefs();
+ if (this.refInsStore[refName] && this.refInsStore[refName].length > 0) {
+ return this.refInsStore[refName][0].current;
+ }
+
+ return null;
+ }
+
+ getAll(refName) {
+ this.clearNullRefs();
+ if (this.refInsStore[refName] && this.refInsStore[refName].length > 0) {
+ return this.refInsStore[refName].map((i) => i.current);
+ }
+
+ return [];
+ }
+
+ linkRef(refName) {
+ const refIns = createRef();
+ this.refInsStore[refName] = this.refInsStore[refName] || [];
+ this.refInsStore[refName].push(refIns);
+ return refIns;
+ }
+}
+
+export default {};
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_10-jsslot/schema.json5 b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_10-jsslot/schema.json5
new file mode 100644
index 0000000000..a499dfc650
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_10-jsslot/schema.json5
@@ -0,0 +1,1206 @@
+{
+ version: '1.0.0',
+ componentsMap: [
+ {
+ devMode: 'lowcode',
+ componentName: 'Slot',
+ },
+ {
+ package: '@alilc/antd-lowcode-materials',
+ version: '0.9.4',
+ exportName: 'Button',
+ main: 'dist/antd-lowcode.esm.js',
+ destructuring: true,
+ componentName: 'Button',
+ },
+ {
+ package: '@alife/mc-assets-1935',
+ version: '0.1.42',
+ exportName: 'AliAutoDiv',
+ main: 'build/lowcode/index.js',
+ destructuring: true,
+ subName: 'default',
+ componentName: 'AliAutoDivDefault',
+ },
+ {
+ package: '@alilc/antd-lowcode-materials',
+ version: '0.9.4',
+ exportName: 'Typography',
+ main: 'dist/antd-lowcode.esm.js',
+ destructuring: true,
+ subName: 'Text',
+ componentName: 'Typography.Text',
+ },
+ {
+ package: '@alilc/antd-lowcode-materials',
+ version: '0.9.4',
+ exportName: 'Typography',
+ main: 'dist/antd-lowcode.esm.js',
+ destructuring: true,
+ subName: 'Link',
+ componentName: 'Typography.Link',
+ },
+ {
+ package: '@alilc/antd-lowcode-materials',
+ version: '0.9.4',
+ exportName: 'Modal',
+ main: 'dist/antd-lowcode.esm.js',
+ destructuring: true,
+ componentName: 'Modal',
+ },
+ {
+ package: '@alilc/antd-lowcode-materials',
+ version: '0.9.4',
+ exportName: 'Select',
+ main: 'dist/antd-lowcode.esm.js',
+ destructuring: true,
+ componentName: 'Select',
+ },
+ {
+ package: '@alilc/antd-lowcode-materials',
+ version: '0.9.4',
+ exportName: 'Form',
+ main: 'dist/antd-lowcode.esm.js',
+ destructuring: true,
+ subName: 'Item',
+ componentName: 'Form.Item',
+ },
+ {
+ package: '@alilc/antd-lowcode-materials',
+ version: '0.9.4',
+ exportName: 'Input',
+ main: 'dist/antd-lowcode.esm.js',
+ destructuring: true,
+ componentName: 'Input',
+ },
+ {
+ package: '@alilc/antd-lowcode-materials',
+ version: '0.9.4',
+ exportName: 'Form',
+ main: 'dist/antd-lowcode.esm.js',
+ destructuring: true,
+ componentName: 'Form',
+ },
+ {
+ package: '@alife/container',
+ version: '0.3.7',
+ exportName: 'P',
+ main: 'lib/index.js',
+ destructuring: true,
+ subName: '',
+ componentName: 'NextP',
+ },
+ {
+ package: '@alife/container',
+ version: '0.3.7',
+ exportName: 'Block',
+ main: 'lib/index.js',
+ destructuring: true,
+ subName: 'Cell',
+ componentName: 'NextBlockCell',
+ },
+ {
+ package: '@alife/container',
+ version: '0.3.7',
+ exportName: 'Block',
+ main: 'lib/index.js',
+ destructuring: true,
+ subName: '',
+ componentName: 'NextBlock',
+ },
+ {
+ package: '@alife/mc-assets-1935',
+ version: '0.1.42',
+ exportName: 'AliAutoSearchTable',
+ main: 'build/lowcode/index.js',
+ destructuring: true,
+ subName: 'default',
+ componentName: 'AliAutoSearchTableDefault',
+ },
+ {
+ package: '@alilc/antd-lowcode-materials',
+ version: '0.9.4',
+ exportName: 'ConfigProvider',
+ main: 'dist/antd-lowcode.esm.js',
+ destructuring: true,
+ componentName: 'ConfigProvider',
+ },
+ {
+ package: '@alilc/antd-lowcode-materials',
+ version: '0.9.4',
+ exportName: 'Empty',
+ main: 'dist/antd-lowcode.esm.js',
+ destructuring: true,
+ componentName: 'Empty',
+ },
+ {
+ package: '@alife/container',
+ version: '0.3.7',
+ exportName: 'Page',
+ main: 'lib/index.js',
+ destructuring: true,
+ subName: '',
+ componentName: 'NextPage',
+ },
+ {
+ devMode: 'lowcode',
+ componentName: 'Page',
+ },
+ {
+ package: '@alilc/antd-lowcode-materials',
+ version: '0.9.4',
+ exportName: 'Tooltip',
+ main: 'dist/antd-lowcode.esm.js',
+ destructuring: true,
+ componentName: 'Tooltip',
+ },
+ ],
+ componentsTree: [
+ {
+ componentName: 'Page',
+ id: 'node_dockcviv8fo1',
+ props: {
+ ref: 'outterView',
+ style: {
+ height: '100%',
+ },
+ },
+ fileName: 'test',
+ dataSource: {
+ list: [],
+ },
+ css: 'body {\n font-size: 12px;\n}\n\n.botton {\n width: 100px;\n color: #ff00ff\n}',
+ lifeCycles: {
+ constructor: {
+ type: 'JSFunction',
+ value: "function() {\n this.__jp__init();\n this.statusDesc = {\n 0: '失败',\n 1: '成功',\n 2: '构建中',\n 3: '构建超时',\n };\n this.pageParams = {};\n }",
+ },
+ componentDidMount: {
+ type: 'JSFunction',
+ value: "function() {\n this.$ds.resolve('PROJECTS', {\n params: {\n size: 5000,\n },\n });\n // if (this.state.init === false) {\n // this.setState({\n // init: true,\n // });\n // }\n }",
+ },
+ componentDidUpdate: {
+ type: 'JSFunction',
+ value: 'function(prevProps, prevState, snapshot) {}',
+ },
+ componentWillUnmount: {
+ type: 'JSFunction',
+ value: 'function() {}',
+ },
+ },
+ methods: {
+ __jp__init: {
+ type: 'JSFunction',
+ value: 'function() { /*...*/ }',
+ },
+ __jp__initRouter: {
+ type: 'JSFunction',
+ value: 'function() {\n if (window.arsenal) {\n this.$router = new window.jianpin.ArsenalRouter({\n app: this.props.microApp,\n });\n } else {\n this.$router = new window.jianpin.ArsenalRouter();\n }\n}',
+ },
+ __jp__initDataSource: {
+ type: 'JSFunction',
+ value: 'function() { /*...*/ }',
+ },
+ __jp__initEnv: {
+ type: 'JSFunction',
+ value: 'function() { /*...*/ }',
+ },
+ __jp__initConfig: {
+ type: 'JSFunction',
+ value: 'function() { /*...*/ }',
+ },
+ __jp__initUtils: {
+ type: 'JSFunction',
+ value: 'function() {\n this.$utils = {\n message: window.jianpin.utils.message,\n axios: window.jianpin.utils.axios,\n moment: window.jianpin.utils.moment,\n };\n}',
+ },
+ fetchPkgs: {
+ type: 'JSFunction',
+ value: 'function() { /*...*/ }',
+ },
+ onPageChange: {
+ type: 'JSFunction',
+ value: 'function(pageIndex, pageSize) {\n this.pageParams = {\n pageIndex,\n pageSize,\n };\n this.fetchPkgs();\n }',
+ },
+ renderTime: {
+ type: 'JSFunction',
+ value: "function(time) {\n return this.$utils.moment(time).format('YYYY-MM-DD HH:mm');\n }",
+ },
+ renderUserName: {
+ type: 'JSFunction',
+ value: 'function(user) {\n return user.user_name;\n }',
+ },
+ reload: {
+ type: 'JSFunction',
+ value: 'function() { /*...*/ }',
+ },
+ handleResult: {
+ type: 'JSFunction',
+ value: 'function() { /*...*/ }',
+ },
+ handleDetail: {
+ type: 'JSFunction',
+ value: 'function() {\n // 跳转详情页面 TODO\n }',
+ },
+ onResultCancel: {
+ type: 'JSFunction',
+ value: 'function() {\n this.setState({\n resultVisible: false,\n });\n }',
+ },
+ formatResult: {
+ type: 'JSFunction',
+ value: "function(item) {\n if (!item) {\n return '暂无结果';\n }\n const { channel, plat, version, status } = item;\n return [channel, plat, version, status].join('-');\n }",
+ },
+ handleDownload: {
+ type: 'JSFunction',
+ value: 'function() { /*...*/ }',
+ },
+ onFinish: {
+ type: 'JSFunction',
+ value: 'function() { /*...*/ }',
+ },
+ },
+ state: {
+ pkgs: [],
+ total: 0,
+ isSearch: false,
+ projects: [],
+ results: [],
+ resultVisible: false,
+ },
+ children: [
+ {
+ componentName: 'Modal',
+ id: 'node_ocksh9yppxb',
+ props: {
+ title: '查看结果',
+ visible: {
+ type: 'JSExpression',
+ value: 'this.state.resultVisible',
+ },
+ footer: {
+ type: 'JSSlot',
+ value: [
+ {
+ componentName: 'Button',
+ id: 'node_ocksh9yppxf',
+ props: {
+ type: 'primary',
+ children: '确定',
+ __events: {
+ eventDataList: [
+ {
+ type: 'componentEvent',
+ name: 'onClick',
+ relatedEventName: 'onResultCancel',
+ },
+ ],
+ eventList: [
+ {
+ name: 'onClick',
+ disabled: true,
+ },
+ ],
+ },
+ onClick: {
+ type: 'JSFunction',
+ value: 'function(){this.onResultCancel.apply(this,Array.prototype.slice.call(arguments).concat([])) }',
+ },
+ },
+ },
+ ],
+ },
+ __events: {
+ eventDataList: [
+ {
+ type: 'componentEvent',
+ name: 'onCancel',
+ relatedEventName: 'onResultCancel',
+ },
+ ],
+ eventList: [
+ {
+ name: 'onCancel',
+ disabled: true,
+ },
+ {
+ name: 'onOk',
+ disabled: false,
+ },
+ ],
+ },
+ onCancel: {
+ type: 'JSFunction',
+ value: 'function(){this.onResultCancel.apply(this,Array.prototype.slice.call(arguments).concat([])) }',
+ },
+ width: '720px',
+ centered: true,
+ },
+ hidden: true,
+ children: [
+ {
+ componentName: 'AliAutoDivDefault',
+ id: 'node_ockshazuxa4',
+ props: {
+ style: {
+ width: '100%',
+ },
+ },
+ loop: {
+ type: 'JSExpression',
+ value: 'this.state.results',
+ },
+ children: [
+ {
+ componentName: 'AliAutoDivDefault',
+ id: 'node_ockshazuxai',
+ props: {
+ style: {
+ width: '100%',
+ textAlign: 'left',
+ marginBottom: '10px',
+ },
+ },
+ condition: {
+ type: 'JSExpression',
+ value: 'this.state.results && this.state.results.length > 0',
+ },
+ children: [
+ {
+ componentName: 'Button',
+ id: 'node_ockshazuxah',
+ props: {
+ type: 'primary',
+ children: '下载全部',
+ size: 'small',
+ __events: {
+ eventDataList: [
+ {
+ type: 'componentEvent',
+ name: 'onClick',
+ relatedEventName: 'handleDownload',
+ },
+ ],
+ eventList: [
+ {
+ name: 'onClick',
+ disabled: true,
+ },
+ ],
+ },
+ onClick: {
+ type: 'JSFunction',
+ value: 'function(){this.handleDownload.apply(this,Array.prototype.slice.call(arguments).concat([])) }',
+ },
+ },
+ },
+ ],
+ },
+ {
+ componentName: 'Typography.Text',
+ id: 'node_ockshazuxa5',
+ props: {
+ children: {
+ type: 'JSExpression',
+ value: 'this.formatResult(this.item)',
+ },
+ },
+ },
+ {
+ componentName: 'Typography.Link',
+ id: 'node_ockshazuxa6',
+ props: {
+ href: {
+ type: 'JSExpression',
+ value: 'this.item.download_link',
+ },
+ target: '_blank',
+ children: ' - 点击下载',
+ },
+ condition: {
+ type: 'JSExpression',
+ value: 'this.item.download_link',
+ },
+ },
+ {
+ componentName: 'Typography.Link',
+ id: 'node_ockshazuxa7',
+ props: {
+ href: {
+ type: 'JSExpression',
+ value: 'this.item.release_notes',
+ },
+ target: '_blank',
+ children: ' - 跳转发布节点',
+ },
+ condition: {
+ type: 'JSExpression',
+ value: 'this.item.release_notes',
+ },
+ },
+ ],
+ },
+ ],
+ },
+ {
+ componentName: 'NextPage',
+ id: 'node_ocko19zplh1',
+ props: {
+ columns: 12,
+ headerDivider: true,
+ placeholderStyle: {
+ gridRowEnd: 'span 1',
+ gridColumnEnd: 'span 12',
+ },
+ placeholder: '页面主体内容:拖拽Block布局组件到这里',
+ header: {
+ type: 'JSSlot',
+ title: 'header',
+ },
+ headerProps: {
+ background: 'surface',
+ },
+ footer: {
+ type: 'JSSlot',
+ title: 'footer',
+ },
+ minHeight: '100vh',
+ },
+ title: '页面',
+ children: [
+ {
+ componentName: 'NextBlock',
+ id: 'node_ocko19zplh2',
+ props: {
+ prefix: 'next-',
+ placeholderStyle: {
+ height: '100%',
+ },
+ noPadding: false,
+ noBorder: false,
+ background: 'surface',
+ layoutmode: 'O',
+ colSpan: 12,
+ rowSpan: 1,
+ childTotalColumns: 12,
+ },
+ title: '区块',
+ children: [
+ {
+ componentName: 'NextBlockCell',
+ id: 'node_ocko19zplh3',
+ props: {
+ title: '',
+ prefix: 'next-',
+ placeholderStyle: {
+ height: '100%',
+ },
+ layoutmode: 'O',
+ childTotalColumns: 12,
+ isAutoContainer: true,
+ colSpan: 12,
+ rowSpan: 1,
+ },
+ children: [
+ {
+ componentName: 'NextP',
+ id: 'node_ocks8dtt1ms',
+ props: {
+ wrap: false,
+ type: 'body2',
+ verAlign: 'middle',
+ textSpacing: true,
+ align: 'left',
+ full: true,
+ flex: true,
+ },
+ title: '段落',
+ children: [
+ {
+ componentName: 'Form',
+ id: 'node_ocks8dtt1mt',
+ props: {
+ labelCol: {
+ span: 10,
+ },
+ wrapperCol: {
+ span: 14,
+ },
+ onFinish: {
+ type: 'JSFunction',
+ value: 'function(){this.onFinish.apply(this,Array.prototype.slice.call(arguments).concat([])) }',
+ },
+ name: 'basic',
+ layout: 'inline',
+ __events: {
+ eventDataList: [
+ {
+ type: 'componentEvent',
+ name: 'onFinish',
+ relatedEventName: 'onFinish',
+ },
+ ],
+ eventList: [
+ {
+ name: 'onFinish',
+ disabled: true,
+ },
+ {
+ name: 'onFinishFailed',
+ disabled: false,
+ },
+ {
+ name: 'onFieldsChange',
+ disabled: false,
+ },
+ {
+ name: 'onValuesChange',
+ disabled: false,
+ },
+ ],
+ },
+ },
+ children: [
+ {
+ componentName: 'Form.Item',
+ id: 'node_ocks8dtt1mz',
+ props: {
+ label: '项目名称/渠道号',
+ name: 'channel_id',
+ },
+ children: [
+ {
+ componentName: 'Select',
+ id: 'node_ocksfuhwhsd',
+ props: {
+ style: {
+ width: '280px',
+ },
+ options: {
+ type: 'JSExpression',
+ value: 'this.state.projects',
+ },
+ showArrow: true,
+ tokenSeparators: [],
+ showSearch: true,
+ },
+ },
+ ],
+ },
+ {
+ componentName: 'Form.Item',
+ id: 'node_ocks8dtt1m12',
+ props: {
+ label: '版本号',
+ name: 'buildId',
+ },
+ children: [
+ {
+ componentName: 'Input',
+ id: 'node_ocksfuhwhs3',
+ props: {
+ placeholder: '请输入',
+ style: {
+ width: '280px',
+ },
+ size: 'middle',
+ },
+ },
+ ],
+ },
+ {
+ componentName: 'Form.Item',
+ id: 'node_ocks8dtt1m18',
+ props: {
+ label: '构建人',
+ name: 'user_id',
+ },
+ children: [
+ {
+ componentName: 'Select',
+ id: 'node_ocksfuhwhsi',
+ props: {
+ style: {
+ width: 200,
+ },
+ options: [
+ {
+ label: 'A',
+ value: 'A',
+ },
+ {
+ label: 'B',
+ value: 'B',
+ },
+ {
+ label: 'C',
+ value: 'C',
+ },
+ ],
+ showSearch: true,
+ },
+ },
+ ],
+ },
+ {
+ componentName: 'Form.Item',
+ id: 'node_ocks8dtt1m19',
+ props: {
+ label: 'ID',
+ name: 'id',
+ },
+ children: [
+ {
+ componentName: 'Input',
+ id: 'node_ocksfuhwhs8',
+ props: {
+ placeholder: '请输入',
+ style: {
+ width: '160px',
+ },
+ },
+ },
+ ],
+ },
+ {
+ componentName: 'Form.Item',
+ id: 'node_ocks8dtt1mw',
+ props: {
+ wrapperCol: {
+ offset: 6,
+ },
+ },
+ children: [
+ {
+ componentName: 'Button',
+ id: 'node_ocks8dtt1mx',
+ props: {
+ type: 'primary',
+ children: '查询',
+ htmlType: 'submit',
+ },
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ {
+ componentName: 'NextBlock',
+ id: 'node_ockshc4ifn1b',
+ props: {
+ childTotalColumns: 12,
+ },
+ title: '区块',
+ children: [
+ {
+ componentName: 'NextBlockCell',
+ id: 'node_ockshc4ifn1c',
+ props: {
+ isAutoContainer: true,
+ colSpan: 12,
+ rowSpan: 1,
+ },
+ title: '子区块',
+ children: [
+ {
+ componentName: 'NextP',
+ id: 'node_ockshc4ifn1d',
+ props: {
+ wrap: false,
+ type: 'body2',
+ verAlign: 'middle',
+ textSpacing: true,
+ align: 'left',
+ flex: true,
+ },
+ title: '段落',
+ children: [
+ {
+ componentName: 'ConfigProvider',
+ id: 'node_ockshc4ifn1e',
+ props: {
+ locale: 'zh-CN',
+ },
+ children: [
+ {
+ componentName: 'AliAutoSearchTableDefault',
+ id: 'node_ocksfuhwhsx',
+ props: {
+ rowKey: 'key',
+ dataSource: {
+ type: 'JSExpression',
+ value: 'this.state.pkgs',
+ },
+ columns: [
+ {
+ title: 'ID',
+ dataIndex: 'id',
+ key: 'name',
+ width: 80,
+ },
+ {
+ title: '渠道号',
+ dataIndex: 'channels',
+ key: 'age',
+ width: 142,
+ render: {
+ type: 'JSSlot',
+ params: ['text', 'record', 'index'],
+ value: [
+ {
+ componentName: 'Typography.Text',
+ id: 'node_ocksh2bq0428',
+ props: {
+ children: {
+ type: 'JSExpression',
+ value: 'this.item',
+ },
+ style: {
+ display: 'block',
+ },
+ },
+ loop: {
+ type: 'JSExpression',
+ value: "this.text.split(',')",
+ },
+ },
+ ],
+ },
+ },
+ {
+ title: '版本号',
+ dataIndex: 'dic_version',
+ key: 'address',
+ render: {
+ type: 'JSSlot',
+ params: ['text', 'record', 'index'],
+ value: [
+ {
+ componentName: 'Tooltip',
+ id: 'node_ocksso4xavj',
+ props: {
+ title: {
+ type: 'JSSlot',
+ value: [
+ {
+ componentName: 'Typography.Text',
+ id: 'node_ocksso4xavn',
+ props: {
+ children: {
+ type: 'JSExpression',
+ value: "this.item. channelId + ' / ' + this.item.version",
+ },
+ style: {
+ display: 'block',
+ color: '#FFFFFF',
+ },
+ },
+ loop: {
+ type: 'JSExpression',
+ value: 'this.text || []',
+ },
+ },
+ ],
+ },
+ },
+ children: [
+ {
+ componentName: 'Typography.Text',
+ id: 'node_ocksso4xavm',
+ props: {
+ children: {
+ type: 'JSExpression',
+ value: 'this.text[0].version',
+ },
+ },
+ },
+ ],
+ },
+ ],
+ },
+ width: 120,
+ },
+ {
+ title: '构建Job',
+ dataIndex: 'job_name',
+ width: 180,
+ },
+ {
+ title: '构建类型',
+ dataIndex: 'packaging_type',
+ width: 94,
+ },
+ {
+ title: '构建状态',
+ dataIndex: 'status',
+ render: {
+ type: 'JSSlot',
+ params: ['text', 'record', 'index'],
+ value: [
+ {
+ componentName: 'Typography.Text',
+ id: 'node_ocksh3jkxzw',
+ props: {
+ children: {
+ type: 'JSExpression',
+ value: 'this.statusDesc[this.text]',
+ },
+ },
+ },
+ {
+ componentName: 'Icon',
+ id: 'node_ocksh3jkxzx',
+ props: {
+ type: 'SyncOutlined',
+ size: 16,
+ spin: true,
+ style: {
+ marginLeft: '10px',
+ },
+ },
+ condition: {
+ type: 'JSExpression',
+ value: 'this.text === 2',
+ },
+ },
+ ],
+ },
+ width: 100,
+ },
+ {
+ title: '构建时间',
+ dataIndex: 'start_time',
+ render: {
+ type: 'JSFunction',
+ value: 'function(){ return this.renderTime.apply(this,Array.prototype.slice.call(arguments).concat([])) }',
+ },
+ width: 148,
+ },
+ {
+ title: '构建人',
+ dataIndex: 'user',
+ render: {
+ type: 'JSFunction',
+ value: 'function(){ return this.renderUserName.apply(this,Array.prototype.slice.call(arguments).concat([])) }',
+ },
+ width: 80,
+ },
+ {
+ title: 'Jenkins 链接',
+ dataIndex: 'jenkins_link',
+ render: {
+ type: 'JSSlot',
+ params: ['text', 'record', 'index'],
+ value: [
+ {
+ componentName: 'Typography.Link',
+ id: 'node_ocksh64kbx21',
+ props: {
+ href: {
+ type: 'JSExpression',
+ value: 'this.text',
+ },
+ target: '_blank',
+ children: '查看',
+ },
+ condition: {
+ type: 'JSExpression',
+ value: 'this.text',
+ },
+ },
+ {
+ componentName: 'Typography.Text',
+ id: 'node_ocksh64kbx22',
+ props: {
+ children: '暂无',
+ },
+ condition: {
+ type: 'JSExpression',
+ value: '!this.text',
+ },
+ },
+ ],
+ },
+ width: 120,
+ },
+ {
+ title: '测试平台链接',
+ dataIndex: 'is_run_testing',
+ width: 120,
+ render: {
+ type: 'JSSlot',
+ params: ['text', 'record', 'index'],
+ value: [
+ {
+ componentName: 'Typography.Link',
+ id: 'node_ocksh3jkxz3e',
+ props: {
+ href: 'http://rivermap.alibaba.net/dashboard/testExecute',
+ target: '_blank',
+ children: '查看',
+ },
+ condition: {
+ type: 'JSExpression',
+ value: 'this.text',
+ },
+ },
+ {
+ componentName: 'Typography.Text',
+ id: 'node_ocksh3jkxz3f',
+ props: {
+ children: '暂无',
+ },
+ condition: {
+ type: 'JSExpression',
+ value: '!this.text',
+ },
+ },
+ ],
+ },
+ },
+ {
+ title: '触发源',
+ dataIndex: 'source',
+ width: 120,
+ },
+ {
+ title: '详情',
+ dataIndex: 'id',
+ render: {
+ type: 'JSSlot',
+ params: ['text', 'record', 'index'],
+ value: [
+ {
+ componentName: 'Button',
+ id: 'node_ocksh8yryw7',
+ props: {
+ type: 'link',
+ children: '查看',
+ size: 'small',
+ style: {
+ padding: '0px',
+ },
+ __events: {
+ eventDataList: [
+ {
+ type: 'componentEvent',
+ name: 'onClick',
+ relatedEventName: 'handleDetail',
+ },
+ ],
+ eventList: [
+ {
+ name: 'onClick',
+ disabled: true,
+ },
+ ],
+ },
+ onClick: {
+ type: 'JSFunction',
+ value: 'function(){this.handleDetail.apply(this,Array.prototype.slice.call(arguments).concat([])) }',
+ },
+ },
+ },
+ ],
+ },
+ width: 80,
+ fixed: 'right',
+ },
+ {
+ title: '结果',
+ dataIndex: 'id',
+ render: {
+ type: 'JSSlot',
+ params: ['text', 'record', 'index'],
+ value: [
+ {
+ componentName: 'Button',
+ id: 'node_ocksh9v6jw7',
+ props: {
+ type: 'link',
+ children: '查看',
+ size: 'small',
+ style: {
+ padding: '0px',
+ },
+ __events: {
+ eventDataList: [
+ {
+ type: 'componentEvent',
+ name: 'onClick',
+ relatedEventName: 'handleResult',
+ paramStr: 'this.text',
+ },
+ ],
+ eventList: [
+ {
+ name: 'onClick',
+ disabled: true,
+ },
+ ],
+ },
+ onClick: {
+ type: 'JSFunction',
+ value: 'function(){this.handleResult.apply(this,Array.prototype.slice.call(arguments).concat([])) }',
+ },
+ ghost: false,
+ href: {
+ type: 'JSExpression',
+ value: 'this.text',
+ },
+ },
+ },
+ ],
+ },
+ width: 80,
+ fixed: 'right',
+ },
+ {
+ title: '重新执行',
+ dataIndex: 'id',
+ width: 92,
+ render: {
+ type: 'JSSlot',
+ params: ['text', 'record', 'index'],
+ value: [
+ {
+ componentName: 'Button',
+ id: 'node_ocksh96rad1g',
+ props: {
+ type: 'text',
+ children: '',
+ icon: {
+ type: 'JSSlot',
+ value: [
+ {
+ componentName: 'Icon',
+ id: 'node_ocksh96rad1j',
+ props: {
+ type: 'ReloadOutlined',
+ size: 14,
+ color: '#0593d3',
+ style: {
+ padding: '3px',
+ border: '1px solid #0593d3',
+ borderRadius: '14px',
+ cursor: 'pointer',
+ height: '22px',
+ },
+ spin: false,
+ },
+ },
+ ],
+ },
+ shape: 'circle',
+ __events: {
+ eventDataList: [
+ {
+ type: 'componentEvent',
+ name: 'onClick',
+ relatedEventName: 'reload',
+ },
+ ],
+ eventList: [
+ {
+ name: 'onClick',
+ disabled: true,
+ },
+ ],
+ },
+ onClick: {
+ type: 'JSFunction',
+ value: 'function(){this.reload.apply(this,Array.prototype.slice.call(arguments).concat([])) }',
+ },
+ },
+ },
+ ],
+ },
+ fixed: 'right',
+ },
+ ],
+ actions: [],
+ pagination: {
+ total: {
+ type: 'JSExpression',
+ value: 'this.state.total',
+ },
+ defaultPageSize: 8,
+ onPageChange: {
+ type: 'JSFunction',
+ value: 'function(){ return this.onPageChange.apply(this,Array.prototype.slice.call(arguments).concat([])) }',
+ },
+ },
+ scrollX: 1200,
+ },
+ condition: {
+ type: 'JSExpression',
+ value: '!this.state.isSearch || (this.state.isSearch && this.state.pkgs.length > 0)',
+ },
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ {
+ componentName: 'NextBlock',
+ id: 'node_ocksk6f8fa3b',
+ props: {
+ childTotalColumns: 12,
+ },
+ title: '区块',
+ children: [
+ {
+ componentName: 'NextBlockCell',
+ id: 'node_ocksk6f8fa3c',
+ props: {
+ isAutoContainer: true,
+ colSpan: 12,
+ rowSpan: 1,
+ },
+ title: '子区块',
+ children: [
+ {
+ componentName: 'NextP',
+ id: 'node_ocksk6f8fa3d',
+ props: {
+ wrap: false,
+ type: 'body2',
+ verAlign: 'middle',
+ textSpacing: true,
+ align: 'left',
+ flex: true,
+ },
+ title: '段落',
+ children: [
+ {
+ componentName: 'Empty',
+ id: 'node_ocksk6f8fa3e',
+ props: {
+ description: '暂无数据',
+ },
+ condition: {
+ type: 'JSExpression',
+ value: 'this.state.pkgs.length < 1 && this.state.isSearch',
+ },
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ i18n: {},
+}
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_11-jsslot-2/expected/demo-project/.browserslistrc b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_11-jsslot-2/expected/demo-project/.browserslistrc
new file mode 100644
index 0000000000..55a130413d
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_11-jsslot-2/expected/demo-project/.browserslistrc
@@ -0,0 +1,3 @@
+defaults
+ios_saf 9
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_11-jsslot-2/expected/demo-project/.gitignore b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_11-jsslot-2/expected/demo-project/.gitignore
new file mode 100644
index 0000000000..4ec178818e
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_11-jsslot-2/expected/demo-project/.gitignore
@@ -0,0 +1,25 @@
+
+# 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
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_11-jsslot-2/expected/demo-project/README.md b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_11-jsslot-2/expected/demo-project/README.md
new file mode 100644
index 0000000000..6d9dd75215
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_11-jsslot-2/expected/demo-project/README.md
@@ -0,0 +1 @@
+This project is generated by lowcode-code-generator & lowcode-solution-icejs3.
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_11-jsslot-2/expected/demo-project/ice.config.mts b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_11-jsslot-2/expected/demo-project/ice.config.mts
new file mode 100644
index 0000000000..e1d8a28141
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_11-jsslot-2/expected/demo-project/ice.config.mts
@@ -0,0 +1,90 @@
+import { join } from 'path';
+import { defineConfig } from '@ice/app';
+import _ from 'lodash';
+import fusion from '@ice/plugin-fusion';
+import locales from '@ice/plugin-moment-locales';
+import type { Plugin } from '@ice/app/esm/types';
+
+interface PluginOptions {
+ id: string;
+}
+
+const plugin: Plugin = (options) => ({
+ // name 可选,插件名称
+ name: 'plugin-name',
+ // setup 必选,用于定制工程构建配置
+ setup: ({ onGetConfig, modifyUserConfig }) => {
+ modifyUserConfig('codeSplitting', 'page');
+
+ onGetConfig((config) => {
+ config.entry = {
+ web: join(process.cwd(), '.ice/entry.client.tsx'),
+ };
+
+ config.cssFilename = '[name].css';
+
+ config.configureWebpack = config.configureWebpack || [];
+ config.configureWebpack?.push((webpackConfig) => {
+ if (webpackConfig.output) {
+ webpackConfig.output.filename = '[name].js';
+ webpackConfig.output.chunkFilename = '[name].js';
+ }
+ return webpackConfig;
+ });
+
+ config.swcOptions = _.merge(config.swcOptions, {
+ compilationConfig: {
+ jsc: {
+ transform: {
+ react: {
+ runtime: 'classic',
+ },
+ },
+ },
+ },
+ });
+
+ // 解决 webpack publicPath 问题
+ config.transforms = config.transforms || [];
+ config.transforms.push((source: string, id: string) => {
+ if (id.includes('.ice/entry.client.tsx')) {
+ let code = `
+ if (!__webpack_public_path__?.startsWith('http') && document.currentScript) {
+ // @ts-ignore
+ __webpack_public_path__ = document.currentScript.src.replace(/^(.*\\/)[^/]+$/, '$1');
+ window.__ICE_ASSETS_MANIFEST__ = window.__ICE_ASSETS_MANIFEST__ || {};
+ window.__ICE_ASSETS_MANIFEST__.publicPath = __webpack_public_path__;
+ }
+ `;
+ code += source;
+ return { code };
+ }
+ });
+ });
+ },
+});
+
+// The project config, see https://v3.ice.work/docs/guide/basic/config
+const minify = process.env.NODE_ENV === 'production' ? 'swc' : false;
+export default defineConfig(() => ({
+ ssr: false,
+ ssg: false,
+ minify,
+
+ externals: {
+ react: 'React',
+ 'react-dom': 'ReactDOM',
+ 'react-dom/client': 'ReactDOM',
+ '@alifd/next': 'Next',
+ lodash: 'var window._',
+ '@alilc/lowcode-engine': 'var window.AliLowCodeEngine',
+ },
+ plugins: [
+ fusion({
+ importStyle: 'sass',
+ }),
+ locales(),
+ plugin(),
+ ],
+}));
+
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_11-jsslot-2/expected/demo-project/package.json b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_11-jsslot-2/expected/demo-project/package.json
new file mode 100644
index 0000000000..89f693efcb
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_11-jsslot-2/expected/demo-project/package.json
@@ -0,0 +1,46 @@
+{
+ "name": "icejs3-demo-app",
+ "version": "0.1.5",
+ "description": "icejs 3 轻量级模板,使用 JavaScript,仅包含基础的 Layout。",
+ "dependencies": {
+ "moment": "^2.24.0",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
+ "react-router": "^6.9.0",
+ "react-router-dom": "^6.9.0",
+ "intl-messageformat": "^9.3.6",
+ "@alifd/next": "1.26.15",
+ "@ice/runtime": "~1.1.0",
+ "@alilc/lowcode-datasource-engine": "^1.0.0",
+ "undefined": "*",
+ "@alilc/antd-lowcode-materials": "0.11.0",
+ "@alife/mc-assets-1935": "0.1.43",
+ "@alife/container": "0.3.7"
+ },
+ "devDependencies": {
+ "@ice/app": "~3.1.0",
+ "@types/react": "^18.0.0",
+ "@types/react-dom": "^18.0.0",
+ "@types/node": "^18.11.17",
+ "@ice/plugin-fusion": "^1.0.1",
+ "@ice/plugin-moment-locales": "^1.0.0",
+ "eslint": "^6.0.1",
+ "stylelint": "^13.2.0"
+ },
+ "scripts": {
+ "start": "ice start",
+ "build": "ice build",
+ "lint": "npm run eslint && npm run stylelint",
+ "eslint": "eslint --cache --ext .js,.jsx ./",
+ "stylelint": "stylelint ./**/*.scss"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "repository": {
+ "type": "git",
+ "url": "http://gitlab.xxx.com/msd/leak-scan/tree/master"
+ },
+ "private": true,
+ "originTemplate": "@alifd/scaffold-lite-js"
+}
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_11-jsslot-2/expected/demo-project/src/app.ts b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_11-jsslot-2/expected/demo-project/src/app.ts
new file mode 100644
index 0000000000..6d5856292d
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_11-jsslot-2/expected/demo-project/src/app.ts
@@ -0,0 +1,13 @@
+import { defineAppConfig } from 'ice';
+
+// App config, see https://v3.ice.work/docs/guide/basic/app
+export default defineAppConfig(() => ({
+ // Set your configs here.
+ app: {
+ rootId: 'App',
+ },
+ router: {
+ type: 'browser',
+ basename: '/',
+ },
+}));
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_11-jsslot-2/expected/demo-project/src/constants.js b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_11-jsslot-2/expected/demo-project/src/constants.js
new file mode 100644
index 0000000000..ea766c9da3
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_11-jsslot-2/expected/demo-project/src/constants.js
@@ -0,0 +1,3 @@
+const __$$constants = {};
+
+export default __$$constants;
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_11-jsslot-2/expected/demo-project/src/document.tsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_11-jsslot-2/expected/demo-project/src/document.tsx
new file mode 100644
index 0000000000..aff0231d95
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_11-jsslot-2/expected/demo-project/src/document.tsx
@@ -0,0 +1,29 @@
+import React from 'react';
+import { Meta, Title, Links, Main, Scripts } from 'ice';
+
+export default function Document() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_11-jsslot-2/expected/demo-project/src/global.scss b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_11-jsslot-2/expected/demo-project/src/global.scss
new file mode 100644
index 0000000000..82ca3eac73
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_11-jsslot-2/expected/demo-project/src/global.scss
@@ -0,0 +1,6 @@
+// 引入默认全局样式
+@import '@alifd/next/reset.scss';
+
+body {
+ -webkit-font-smoothing: antialiased;
+}
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_11-jsslot-2/expected/demo-project/src/i18n.js b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_11-jsslot-2/expected/demo-project/src/i18n.js
new file mode 100644
index 0000000000..1334d2502b
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_11-jsslot-2/expected/demo-project/src/i18n.js
@@ -0,0 +1,77 @@
+const i18nConfig = {};
+
+let locale =
+ typeof navigator === 'object' && typeof navigator.language === 'string'
+ ? navigator.language
+ : 'zh-CN';
+
+const getLocale = () => locale;
+
+const setLocale = (target) => {
+ locale = target;
+};
+
+const isEmptyVariables = (variables) =>
+ (Array.isArray(variables) && variables.length === 0) ||
+ (typeof variables === 'object' &&
+ (!variables || Object.keys(variables).length === 0));
+
+// 按低代码规范里面的要求进行变量替换
+const format = (msg, variables) =>
+ typeof msg === 'string'
+ ? msg.replace(/\$?\{(\w+)\}/g, (match, key) => variables?.[key] ?? '')
+ : msg;
+
+const i18nFormat = ({ id, defaultMessage, fallback }, variables) => {
+ const msg =
+ i18nConfig[locale]?.[id] ??
+ i18nConfig[locale.replace('-', '_')]?.[id] ??
+ defaultMessage;
+ if (msg == null) {
+ console.warn('[i18n]: unknown message id: %o (locale=%o)', id, locale);
+ return fallback === undefined ? `${id}` : fallback;
+ }
+
+ return format(msg, variables);
+};
+
+const i18n = (id, params) => {
+ return i18nFormat({ id }, params);
+};
+
+// 将国际化的一些方法注入到目标对象&上下文中
+const _inject2 = (target) => {
+ target.i18n = i18n;
+ target.getLocale = getLocale;
+ target.setLocale = (locale) => {
+ setLocale(locale);
+ target.forceUpdate();
+ };
+ target._i18nText = (t) => {
+ // 优先取直接传过来的语料
+ const localMsg = t[locale] ?? t[String(locale).replace('-', '_')];
+ if (localMsg != null) {
+ return format(localMsg, t.params);
+ }
+
+ // 其次用项目级别的
+ const projectMsg = i18nFormat({ id: t.key, fallback: null }, t.params);
+ if (projectMsg != null) {
+ return projectMsg;
+ }
+
+ // 兜底用 use 指定的或默认语言的
+ return format(t[t.use || 'zh-CN'] ?? t.en_US, t.params);
+ };
+
+ // 注入到上下文中去
+ if (target._context && target._context !== target) {
+ Object.assign(target._context, {
+ i18n,
+ getLocale,
+ setLocale: target.setLocale,
+ });
+ }
+};
+
+export { getLocale, setLocale, i18n, i18nFormat, _inject2 };
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_11-jsslot-2/expected/demo-project/src/layouts/BasicLayout/components/Footer/index.jsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_11-jsslot-2/expected/demo-project/src/layouts/BasicLayout/components/Footer/index.jsx
new file mode 100644
index 0000000000..cc70d53bea
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_11-jsslot-2/expected/demo-project/src/layouts/BasicLayout/components/Footer/index.jsx
@@ -0,0 +1,14 @@
+
+import React from 'react';
+import styles from './index.module.scss';
+
+export default function Footer() {
+ return (
+
+ Alibaba Fusion
+
+ © 2019-现在 Alibaba Fusion & ICE
+
+ );
+}
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_11-jsslot-2/expected/demo-project/src/layouts/BasicLayout/components/Footer/index.module.scss b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_11-jsslot-2/expected/demo-project/src/layouts/BasicLayout/components/Footer/index.module.scss
new file mode 100644
index 0000000000..81e77fda5f
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_11-jsslot-2/expected/demo-project/src/layouts/BasicLayout/components/Footer/index.module.scss
@@ -0,0 +1,15 @@
+
+.footer {
+ line-height: 20px;
+ text-align: center;
+}
+
+.logo {
+ font-weight: bold;
+ font-size: 16px;
+}
+
+.copyright {
+ font-size: 12px;
+}
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_11-jsslot-2/expected/demo-project/src/layouts/BasicLayout/components/Logo/index.jsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_11-jsslot-2/expected/demo-project/src/layouts/BasicLayout/components/Logo/index.jsx
new file mode 100644
index 0000000000..265bfdaa07
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_11-jsslot-2/expected/demo-project/src/layouts/BasicLayout/components/Logo/index.jsx
@@ -0,0 +1,16 @@
+
+import React from 'react';
+import { Link } from 'ice';
+import styles from './index.module.scss';
+
+export default function Logo({ image, text, url }) {
+ return (
+
+
+ {image &&

}
+
{text}
+
+
+ );
+}
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_11-jsslot-2/expected/demo-project/src/layouts/BasicLayout/components/Logo/index.module.scss b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_11-jsslot-2/expected/demo-project/src/layouts/BasicLayout/components/Logo/index.module.scss
new file mode 100644
index 0000000000..dad05a263f
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_11-jsslot-2/expected/demo-project/src/layouts/BasicLayout/components/Logo/index.module.scss
@@ -0,0 +1,20 @@
+
+.logo{
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: #FF7300;
+ font-weight: bold;
+ font-size: 14px;
+ line-height: 22px;
+
+ &:visited, &:link {
+ color: #FF7300;
+ }
+
+ img {
+ height: 24px;
+ margin-right: 10px;
+ }
+}
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_11-jsslot-2/expected/demo-project/src/layouts/BasicLayout/components/PageNav/index.jsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_11-jsslot-2/expected/demo-project/src/layouts/BasicLayout/components/PageNav/index.jsx
new file mode 100644
index 0000000000..911998b0d3
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_11-jsslot-2/expected/demo-project/src/layouts/BasicLayout/components/PageNav/index.jsx
@@ -0,0 +1,68 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { Link, useLocation } from 'ice';
+import { Nav } from '@alifd/next';
+import { asideMenuConfig } from '../../menuConfig';
+
+const { SubNav } = Nav;
+const NavItem = Nav.Item;
+
+function getNavMenuItems(menusData) {
+ if (!menusData) {
+ return [];
+ }
+
+ return menusData
+ .filter(item => item.name && !item.hideInMenu)
+ .map((item, index) => getSubMenuOrItem(item, index));
+}
+
+function getSubMenuOrItem(item, index) {
+ if (item.children && item.children.some(child => child.name)) {
+ const childrenItems = getNavMenuItems(item.children);
+
+ if (childrenItems && childrenItems.length > 0) {
+ const subNav = (
+
+ {childrenItems}
+
+ );
+ return subNav;
+ }
+
+ return null;
+ }
+
+ const navItem = (
+
+ {item.name}
+
+ );
+ return navItem;
+}
+
+const Navigation = (props, context) => {
+ const location = useLocation();
+ const { pathname } = location;
+ const { isCollapse } = context;
+ return (
+
+ );
+};
+
+Navigation.contextTypes = {
+ isCollapse: PropTypes.bool,
+};
+export default Navigation;
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_11-jsslot-2/expected/demo-project/src/layouts/BasicLayout/index.jsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_11-jsslot-2/expected/demo-project/src/layouts/BasicLayout/index.jsx
new file mode 100644
index 0000000000..18db44df5e
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_11-jsslot-2/expected/demo-project/src/layouts/BasicLayout/index.jsx
@@ -0,0 +1,81 @@
+
+import React, { useState } from 'react';
+import { Shell, ConfigProvider } from '@alifd/next';
+import PageNav from './components/PageNav';
+import Logo from './components/Logo';
+import Footer from './components/Footer';
+
+(function() {
+ const throttle = function(type, name, obj = window) {
+ let running = false;
+
+ const func = () => {
+ if (running) {
+ return;
+ }
+
+ running = true;
+ requestAnimationFrame(() => {
+ obj.dispatchEvent(new CustomEvent(name));
+ running = false;
+ });
+ };
+
+ obj.addEventListener(type, func);
+ };
+
+ throttle('resize', 'optimizedResize');
+})();
+
+export default function BasicLayout({ children }) {
+ const getDevice = width => {
+ const isPhone =
+ typeof navigator !== 'undefined' && navigator && navigator.userAgent.match(/phone/gi);
+
+ if (width < 680 || isPhone) {
+ return 'phone';
+ }
+ if (width < 1280 && width > 680) {
+ return 'tablet';
+ }
+ return 'desktop';
+ };
+
+ const [device, setDevice] = useState(getDevice(NaN));
+ window.addEventListener('optimizedResize', e => {
+ setDevice(getDevice(e && e.target && e.target.innerWidth));
+ });
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ {children}
+
+
+
+
+
+ );
+}
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_11-jsslot-2/expected/demo-project/src/layouts/BasicLayout/menuConfig.js b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_11-jsslot-2/expected/demo-project/src/layouts/BasicLayout/menuConfig.js
new file mode 100644
index 0000000000..5332202be4
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_11-jsslot-2/expected/demo-project/src/layouts/BasicLayout/menuConfig.js
@@ -0,0 +1,11 @@
+
+const headerMenuConfig = [];
+const asideMenuConfig = [
+ {
+ name: 'Dashboard',
+ path: '/',
+ icon: 'smile',
+ },
+];
+export { headerMenuConfig, asideMenuConfig };
+
\ No newline at end of file
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_11-jsslot-2/expected/demo-project/src/pages/Test/index.css b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_11-jsslot-2/expected/demo-project/src/pages/Test/index.css
new file mode 100644
index 0000000000..066114aeeb
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_11-jsslot-2/expected/demo-project/src/pages/Test/index.css
@@ -0,0 +1,8 @@
+body {
+ font-size: 12px;
+}
+
+.botton {
+ width: 100px;
+ color: #ff00ff;
+}
diff --git a/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_11-jsslot-2/expected/demo-project/src/pages/Test/index.jsx b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_11-jsslot-2/expected/demo-project/src/pages/Test/index.jsx
new file mode 100644
index 0000000000..5630342f37
--- /dev/null
+++ b/modules/code-generator/tests/fixtures/test-cases/icejs3-app/demo_11-jsslot-2/expected/demo-project/src/pages/Test/index.jsx
@@ -0,0 +1,976 @@
+// 注意: 出码引擎注入的临时变量默认都以 "__$$" 开头,禁止在搭建的代码中直接访问。
+// 例外:react 框架的导出名和各种组件名除外。
+import React from 'react';
+
+import {
+ Modal,
+ Button,
+ Typography,
+ Form,
+ Select,
+ Input,
+ Tooltip,
+ Icon,
+ Empty,
+} from '@alilc/antd-lowcode-materials/dist/antd-lowcode.esm.js';
+
+import {
+ AliAutoDiv,
+ AliAutoSearchTable,
+} from '@alife/mc-assets-1935/build/lowcode/index.js';
+
+import {
+ Page as NextPage,
+ Block as NextBlock,
+ P as NextP,
+} from '@alife/container/lib/index.js';
+
+import utils, { RefsManager } from '../../utils';
+
+import * as __$$i18n from '../../i18n';
+
+import __$$constants from '../../constants';
+
+import './index.css';
+
+const AliAutoDivDefault = AliAutoDiv.default;
+
+const AliAutoSearchTableDefault = AliAutoSearchTable.default;
+
+const NextBlockCell = NextBlock.Cell;
+
+class Test$$Page extends React.Component {
+ _context = this;
+
+ get constants() {
+ return __$$constants || {};
+ }
+
+ constructor(props, context) {
+ super(props);
+
+ this.utils = utils;
+
+ this._refsManager = new RefsManager();
+
+ __$$i18n._inject2(this);
+
+ this.state = {
+ pkgs: [],
+ total: 0,
+ isSearch: false,
+ projects: [],
+ results: [],
+ resultVisible: false,
+ userOptions: [],
+ searchValues: { user_id: '', channel_id: '' },
+ };
+
+ this.__jp__init();
+ this.statusDesc = {
+ 0: '失败',
+ 1: '成功',
+ 2: '构建中',
+ 3: '构建超时',
+ };
+ this.pageParams = {};
+ this.searchParams = {};
+ this.userTimeout = null;
+ this.currentUser = null;
+ this.notFoundContent = null;
+ this.projectTimeout = null;
+ this.currentProject = null;
+ }
+
+ $ = (refName) => {
+ return this._refsManager.get(refName);
+ };
+
+ $$ = (refName) => {
+ return this._refsManager.getAll(refName);
+ };
+
+ componentDidUpdate(prevProps, prevState, snapshot) {}
+
+ componentWillUnmount() {}
+
+ __jp__init() {
+ /*...*/
+ }
+
+ __jp__initRouter() {
+ /*...*/
+ }
+
+ __jp__initDataSource() {
+ /*...*/
+ }
+
+ __jp__initEnv() {
+ /*...*/
+ }
+
+ __jp__initConfig() {
+ /*...*/
+ }
+
+ __jp__initUtils() {
+ /*...*/
+ }
+
+ setSearchItem() {
+ /*...*/
+ }
+
+ fetchProject() {
+ /*...*/
+ }
+
+ handleProjectSearch() {
+ /*...*/
+ }
+
+ handleProjectChange(id) {
+ this.setSearchItem({
+ channel_id: id,
+ });
+ }
+
+ fetchUser() {
+ /*...*/
+ }
+
+ handleUserSearch() {
+ /*...*/
+ }
+
+ handleUserChange(user) {
+ console.log('debug user', user);
+ this.setSearchItem({
+ user_id: user,
+ });
+ }
+
+ fetchPkgs() {
+ /*...*/
+ }
+
+ onPageChange(pageIndex, pageSize) {
+ this.pageParams = {
+ pageIndex,
+ pageSize,
+ };
+ this.fetchPkgs();
+ }
+
+ renderTime(time) {
+ return this.$utils.moment(time).format('YYYY-MM-DD HH:mm');
+ }
+
+ renderUserName(user) {
+ return user.user_name;
+ }
+
+ reload() {
+ /*...*/
+ }
+
+ handleResult() {
+ /*...*/
+ }
+
+ handleDetail() {
+ /*...*/
+ }
+
+ onResultCancel() {
+ /*...*/
+ }
+
+ formatResult() {
+ /*...*/
+ }
+
+ handleDownload() {
+ /*...*/
+ }
+
+ onFinish() {
+ /*...*/
+ }
+
+ componentDidMount() {
+ this.$ds.resolve('PROJECTS');
+ if (this.userTimeout) {
+ clearTimeout(this.userTimeout);
+ this.userTimeout = null;
+ }
+ if (this.projectTimeout) {
+ clearTimeout(this.projectTimeout);
+ this.projectTimeout = null;
+ }
+ }
+
+ render() {
+ const __$$context = this._context || this;
+ const { state } = __$$context;
+ return (
+
+
this.state.resultVisible)}
+ footer={
+
+ }
+ __events={{
+ eventDataList: [
+ {
+ type: 'componentEvent',
+ name: 'onCancel',
+ relatedEventName: 'onResultCancel',
+ },
+ ],
+ eventList: [
+ { name: 'onCancel', disabled: true },
+ { name: 'onOk', disabled: false },
+ ],
+ }}
+ onCancel={function () {
+ this.onResultCancel.apply(
+ this,
+ Array.prototype.slice.call(arguments).concat([])
+ );
+ }.bind(this)}
+ width="720px"
+ centered={true}
+ closable={true}
+ keyboard={true}
+ mask={true}
+ maskClosable={true}
+ >
+
+ {!!__$$eval(
+ () => this.state.results && this.state.results.length > 0
+ ) && (
+
+
+
+ )}
+ {__$$evalArray(() => this.state.results).map((item, index) =>
+ ((__$$context) => (
+
+
+ {__$$eval(() => __$$context.formatResult(item))}
+
+ {!!__$$eval(() => item.download_link) && (
+ item.download_link)}
+ target="_blank"
+ >
+ {' '}
+ - 点击下载
+
+ )}
+ {!!__$$eval(() => item.release_notes) && (
+ item.release_notes)}
+ target="_blank"
+ >
+ {' '}
+ - 跳转发布节点
+
+ )}
+
+ ))(__$$createChildContext(__$$context, { item, index }))
+ )}
+
+
+
+
+
+
+
+
+