(methodName => ({
+ type: ChunkType.STRING,
+ fileType: FileType.JSX,
+ name: REACT_CHUNK_NAME.ClassMethod,
+ content: transformFuncExpr2MethodMember(
+ methodName,
+ (methods[methodName] as IJSExpression).value,
+ ),
+ linkAfter: [
+ REACT_CHUNK_NAME.ClassStart,
+ REACT_CHUNK_NAME.ClassConstructorEnd,
+ REACT_CHUNK_NAME.ClassLifeCycle,
+ ],
+ }));
+
+ next.chunks.push.apply(next.chunks, chunks);
+ }
+
+ return next;
+};
+
+export default plugin;
diff --git a/packages/code-generator/src/plugins/component/react/jsx.ts b/packages/code-generator/src/plugins/component/react/jsx.ts
new file mode 100644
index 000000000..ff2a3f8cf
--- /dev/null
+++ b/packages/code-generator/src/plugins/component/react/jsx.ts
@@ -0,0 +1,122 @@
+import { REACT_CHUNK_NAME } from './const';
+
+import { generateCompositeType } from '../../utils/compositeType';
+
+import {
+ BuilderComponentPlugin,
+ ChunkType,
+ FileType,
+ ICodeStruct,
+ IComponentNodeItem,
+ IContainerInfo,
+ IInlineStyle,
+ IJSExpression,
+} from '../../../types';
+
+function generateInlineStyle(style: IInlineStyle): string | null {
+ const attrLines = Object.keys(style).map((cssAttribute: string) => {
+ const [isString, valueStr] = generateCompositeType(style[cssAttribute]);
+ const valuePart = isString ? `'${valueStr}'` : valueStr;
+ return `${cssAttribute}: ${valuePart},`;
+ });
+
+ if (attrLines.length === 0) {
+ return null;
+ }
+
+ return `{ ${attrLines.join('')} }`;
+}
+
+function generateAttr(attrName: string, attrValue: any): string {
+ const [isString, valueStr] = generateCompositeType(attrValue);
+ return `${attrName}=${isString ? `"${valueStr}"` : `{${valueStr}}`}`;
+}
+
+function generateNode(nodeItem: IComponentNodeItem): string {
+ const codePieces: string[] = [];
+ const { className, style, ...props } = nodeItem.props;
+
+ codePieces.push(`<${nodeItem.componentName}`);
+ if (className) {
+ codePieces.push(`className="${className}"`);
+ }
+ if (style) {
+ const inlineStyle = generateInlineStyle(style);
+ if (inlineStyle !== null) {
+ codePieces.push(`style={${inlineStyle}}`);
+ }
+ }
+
+ const propLines = Object.keys(props).map((propName: string) =>
+ generateAttr(propName, props[propName]),
+ );
+ codePieces.push.apply(codePieces, propLines);
+
+ if (nodeItem.children && nodeItem.children.length > 0) {
+ codePieces.push('>');
+ const childrenLines = nodeItem.children.map(child => generateNode(child));
+ codePieces.push.apply(codePieces, childrenLines);
+ codePieces.push(`${nodeItem.componentName}>`);
+ } else {
+ codePieces.push('/>');
+ }
+
+ if (nodeItem.loop && nodeItem.loopArgs) {
+ let loopDataExp;
+ if ((nodeItem.loop as IJSExpression).type === 'JSExpression') {
+ loopDataExp = `(${(nodeItem.loop as IJSExpression).value})`;
+ } else {
+ loopDataExp = JSON.stringify(nodeItem.loop);
+ }
+ codePieces.unshift(
+ `${loopDataExp}.map((${nodeItem.loopArgs[0]}, ${nodeItem.loopArgs[1]}) => (`,
+ );
+ codePieces.push('))');
+ }
+
+ if (nodeItem.condition) {
+ codePieces.unshift(`(${generateCompositeType(nodeItem.condition)}) && (`);
+ codePieces.push(')');
+ }
+
+ if (nodeItem.condition || (nodeItem.loop && nodeItem.loopArgs)) {
+ codePieces.unshift('{');
+ codePieces.push('}');
+ }
+
+ return codePieces.join(' ');
+}
+
+const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
+ const next: ICodeStruct = {
+ ...pre,
+ };
+
+ const ir = next.ir as IContainerInfo;
+
+ let jsxContent: string;
+ if (!ir.children || ir.children.length === 0) {
+ jsxContent = 'null';
+ } else if (ir.children.length === 1) {
+ jsxContent = `(${generateNode(ir.children[0])})`;
+ } else {
+ jsxContent = `(${ir.children
+ .map(child => generateNode(child))
+ .join('')})`;
+ }
+
+ next.chunks.push({
+ type: ChunkType.STRING,
+ fileType: FileType.JSX,
+ name: REACT_CHUNK_NAME.ClassRenderJSX,
+ content: `return ${jsxContent};`,
+ linkAfter: [
+ REACT_CHUNK_NAME.ClassRenderStart,
+ REACT_CHUNK_NAME.ClassRenderPre,
+ ],
+ });
+
+ return next;
+};
+
+export default plugin;
diff --git a/packages/code-generator/src/plugins/component/react/reactCommonDeps.ts b/packages/code-generator/src/plugins/component/react/reactCommonDeps.ts
new file mode 100644
index 000000000..38936fc68
--- /dev/null
+++ b/packages/code-generator/src/plugins/component/react/reactCommonDeps.ts
@@ -0,0 +1,28 @@
+import { COMMON_CHUNK_NAME } from '../../../const/generator';
+
+import {
+ BuilderComponentPlugin,
+ ChunkType,
+ FileType,
+ ICodeStruct,
+ IContainerInfo,
+} from '../../../types';
+
+// TODO: How to merge this logic to common deps
+const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
+ const next: ICodeStruct = {
+ ...pre,
+ };
+
+ next.chunks.push({
+ type: ChunkType.STRING,
+ fileType: FileType.JSX,
+ name: COMMON_CHUNK_NAME.ExternalDepsImport,
+ content: `import React from 'react';`,
+ linkAfter: [],
+ });
+
+ return next;
+};
+
+export default plugin;
diff --git a/packages/code-generator/src/plugins/component/style/css.ts b/packages/code-generator/src/plugins/component/style/css.ts
new file mode 100644
index 000000000..c4ce2160e
--- /dev/null
+++ b/packages/code-generator/src/plugins/component/style/css.ts
@@ -0,0 +1,37 @@
+import { COMMON_CHUNK_NAME } from '../../../const/generator';
+
+import {
+ BuilderComponentPlugin,
+ ChunkType,
+ FileType,
+ ICodeStruct,
+ IContainerInfo,
+} from '../../../types';
+
+const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
+ const next: ICodeStruct = {
+ ...pre,
+ };
+
+ const ir = next.ir as IContainerInfo;
+
+ next.chunks.push({
+ type: ChunkType.STRING,
+ fileType: FileType.CSS,
+ name: COMMON_CHUNK_NAME.StyleCssContent,
+ content: ir.css,
+ linkAfter: [COMMON_CHUNK_NAME.StyleDepsImport],
+ });
+
+ next.chunks.push({
+ type: ChunkType.STRING,
+ fileType: FileType.JSX,
+ name: COMMON_CHUNK_NAME.InternalDepsImport,
+ content: `import './index.css';`,
+ linkAfter: [COMMON_CHUNK_NAME.ExternalDepsImport],
+ });
+
+ return next;
+};
+
+export default plugin;
diff --git a/packages/code-generator/src/plugins/project/constants.ts b/packages/code-generator/src/plugins/project/constants.ts
new file mode 100644
index 000000000..6e6e887b6
--- /dev/null
+++ b/packages/code-generator/src/plugins/project/constants.ts
@@ -0,0 +1,54 @@
+import { COMMON_CHUNK_NAME } from '@/const/generator';
+import { generateCompositeType } from '@/plugins/utils/compositeType';
+import {
+ BuilderComponentPlugin,
+ ChunkType,
+ FileType,
+ ICodeStruct,
+ IProjectInfo,
+} from '@/types';
+
+// TODO: How to merge this logic to common deps
+const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
+ const next: ICodeStruct = {
+ ...pre,
+ };
+
+ const ir = next.ir as IProjectInfo;
+ if (ir.constants) {
+ const [, constantStr] = generateCompositeType(ir.constants);
+
+ next.chunks.push({
+ type: ChunkType.STRING,
+ fileType: FileType.JS,
+ name: COMMON_CHUNK_NAME.FileVarDefine,
+ content: `
+ const constantConfig = ${constantStr};
+ `,
+ linkAfter: [
+ COMMON_CHUNK_NAME.ExternalDepsImport,
+ COMMON_CHUNK_NAME.InternalDepsImport,
+ ],
+ });
+
+ next.chunks.push({
+ type: ChunkType.STRING,
+ fileType: FileType.JS,
+ name: COMMON_CHUNK_NAME.FileExport,
+ content: `
+ export default constantConfig;
+ `,
+ linkAfter: [
+ COMMON_CHUNK_NAME.ExternalDepsImport,
+ COMMON_CHUNK_NAME.InternalDepsImport,
+ COMMON_CHUNK_NAME.FileVarDefine,
+ COMMON_CHUNK_NAME.FileUtilDefine,
+ COMMON_CHUNK_NAME.FileMainContent,
+ ],
+ });
+ }
+
+ return next;
+};
+
+export default plugin;
diff --git a/packages/code-generator/src/plugins/project/framework/icejs/plugins/entry.ts b/packages/code-generator/src/plugins/project/framework/icejs/plugins/entry.ts
new file mode 100644
index 000000000..488e573b5
--- /dev/null
+++ b/packages/code-generator/src/plugins/project/framework/icejs/plugins/entry.ts
@@ -0,0 +1,55 @@
+import { COMMON_CHUNK_NAME } from '@/const/generator';
+
+import {
+ BuilderComponentPlugin,
+ ChunkType,
+ FileType,
+ ICodeStruct,
+ IProjectInfo,
+} from '@/types';
+
+// TODO: How to merge this logic to common deps
+const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
+ const next: ICodeStruct = {
+ ...pre,
+ };
+
+ const ir = next.ir as IProjectInfo;
+
+ next.chunks.push({
+ type: ChunkType.STRING,
+ fileType: FileType.JS,
+ name: COMMON_CHUNK_NAME.ExternalDepsImport,
+ content: `
+ import { createApp } from 'ice';
+ `,
+ linkAfter: [],
+ });
+
+ next.chunks.push({
+ type: ChunkType.STRING,
+ fileType: FileType.JS,
+ name: COMMON_CHUNK_NAME.FileMainContent,
+ content: `
+ const appConfig = {
+ app: {
+ rootId: '${ir.config.targetRootID}',
+ },
+ router: {
+ type: '${ir.config.historyMode}',
+ },
+ };
+ createApp(appConfig);
+ `,
+ linkAfter: [
+ COMMON_CHUNK_NAME.ExternalDepsImport,
+ COMMON_CHUNK_NAME.InternalDepsImport,
+ COMMON_CHUNK_NAME.FileVarDefine,
+ COMMON_CHUNK_NAME.FileUtilDefine,
+ ],
+ });
+
+ return next;
+};
+
+export default plugin;
diff --git a/packages/code-generator/src/plugins/project/framework/icejs/plugins/entryHtml.ts b/packages/code-generator/src/plugins/project/framework/icejs/plugins/entryHtml.ts
new file mode 100644
index 000000000..7b12c4252
--- /dev/null
+++ b/packages/code-generator/src/plugins/project/framework/icejs/plugins/entryHtml.ts
@@ -0,0 +1,43 @@
+import { COMMON_CHUNK_NAME } from '@/const/generator';
+
+import {
+ BuilderComponentPlugin,
+ ChunkType,
+ FileType,
+ ICodeStruct,
+ IProjectInfo,
+} from '@/types';
+
+// TODO: How to merge this logic to common deps
+const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
+ const next: ICodeStruct = {
+ ...pre,
+ };
+
+ const ir = next.ir as IProjectInfo;
+
+ next.chunks.push({
+ type: ChunkType.STRING,
+ fileType: FileType.HTML,
+ name: COMMON_CHUNK_NAME.HtmlContent,
+ content: `
+
+
+
+
+
+
+ ${ir.meta.name}
+
+
+
+
+
+ `,
+ linkAfter: [],
+ });
+
+ return next;
+};
+
+export default plugin;
diff --git a/packages/code-generator/src/plugins/project/framework/icejs/plugins/globalStyle.ts b/packages/code-generator/src/plugins/project/framework/icejs/plugins/globalStyle.ts
new file mode 100644
index 000000000..9f3bbf142
--- /dev/null
+++ b/packages/code-generator/src/plugins/project/framework/icejs/plugins/globalStyle.ts
@@ -0,0 +1,53 @@
+import { COMMON_CHUNK_NAME } from '@/const/generator';
+
+import {
+ BuilderComponentPlugin,
+ ChunkType,
+ FileType,
+ ICodeStruct,
+ IProjectInfo,
+} from '@/types';
+
+// TODO: How to merge this logic to common deps
+const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
+ const next: ICodeStruct = {
+ ...pre,
+ };
+
+ const ir = next.ir as IProjectInfo;
+
+ next.chunks.push({
+ type: ChunkType.STRING,
+ fileType: FileType.CSS,
+ name: COMMON_CHUNK_NAME.StyleDepsImport,
+ content: `
+ // 引入默认全局样式
+ @import '@alifd/next/reset.scss';
+ `,
+ linkAfter: [],
+ });
+
+ next.chunks.push({
+ type: ChunkType.STRING,
+ fileType: FileType.CSS,
+ name: COMMON_CHUNK_NAME.StyleCssContent,
+ content: `
+ body {
+ -webkit-font-smoothing: antialiased;
+ }
+ `,
+ linkAfter: [COMMON_CHUNK_NAME.StyleDepsImport],
+ });
+
+ next.chunks.push({
+ type: ChunkType.STRING,
+ fileType: FileType.CSS,
+ name: COMMON_CHUNK_NAME.StyleCssContent,
+ content: ir.css || '',
+ linkAfter: [COMMON_CHUNK_NAME.StyleDepsImport],
+ });
+
+ return next;
+};
+
+export default plugin;
diff --git a/packages/code-generator/src/plugins/project/framework/icejs/plugins/packageJSON.ts b/packages/code-generator/src/plugins/project/framework/icejs/plugins/packageJSON.ts
new file mode 100644
index 000000000..b5cac7b4f
--- /dev/null
+++ b/packages/code-generator/src/plugins/project/framework/icejs/plugins/packageJSON.ts
@@ -0,0 +1,87 @@
+import { COMMON_CHUNK_NAME } from '@/const/generator';
+
+import {
+ BuilderComponentPlugin,
+ ChunkType,
+ FileType,
+ ICodeStruct,
+ IPackageJSON,
+ IProjectInfo,
+} from '@/types';
+
+interface IIceJsPackageJSON extends IPackageJSON {
+ ideMode: {
+ name: string;
+ };
+ iceworks: {
+ type: string;
+ adapter: string;
+ };
+ originTemplate: string;
+}
+
+// TODO: How to merge this logic to common deps
+const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
+ const next: ICodeStruct = {
+ ...pre,
+ };
+
+ const ir = next.ir as IProjectInfo;
+
+ const packageJson: IIceJsPackageJSON = {
+ name: '@alifd/scaffold-lite-js',
+ version: '0.1.5',
+ description: '轻量级模板,使用 JavaScript,仅包含基础的 Layout。',
+ dependencies: {
+ '@alifd/next': '^1.19.4',
+ moment: '^2.24.0',
+ react: '^16.4.1',
+ 'react-dom': '^16.4.1',
+ '@alifd/theme-design-pro': '^0.x',
+ },
+ devDependencies: {
+ '@ice/spec': '^1.0.0',
+ 'build-plugin-fusion': '^0.1.0',
+ 'build-plugin-moment-locales': '^0.1.0',
+ eslint: '^6.0.1',
+ 'ice.js': '^1.0.0',
+ stylelint: '^13.2.0',
+ '@ali/build-plugin-ice-def': '^0.1.0',
+ },
+ scripts: {
+ start: 'icejs start',
+ build: 'icejs build',
+ lint: 'npm run eslint && npm run stylelint',
+ eslint: 'eslint --cache --ext .js,.jsx ./',
+ stylelint: 'stylelint ./**/*.scss',
+ },
+ ideMode: {
+ name: 'ice-react',
+ },
+ iceworks: {
+ type: 'react',
+ adapter: 'adapter-react-v3',
+ },
+ engines: {
+ node: '>=8.0.0',
+ },
+ repository: {
+ type: 'git',
+ url: 'http://gitlab.alibaba-inc.com/msd/leak-scan/tree/master',
+ },
+ private: true,
+ originTemplate: '@alifd/scaffold-lite-js',
+ };
+
+ next.chunks.push({
+ type: ChunkType.JSON,
+ fileType: FileType.JSON,
+ name: COMMON_CHUNK_NAME.FileMainContent,
+ content: packageJson,
+ linkAfter: [],
+ });
+
+ return next;
+};
+
+export default plugin;
diff --git a/packages/code-generator/src/plugins/project/framework/icejs/plugins/router.ts b/packages/code-generator/src/plugins/project/framework/icejs/plugins/router.ts
new file mode 100644
index 000000000..29a9dd995
--- /dev/null
+++ b/packages/code-generator/src/plugins/project/framework/icejs/plugins/router.ts
@@ -0,0 +1,79 @@
+import { COMMON_CHUNK_NAME } from '@/const/generator';
+
+import {
+ BuilderComponentPlugin,
+ ChunkType,
+ FileType,
+ ICodeStruct,
+ IRouterInfo,
+} from '@/types';
+
+// TODO: How to merge this logic to common deps
+const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
+ const next: ICodeStruct = {
+ ...pre,
+ };
+
+ const ir = next.ir as IRouterInfo;
+
+ next.chunks.push({
+ type: ChunkType.STRING,
+ fileType: FileType.JS,
+ name: COMMON_CHUNK_NAME.InternalDepsImport,
+ content: `
+ import BasicLayout from '@/layouts/BasicLayout';
+ `,
+ linkAfter: [COMMON_CHUNK_NAME.ExternalDepsImport],
+ });
+
+ next.chunks.push({
+ type: ChunkType.STRING,
+ fileType: FileType.JS,
+ name: COMMON_CHUNK_NAME.FileVarDefine,
+ content: `
+ const routerConfig = [
+ {
+ path: '/',
+ component: BasicLayout,
+ children: [
+ ${ir.routes
+ .map(
+ route => `
+ {
+ path: '${route.path}',
+ component: ${route.componentName},
+ }
+ `,
+ )
+ .join(',')}
+ ],
+ },
+ ];
+ `,
+ linkAfter: [
+ COMMON_CHUNK_NAME.ExternalDepsImport,
+ COMMON_CHUNK_NAME.InternalDepsImport,
+ COMMON_CHUNK_NAME.FileUtilDefine,
+ ],
+ });
+
+ next.chunks.push({
+ type: ChunkType.STRING,
+ fileType: FileType.JS,
+ name: COMMON_CHUNK_NAME.FileExport,
+ content: `
+ export default routerConfig;
+ `,
+ linkAfter: [
+ COMMON_CHUNK_NAME.ExternalDepsImport,
+ COMMON_CHUNK_NAME.InternalDepsImport,
+ COMMON_CHUNK_NAME.FileUtilDefine,
+ COMMON_CHUNK_NAME.FileVarDefine,
+ COMMON_CHUNK_NAME.FileMainContent,
+ ],
+ });
+
+ return next;
+};
+
+export default plugin;
diff --git a/packages/code-generator/src/plugins/project/framework/icejs/template/files/README.md.ts b/packages/code-generator/src/plugins/project/framework/icejs/template/files/README.md.ts
new file mode 100644
index 000000000..62933484a
--- /dev/null
+++ b/packages/code-generator/src/plugins/project/framework/icejs/template/files/README.md.ts
@@ -0,0 +1,73 @@
+import ResultFile from '@/model/ResultFile';
+import { IResultFile } from '@/types';
+
+export default function getFile(): [string[], IResultFile] {
+ const file = new ResultFile(
+ 'README',
+ 'md',
+ `
+## Scaffold Lite
+
+> 轻量级模板,使用 JavaScript,仅包含基础的 Layout。
+
+## 使用
+
+\`\`\`bash
+# 安装依赖
+$ npm install
+
+# 启动服务
+$ npm start # visit http://localhost:3333
+\`\`\`
+
+[More docs](https://ice.work/docs/guide/about).
+
+## 目录
+
+\`\`\`md
+├── build/ # 构建产物
+├── mock/ # 本地模拟数据
+│ ├── index.[j,t]s
+├── public/
+│ ├── index.html # 应用入口 HTML
+│ └── favicon.png # Favicon
+├── src/ # 源码路径
+│ ├── components/ # 自定义业务组件
+│ │ └── Guide/
+│ │ ├── index.[j,t]sx
+│ │ ├── index.module.scss
+│ ├── layouts/ # 布局组件
+│ │ └── BasicLayout/
+│ │ ├── index.[j,t]sx
+│ │ └── index.module.scss
+│ ├── pages/ # 页面
+│ │ └── Home/ # home 页面,约定路由转成小写
+│ │ ├── components/ # 页面级自定义业务组件
+│ │ ├── models.[j,t]sx # 页面级数据状态
+│ │ ├── index.[j,t]sx # 页面入口
+│ │ └── index.module.scss # 页面样式文件
+│ ├── configs/ # [可选] 配置文件
+│ │ └── menu.[j,t]s # [可选] 菜单配置
+│ ├── models/ # [可选] 应用级数据状态
+│ │ └── user.[j,t]s
+│ ├── utils/ # [可选] 工具库
+│ ├── global.scss # 全局样式
+│ ├── routes.[j,t]s # 路由配置
+│ └── app.[j,t]s[x] # 应用入口脚本
+├── build.json # 工程配置
+├── README.md
+├── package.json
+├── .editorconfig
+├── .eslintignore
+├── .eslintrc.[j,t]s
+├── .gitignore
+├── .stylelintignore
+├── .stylelintrc.[j,t]s
+├── .gitignore
+└── [j,t]sconfig.json
+\`\`\`
+ `,
+ );
+
+ return [[], file];
+}
diff --git a/packages/code-generator/src/plugins/project/framework/icejs/template/files/abc.json.ts b/packages/code-generator/src/plugins/project/framework/icejs/template/files/abc.json.ts
new file mode 100644
index 000000000..b7e2a9565
--- /dev/null
+++ b/packages/code-generator/src/plugins/project/framework/icejs/template/files/abc.json.ts
@@ -0,0 +1,17 @@
+import ResultFile from '@/model/ResultFile';
+import { IResultFile } from '@/types';
+
+export default function getFile(): [string[], IResultFile] {
+ const file = new ResultFile(
+ 'abc',
+ 'json',
+ `
+{
+ "type": "ice-app",
+ "builder": "@ali/builder-ice-app"
+}
+ `,
+ );
+
+ return [[], file];
+}
diff --git a/packages/code-generator/src/plugins/project/framework/icejs/template/files/build.json.ts b/packages/code-generator/src/plugins/project/framework/icejs/template/files/build.json.ts
new file mode 100644
index 000000000..359a1b6b5
--- /dev/null
+++ b/packages/code-generator/src/plugins/project/framework/icejs/template/files/build.json.ts
@@ -0,0 +1,33 @@
+import ResultFile from '@/model/ResultFile';
+import { IResultFile } from '@/types';
+
+export default function getFile(): [string[], IResultFile] {
+ const file = new ResultFile(
+ 'build',
+ 'json',
+ `
+{
+ "entry": "src/app.js",
+ "plugins": [
+ [
+ "build-plugin-fusion",
+ {
+ "themePackage": "@alifd/theme-design-pro"
+ }
+ ],
+ [
+ "build-plugin-moment-locales",
+ {
+ "locales": [
+ "zh-cn"
+ ]
+ }
+ ],
+ "@ali/build-plugin-ice-def"
+ ]
+}
+ `,
+ );
+
+ return [[], file];
+}
diff --git a/packages/code-generator/src/plugins/project/framework/icejs/template/files/editorconfig.ts b/packages/code-generator/src/plugins/project/framework/icejs/template/files/editorconfig.ts
new file mode 100644
index 000000000..445fe9647
--- /dev/null
+++ b/packages/code-generator/src/plugins/project/framework/icejs/template/files/editorconfig.ts
@@ -0,0 +1,25 @@
+import ResultFile from '@/model/ResultFile';
+import { IResultFile } from '@/types';
+
+export default function getFile(): [string[], IResultFile] {
+ const file = new ResultFile(
+ '.editorconfig',
+ '',
+ `
+# http://editorconfig.org
+root = true
+
+[*]
+indent_style = space
+indent_size = 2
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+
+[*.md]
+trim_trailing_whitespace = false
+ `,
+ );
+
+ return [[], file];
+}
diff --git a/packages/code-generator/src/plugins/project/framework/icejs/template/files/eslintignore.ts b/packages/code-generator/src/plugins/project/framework/icejs/template/files/eslintignore.ts
new file mode 100644
index 000000000..f115ec386
--- /dev/null
+++ b/packages/code-generator/src/plugins/project/framework/icejs/template/files/eslintignore.ts
@@ -0,0 +1,28 @@
+import ResultFile from '@/model/ResultFile';
+import { IResultFile } from '@/types';
+
+export default function getFile(): [string[], IResultFile] {
+ const file = new ResultFile(
+ '.eslintignore',
+ '',
+ `
+# 忽略目录
+build/
+tests/
+demo/
+.ice/
+
+# node 覆盖率文件
+coverage/
+
+# 忽略文件
+**/*-min.js
+**/*.min.js
+
+package-lock.json
+yarn.lock
+ `,
+ );
+
+ return [[], file];
+}
diff --git a/packages/code-generator/src/plugins/project/framework/icejs/template/files/eslintrc.js.ts b/packages/code-generator/src/plugins/project/framework/icejs/template/files/eslintrc.js.ts
new file mode 100644
index 000000000..02aed6eec
--- /dev/null
+++ b/packages/code-generator/src/plugins/project/framework/icejs/template/files/eslintrc.js.ts
@@ -0,0 +1,16 @@
+import ResultFile from '@/model/ResultFile';
+import { IResultFile } from '@/types';
+
+export default function getFile(): [string[], IResultFile] {
+ const file = new ResultFile(
+ '.eslintrc',
+ 'js',
+ `
+const { eslint } = require('@ice/spec');
+
+module.exports = eslint;
+ `,
+ );
+
+ return [[], file];
+}
diff --git a/packages/code-generator/src/plugins/project/framework/icejs/template/files/gitignore.ts b/packages/code-generator/src/plugins/project/framework/icejs/template/files/gitignore.ts
new file mode 100644
index 000000000..e4c31b7ca
--- /dev/null
+++ b/packages/code-generator/src/plugins/project/framework/icejs/template/files/gitignore.ts
@@ -0,0 +1,36 @@
+import ResultFile from '@/model/ResultFile';
+import { IResultFile } from '@/types';
+
+export default function getFile(): [string[], IResultFile] {
+ const file = new ResultFile(
+ '.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/packages/code-generator/src/plugins/project/framework/icejs/template/files/jsconfig.json.ts b/packages/code-generator/src/plugins/project/framework/icejs/template/files/jsconfig.json.ts
new file mode 100644
index 000000000..387a02b1b
--- /dev/null
+++ b/packages/code-generator/src/plugins/project/framework/icejs/template/files/jsconfig.json.ts
@@ -0,0 +1,24 @@
+import ResultFile from '@/model/ResultFile';
+import { IResultFile } from '@/types';
+
+export default function getFile(): [string[], IResultFile] {
+ const file = new ResultFile(
+ 'jsconfig',
+ 'json',
+ `
+{
+ "compilerOptions": {
+ "baseUrl": ".",
+ "jsx": "react",
+ "paths": {
+ "@/*": ["./src/*"],
+ "ice": [".ice/index.ts"],
+ "ice/*": [".ice/pages/*"]
+ }
+ }
+}
+ `,
+ );
+
+ return [[], file];
+}
diff --git a/packages/code-generator/src/plugins/project/framework/icejs/template/files/prettierignore.ts b/packages/code-generator/src/plugins/project/framework/icejs/template/files/prettierignore.ts
new file mode 100644
index 000000000..f50c843d1
--- /dev/null
+++ b/packages/code-generator/src/plugins/project/framework/icejs/template/files/prettierignore.ts
@@ -0,0 +1,22 @@
+import ResultFile from '@/model/ResultFile';
+import { IResultFile } from '@/types';
+
+export default function getFile(): [string[], IResultFile] {
+ const file = new ResultFile(
+ '.prettierignore',
+ '',
+ `
+build/
+tests/
+demo/
+.ice/
+coverage/
+**/*-min.js
+**/*.min.js
+package-lock.json
+yarn.lock
+ `,
+ );
+
+ return [[], file];
+}
diff --git a/packages/code-generator/src/plugins/project/framework/icejs/template/files/prettierrc.js.ts b/packages/code-generator/src/plugins/project/framework/icejs/template/files/prettierrc.js.ts
new file mode 100644
index 000000000..83b4dc8b7
--- /dev/null
+++ b/packages/code-generator/src/plugins/project/framework/icejs/template/files/prettierrc.js.ts
@@ -0,0 +1,16 @@
+import ResultFile from '@/model/ResultFile';
+import { IResultFile } from '@/types';
+
+export default function getFile(): [string[], IResultFile] {
+ const file = new ResultFile(
+ '.prettierrc',
+ 'js',
+ `
+const { prettier } = require('@ice/spec');
+
+module.exports = prettier;
+ `,
+ );
+
+ return [[], file];
+}
diff --git a/packages/code-generator/src/plugins/project/framework/icejs/template/files/src/layouts/BasicLayout/components/Footer/index.jsx.ts b/packages/code-generator/src/plugins/project/framework/icejs/template/files/src/layouts/BasicLayout/components/Footer/index.jsx.ts
new file mode 100644
index 000000000..aa1f1bd08
--- /dev/null
+++ b/packages/code-generator/src/plugins/project/framework/icejs/template/files/src/layouts/BasicLayout/components/Footer/index.jsx.ts
@@ -0,0 +1,25 @@
+import ResultFile from '@/model/ResultFile';
+import { IResultFile } from '@/types';
+
+export default function getFile(): [string[], IResultFile] {
+ const file = new ResultFile(
+ '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/packages/code-generator/src/plugins/project/framework/icejs/template/files/src/layouts/BasicLayout/components/Footer/index.module.scss.ts b/packages/code-generator/src/plugins/project/framework/icejs/template/files/src/layouts/BasicLayout/components/Footer/index.module.scss.ts
new file mode 100644
index 000000000..3f1f98511
--- /dev/null
+++ b/packages/code-generator/src/plugins/project/framework/icejs/template/files/src/layouts/BasicLayout/components/Footer/index.module.scss.ts
@@ -0,0 +1,26 @@
+import ResultFile from '@/model/ResultFile';
+import { IResultFile } from '@/types';
+
+export default function getFile(): [string[], IResultFile] {
+ const file = new ResultFile(
+ '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/packages/code-generator/src/plugins/project/framework/icejs/template/files/src/layouts/BasicLayout/components/Logo/index.jsx.ts b/packages/code-generator/src/plugins/project/framework/icejs/template/files/src/layouts/BasicLayout/components/Logo/index.jsx.ts
new file mode 100644
index 000000000..517972a5b
--- /dev/null
+++ b/packages/code-generator/src/plugins/project/framework/icejs/template/files/src/layouts/BasicLayout/components/Logo/index.jsx.ts
@@ -0,0 +1,27 @@
+import ResultFile from '@/model/ResultFile';
+import { IResultFile } from '@/types';
+
+export default function getFile(): [string[], IResultFile] {
+ const file = new ResultFile(
+ '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/packages/code-generator/src/plugins/project/framework/icejs/template/files/src/layouts/BasicLayout/components/Logo/index.module.scss.ts b/packages/code-generator/src/plugins/project/framework/icejs/template/files/src/layouts/BasicLayout/components/Logo/index.module.scss.ts
new file mode 100644
index 000000000..214dcf88a
--- /dev/null
+++ b/packages/code-generator/src/plugins/project/framework/icejs/template/files/src/layouts/BasicLayout/components/Logo/index.module.scss.ts
@@ -0,0 +1,31 @@
+import ResultFile from '@/model/ResultFile';
+import { IResultFile } from '@/types';
+
+export default function getFile(): [string[], IResultFile] {
+ const file = new ResultFile(
+ 'index',
+ 'module.scss',
+ `
+.logo{
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: $color-text1-1;
+ font-weight: bold;
+ font-size: 14px;
+ line-height: 22px;
+
+ &:visited, &:link {
+ color: $color-text1-1;
+ }
+
+ img {
+ height: 24px;
+ margin-right: 10px;
+ }
+}
+ `,
+ );
+
+ return [['src', 'layouts', 'BasicLayout', 'components', 'Logo'], file];
+}
diff --git a/packages/code-generator/src/plugins/project/framework/icejs/template/files/src/layouts/BasicLayout/components/PageNav/index.jsx.ts b/packages/code-generator/src/plugins/project/framework/icejs/template/files/src/layouts/BasicLayout/components/PageNav/index.jsx.ts
new file mode 100644
index 000000000..4e81e4b67
--- /dev/null
+++ b/packages/code-generator/src/plugins/project/framework/icejs/template/files/src/layouts/BasicLayout/components/PageNav/index.jsx.ts
@@ -0,0 +1,81 @@
+import ResultFile from '@/model/ResultFile';
+import { IResultFile } from '@/types';
+
+export default function getFile(): [string[], IResultFile] {
+ const file = new ResultFile(
+ 'index',
+ 'jsx',
+ `
+import React from 'react';
+import PropTypes from 'prop-types';
+import { Link, withRouter } 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 } = props;
+ const { pathname } = location;
+ const { isCollapse } = context;
+ return (
+
+ );
+};
+
+Navigation.contextTypes = {
+ isCollapse: PropTypes.bool,
+};
+const PageNav = withRouter(Navigation);
+export default PageNav;
+ `,
+ );
+
+ return [['src', 'layouts', 'BasicLayout', 'components', 'PageNav'], file];
+}
diff --git a/packages/code-generator/src/plugins/project/framework/icejs/template/files/src/layouts/BasicLayout/index.jsx.ts b/packages/code-generator/src/plugins/project/framework/icejs/template/files/src/layouts/BasicLayout/index.jsx.ts
new file mode 100644
index 000000000..6c3fd945e
--- /dev/null
+++ b/packages/code-generator/src/plugins/project/framework/icejs/template/files/src/layouts/BasicLayout/index.jsx.ts
@@ -0,0 +1,92 @@
+import ResultFile from '@/model/ResultFile';
+import { IResultFile } from '@/types';
+
+export default function getFile(): [string[], IResultFile] {
+ const file = new ResultFile(
+ '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/packages/code-generator/src/plugins/project/framework/icejs/template/files/src/layouts/BasicLayout/menuConfig.js.ts b/packages/code-generator/src/plugins/project/framework/icejs/template/files/src/layouts/BasicLayout/menuConfig.js.ts
new file mode 100644
index 000000000..1ed76f426
--- /dev/null
+++ b/packages/code-generator/src/plugins/project/framework/icejs/template/files/src/layouts/BasicLayout/menuConfig.js.ts
@@ -0,0 +1,22 @@
+import ResultFile from '@/model/ResultFile';
+import { IResultFile } from '@/types';
+
+export default function getFile(): [string[], IResultFile] {
+ const file = new ResultFile(
+ 'menuConfig',
+ 'js',
+ `
+const headerMenuConfig = [];
+const asideMenuConfig = [
+ {
+ name: 'Dashboard',
+ path: '/',
+ icon: 'smile',
+ },
+];
+export { headerMenuConfig, asideMenuConfig };
+ `,
+ );
+
+ return [['src', 'layouts', 'BasicLayout'], file];
+}
diff --git a/packages/code-generator/src/plugins/project/framework/icejs/template/files/stylelintignore.ts b/packages/code-generator/src/plugins/project/framework/icejs/template/files/stylelintignore.ts
new file mode 100644
index 000000000..ee0c9367f
--- /dev/null
+++ b/packages/code-generator/src/plugins/project/framework/icejs/template/files/stylelintignore.ts
@@ -0,0 +1,20 @@
+import ResultFile from '@/model/ResultFile';
+import { IResultFile } from '@/types';
+
+export default function getFile(): [string[], IResultFile] {
+ const file = new ResultFile(
+ '.stylelintignore',
+ '',
+ `
+# 忽略目录
+build/
+tests/
+demo/
+
+# node 覆盖率文件
+coverage/
+ `,
+ );
+
+ return [[], file];
+}
diff --git a/packages/code-generator/src/plugins/project/framework/icejs/template/files/stylelintrc.js.ts b/packages/code-generator/src/plugins/project/framework/icejs/template/files/stylelintrc.js.ts
new file mode 100644
index 000000000..51f420222
--- /dev/null
+++ b/packages/code-generator/src/plugins/project/framework/icejs/template/files/stylelintrc.js.ts
@@ -0,0 +1,16 @@
+import ResultFile from '@/model/ResultFile';
+import { IResultFile } from '@/types';
+
+export default function getFile(): [string[], IResultFile] {
+ const file = new ResultFile(
+ '.stylelintrc',
+ 'js',
+ `
+const { stylelint } = require('@ice/spec');
+
+module.exports = stylelint;
+ `,
+ );
+
+ return [[], file];
+}
diff --git a/packages/code-generator/src/plugins/project/framework/icejs/template/files/tsconfig.json.ts b/packages/code-generator/src/plugins/project/framework/icejs/template/files/tsconfig.json.ts
new file mode 100644
index 000000000..7d2c26261
--- /dev/null
+++ b/packages/code-generator/src/plugins/project/framework/icejs/template/files/tsconfig.json.ts
@@ -0,0 +1,46 @@
+import ResultFile from '@/model/ResultFile';
+import { IResultFile } from '@/types';
+
+export default function getFile(): [string[], IResultFile] {
+ const file = new ResultFile(
+ 'tsconfig',
+ 'json',
+ `
+{
+ "compileOnSave": false,
+ "buildOnSave": false,
+ "compilerOptions": {
+ "baseUrl": ".",
+ "outDir": "build",
+ "module": "esnext",
+ "target": "es6",
+ "jsx": "react",
+ "moduleResolution": "node",
+ "allowSyntheticDefaultImports": true,
+ "lib": ["es6", "dom"],
+ "sourceMap": true,
+ "allowJs": true,
+ "rootDir": "./",
+ "forceConsistentCasingInFileNames": true,
+ "noImplicitReturns": true,
+ "noImplicitThis": true,
+ "noImplicitAny": false,
+ "importHelpers": true,
+ "strictNullChecks": true,
+ "suppressImplicitAnyIndexErrors": true,
+ "noUnusedLocals": true,
+ "skipLibCheck": true,
+ "paths": {
+ "@/*": ["./src/*"],
+ "ice": [".ice/index.ts"],
+ "ice/*": [".ice/pages/*"]
+ }
+ },
+ "include": ["src/*", ".ice"],
+ "exclude": ["node_modules", "build", "public"]
+}
+ `,
+ );
+
+ return [[], file];
+}
diff --git a/packages/code-generator/src/plugins/project/framework/icejs/template/index.ts b/packages/code-generator/src/plugins/project/framework/icejs/template/index.ts
new file mode 100644
index 000000000..326290e3e
--- /dev/null
+++ b/packages/code-generator/src/plugins/project/framework/icejs/template/index.ts
@@ -0,0 +1,118 @@
+import ResultDir from '@/model/ResultDir';
+import { IProjectTemplate, IResultDir, IResultFile } from '@/types';
+
+import file12 from './files/abc.json';
+import file11 from './files/build.json';
+import file10 from './files/editorconfig';
+import file9 from './files/eslintignore';
+import file8 from './files/eslintrc.js';
+import file7 from './files/gitignore';
+import file6 from './files/jsconfig.json';
+import file5 from './files/prettierignore';
+import file4 from './files/prettierrc.js';
+import file13 from './files/README.md';
+import file16 from './files/src/layouts/BasicLayout/components/Footer/index.jsx';
+import file17 from './files/src/layouts/BasicLayout/components/Footer/index.module.scss';
+import file18 from './files/src/layouts/BasicLayout/components/Logo/index.jsx';
+import file19 from './files/src/layouts/BasicLayout/components/Logo/index.module.scss';
+import file20 from './files/src/layouts/BasicLayout/components/PageNav/index.jsx';
+import file14 from './files/src/layouts/BasicLayout/index.jsx';
+import file15 from './files/src/layouts/BasicLayout/menuConfig.js';
+import file3 from './files/stylelintignore';
+import file2 from './files/stylelintrc.js';
+import file1 from './files/tsconfig.json';
+
+type FuncFileGenerator = () => [string[], IResultFile];
+
+function insertFile(root: IResultDir, path: string[], file: IResultFile) {
+ let current: IResultDir = root;
+ path.forEach(pathNode => {
+ const dir = current.dirs.find(d => d.name === pathNode);
+ if (dir) {
+ current = dir;
+ } else {
+ const newDir = new ResultDir(pathNode);
+ current.addDirectory(newDir);
+ current = newDir;
+ }
+ });
+
+ current.addFile(file);
+}
+
+function runFileGenerator(root: IResultDir, fun: FuncFileGenerator) {
+ const [path, file] = fun();
+ insertFile(root, path, file);
+}
+
+const icejsTemplate: IProjectTemplate = {
+ slots: {
+ components: {
+ path: ['src', 'components'],
+ },
+ pages: {
+ path: ['src', 'pages'],
+ },
+ router: {
+ path: ['src'],
+ fileName: 'routes',
+ },
+ 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',
+ },
+ htmlEntry: {
+ path: ['public'],
+ fileName: 'index',
+ },
+ packageJSON: {
+ path: [],
+ fileName: 'package',
+ },
+ },
+
+ generateTemplate(): IResultDir {
+ const root = new 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);
+ runFileGenerator(root, file13);
+ runFileGenerator(root, file14);
+ runFileGenerator(root, file15);
+ runFileGenerator(root, file16);
+ runFileGenerator(root, file17);
+ runFileGenerator(root, file18);
+ runFileGenerator(root, file19);
+ runFileGenerator(root, file20);
+
+ return root;
+ },
+};
+
+export default icejsTemplate;
diff --git a/packages/code-generator/src/plugins/project/i18n.ts b/packages/code-generator/src/plugins/project/i18n.ts
new file mode 100644
index 000000000..59e05b8d4
--- /dev/null
+++ b/packages/code-generator/src/plugins/project/i18n.ts
@@ -0,0 +1,66 @@
+import { COMMON_CHUNK_NAME } from '@/const/generator';
+import { generateCompositeType } from '@/plugins/utils/compositeType';
+import {
+ BuilderComponentPlugin,
+ ChunkType,
+ FileType,
+ ICodeStruct,
+ IProjectInfo,
+} from '@/types';
+
+// TODO: How to merge this logic to common deps
+const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
+ const next: ICodeStruct = {
+ ...pre,
+ };
+
+ const ir = next.ir as IProjectInfo;
+ if (ir.i18n) {
+ const [, i18nStr] = generateCompositeType(ir.i18n);
+
+ next.chunks.push({
+ type: ChunkType.STRING,
+ fileType: FileType.JS,
+ name: COMMON_CHUNK_NAME.FileMainContent,
+ content: `
+ const i18nConfig = ${i18nStr};
+ let locale = 'en_US';
+
+ const changeLocale = (target) => {
+ locale = target;
+ };
+
+ const i18n = key => i18nConfig && i18nConfig[locale] && i18nConfig[locale][key] || '';
+ `,
+ linkAfter: [
+ COMMON_CHUNK_NAME.ExternalDepsImport,
+ COMMON_CHUNK_NAME.InternalDepsImport,
+ COMMON_CHUNK_NAME.FileVarDefine,
+ COMMON_CHUNK_NAME.FileUtilDefine,
+ ],
+ });
+
+ next.chunks.push({
+ type: ChunkType.STRING,
+ fileType: FileType.JS,
+ name: COMMON_CHUNK_NAME.FileExport,
+ content: `
+ export {
+ changeLocale,
+ i18n,
+ };
+ `,
+ linkAfter: [
+ COMMON_CHUNK_NAME.ExternalDepsImport,
+ COMMON_CHUNK_NAME.InternalDepsImport,
+ COMMON_CHUNK_NAME.FileVarDefine,
+ COMMON_CHUNK_NAME.FileUtilDefine,
+ COMMON_CHUNK_NAME.FileMainContent,
+ ],
+ });
+ }
+
+ return next;
+};
+
+export default plugin;
diff --git a/packages/code-generator/src/plugins/project/utils.ts b/packages/code-generator/src/plugins/project/utils.ts
new file mode 100644
index 000000000..8a104dc45
--- /dev/null
+++ b/packages/code-generator/src/plugins/project/utils.ts
@@ -0,0 +1,90 @@
+import { COMMON_CHUNK_NAME } from '@/const/generator';
+import { generateCompositeType } from '@/plugins/utils/compositeType';
+// import { } from '@/plugins/utils/jsExpression';
+import {
+ BuilderComponentPlugin,
+ ChunkType,
+ FileType,
+ ICodeStruct,
+ IUtilInfo,
+} from '@/types';
+
+// TODO: How to merge this logic to common deps
+const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => {
+ const next: ICodeStruct = {
+ ...pre,
+ };
+
+ const ir = next.ir as IUtilInfo;
+
+ if (ir.utils) {
+ next.chunks.push({
+ type: ChunkType.STRING,
+ fileType: FileType.JS,
+ name: COMMON_CHUNK_NAME.FileExport,
+ content: `
+ export default {
+ `,
+ linkAfter: [
+ COMMON_CHUNK_NAME.ExternalDepsImport,
+ COMMON_CHUNK_NAME.InternalDepsImport,
+ COMMON_CHUNK_NAME.FileVarDefine,
+ COMMON_CHUNK_NAME.FileUtilDefine,
+ COMMON_CHUNK_NAME.FileMainContent,
+ ],
+ });
+
+ ir.utils.forEach(util => {
+ if (util.type === 'function') {
+ next.chunks.push({
+ type: ChunkType.STRING,
+ fileType: FileType.JS,
+ name: COMMON_CHUNK_NAME.FileVarDefine,
+ content: `
+ const ${util.name} = ${util.content};
+ `,
+ linkAfter: [
+ COMMON_CHUNK_NAME.ExternalDepsImport,
+ COMMON_CHUNK_NAME.InternalDepsImport,
+ ],
+ });
+ }
+
+ next.chunks.push({
+ type: ChunkType.STRING,
+ fileType: FileType.JS,
+ name: COMMON_CHUNK_NAME.FileExport,
+ content: `
+ ${util.name},
+ `,
+ linkAfter: [
+ COMMON_CHUNK_NAME.ExternalDepsImport,
+ COMMON_CHUNK_NAME.InternalDepsImport,
+ COMMON_CHUNK_NAME.FileVarDefine,
+ COMMON_CHUNK_NAME.FileUtilDefine,
+ COMMON_CHUNK_NAME.FileMainContent,
+ ],
+ });
+ });
+
+ next.chunks.push({
+ type: ChunkType.STRING,
+ fileType: FileType.JS,
+ name: COMMON_CHUNK_NAME.FileExport,
+ content: `
+ };
+ `,
+ linkAfter: [
+ COMMON_CHUNK_NAME.ExternalDepsImport,
+ COMMON_CHUNK_NAME.InternalDepsImport,
+ COMMON_CHUNK_NAME.FileVarDefine,
+ COMMON_CHUNK_NAME.FileUtilDefine,
+ COMMON_CHUNK_NAME.FileMainContent,
+ ],
+ });
+ }
+
+ return next;
+};
+
+export default plugin;
diff --git a/packages/code-generator/src/plugins/utils/compositeType.ts b/packages/code-generator/src/plugins/utils/compositeType.ts
new file mode 100644
index 000000000..a1bfdf220
--- /dev/null
+++ b/packages/code-generator/src/plugins/utils/compositeType.ts
@@ -0,0 +1,45 @@
+import { CompositeArray, CompositeValue, ICompositeObject } from '@/types';
+import { generateValue, isJsExpression } from './jsExpression';
+
+function generateArray(value: CompositeArray): string {
+ const body = value.map(v => generateUnknownType(v)).join(',');
+ return `[${body}]`;
+}
+
+function generateObject(value: ICompositeObject): string {
+ if (isJsExpression(value)) {
+ return generateValue(value);
+ }
+
+ const body = Object.keys(value)
+ .map(key => {
+ const v = generateUnknownType(value[key]);
+ return `${key}: ${v}`;
+ })
+ .join(',');
+
+ return `{${body}}`;
+}
+
+function generateUnknownType(value: CompositeValue): string {
+ if (Array.isArray(value)) {
+ return generateArray(value as CompositeArray);
+ } else if (typeof value === 'object') {
+ return generateObject(value as ICompositeObject);
+ } else if (typeof value === 'string') {
+ return `'${value}'`;
+ }
+ return `${value}`;
+}
+
+export function generateCompositeType(
+ value: CompositeValue,
+): [boolean, string] {
+ const result = generateUnknownType(value);
+
+ if (result.substr(0, 1) === "'" && result.substr(-1, 1) === "'") {
+ return [true, result.substring(1, result.length - 1)];
+ }
+
+ return [false, result];
+}
diff --git a/packages/code-generator/src/plugins/utils/jsExpression.ts b/packages/code-generator/src/plugins/utils/jsExpression.ts
new file mode 100644
index 000000000..eda1883bc
--- /dev/null
+++ b/packages/code-generator/src/plugins/utils/jsExpression.ts
@@ -0,0 +1,39 @@
+import { CodeGeneratorError, IJSExpression } from '../../types';
+
+export function transformFuncExpr2MethodMember(
+ methodName: string,
+ functionBody: string,
+): string {
+ if (functionBody.indexOf('function') < 8) {
+ return functionBody.replace('function', methodName);
+ }
+ return functionBody;
+}
+
+export function getFuncExprBody(functionBody: string) {
+ const start = functionBody.indexOf('{');
+ const end = functionBody.lastIndexOf('}');
+
+ if (start < 0 || end < 0 || end < start) {
+ throw new CodeGeneratorError('JSExpression has no valid body.');
+ }
+
+ const body = functionBody.slice(start + 1, end);
+ return body;
+}
+
+export function generateValue(value: any): string {
+ if (value && (value as IJSExpression).type === 'JSExpression') {
+ return (value as IJSExpression).value;
+ }
+
+ throw new CodeGeneratorError('Not a JSExpression');
+}
+
+export function isJsExpression(value: any): boolean {
+ return (
+ value &&
+ typeof value === 'object' &&
+ (value as IJSExpression).type === 'JSExpression'
+ );
+}
diff --git a/packages/code-generator/src/publisher/disk/index.ts b/packages/code-generator/src/publisher/disk/index.ts
new file mode 100644
index 000000000..a806e2ce4
--- /dev/null
+++ b/packages/code-generator/src/publisher/disk/index.ts
@@ -0,0 +1,89 @@
+import { CodeGeneratorError, IResultDir } from '@/types';
+
+export type PublisherFactory = (configuration?: Partial) => U;
+
+export interface IPublisher {
+ publish: (options?: T) => Promise>;
+ getProject: () => IResultDir | void;
+ setProject: (project: IResultDir) => void;
+}
+
+export interface IPublisherFactoryParams {
+ project?: IResultDir;
+}
+export interface IPublisherResponse {
+ success: boolean;
+ payload?: T;
+}
+
+import { writeFolder } from './utils';
+
+export interface IDiskFactoryParams extends IPublisherFactoryParams {
+ outputPath?: string;
+ projectSlug?: string;
+ createProjectFolder?: boolean;
+}
+
+export interface IDiskPublisher extends IPublisher {
+ getOutputPath: () => string;
+ setOutputPath: (path: string) => void;
+}
+
+export const createDiskPublisher: PublisherFactory<
+ IDiskFactoryParams,
+ IDiskPublisher
+> = (params: IDiskFactoryParams = {}): IDiskPublisher => {
+ let { project, outputPath = './' } = params;
+
+ const getProject = (): IResultDir => {
+ if (!project) {
+ throw new CodeGeneratorError('Missing Project');
+ }
+ return project;
+ };
+ const setProject = (projectToSet: IResultDir): void => {
+ project = projectToSet;
+ };
+
+ const getOutputPath = (): string => {
+ return outputPath;
+ };
+ const setOutputPath = (path: string): void => {
+ outputPath = path;
+ };
+
+ const publish = async (options: IDiskFactoryParams = {}) => {
+ const projectToPublish = options.project || project;
+ if (!projectToPublish) {
+ throw new CodeGeneratorError('Missing Project');
+ }
+
+ const projectOutputPath = options.outputPath || outputPath;
+ const overrideProjectSlug = options.projectSlug || params.projectSlug;
+ const createProjectFolder =
+ options.createProjectFolder || params.createProjectFolder;
+
+ if (overrideProjectSlug) {
+ projectToPublish.name = overrideProjectSlug;
+ }
+
+ try {
+ await writeFolder(
+ projectToPublish,
+ projectOutputPath,
+ createProjectFolder,
+ );
+ return { success: true, payload: projectOutputPath };
+ } catch (error) {
+ throw new CodeGeneratorError(error);
+ }
+ };
+
+ return {
+ publish,
+ getProject,
+ setProject,
+ getOutputPath,
+ setOutputPath,
+ };
+};
diff --git a/packages/code-generator/src/publisher/disk/utils.ts b/packages/code-generator/src/publisher/disk/utils.ts
new file mode 100644
index 000000000..4352031ef
--- /dev/null
+++ b/packages/code-generator/src/publisher/disk/utils.ts
@@ -0,0 +1,71 @@
+import { existsSync, mkdir, writeFile } from 'fs';
+import { join } from 'path';
+
+import { IResultDir, IResultFile } from '@/types';
+
+export const writeFolder = async (
+ folder: IResultDir,
+ currentPath: string,
+ createProjectFolder = true,
+): Promise => {
+ const { name, files, dirs } = folder;
+
+ const folderPath = createProjectFolder
+ ? join(currentPath, name)
+ : currentPath;
+
+ if (!existsSync(folderPath)) {
+ await createDirectory(folderPath);
+ }
+
+ const promises = [
+ writeFilesToFolder(folderPath, files),
+ writeSubFoldersToFolder(folderPath, dirs),
+ ];
+
+ await Promise.all(promises);
+};
+
+const writeFilesToFolder = async (
+ folderPath: string,
+ files: IResultFile[],
+): Promise => {
+ const promises = files.map(file => {
+ const fileName = file.ext ? `${file.name}.${file.ext}` : file.name;
+ const filePath = join(folderPath, fileName);
+ return writeContentToFile(filePath, file.content);
+ });
+
+ await Promise.all(promises);
+};
+
+const writeSubFoldersToFolder = async (
+ folderPath: string,
+ subFolders: IResultDir[],
+): Promise => {
+ const promises = subFolders.map(subFolder => {
+ return writeFolder(subFolder, folderPath);
+ });
+
+ await Promise.all(promises);
+};
+
+const createDirectory = (pathToDir: string): Promise => {
+ return new Promise((resolve, reject) => {
+ mkdir(pathToDir, { recursive: true }, err => {
+ err ? reject(err) : resolve();
+ });
+ });
+};
+
+const writeContentToFile = (
+ filePath: string,
+ fileContent: string,
+ encoding: string = 'utf8',
+): Promise => {
+ return new Promise((resolve, reject) => {
+ writeFile(filePath, fileContent, encoding, err => {
+ err ? reject(err) : resolve();
+ });
+ });
+};
diff --git a/packages/code-generator/src/solutions/icejs.ts b/packages/code-generator/src/solutions/icejs.ts
new file mode 100644
index 000000000..8e296f994
--- /dev/null
+++ b/packages/code-generator/src/solutions/icejs.ts
@@ -0,0 +1,60 @@
+import { IProjectBuilder } from '@/types';
+
+import { createProjectBuilder } from '@/generator/ProjectBuilder';
+
+import esmodule from '@/plugins/common/esmodule';
+import containerClass from '@/plugins/component/react/containerClass';
+import containerInitState from '@/plugins/component/react/containerInitState';
+import containerInjectUtils from '@/plugins/component/react/containerInjectUtils';
+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 iceJsEntry from '@/plugins/project/framework/icejs/plugins/entry';
+import iceJsEntryHtml from '@/plugins/project/framework/icejs/plugins/entryHtml';
+import iceJsGlobalStyle from '@/plugins/project/framework/icejs/plugins/globalStyle';
+import iceJsPackageJSON from '@/plugins/project/framework/icejs/plugins/packageJSON';
+import iceJsRouter from '@/plugins/project/framework/icejs/plugins/router';
+import template from '@/plugins/project/framework/icejs/template';
+import i18n from '@/plugins/project/i18n';
+import utils from '@/plugins/project/utils';
+
+export default function createIceJsProjectBuilder(): IProjectBuilder {
+ return createProjectBuilder({
+ template,
+ plugins: {
+ components: [
+ reactCommonDeps,
+ esmodule,
+ containerClass,
+ containerInjectUtils,
+ containerInitState,
+ containerLifeCycle,
+ containerMethod,
+ jsx,
+ css,
+ ],
+ pages: [
+ reactCommonDeps,
+ esmodule,
+ containerClass,
+ containerInjectUtils,
+ containerInitState,
+ containerLifeCycle,
+ containerMethod,
+ jsx,
+ css,
+ ],
+ router: [esmodule, iceJsRouter],
+ entry: [iceJsEntry],
+ constants: [constants],
+ utils: [esmodule, utils],
+ i18n: [i18n],
+ globalStyle: [iceJsGlobalStyle],
+ htmlEntry: [iceJsEntryHtml],
+ packageJSON: [iceJsPackageJSON],
+ },
+ });
+}
diff --git a/packages/code-generator/src/types/core.ts b/packages/code-generator/src/types/core.ts
new file mode 100644
index 000000000..39e2a08c7
--- /dev/null
+++ b/packages/code-generator/src/types/core.ts
@@ -0,0 +1,142 @@
+import {
+ IBasicSchema,
+ IParseResult,
+ IProjectSchema,
+ IResultDir,
+ IResultFile,
+} from './index';
+
+export enum FileType {
+ CSS = 'css',
+ HTML = 'html',
+ JS = 'js',
+ JSX = 'jsx',
+ JSON = 'json',
+}
+
+export enum ChunkType {
+ AST = 'ast',
+ STRING = 'string',
+ JSON = 'json',
+}
+
+export enum PluginType {
+ COMPONENT = 'component',
+ UTILS = 'utils',
+ I18N = 'i18n',
+}
+
+export type ChunkContent = string | any;
+export type CodeGeneratorFunction = (content: T) => string;
+
+export interface ICodeChunk {
+ type: ChunkType;
+ fileType: FileType;
+ name: string;
+ subModule?: string;
+ content: ChunkContent;
+ linkAfter: string[];
+}
+
+export interface IBaseCodeStruct {
+ chunks: ICodeChunk[];
+ depNames: string[];
+}
+
+export interface ICodeStruct extends IBaseCodeStruct {
+ ir: any;
+ chunks: ICodeChunk[];
+}
+
+export type BuilderComponentPlugin = (
+ initStruct: ICodeStruct,
+) => Promise;
+
+export interface IChunkBuilder {
+ run(
+ ir: any,
+ initialStructure?: ICodeStruct,
+ ): Promise<{ chunks: ICodeChunk[][] }>;
+ getPlugins(): BuilderComponentPlugin[];
+ addPlugin(plugin: BuilderComponentPlugin): void;
+}
+
+export interface ICodeBuilder {
+ link(chunkDefinitions: ICodeChunk[]): string;
+ generateByType(type: string, content: unknown): string;
+}
+
+export interface ICompiledModule {
+ files: IResultFile[];
+}
+
+export interface IModuleBuilder {
+ generateModule: (input: unknown) => Promise;
+ linkCodeChunks: (
+ chunks: Record,
+ fileName: string,
+ ) => IResultFile[];
+ addPlugin: (plugin: BuilderComponentPlugin) => void;
+}
+
+/**
+ * 引擎对外接口
+ *
+ * @export
+ * @interface ICodeGenerator
+ */
+export interface ICodeGenerator {
+ /**
+ * 出码接口,把 Schema 转换成代码文件系统描述
+ *
+ * @param {(IBasicSchema)} schema 传入的 Schema
+ * @returns {IResultDir}
+ * @memberof ICodeGenerator
+ */
+ toCode(schema: IBasicSchema): Promise;
+}
+
+export interface ISchemaParser {
+ validate(schema: IBasicSchema): boolean;
+ parse(schema: IBasicSchema): IParseResult;
+}
+
+export interface IProjectTemplate {
+ slots: IProjectSlots;
+ generateTemplate(): IResultDir;
+}
+
+export interface IProjectSlot {
+ path: string[];
+ fileName?: string;
+}
+
+export interface IProjectSlots {
+ components: IProjectSlot;
+ pages: IProjectSlot;
+ router: IProjectSlot;
+ entry: IProjectSlot;
+ constants?: IProjectSlot;
+ utils?: IProjectSlot;
+ i18n?: IProjectSlot;
+ globalStyle: IProjectSlot;
+ htmlEntry: IProjectSlot;
+ packageJSON: IProjectSlot;
+}
+
+export interface IProjectPlugins {
+ components: BuilderComponentPlugin[];
+ pages: BuilderComponentPlugin[];
+ router: BuilderComponentPlugin[];
+ entry: BuilderComponentPlugin[];
+ constants?: BuilderComponentPlugin[];
+ utils?: BuilderComponentPlugin[];
+ i18n?: BuilderComponentPlugin[];
+ globalStyle: BuilderComponentPlugin[];
+ htmlEntry: BuilderComponentPlugin[];
+ packageJSON: BuilderComponentPlugin[];
+}
+
+export interface IProjectBuilder {
+ generateProject(schema: IProjectSchema): Promise;
+}
diff --git a/packages/code-generator/src/types/deps.ts b/packages/code-generator/src/types/deps.ts
new file mode 100644
index 000000000..8833b1dde
--- /dev/null
+++ b/packages/code-generator/src/types/deps.ts
@@ -0,0 +1,36 @@
+/**
+ * 外部依赖描述
+ *
+ * @export
+ * @interface IExternalDependency
+ */
+export interface IExternalDependency extends IDependency {
+ package: string; // 组件包的名称
+ version: string; // 组件包的版本
+}
+
+export enum InternalDependencyType {
+ PAGE = 'page',
+ BLOCK = 'block',
+ COMPONENT = 'component',
+ UTILS = 'utils',
+}
+
+export enum DependencyType {
+ External = 'External',
+ Internal = 'Internal',
+}
+
+export interface IInternalDependency extends IDependency {
+ type: InternalDependencyType;
+ moduleName: string;
+}
+
+export interface IDependency {
+ destructuring: boolean; // 组件是否是解构方式方式导出
+ exportName: string; // 导出命名
+ subName?: string; // 下标子组件名称
+ main?: string; // 包导出组件入口文件路径 /lib/input
+ dependencyType?: DependencyType; // 依赖类型 内/外
+ importName?: string; // 导入后名称
+}
diff --git a/packages/code-generator/src/types/error.ts b/packages/code-generator/src/types/error.ts
new file mode 100644
index 000000000..b58e9fe90
--- /dev/null
+++ b/packages/code-generator/src/types/error.ts
@@ -0,0 +1,20 @@
+export class CodeGeneratorError extends Error {
+ constructor(message: string) {
+ super(message);
+ this.name = this.constructor.name;
+ }
+}
+
+// tslint:disable-next-line: max-classes-per-file
+export class ComponentValidationError extends CodeGeneratorError {
+ constructor(errorString: string) {
+ super(errorString);
+ }
+}
+
+// tslint:disable-next-line: max-classes-per-file
+export class CompatibilityError extends CodeGeneratorError {
+ constructor(errorString: string) {
+ super(errorString);
+ }
+}
diff --git a/packages/code-generator/src/types/index.ts b/packages/code-generator/src/types/index.ts
new file mode 100644
index 000000000..1938ed5f5
--- /dev/null
+++ b/packages/code-generator/src/types/index.ts
@@ -0,0 +1,6 @@
+export * from './core';
+export * from './deps';
+export * from './error';
+export * from './result';
+export * from './schema';
+export * from './intermediate';
diff --git a/packages/code-generator/src/types/intermediate.ts b/packages/code-generator/src/types/intermediate.ts
new file mode 100644
index 000000000..57d696b82
--- /dev/null
+++ b/packages/code-generator/src/types/intermediate.ts
@@ -0,0 +1,76 @@
+import {
+ IAppConfig,
+ IAppMeta,
+ IContainerNodeItem,
+ IDependency,
+ II18nMap,
+ IInternalDependency,
+ IUtilItem,
+} from './index';
+
+export interface IPackageJSON {
+ name: string;
+ description: string;
+ version: string;
+ main?: string;
+ author?: string;
+ license?: string;
+ scripts?: Record;
+ dependencies?: Record;
+ [key: string]: unknown;
+}
+
+export interface IParseResult {
+ containers: IContainerInfo[];
+ globalUtils?: IUtilInfo;
+ globalI18n?: II18nMap;
+ globalRouter?: IRouterInfo;
+ project?: IProjectInfo;
+}
+
+export interface IContainerInfo extends IContainerNodeItem, IWithDependency {
+ componentName: string;
+ containerType: string;
+}
+
+export interface IWithDependency {
+ deps?: IDependency[];
+}
+
+export interface IUtilInfo extends IWithDependency {
+ utils: IUtilItem[];
+}
+
+export interface IRouterInfo extends IWithDependency {
+ routes: Array<{
+ path: string;
+ componentName: string;
+ }>;
+}
+
+export interface IProjectInfo {
+ config: IAppConfig;
+ meta: IAppMeta;
+ css?: string;
+ constants?: Record;
+ i18n?: II18nMap;
+}
+
+/**
+ * From meta
+ * page title
+ * router
+ * spmb
+ *
+ * Utils
+ *
+ * constants
+ *
+ * i18n
+ *
+ * components
+ *
+ * pages
+ *
+ * layout
+ */
diff --git a/packages/code-generator/src/types/result.ts b/packages/code-generator/src/types/result.ts
new file mode 100644
index 000000000..75ed7bb1a
--- /dev/null
+++ b/packages/code-generator/src/types/result.ts
@@ -0,0 +1,88 @@
+/**
+ * 导出内容结构,文件夹
+ *
+ * @export
+ * @interface IResultDir
+ */
+export interface IResultDir {
+ /**
+ * 文件夹名称,Root 名称默认为 .
+ *
+ * @type {string}
+ * @memberof IResultDir
+ */
+ name: string;
+ /**
+ * 子目录
+ *
+ * @type {IResultDir[]}
+ * @memberof IResultDir
+ */
+ dirs: IResultDir[];
+ /**
+ * 文件夹内文件
+ *
+ * @type {IResultFile[]}
+ * @memberof IResultDir
+ */
+ files: IResultFile[];
+ /**
+ * 添加文件
+ *
+ * @param {IResultFile} file
+ * @memberof IResultDir
+ */
+ addFile(file: IResultFile): void;
+ /**
+ * 添加子目录
+ *
+ * @param {IResultDir} dir
+ * @memberof IResultDir
+ */
+ addDirectory(dir: IResultDir): void;
+}
+
+/**
+ * 导出内容,对文件的描述
+ *
+ * @export
+ * @interface IResultFile
+ */
+export interface IResultFile {
+ /**
+ * 文件名
+ *
+ * @type {string}
+ * @memberof IResultFile
+ */
+ name: string;
+ /**
+ * 文件类型扩展名,例如 .js .less
+ *
+ * @type {string}
+ * @memberof IResultFile
+ */
+ ext: string;
+ /**
+ * 文件内容
+ *
+ * @type {string}
+ * @memberof IResultFile
+ */
+ content: string;
+}
+
+export interface IPackageJSON {
+ name: string;
+ version: string;
+ description?: string;
+ dependencies: Record;
+ devDependencies: Record;
+ scripts?: Record;
+ engines?: Record;
+ repository?: {
+ type: string;
+ url: string;
+ };
+ private?: boolean;
+}
diff --git a/packages/code-generator/src/types/schema.ts b/packages/code-generator/src/types/schema.ts
new file mode 100644
index 000000000..071e94c81
--- /dev/null
+++ b/packages/code-generator/src/types/schema.ts
@@ -0,0 +1,253 @@
+// 搭建基础协议、搭建入料协议的数据规范
+import { IExternalDependency } from './index';
+
+/**
+ * 搭建基础协议 - 函数表达式
+ *
+ * @export
+ * @interface IJSExpression
+ */
+export interface IJSExpression {
+ type: 'JSExpression';
+ value: string;
+}
+
+// JSON 基本类型
+export interface IJSONObject {
+ [key: string]: JSONValue;
+}
+
+export type JSONValue =
+ | boolean
+ | string
+ | number
+ | null
+ | JSONArray
+ | IJSONObject;
+export type JSONArray = JSONValue[];
+
+export type CompositeArray = CompositeValue[];
+export interface ICompositeObject {
+ [key: string]: CompositeValue;
+}
+
+// 复合类型
+export type CompositeValue =
+ | JSONValue
+ | IJSExpression
+ | CompositeArray
+ | ICompositeObject;
+
+/**
+ * 搭建基础协议 - 多语言描述
+ *
+ * @export
+ * @interface II18nMap
+ */
+export interface II18nMap {
+ [lang: string]: {
+ [key: string]: string;
+ };
+}
+
+/**
+ * 搭建基础协议
+ *
+ * @export
+ * @interface IBasicSchema
+ */
+export interface IBasicSchema {
+ version: string; // 当前协议版本号
+ componentsMap: IComponentsMapItem[]; // 组件映射关系
+ componentsTree: Array; // 描述模版/页面/区块/低代码业务组件的组件树 低代码业务组件树描述,固定长度为1,且顶层为低代码业务组件容器描述
+ utils?: IUtilItem[]; // 工具类扩展映射关系 低代码业务组件不包含
+ i18n?: II18nMap; // 国际化语料
+}
+
+export interface IProjectSchema extends IBasicSchema {
+ constants: Record; // 应用范围内的全局常量;
+ css: string; // 应用范围内的全局样式;
+ config: IAppConfig; // 当前应用配置信息
+ meta: IAppMeta; // 当前应用元数据信息
+}
+
+/**
+ * 搭建基础协议 - 单个组件描述
+ *
+ * @export
+ * @interface IComponentsMapItem
+ */
+export interface IComponentsMapItem extends IExternalDependency {
+ componentName: string; // 组件名称
+}
+
+export interface IUtilItem {
+ name: string;
+ type: 'npm' | 'tnpm' | 'function';
+ content: IExternalDependency | IJSExpression;
+}
+
+export interface IInlineStyle {
+ [cssAttribute: string]: string | number | IJSExpression;
+}
+
+type ChildNodeItem = string | IJSExpression | IComponentNodeItem;
+type ChildNodeType = ChildNodeItem | ChildNodeItem[];
+
+/**
+ * 搭建基础协议 - 单个组件树节点描述
+ * 转换成一个 .jsx 文件内 React Class 类 render 函数返回的 jsx 代码
+ *
+ * @export
+ * @interface IComponentNodeItem
+ */
+export interface IComponentNodeItem {
+ componentName: string; // 组件名称 必填、首字母大写
+ props: {
+ className?: string; // 组件样式类名
+ style?: IInlineStyle; // 组件内联样式
+ [propName: string]: any; // 业务属性
+ }; // 组件属性对象
+ condition?: CompositeValue; // 渲染条件
+ loop?: CompositeValue; // 循环数据
+ loopArgs?: [string, string]; // 循环迭代对象、索引名称 ["item", "index"]
+ children?: ChildNodeType; // 子节点
+}
+
+/**
+ * 搭建基础协议 - 单个容器节点描述
+ *
+ * @export
+ * @interface IContainerNodeItem
+ * @extends {IComponentNodeItem}
+ */
+export interface IContainerNodeItem extends IComponentNodeItem {
+ componentName: string; // 'Page' | 'Block' | 'Component' 组件类型 必填、首字母大写
+ fileName: string; // 文件名称 必填、英文
+ defaultProps?: {
+ [propName: string]: any; // 业务属性
+ };
+ state?: {
+ [stateName: string]: any; // 容器初始数据
+ };
+ css: string; // 样式文件 用于描述容器组件内部节点的样式,对应生成一个独立的样式文件,在对应容器组件生成的 .jsx 文件中 import 引入;
+ /**
+ * LifeCycle
+ * • constructor(props, context)
+ * • 说明:初始化渲染时执行,常用于设置state值;
+ * • render()
+ * • 说明:执行于容器组件React Class的render方法最前,常用于计算变量挂载到this对象上,供props上属性绑定。此render()方法不需要设置return返回值。
+ * • componentDidMount()
+ * • componentDidUpdate(prevProps, prevState, snapshot)
+ * • componentWillUnmount()
+ * • componentDidCatch(error, info)
+ */
+ lifeCycles?: {
+ constructor?: IJSExpression;
+ render?: IJSExpression;
+ componentDidMount?: IJSExpression;
+ componentDidUpdate?: IJSExpression;
+ componentWillUnmount?: IJSExpression;
+ componentDidCatch?: IJSExpression;
+ }; // 生命周期Hook方法
+ methods?: {
+ [methodName: string]: IJSExpression;
+ }; // 自定义方法设置
+ dataSource?: IDataSource; // 异步数据源配置
+ meta?: IBasicMeta | IPageMeta;
+}
+
+/**
+ * 搭建基础协议 - 数据源
+ *
+ * @export
+ * @interface IDataSource
+ */
+export interface IDataSource {
+ list: IDataSourceConfig[]; // 成为为单个请求配置
+ /**
+ * 参数:为dataMap对象,key:数据id, value: 单个请求结果
+ * 返回值:数据对象data,将会在渲染引擎和schemaToCode中通过调用this.setState(...)将返回的数据对象生效到state中;
+ * 支持返回一个Promise,通过resolve(返回数据),常用于串型发送请求场景,配合this.dataSourceMap[oneRequest.id].load()使用;
+ */
+ dataHandler: IJSExpression;
+}
+
+/**
+ * 搭建基础协议 - 数据源单个配置
+ *
+ * @export
+ * @interface IDataSourceConfig
+ */
+export interface IDataSourceConfig {
+ id: string; // 数据请求ID标识
+ isInit: boolean; // 是否为初始数据 支持表达式 值为true时,将在组件初始化渲染时自动发送当前数据请求
+ type: 'fetch' | 'mtop' | 'jsonp' | 'custom'; // 数据请求类型
+ requestHandler?: IJSExpression; // 自定义扩展的外部请求处理器 仅type='custom'时生效
+ options?: IFetchOptions; // 请求参数配置 每种请求类型对应不同参数
+ dataHandler?: IJSExpression; // 数据结果处理函数,形如:(data, err) => Object
+}
+
+/**
+ * 搭建基础协议 - 请求参数配置
+ *
+ * @export
+ * @interface IFetchOptions
+ */
+export interface IFetchOptions {
+ uri: string; // 请求地址 支持表达式
+ params?: {
+ // 请求参数
+ [key: string]: any;
+ };
+ method: 'GET' | 'POST';
+ isCors: boolean; // 是否支持跨域,对应credentials = 'include'
+ timeout: number; // 超时时长
+ headers?: {
+ // 自定义请求头
+ [key: string]: string;
+ };
+}
+
+export interface IBasicMeta {
+ title: string; // 标题描述
+}
+
+export interface IPageMeta extends IBasicMeta {
+ router: string; // 页面路由
+ spmb: string; // spm
+}
+
+// "theme": {
+// //for Fusion use dpl defined
+// "package": "@alife/theme-fusion",
+// "version": "^0.1.0",
+
+// //for Antd use variable
+// "primary": "#ff9966"
+// }
+
+// "layout": {
+// "componentName": "BasicLayout",
+// "props": {
+// "logo": "...",
+// "name": "测试网站"
+// },
+// },
+
+export interface IAppConfig {
+ sdkVersion: string; // 渲染模块版本
+ historyMode: 'brower' | 'hash'; // 浏览器路由:brower 哈希路由:hash
+ targetRootID: string; // 渲染根节点 ID
+ layout: IComponentNodeItem;
+ theme: object; // 主题配置,根据接入的主题模块不同
+}
+
+export interface IAppMeta {
+ name: string; // 应用中文名称
+ git_group?: string; // 应用对应git分组名
+ project_name?: string; // 应用对应git的project名称
+ description?: string; // 应用描述
+ spma: string; // 应用spma A位信息
+ creator?: string; // author
+}
diff --git a/packages/code-generator/src/utils/common.ts b/packages/code-generator/src/utils/common.ts
new file mode 100644
index 000000000..5e58886bd
--- /dev/null
+++ b/packages/code-generator/src/utils/common.ts
@@ -0,0 +1,28 @@
+import changeCase from 'change-case';
+import short from 'short-uuid';
+
+// Doc: https://www.npmjs.com/package/change-case
+
+export function camel2dash(input: string): string {
+ return changeCase.paramCase(input);
+}
+
+/**
+ * 转为驼峰
+ */
+export function camelize(str: string): string {
+ return changeCase.camelCase(str);
+}
+
+export function generateID(): string {
+ return short.generate();
+}
+
+export function upperCaseFirst(inputValue: string): string {
+ return changeCase.upperCaseFirst(inputValue);
+}
+
+export function uniqueArray(arr: T[]) {
+ const uniqueItems = [...new Set(arr)];
+ return uniqueItems;
+}
diff --git a/packages/code-generator/tsconfig.json b/packages/code-generator/tsconfig.json
new file mode 100644
index 000000000..ddd9bd3f8
--- /dev/null
+++ b/packages/code-generator/tsconfig.json
@@ -0,0 +1,17 @@
+{
+ "extends": "../../tsconfig.json",
+ "compilerOptions": {
+ "paths": {
+ "@/*": ["./src/*"]
+ },
+ "outDir": "./lib",
+ "lib": [
+ "es6"
+ ],
+ "types": ["node"],
+ "baseUrl": ".", /* Base directory to resolve non-absolute module names. */
+ },
+ "include": [
+ "src/**/*"
+ ]
+}