diff --git a/packages/code-generator/src/generator/ProjectBuilder.ts b/packages/code-generator/src/generator/ProjectBuilder.ts index a04d08c45..ff90c9ffb 100644 --- a/packages/code-generator/src/generator/ProjectBuilder.ts +++ b/packages/code-generator/src/generator/ProjectBuilder.ts @@ -158,8 +158,8 @@ export class ProjectBuilder implements IProjectBuilder { } // i18n? - if (parseResult.globalI18n && builders.i18n && this.template.slots.i18n) { - const { files } = await builders.i18n.generateModule(parseResult.globalI18n); + if (builders.i18n && this.template.slots.i18n) { + const { files } = await builders.i18n.generateModule(parseResult.project); buildResult.push({ path: this.template.slots.i18n.path, diff --git a/packages/code-generator/src/index.ts b/packages/code-generator/src/index.ts index 54e324f1b..3d368be5a 100644 --- a/packages/code-generator/src/index.ts +++ b/packages/code-generator/src/index.ts @@ -6,8 +6,9 @@ import { createProjectBuilder } from './generator/ProjectBuilder'; import { createModuleBuilder } from './generator/ModuleBuilder'; import { createDiskPublisher } from './publisher/disk'; import { createZipPublisher } from './publisher/zip'; -// import createIceJsProjectBuilder from './solutions/icejs'; +import createIceJsProjectBuilder from './solutions/icejs'; // import createRecoreProjectBuilder from './solutions/recore'; +import createRaxAppProjectBuilder from './solutions/rax-app'; // 引入说明 import { REACT_CHUNK_NAME } from './plugins/component/react/const'; @@ -42,6 +43,7 @@ import * as utilsValidate from './utils/validate'; // 引入内置解决方案模块 import icejs from './plugins/project/framework/icejs'; +import rax from './plugins/project/framework/rax'; export * from './types'; @@ -49,11 +51,13 @@ export default { createProjectBuilder, createModuleBuilder, solutions: { - // icejs: createIceJsProjectBuilder, + icejs: createIceJsProjectBuilder, // recore: createRecoreProjectBuilder, + rax: createRaxAppProjectBuilder, }, solutionParts: { icejs, + rax, }, publishers: { disk: createDiskPublisher, diff --git a/packages/code-generator/src/plugins/component/rax/containerInjectContext.ts b/packages/code-generator/src/plugins/component/rax/containerInjectContext.ts index 26fc15e36..3c993a2cb 100644 --- a/packages/code-generator/src/plugins/component/rax/containerInjectContext.ts +++ b/packages/code-generator/src/plugins/component/rax/containerInjectContext.ts @@ -32,6 +32,15 @@ const pluginFactory: BuilderComponentPluginFactory = (config?) => linkAfter: [COMMON_CHUNK_NAME.ExternalDepsImport], }); + // TODO: i18n 是可选的,如果没有 i18n 这个文件怎么办?该怎么判断? + next.chunks.push({ + type: ChunkType.STRING, + fileType: cfg.fileType, + name: COMMON_CHUNK_NAME.InternalDepsImport, + content: `import * as __$$i18n from '../../i18n';`, + linkAfter: [COMMON_CHUNK_NAME.ExternalDepsImport], + }); + next.chunks.push({ type: ChunkType.STRING, fileType: cfg.fileType, @@ -78,6 +87,16 @@ const pluginFactory: BuilderComponentPluginFactory = (config?) => get constants() { return __$$constants; }, + get i18n() { + return self._i18n; + }, + getLocale() { + return __$$i18n.getLocale(); + }, + setLocale(locale) { + __$$i18n.setLocale(locale); + self.forceUpdate(); + }, ...this._methods, }; @@ -87,6 +106,34 @@ const pluginFactory: BuilderComponentPluginFactory = (config?) => linkAfter: [RAX_CHUNK_NAME.ClassRenderEnd], }); + next.chunks.push({ + type: ChunkType.STRING, + fileType: cfg.fileType, + name: CLASS_DEFINE_CHUNK_NAME.InsVar, + content: ` + _i18n = this._createI18nDelegate(); + `, + linkAfter: [CLASS_DEFINE_CHUNK_NAME.Start], + }); + + next.chunks.push({ + type: ChunkType.STRING, + fileType: cfg.fileType, + name: CLASS_DEFINE_CHUNK_NAME.InsPrivateMethod, + content: ` + _createI18nDelegate() { + return new Proxy( + {}, + { + get(target, prop) { + return __$$i18n.i18n(prop); + }, + }, + ); + } + `, + linkAfter: [RAX_CHUNK_NAME.ClassRenderEnd], + }); return next; }; return plugin; diff --git a/packages/code-generator/src/plugins/component/rax/containerInjectUtils.ts b/packages/code-generator/src/plugins/component/rax/containerInjectUtils.ts index 5fda869f2..b4a4c52f2 100644 --- a/packages/code-generator/src/plugins/component/rax/containerInjectUtils.ts +++ b/packages/code-generator/src/plugins/component/rax/containerInjectUtils.ts @@ -43,14 +43,12 @@ const pluginFactory: BuilderComponentPluginFactory = (config?) => linkAfter: [CLASS_DEFINE_CHUNK_NAME.Start], }); - // TODO: Page methods... next.chunks.push({ type: ChunkType.STRING, fileType: cfg.fileType, name: CLASS_DEFINE_CHUNK_NAME.InsPrivateMethod, // 绑定下上下文,这样在所有的 utils 里面都能通过 this.xxx 来访问上下文了 - // TODO: 要不要优化为通过 Proxy 的方式懒绑定? content: ` _defineUtils() { const utils = { diff --git a/packages/code-generator/src/plugins/component/rax/jsx.ts b/packages/code-generator/src/plugins/component/rax/jsx.ts index 93edbf6da..3204ebccc 100644 --- a/packages/code-generator/src/plugins/component/rax/jsx.ts +++ b/packages/code-generator/src/plugins/component/rax/jsx.ts @@ -1,7 +1,8 @@ -import { NodeSchema, JSExpression, NpmInfo, CompositeValue } from '@ali/lowcode-types'; +import { NodeSchema, JSExpression, NpmInfo, CompositeValue, isJSExpression } from '@ali/lowcode-types'; import _ from 'lodash'; import changeCase from 'change-case'; +import { Expression } from '@babel/types'; import { BuilderComponentPlugin, BuilderComponentPluginFactory, @@ -23,7 +24,11 @@ import { createNodeGenerator, generateReactCtrlLine, generateAttr } from '../../ import { generateCompositeType } from '../../../utils/compositeType'; import { IScopeBindings, ScopeBindings } from '../../../utils/ScopeBindings'; -import { parseExpressionConvertThis2Context, parseExpressionGetGlobalVariables } from '../../../utils/expressionParser'; +import { + parseExpression, + parseExpressionConvertThis2Context, + parseExpressionGetGlobalVariables, +} from '../../../utils/expressionParser'; type PluginConfig = { fileType: string; @@ -37,23 +42,6 @@ const pluginFactory: BuilderComponentPluginFactory = (config?) => ...config, }; - // 什么都不做的的话,会有 3 个问题: - // 1. 小程序出码的时候,循环变量没法拿到 - // 2. 小程序出码的时候,很容易出现 Uncaught TypeError: Cannot read property 'avatar' of undefined 这样的异常(如下图的 50 行) -- 因为若直接出码,Rax 构建到小程序的时候会立即计算所有在视图中用到的变量 - // 3. 通过 this.xxx 能拿到的东西太多了,而且自定义的 methods 可能会无意间破坏 Rax 框架或小程序框架在页面 this 上的东东 - // const transformers = { - // transformThis2Context: (expr: string) => expr, - // transformJsExpr: (expr: string) => expr, - // transformLoopExpr: (expr: string) => expr, - // }; - - // 不转换 this.xxx 到 __$$context.xxx 的话,依然会有上述的 1 和 3 的问题。 - // const transformers = { - // transformThis2Context: (expr: string) => expr, - // transformJsExpr: (expr: string) => (isLiteralAtomicExpr(expr) ? expr : `__$$eval(() => (${expr}))`), - // transformLoopExpr: (expr: string) => `__$$evalArray(() => (${expr}))`, - // }; - const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const next: ICodeStruct = { ...pre, @@ -70,12 +58,17 @@ const pluginFactory: BuilderComponentPluginFactory = (config?) => } }); + // 注意:这里其实隐含了一个假设:schema 中的 componentName 应该是一个有效的 JS 标识符,而且是大写字母打头的 const mapComponentNameToAliasOrKeepIt = (componentName: string) => componentsNameAliasMap.get(componentName) || componentName; // 然后过滤掉所有的别名 chunks next.chunks = next.chunks.filter((chunk) => !isImportAliasDefineChunk(chunk)); + // 如果直接按目前的 React 的方式之间出码 JSX 的话,会有 3 个问题: + // 1. 小程序出码的时候,循环变量没法拿到 + // 2. 小程序出码的时候,很容易出现 Uncaught TypeError: Cannot read property 'avatar' of undefined 这样的异常(如下图的 50 行) -- 因为若直接出码,Rax 构建到小程序的时候会立即计算所有在视图中用到的变量 + // 3. 通过 this.xxx 能拿到的东西太多了,而且自定义的 methods 可能会无意间破坏 Rax 框架或小程序框架在页面 this 上的东东 const customHandlers: HandlerSet = { expression(this: CustomHandlerSet, input: JSExpression) { return transformJsExpr(generateExpression(input), this); @@ -176,7 +169,49 @@ function transformLoopExpr(expr: string, handlers: CustomHandlerSet) { } function transformJsExpr(expr: string, handlers: CustomHandlerSet) { - return isLiteralAtomicExpr(expr) ? expr : `__$$eval(() => (${transformThis2Context(expr, handlers)}))`; + if (!expr) { + return 'undefined'; + } + + if (isLiteralAtomicExpr(expr)) { + return expr; + } + + const exprAst = parseExpression(expr); + + // 对于下面这些比较安全的字面值,可以直接返回对应的表达式,而非包一层 + if (isSimpleStraightLiteral(exprAst)) { + return expr; + } + + switch (exprAst.type) { + // 对于直接写个函数的,则不用再包下,因为这样不会抛出异常的 + case 'ArrowFunctionExpression': + case 'FunctionExpression': + return transformThis2Context(exprAst, handlers); + + default: + break; + } + + // 其他的都需要包一层 + return `__$$eval(() => (${transformThis2Context(exprAst, handlers)}))`; +} + +/** 判断是非是一些简单直接的字面值 */ +function isSimpleStraightLiteral(expr: Expression): boolean { + switch (expr.type) { + case 'BigIntLiteral': + case 'BooleanLiteral': + case 'DecimalLiteral': + case 'NullLiteral': + case 'NumericLiteral': + case 'RegExpLiteral': + case 'StringLiteral': + return true; + default: + return false; + } } function isImportAliasDefineChunk( @@ -201,14 +236,15 @@ function isImportAliasDefineChunk( * 判断是否是原子类型的表达式 */ function isLiteralAtomicExpr(expr: string): boolean { - return expr === 'null' || expr === 'undefined' || expr === 'true' || expr === 'false' || /^\d+$/.test(expr); + return expr === 'null' || expr === 'undefined' || expr === 'true' || expr === 'false' || /^-?\d+(\.\d+)?$/.test(expr); } /** * 将所有的 this.xxx 替换为 __$$context.xxx * @param expr */ -function transformThis2Context(expr: string, customHandlers: CustomHandlerSet): string { +function transformThis2Context(expr: string | Expression, customHandlers: CustomHandlerSet): string { + // 下面这种字符串替换的方式虽然简单直接,但是对于复杂场景会误匹配,故后期改成了解析 AST 然后修改 AST 最后再重新生成代码的方式 // return expr // .replace(/\bthis\.item\./g, () => 'item.') // .replace(/\bthis\.index\./g, () => 'index.') @@ -224,12 +260,24 @@ function generateNodeAttrForRax(this: CustomHandlerSet, attrName: string, attrVa nodeAttr: undefined, }); } + // else: onXxx 的都是事件处理函数需要特殊处理下 - // 先出个码 - const valueExpr = generateCompositeType(attrValue, this); + return generateEventHandlerAttrForRax(attrName, attrValue, this); +} + +function generateEventHandlerAttrForRax( + attrName: string, + attrValue: CompositeValue, + handlers: CustomHandlerSet, +): CodePiece[] { + // -- 事件处理函数中 JSExpression 转成 JSFunction 来处理,避免当 JSExpression 处理的时候多包一层 eval 而导致 Rax 转码成小程序的时候出问题 + const valueExpr = generateCompositeType( + isJSExpression(attrValue) ? { type: 'JSFunction', value: attrValue.value } : attrValue, + handlers, + ); // 查询当前作用域下的变量 - const currentScopeVariables = this.scopeBindings?.getAllBindings() || []; + const currentScopeVariables = handlers.scopeBindings?.getAllBindings() || []; if (currentScopeVariables.length <= 0) { return [ { diff --git a/packages/code-generator/src/plugins/project/framework/rax/plugins/globalStyle.ts b/packages/code-generator/src/plugins/project/framework/rax/plugins/globalStyle.ts index 78e409fd2..ce3689f2b 100644 --- a/packages/code-generator/src/plugins/project/framework/rax/plugins/globalStyle.ts +++ b/packages/code-generator/src/plugins/project/framework/rax/plugins/globalStyle.ts @@ -9,7 +9,18 @@ import { IProjectInfo, } from '../../../../../types'; -const pluginFactory: BuilderComponentPluginFactory = () => { +export type GlobalStylePluginConfig = { + fileType: string; +}; + +const pluginFactory: BuilderComponentPluginFactory = ( + config?: Partial, +) => { + const cfg: GlobalStylePluginConfig = { + fileType: FileType.SCSS, + ...config, + }; + const plugin: BuilderComponentPlugin = async (pre: ICodeStruct) => { const next: ICodeStruct = { ...pre, @@ -19,7 +30,7 @@ const pluginFactory: BuilderComponentPluginFactory = () => { next.chunks.push({ type: ChunkType.STRING, - fileType: FileType.SCSS, // TODO: 样式文件的类型定制化? + fileType: cfg.fileType, name: COMMON_CHUNK_NAME.StyleDepsImport, content: ``, linkAfter: [], @@ -27,7 +38,7 @@ const pluginFactory: BuilderComponentPluginFactory = () => { next.chunks.push({ type: ChunkType.STRING, - fileType: FileType.SCSS, + fileType: cfg.fileType, name: COMMON_CHUNK_NAME.StyleCssContent, content: ` body { @@ -39,7 +50,7 @@ body { next.chunks.push({ type: ChunkType.STRING, - fileType: FileType.SCSS, + fileType: cfg.fileType, name: COMMON_CHUNK_NAME.StyleCssContent, content: ir.css || '', linkAfter: [COMMON_CHUNK_NAME.StyleDepsImport], diff --git a/packages/code-generator/src/plugins/project/i18n.ts b/packages/code-generator/src/plugins/project/i18n.ts index c8e8067d2..a595c8afa 100644 --- a/packages/code-generator/src/plugins/project/i18n.ts +++ b/packages/code-generator/src/plugins/project/i18n.ts @@ -1,5 +1,5 @@ import { COMMON_CHUNK_NAME } from '../../const/generator'; -import { generateCompositeType } from '../../utils/compositeType'; + import { BuilderComponentPlugin, BuilderComponentPluginFactory, @@ -16,53 +16,54 @@ const pluginFactory: BuilderComponentPluginFactory = () => { }; const ir = next.ir as IProjectInfo; - if (ir.i18n) { - const i18nStr = generateCompositeType(ir.i18n); + const i18nStr = ir.i18n ? JSON.stringify(ir.i18n, null, 2) : '{}'; - next.chunks.push({ - type: ChunkType.STRING, - fileType: FileType.JS, - name: COMMON_CHUNK_NAME.FileMainContent, - content: ` - const i18nConfig = ${i18nStr}; - let locale = 'en_US'; + next.chunks.push({ + type: ChunkType.STRING, + fileType: FileType.JS, + name: COMMON_CHUNK_NAME.FileMainContent, + content: ` + const i18nConfig = ${i18nStr}; - const changeLocale = (target) => { - locale = target; - }; + let locale = 'en-US'; - const i18n = key => i18nConfig && i18nConfig[locale] && i18nConfig[locale][key] || ''; - `, - linkAfter: [ - COMMON_CHUNK_NAME.ExternalDepsImport, - COMMON_CHUNK_NAME.InternalDepsImport, - COMMON_CHUNK_NAME.ImportAliasDefine, - COMMON_CHUNK_NAME.FileVarDefine, - COMMON_CHUNK_NAME.FileUtilDefine, - ], - }); + const getLocale = () => locale; - 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.ImportAliasDefine, - COMMON_CHUNK_NAME.FileVarDefine, - COMMON_CHUNK_NAME.FileUtilDefine, - COMMON_CHUNK_NAME.FileMainContent, - ], - }); - } + const setLocale = (target) => { + locale = target; + }; + const i18n = key => i18nConfig && i18nConfig[locale] && i18nConfig[locale][key] || ''; + `, + linkAfter: [ + COMMON_CHUNK_NAME.ExternalDepsImport, + COMMON_CHUNK_NAME.InternalDepsImport, + COMMON_CHUNK_NAME.ImportAliasDefine, + COMMON_CHUNK_NAME.FileVarDefine, + COMMON_CHUNK_NAME.FileUtilDefine, + ], + }); + + next.chunks.push({ + type: ChunkType.STRING, + fileType: FileType.JS, + name: COMMON_CHUNK_NAME.FileExport, + content: ` + export { + getLocale, + setLocale, + i18n, + }; + `, + linkAfter: [ + COMMON_CHUNK_NAME.ExternalDepsImport, + COMMON_CHUNK_NAME.InternalDepsImport, + COMMON_CHUNK_NAME.ImportAliasDefine, + COMMON_CHUNK_NAME.FileVarDefine, + COMMON_CHUNK_NAME.FileUtilDefine, + COMMON_CHUNK_NAME.FileMainContent, + ], + }); return next; }; return plugin; diff --git a/packages/code-generator/src/utils/expressionParser.ts b/packages/code-generator/src/utils/expressionParser.ts index d764798ef..6c488c659 100644 --- a/packages/code-generator/src/utils/expressionParser.ts +++ b/packages/code-generator/src/utils/expressionParser.ts @@ -7,8 +7,8 @@ import { isIdentifier, Node } from '@babel/types'; import { OrderedSet } from './OrderedSet'; export class ParseError extends Error { - constructor(readonly expr: string, readonly detail: unknown) { - super(`Failed to parse expression "${expr}"`); + constructor(readonly expr: string | t.Expression, readonly detail: unknown) { + super(`Failed to parse expression "${typeof expr === 'string' ? expr : generate(expr)}"`); Object.setPrototypeOf(this, new.target.prototype); } } @@ -159,7 +159,7 @@ export function parseExpressionGetGlobalVariables( } export function parseExpressionConvertThis2Context( - expr: string, + expr: string | t.Expression, contextName = '__$$context', localVariables: string[] = [], ): string { @@ -168,7 +168,7 @@ export function parseExpressionConvertThis2Context( } try { - const exprAst = parser.parseExpression(expr); + const exprAst = typeof expr === 'string' ? parser.parseExpression(expr) : expr; const exprWrapAst = t.expressionStatement(exprAst); const fileAst = t.file(t.program([exprWrapAst])); @@ -229,11 +229,14 @@ export function parseExpressionConvertThis2Context( const { code } = generate(exprWrapAst.expression, { sourceMaps: false }); return code; } catch (e) { - // throw new ParseError(expr, e); - throw e; + throw new ParseError(expr, e); } } -function indent(level: number) { - return ' '.repeat(level); +export function parseExpression(expr: string) { + try { + return parser.parseExpression(expr); + } catch (e) { + throw new ParseError(expr, e); + } } diff --git a/packages/code-generator/test-cases/rax-app/demo1/expected/demo-project/src/i18n.js b/packages/code-generator/test-cases/rax-app/demo1/expected/demo-project/src/i18n.js new file mode 100644 index 000000000..705ba5495 --- /dev/null +++ b/packages/code-generator/test-cases/rax-app/demo1/expected/demo-project/src/i18n.js @@ -0,0 +1,13 @@ +const i18nConfig = {}; + +let locale = 'en-US'; + +const getLocale = () => locale; + +const setLocale = (target) => { + locale = target; +}; + +const i18n = (key) => (i18nConfig && i18nConfig[locale] && i18nConfig[locale][key]) || ''; + +export { getLocale, setLocale, i18n }; diff --git a/packages/code-generator/test-cases/rax-app/demo1/expected/demo-project/src/pages/Home/index.jsx b/packages/code-generator/test-cases/rax-app/demo1/expected/demo-project/src/pages/Home/index.jsx index fce1c34a9..e3e89c9b0 100644 --- a/packages/code-generator/test-cases/rax-app/demo1/expected/demo-project/src/pages/Home/index.jsx +++ b/packages/code-generator/test-cases/rax-app/demo1/expected/demo-project/src/pages/Home/index.jsx @@ -13,6 +13,8 @@ import { isMiniApp as __$$isMiniApp } from 'universal-env'; import __$$constants from '../../constants'; +import * as __$$i18n from '../../i18n'; + import __$$projectUtils from '../../utils'; import './index.css'; @@ -22,6 +24,8 @@ class Home$$Page extends Component { _context = this._createContext(); + _i18n = this._createI18nDelegate(); + _dataSourceConfig = this._defineDataSourceConfig(); _dataSourceEngine = __$$createDataSourceEngine(this._dataSourceConfig, this._context, { runtimeConfig: true }); @@ -74,12 +78,33 @@ class Home$$Page extends Component { get constants() { return __$$constants; }, + get i18n() { + return self._i18n; + }, + getLocale() { + return __$$i18n.getLocale(); + }, + setLocale(locale) { + __$$i18n.setLocale(locale); + self.forceUpdate(); + }, ...this._methods, }; return context; } + _createI18nDelegate() { + return new Proxy( + {}, + { + get(target, prop) { + return __$$i18n.i18n(prop); + }, + }, + ); + } + _defineDataSourceConfig() { const __$$context = this._context; return { list: [] }; diff --git a/packages/code-generator/test-cases/rax-app/demo1/schema.json5 b/packages/code-generator/test-cases/rax-app/demo1/schema.json5 index 1d405aee8..282f979e8 100644 --- a/packages/code-generator/test-cases/rax-app/demo1/schema.json5 +++ b/packages/code-generator/test-cases/rax-app/demo1/schema.json5 @@ -1,4 +1,5 @@ { + // 本例是一个非常简单的 Hello world 页面 // Schema 参见:https://yuque.antfin-inc.com/mo/spec/spec-materials#eNCJr version: '1.0.0', componentsMap: [ diff --git a/packages/code-generator/test-cases/rax-app/demo2/expected/demo-project/src/i18n.js b/packages/code-generator/test-cases/rax-app/demo2/expected/demo-project/src/i18n.js new file mode 100644 index 000000000..705ba5495 --- /dev/null +++ b/packages/code-generator/test-cases/rax-app/demo2/expected/demo-project/src/i18n.js @@ -0,0 +1,13 @@ +const i18nConfig = {}; + +let locale = 'en-US'; + +const getLocale = () => locale; + +const setLocale = (target) => { + locale = target; +}; + +const i18n = (key) => (i18nConfig && i18nConfig[locale] && i18nConfig[locale][key]) || ''; + +export { getLocale, setLocale, i18n }; diff --git a/packages/code-generator/test-cases/rax-app/demo2/expected/demo-project/src/pages/Home/index.jsx b/packages/code-generator/test-cases/rax-app/demo2/expected/demo-project/src/pages/Home/index.jsx index b1b50c437..f1dcacb95 100644 --- a/packages/code-generator/test-cases/rax-app/demo2/expected/demo-project/src/pages/Home/index.jsx +++ b/packages/code-generator/test-cases/rax-app/demo2/expected/demo-project/src/pages/Home/index.jsx @@ -19,6 +19,8 @@ import { isMiniApp as __$$isMiniApp } from 'universal-env'; import __$$constants from '../../constants'; +import * as __$$i18n from '../../i18n'; + import __$$projectUtils from '../../utils'; import './index.css'; @@ -45,6 +47,8 @@ class Home$$Page extends Component { _context = this._createContext(); + _i18n = this._createI18nDelegate(); + _dataSourceConfig = this._defineDataSourceConfig(); _dataSourceEngine = __$$createDataSourceEngine(this._dataSourceConfig, this._context, { runtimeConfig: true, @@ -171,12 +175,33 @@ class Home$$Page extends Component { get constants() { return __$$constants; }, + get i18n() { + return self._i18n; + }, + getLocale() { + return __$$i18n.getLocale(); + }, + setLocale(locale) { + __$$i18n.setLocale(locale); + self.forceUpdate(); + }, ...this._methods, }; return context; } + _createI18nDelegate() { + return new Proxy( + {}, + { + get(target, prop) { + return __$$i18n.i18n(prop); + }, + }, + ); + } + _defineDataSourceConfig() { const __$$context = this._context; return { diff --git a/packages/code-generator/test-cases/rax-app/demo2/schema.json5 b/packages/code-generator/test-cases/rax-app/demo2/schema.json5 index 96818e695..9f7778e7f 100644 --- a/packages/code-generator/test-cases/rax-app/demo2/schema.json5 +++ b/packages/code-generator/test-cases/rax-app/demo2/schema.json5 @@ -1,4 +1,5 @@ { + // 本例是一个比较复杂的,带有循环和条件渲染的,以及有各种事件处理函数的页面 // Schema 参见:https://yuque.antfin-inc.com/mo/spec/spec-materials#eNCJr version: '1.0.0', componentsMap: [ @@ -87,7 +88,7 @@ isSync: true, }, dataHandler: { - type: 'JSFunction', + type: 'JSExpression', value: 'function (response) {\nif (!response.success){\n throw new Error(response.message);\n }\n return response.data;\n}', }, }, @@ -104,13 +105,13 @@ isSync: true, }, dataHandler: { - type: 'JSFunction', + type: 'JSExpression', value: 'function (response) {\nif (!response.success){\n throw new Error(response.message);\n }\n return response.data.result;\n}', }, }, ], dataHandler: { - type: 'JSFunction', + type: 'JSExpression', value: 'function (dataMap) {\n console.info("All datasources loaded:", dataMap);\n}', }, }, @@ -164,7 +165,7 @@ componentName: 'View', props: { onClick: { - type: 'JSFunction', + type: 'JSExpression', value: 'this.hello', }, }, @@ -210,7 +211,7 @@ props: { style: { flexDirection: 'row' }, onClick: { - type: 'JSFunction', + type: 'JSExpression', value: 'function(){ this.utils.recordEvent(`CLICK_ORDER`, this.order.title) }', }, }, @@ -260,7 +261,7 @@ componentName: 'View', props: { onClick: { - type: 'JSFunction', + type: 'JSExpression', value: 'function (){ this.setState({ clickCount: this.state.clickCount + 1 }) }', }, }, @@ -312,7 +313,7 @@ name: 'formatPrice', type: 'function', content: { - type: 'JSFunction', + type: 'JSExpression', value: 'function formatPrice(price, unit) { return Number(price).toFixed(2) + unit; }', }, }, @@ -321,7 +322,7 @@ name: 'recordEvent', type: 'function', content: { - type: 'JSFunction', + type: 'JSExpression', value: 'function recordEvent(eventName, eventDetail) { \n this.utils.Toast.show(`[EVENT]: ${eventName} ${eventDetail}`);\n console.log(`[EVENT]: ${eventName} (detail: %o) (user: %o)`, eventDetail, this.state.user); }', }, }, diff --git a/packages/code-generator/test-cases/rax-app/demo3/expected/demo-project/src/i18n.js b/packages/code-generator/test-cases/rax-app/demo3/expected/demo-project/src/i18n.js new file mode 100644 index 000000000..705ba5495 --- /dev/null +++ b/packages/code-generator/test-cases/rax-app/demo3/expected/demo-project/src/i18n.js @@ -0,0 +1,13 @@ +const i18nConfig = {}; + +let locale = 'en-US'; + +const getLocale = () => locale; + +const setLocale = (target) => { + locale = target; +}; + +const i18n = (key) => (i18nConfig && i18nConfig[locale] && i18nConfig[locale][key]) || ''; + +export { getLocale, setLocale, i18n }; diff --git a/packages/code-generator/test-cases/rax-app/demo3/expected/demo-project/src/pages/Detail/index.jsx b/packages/code-generator/test-cases/rax-app/demo3/expected/demo-project/src/pages/Detail/index.jsx index 1e104c597..c3f22edd7 100644 --- a/packages/code-generator/test-cases/rax-app/demo3/expected/demo-project/src/pages/Detail/index.jsx +++ b/packages/code-generator/test-cases/rax-app/demo3/expected/demo-project/src/pages/Detail/index.jsx @@ -17,6 +17,8 @@ import { isMiniApp as __$$isMiniApp } from 'universal-env'; import __$$constants from '../../constants'; +import * as __$$i18n from '../../i18n'; + import __$$projectUtils from '../../utils'; import './index.css'; @@ -28,6 +30,8 @@ class Detail$$Page extends Component { _context = this._createContext(); + _i18n = this._createI18nDelegate(); + _dataSourceConfig = this._defineDataSourceConfig(); _dataSourceEngine = __$$createDataSourceEngine(this._dataSourceConfig, this._context, { runtimeConfig: true }); @@ -85,12 +89,33 @@ class Detail$$Page extends Component { get constants() { return __$$constants; }, + get i18n() { + return self._i18n; + }, + getLocale() { + return __$$i18n.getLocale(); + }, + setLocale(locale) { + __$$i18n.setLocale(locale); + self.forceUpdate(); + }, ...this._methods, }; return context; } + _createI18nDelegate() { + return new Proxy( + {}, + { + get(target, prop) { + return __$$i18n.i18n(prop); + }, + }, + ); + } + _defineDataSourceConfig() { const __$$context = this._context; return { list: [] }; diff --git a/packages/code-generator/test-cases/rax-app/demo3/expected/demo-project/src/pages/Home/index.jsx b/packages/code-generator/test-cases/rax-app/demo3/expected/demo-project/src/pages/Home/index.jsx index cb2bd3bab..dbd0e81ad 100644 --- a/packages/code-generator/test-cases/rax-app/demo3/expected/demo-project/src/pages/Home/index.jsx +++ b/packages/code-generator/test-cases/rax-app/demo3/expected/demo-project/src/pages/Home/index.jsx @@ -17,6 +17,8 @@ import { isMiniApp as __$$isMiniApp } from 'universal-env'; import __$$constants from '../../constants'; +import * as __$$i18n from '../../i18n'; + import __$$projectUtils from '../../utils'; import './index.css'; @@ -28,6 +30,8 @@ class Home$$Page extends Component { _context = this._createContext(); + _i18n = this._createI18nDelegate(); + _dataSourceConfig = this._defineDataSourceConfig(); _dataSourceEngine = __$$createDataSourceEngine(this._dataSourceConfig, this._context, { runtimeConfig: true }); @@ -85,12 +89,33 @@ class Home$$Page extends Component { get constants() { return __$$constants; }, + get i18n() { + return self._i18n; + }, + getLocale() { + return __$$i18n.getLocale(); + }, + setLocale(locale) { + __$$i18n.setLocale(locale); + self.forceUpdate(); + }, ...this._methods, }; return context; } + _createI18nDelegate() { + return new Proxy( + {}, + { + get(target, prop) { + return __$$i18n.i18n(prop); + }, + }, + ); + } + _defineDataSourceConfig() { const __$$context = this._context; return { list: [] }; diff --git a/packages/code-generator/test-cases/rax-app/demo3/expected/demo-project/src/pages/List/index.jsx b/packages/code-generator/test-cases/rax-app/demo3/expected/demo-project/src/pages/List/index.jsx index bd95523fb..c4e62dd2d 100644 --- a/packages/code-generator/test-cases/rax-app/demo3/expected/demo-project/src/pages/List/index.jsx +++ b/packages/code-generator/test-cases/rax-app/demo3/expected/demo-project/src/pages/List/index.jsx @@ -17,6 +17,8 @@ import { isMiniApp as __$$isMiniApp } from 'universal-env'; import __$$constants from '../../constants'; +import * as __$$i18n from '../../i18n'; + import __$$projectUtils from '../../utils'; import './index.css'; @@ -28,6 +30,8 @@ class List$$Page extends Component { _context = this._createContext(); + _i18n = this._createI18nDelegate(); + _dataSourceConfig = this._defineDataSourceConfig(); _dataSourceEngine = __$$createDataSourceEngine(this._dataSourceConfig, this._context, { runtimeConfig: true }); @@ -88,12 +92,33 @@ class List$$Page extends Component { get constants() { return __$$constants; }, + get i18n() { + return self._i18n; + }, + getLocale() { + return __$$i18n.getLocale(); + }, + setLocale(locale) { + __$$i18n.setLocale(locale); + self.forceUpdate(); + }, ...this._methods, }; return context; } + _createI18nDelegate() { + return new Proxy( + {}, + { + get(target, prop) { + return __$$i18n.i18n(prop); + }, + }, + ); + } + _defineDataSourceConfig() { const __$$context = this._context; return { list: [] }; diff --git a/packages/code-generator/test-cases/rax-app/demo3/schema.json5 b/packages/code-generator/test-cases/rax-app/demo3/schema.json5 index cb8ba2f85..5652b1db1 100644 --- a/packages/code-generator/test-cases/rax-app/demo3/schema.json5 +++ b/packages/code-generator/test-cases/rax-app/demo3/schema.json5 @@ -1,4 +1,5 @@ { + // 本例是一个路由测试页面,里面有几个页面,相互之间有跳转关系的 // Schema 参见:https://yuque.antfin-inc.com/mo/spec/spec-materials#eNCJr version: '1.0.0', componentsMap: [ diff --git a/packages/code-generator/test-cases/rax-app/demo4/expected/demo-project/src/i18n.js b/packages/code-generator/test-cases/rax-app/demo4/expected/demo-project/src/i18n.js new file mode 100644 index 000000000..705ba5495 --- /dev/null +++ b/packages/code-generator/test-cases/rax-app/demo4/expected/demo-project/src/i18n.js @@ -0,0 +1,13 @@ +const i18nConfig = {}; + +let locale = 'en-US'; + +const getLocale = () => locale; + +const setLocale = (target) => { + locale = target; +}; + +const i18n = (key) => (i18nConfig && i18nConfig[locale] && i18nConfig[locale][key]) || ''; + +export { getLocale, setLocale, i18n }; diff --git a/packages/code-generator/test-cases/rax-app/demo4/expected/demo-project/src/pages/Home/index.jsx b/packages/code-generator/test-cases/rax-app/demo4/expected/demo-project/src/pages/Home/index.jsx index 287c12ad2..0a93e8574 100644 --- a/packages/code-generator/test-cases/rax-app/demo4/expected/demo-project/src/pages/Home/index.jsx +++ b/packages/code-generator/test-cases/rax-app/demo4/expected/demo-project/src/pages/Home/index.jsx @@ -17,6 +17,8 @@ import { isMiniApp as __$$isMiniApp } from 'universal-env'; import __$$constants from '../../constants'; +import * as __$$i18n from '../../i18n'; + import __$$projectUtils from '../../utils'; import './index.css'; @@ -28,6 +30,8 @@ class Home$$Page extends Component { _context = this._createContext(); + _i18n = this._createI18nDelegate(); + _dataSourceConfig = this._defineDataSourceConfig(); _dataSourceEngine = __$$createDataSourceEngine(this._dataSourceConfig, this._context, { runtimeConfig: true }); @@ -82,12 +86,33 @@ class Home$$Page extends Component { get constants() { return __$$constants; }, + get i18n() { + return self._i18n; + }, + getLocale() { + return __$$i18n.getLocale(); + }, + setLocale(locale) { + __$$i18n.setLocale(locale); + self.forceUpdate(); + }, ...this._methods, }; return context; } + _createI18nDelegate() { + return new Proxy( + {}, + { + get(target, prop) { + return __$$i18n.i18n(prop); + }, + }, + ); + } + _defineDataSourceConfig() { const __$$context = this._context; return { list: [] }; diff --git a/packages/code-generator/test-cases/rax-app/demo4/schema.json5 b/packages/code-generator/test-cases/rax-app/demo4/schema.json5 index 08afc1463..efa43c840 100644 --- a/packages/code-generator/test-cases/rax-app/demo4/schema.json5 +++ b/packages/code-generator/test-cases/rax-app/demo4/schema.json5 @@ -1,4 +1,5 @@ { + // 本例是为了测试一下小程序的 runtime 模式 version: '1.0.0', componentsMap: [ { diff --git a/packages/code-generator/test-cases/rax-app/demo5/expected/demo-project/.editorconfig b/packages/code-generator/test-cases/rax-app/demo5/expected/demo-project/.editorconfig new file mode 100644 index 000000000..5760be583 --- /dev/null +++ b/packages/code-generator/test-cases/rax-app/demo5/expected/demo-project/.editorconfig @@ -0,0 +1,12 @@ +# 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 diff --git a/packages/code-generator/test-cases/rax-app/demo5/expected/demo-project/.eslintignore b/packages/code-generator/test-cases/rax-app/demo5/expected/demo-project/.eslintignore new file mode 100644 index 000000000..3b437e614 --- /dev/null +++ b/packages/code-generator/test-cases/rax-app/demo5/expected/demo-project/.eslintignore @@ -0,0 +1,11 @@ +# 忽略目录 +build/ +tests/ +demo/ + +# node 覆盖率文件 +coverage/ + +# 忽略文件 +**/*-min.js +**/*.min.js diff --git a/packages/code-generator/test-cases/rax-app/demo5/expected/demo-project/.eslintrc.js b/packages/code-generator/test-cases/rax-app/demo5/expected/demo-project/.eslintrc.js new file mode 100644 index 000000000..e2a7c5b54 --- /dev/null +++ b/packages/code-generator/test-cases/rax-app/demo5/expected/demo-project/.eslintrc.js @@ -0,0 +1,3 @@ +module.exports = { + extends: ['rax'], +}; diff --git a/packages/code-generator/test-cases/rax-app/demo5/expected/demo-project/.gitignore b/packages/code-generator/test-cases/rax-app/demo5/expected/demo-project/.gitignore new file mode 100644 index 000000000..50a53dace --- /dev/null +++ b/packages/code-generator/test-cases/rax-app/demo5/expected/demo-project/.gitignore @@ -0,0 +1,17 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +*~ +*.swp +*.log + +.DS_Store +.idea/ +.temp/ + +build/ +dist/ +lib/ +coverage/ +node_modules/ + +template.yml diff --git a/packages/code-generator/test-cases/rax-app/demo5/expected/demo-project/README.md b/packages/code-generator/test-cases/rax-app/demo5/expected/demo-project/README.md new file mode 100644 index 000000000..6eff85d41 --- /dev/null +++ b/packages/code-generator/test-cases/rax-app/demo5/expected/demo-project/README.md @@ -0,0 +1,15 @@ +# @ali/rax-component-demo + +## Getting Started + +### `npm run start` + +Runs the app in development mode. + +Open [http://localhost:9999](http://localhost:9999) to view it in the browser. + +The page will reload if you make edits. + +### `npm run build` + +Builds the app for production to the `build` folder. diff --git a/packages/code-generator/test-cases/rax-app/demo5/expected/demo-project/abc.json b/packages/code-generator/test-cases/rax-app/demo5/expected/demo-project/abc.json new file mode 100644 index 000000000..f9ee40f71 --- /dev/null +++ b/packages/code-generator/test-cases/rax-app/demo5/expected/demo-project/abc.json @@ -0,0 +1,7 @@ +{ + "type": "rax", + "builder": "@ali/builder-rax-v1", + "info": { + "raxVersion": "1.x" + } +} diff --git a/packages/code-generator/test-cases/rax-app/demo5/expected/demo-project/build.json b/packages/code-generator/test-cases/rax-app/demo5/expected/demo-project/build.json new file mode 100644 index 000000000..f3e9b9323 --- /dev/null +++ b/packages/code-generator/test-cases/rax-app/demo5/expected/demo-project/build.json @@ -0,0 +1,12 @@ +{ + "inlineStyle": false, + "plugins": [ + [ + "build-plugin-rax-app", + { + "targets": ["web", "miniapp"] + } + ], + "@ali/build-plugin-rax-app-def" + ] +} diff --git a/packages/code-generator/test-cases/rax-app/demo5/expected/demo-project/package.json b/packages/code-generator/test-cases/rax-app/demo5/expected/demo-project/package.json new file mode 100644 index 000000000..a42398577 --- /dev/null +++ b/packages/code-generator/test-cases/rax-app/demo5/expected/demo-project/package.json @@ -0,0 +1,32 @@ +{ + "name": "@ali/rax-app-demo", + "private": true, + "version": "1.0.0", + "scripts": { + "build": "build-scripts build", + "start": "build-scripts start", + "lint": "eslint --ext .js --ext .jsx ./" + }, + "dependencies": { + "@ali/lowcode-datasource-engine": "^0.1.0", + "universal-env": "^3.2.0", + "rax": "^1.1.0", + "rax-app": "^2.0.0", + "rax-document": "^0.1.0", + "rax-view": "^1.0.0", + "rax-text": "^1.0.0" + }, + "devDependencies": { + "build-plugin-rax-app": "^5.0.0", + "@alib/build-scripts": "^0.1.0", + "@typescript-eslint/eslint-plugin": "^2.11.0", + "@typescript-eslint/parser": "^2.11.0", + "babel-eslint": "^10.0.3", + "eslint": "^6.8.0", + "eslint-config-rax": "^0.1.0", + "eslint-plugin-import": "^2.20.0", + "eslint-plugin-module": "^0.1.0", + "eslint-plugin-react": "^7.18.0", + "@ali/build-plugin-rax-app-def": "^1.0.0" + } +} diff --git a/packages/code-generator/test-cases/rax-app/demo5/expected/demo-project/src/app.js b/packages/code-generator/test-cases/rax-app/demo5/expected/demo-project/src/app.js new file mode 100644 index 000000000..bc474c6e3 --- /dev/null +++ b/packages/code-generator/test-cases/rax-app/demo5/expected/demo-project/src/app.js @@ -0,0 +1,6 @@ +import { runApp } from 'rax-app'; +import appConfig from './app.json'; + +import './global.scss'; + +runApp(appConfig); diff --git a/packages/code-generator/test-cases/rax-app/demo5/expected/demo-project/src/app.json b/packages/code-generator/test-cases/rax-app/demo5/expected/demo-project/src/app.json new file mode 100644 index 000000000..63dec4d7d --- /dev/null +++ b/packages/code-generator/test-cases/rax-app/demo5/expected/demo-project/src/app.json @@ -0,0 +1,11 @@ +{ + "routes": [ + { + "path": "/", + "source": "pages/Home/index" + } + ], + "window": { + "title": "Rax App Demo" + } +} diff --git a/packages/code-generator/test-cases/rax-app/demo5/expected/demo-project/src/constants.js b/packages/code-generator/test-cases/rax-app/demo5/expected/demo-project/src/constants.js new file mode 100644 index 000000000..ea766c9da --- /dev/null +++ b/packages/code-generator/test-cases/rax-app/demo5/expected/demo-project/src/constants.js @@ -0,0 +1,3 @@ +const __$$constants = {}; + +export default __$$constants; diff --git a/packages/code-generator/test-cases/rax-app/demo5/expected/demo-project/src/document/index.jsx b/packages/code-generator/test-cases/rax-app/demo5/expected/demo-project/src/document/index.jsx new file mode 100644 index 000000000..50569a9b6 --- /dev/null +++ b/packages/code-generator/test-cases/rax-app/demo5/expected/demo-project/src/document/index.jsx @@ -0,0 +1,25 @@ +import { createElement } from 'rax'; +import { Root, Style, Script } from 'rax-document'; + +function Document() { + return ( + + + + + Rax App Demo +