代码拉取完成,页面将自动刷新
本示例将介绍如何通过ts抽象语法树,根据自定义装饰器,自动生成代码。
插件初始化
创建ts工程,并添加hvigor依赖,可以使用hvigor中配置的解析和打包方法
npm init
npm install --save-dev @types/node hvigor @ohos/hvigor-ohos-plugin
npm install typescript handlebars
初始化ts配置
./node_modules/.bin/tsc --init
修改package.json文件
{
"name": "autobuildrouter",
"version": "1.0.0",
"description": "",
"main": "lib/index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "tsc && node lib/index.js",
"build": "tsc"
},
...
修改tsconfig.json,指定编译相关的选项
{
"compilerOptions": {
"target": "es2021", /* Set the JavaScript language version for emitted JavaScript and include compatible library
"module": "commonjs", /* Specify what module code is generated. */
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
"skipLibCheck": true, /* Skip type checking all .d.ts files. */
"sourceMap": true,
"outDir": "./lib"
},
"include": [".eslintrc.js","src/**/*"],
"exclude": ["node_modules","lib/**/*"]
}
创建插件文件src/index.ts,用于编写插件代码
插件代码编写完成后,运行/node_modules/.bin/tsc.cmd,在hvigor/hvigor-config.json5中配置插件后即可使用。
插件实现
根据hvigor文件中的配置,初始化插件的参数
export function etsGeneratorPlugin(pluginConfig: PluginConfig): HvigorPlugin {
...
return {
pluginId: PLUGIN_ID,
apply(node: HvigorNode) {
// 获取模块名
pluginConfig.moduleName = node.getNodeName();
// 获取模块路径
pluginConfig.modulePath = node.getNodePath();
pluginExec(pluginConfig);
}
}
扫描hvigor配置的路径下的所有文件,获取装饰器中配置的路由信息
function pluginExec(config: PluginConfig) {
...
// 遍历需要扫描的文件列表
config.scanFiles.forEach((file) => {
// 文件绝对路径
let sourcePath = `${config.modulePath}/${file}`;
// 如果配置的文件路径没有ets后缀,则添加后缀名
if(!sourcePath.endsWith('.ets')){
sourcePath = sourcePath+'.ets';
}
// 获取文件相对路径
const importPath = path.relative(`${config.modulePath}/${config.builderDir}`, sourcePath).replaceAll("\\", "/").replaceAll(".ets", "");
const analyzer = new EtsAnalyzer(config, sourcePath);
// 开始解析文件
analyzer.start();
console.log(JSON.stringify(analyzer.analyzeResult));
console.log(importPath);
// 如果解析的文件中存在装饰器,则将结果保存到列表中
if (analyzer.routerAnnotationExisted) {
templateModel.viewList.push({
viewName: analyzer.analyzeResult.viewName,
importPath: importPath,
functionName:analyzer.analyzeResult.functionName
});
routerMap.routerMap.push({
name: analyzer.analyzeResult.name,
pageModule: config.moduleName,
pageSourceFile: `${config.builderDir}/${config.builderFileName}`,
registerFunction: `${analyzer.analyzeResult.functionName}Register`
});
}
})
}
读取文件内容,通过遍历ts抽象语法树,找到自定义装饰器,读取路由信息,并通过关键字确认自定义组件的名称,其他的节点可以忽略
// ets文件解析
export class EtsAnalyzer {
start() {
// 读取文件
const sourceCode = readFileSync(this.sourcePath, "utf-8");
// 解析文件,生成抽象语法树
const sourceFile = ts.createSourceFile(this.sourcePath, sourceCode, ts.ScriptTarget.ES2021, false);
// 遍历语法树
ts.forEachChild(sourceFile, (node: ts.Node) => {
this.resolveNode(node);
});
}
// 根据类型对语法树节点进行处理
resolveNode(node: ts.Node): NodeInfo | undefined {
switch (node.kind) {
case ts.SyntaxKind.ImportDeclaration:
this.resolveImportDeclaration(node);
break;
case ts.SyntaxKind.MissingDeclaration:
this.resolveMissDeclaration(node);
break;
case ts.SyntaxKind.Decorator:
this.resolveDecoration(node);
break;
case ts.SyntaxKind.ExpressionStatement:
this.resolveExpression(node);
break;
...
}
...
}
// 解析装饰器节点,根据名称找到自定义装饰器,并获取装饰器中配置的路由信息
resolveDecoration(node: ts.Node) {
// 转换为装饰器节点类型
let decorator = node as ts.Decorator;
// 判断表达式是否是函数调用
if (decorator.expression.kind === ts.SyntaxKind.CallExpression) {
const callExpression = decorator.expression as ts.CallExpression;
// 表达式类型是否是标识符
if (callExpression.expression.kind === ts.SyntaxKind.Identifier) {
const identifier = callExpression.expression as ts.Identifier;
// 标识符是否是自定义的装饰器
if (identifier.text === this.pluginConfig.annotation) {
this.routerAnnotationExisted = true;
const arg = callExpression.arguments[0];
// 调用方法的第一个参数是否是表达式
if (arg.kind === ts.SyntaxKind.ObjectLiteralExpression) {
const properties = (arg as ts.ObjectLiteralExpression).properties;
// 第一个参数是否是表达式
if (properties[0].kind === ts.SyntaxKind.PropertyAssignment) {
// 设置的第一个参数是否是自定义装饰器中的变量名
if ((properties[0].name as ts.Identifier).escapedText === "name") {
// 将装饰器中的变量的值赋值给解析结果中的变量
this.analyzeResult.name = (properties[0].initializer as ts.StringLiteral).text;
}
}
}
}
}
}
}
// 由于一个ets文件中可能有多个自定义组件,只有与hvigor中配置的关键字相同时,才是需要通过路由加载的组件
resolveExpression(node: ts.Node) {
let args = node as ts.ExpressionStatement;
let identifier = this.resolveNode(args.expression);
if (this.analyzeResult.name.endsWith(identifier?.value)) {
this.analyzeResult.viewName = identifier?.value;
// 通过identifier获取到的自定义组件的名字是大驼峰,方法名需要使用小驼峰,所以此处对组件名首字母进行处理,变为符合规则的小驼峰格式的方法名
let viewName: string = identifier?.value.toString();
viewName = `${viewName.charAt(0).toLowerCase()}${viewName.slice(1, viewName.length)}`;
this.analyzeResult.functionName = viewName;
}
}
生成路由表、组件注册类和Index.ets文件
...
generateBuilder(templateModel, config);
generateRouterMap(routerMap, config);
generateIndex(config);
...
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。