router-register-plugin
Version:
鸿蒙ZRouter动态路由框架页面模板化插件
354 lines (305 loc) • 14.8 kB
text/typescript
/**
* @author: HZWei
* @date: 2024/7/16
* @desc:
*/
import {FileUtil, hvigor, HvigorNode, HvigorPlugin} from '@ohos/hvigor';
import * as path from "path";
import * as fs from "fs"
import {readdirSync, readFileSync, writeFileSync} from "fs"
import {AnalyzerParam, AnalyzerResult, AnnotationType, PageInfo, PluginConfig,} from "./models/model";
import {LogConfig, logger, loggerNode} from "./utils/logger";
import {isEmpty} from "./utils/string"
import JSON5 from "json5";
import {Analyzer} from "./analyzer";
import Constants from "./models/constants";
import FileHelper from "./utils/file-helper";
import {RouteInfo, RouteMap, RouteMetadata} from "./models/route-map";
import AnnotationMgr from "./utils/annotation-mgr";
import {TaskMgr} from "./task-mgr";
const builderRegisterFunFileName: string = Constants.BUILDER_REGISTER_FUN_FILE_NAME
const prefixZR: string = Constants.PREFIX_ZR
export function routerRegisterPlugin(config: PluginConfig): HvigorPlugin {
return {
pluginId: Constants.ROUTER_PLUGIN_ID,
apply(node: HvigorNode) {
const isRoot = node.getParentNode() === undefined
console.log("apply isRoot: ", isRoot , node.getNodeName())
LogConfig.init(config)
logger('apply', 'routerRegisterPlugin!');
logger('apply', `dirname: ${__dirname} `)
logger('apply cwd: ', process.cwd()) // 应用项目的根目录
new TaskMgr(config, node)
.start((config, node) => {
executePlugin(config, node)
})
}
}
}
function executePlugin(config: PluginConfig, node: HvigorNode) {
logger('executePlugin...')
const modName = node.getNodeName()
const modDir = node.getNodePath()
logger(modName, modDir)
const routeMap = new RouteMap()
const pageList = new Array<PageInfo>()
const serviceList = new Array<PageInfo>()
const zRouterPath = FileHelper.findZRouterModuleName(node)
const templateNavDestList = new Array<PageInfo>()
const lifecycleObserverList = new Array<PageInfo>()
const attributeList = new Array<PageInfo>()
if (config.isAutoDeleteHistoryFiles) {
FileHelper.deleteDirFile(config.generatedDir)
}
function assembleFileContent(result: AnalyzerResult, filePath: string) {
const fileName = `${prefixZR}${path.basename(filePath)}`
if (!isEmpty(result.name) && !isEmpty(result.pageName)) {
const pageInfo = new PageInfo()
let buildFunction = ''
if (result) {
if (AnnotationMgr.isRouteAnnotation(result.annotation)) {
// 页面路由 路由表信息
const routeInfo = new RouteInfo()
routeInfo.name = result.name
routeInfo.buildFunction = `${modName}${result.pageName}Builder`
routeInfo.pageSourceFile = getRelativeModPath(getEtsRelativePathByGeneratedDir(config, fileName), modDir)
const routeMetadata = new RouteMetadata()
routeMetadata.description = result.description
routeMetadata.needLogin = `${result.needLogin}`
routeMetadata.extra = result.extra
if (isEmpty(routeMetadata.description)) {
delete routeMetadata.description
}
if (isEmpty(routeMetadata.extra)) {
delete routeMetadata.extra
}
routeInfo.data = routeMetadata
routeMap.routerMap.push(routeInfo)
buildFunction = routeInfo.buildFunction
// Builder函数注册信息
pageInfo.name = result.name
pageInfo.buildFileName = fileName
pageInfo.pageName = result.pageName
pageInfo.importPath = getImportPath(config.generatedDir, filePath)
pageInfo.buildFunctionName = buildFunction
pageInfo.isDefaultExport = result.isDefaultExport
pageInfo.annotation = result.annotation
pageInfo.useTemplate = result.useTemplate
pageInfo.zRouterPath = zRouterPath
pageInfo.title = result.title
pageInfo.useV2 = result.useV2
pageInfo.hideTitleBar = result.hideTitleBar
pageInfo.lifecycleObserverAttributeName = result.loAttributeName
pageInfo.paramStr = result.paramStr
pageList.push(pageInfo)
} else if (AnnotationMgr.isServiceAnnotation(result.annotation)) {
// 服务路由
logger('服务路由: ', result)
pageInfo.name = result.name
pageInfo.buildFileName = `${prefixZR}Service`
pageInfo.pageName = result.pageName
pageInfo.importPath = getImportPath(config.generatedDir, filePath)
pageInfo.annotation = result.annotation
pageInfo.zRouterPath = zRouterPath
pageList.push(pageInfo)
} else if (AnnotationMgr.isLifecycleAnnotation(result.annotation) ||
AnnotationMgr.isAttrAnnotation(result.annotation)) {
// 生命周期注解和属性注解
logger('生命周期注解或属性注解: ', result)
pageInfo.name = result.name
pageInfo.pageName = result.pageName
pageInfo.importPath = getImportPath(config.generatedDir, filePath)
pageInfo.annotation = result.annotation
pageInfo.zRouterPath = zRouterPath
pageList.push(pageInfo)
}
}
}
}
config.scanDirs.forEach(scanDir => {
const files = FileHelper.getFilesInDir(scanDir)
files.forEach((filePath) => {
if (fs.existsSync(filePath)) {
let analyzer = new Analyzer(AnalyzerParam.create(filePath, modName, modDir))
analyzer.start()
analyzer.results.forEach((result) => {
if (!AnnotationMgr.isUnknownAnnotation(result.annotation)) {
assembleFileContent(result, filePath);
}
})
// 过滤出所有的模版文件, 最后处理
templateNavDestList.push(...pageList.filter(pageInfo => AnnotationMgr.isRouteAnnotation(pageInfo.annotation) && pageInfo.useTemplate))
lifecycleObserverList.push(...pageList.filter(pageInfo => AnnotationMgr.isLifecycleAnnotation(pageInfo.annotation)))
attributeList.push(...pageList.filter(pageInfo => AnnotationMgr.isAttrAnnotation(pageInfo.annotation)))
// 常规NavDest文件
const routerPageList = pageList.filter(pageInfo => AnnotationMgr.isRouteAnnotation(pageInfo.annotation) && !pageInfo.useTemplate)
const servicePageList = pageList.filter(pageInfo => AnnotationMgr.isServiceAnnotation(pageInfo.annotation))
generateRouterRegisterFile(config, routerPageList)
serviceList.push(...servicePageList)
pageList.length = 0
}
})
})
// 对NavDest模版文件进行处理
templateNavDestList.forEach((item, index) => {
item.lifecycleObserver = lifecycleObserverList.find((value) => item.name === value.name)
item.attributes = attributeList.find((value) => item.name === value.name)
item.lifecycleObserverAttributeName = isEmpty(item.lifecycleObserverAttributeName) ? config.lifecycleObserverAttributeName : item.lifecycleObserverAttributeName
})
if (templateNavDestList.length > 0) {
logger('templateNavDestList: ', templateNavDestList)
logger('lifecycleObserverList: ', lifecycleObserverList)
logger('attributes: ', attributeList)
templateNavDestList.forEach((item, index) => {
generateRouterRegisterFile(config, [item])
})
}
try {
generateRouterMap(config, routeMap)
checkIfModuleRouterMapConfig(config, routeMap)
generateServiceFile(config, serviceList, modName)
// 删除历史产物
deleteIndexImport(config)
deleteGeneratedFiles(config)
} catch (e) {
console.error('executePlugin error: ', e)
}
}
function generateServiceFile(config: PluginConfig, pageList: Array<PageInfo>, modName: string) {
if (pageList.length === 0) return
/**
* 分两步
* 1、根据模版生成文件
* 2、将文件在Index.ets文件中导出
*/
// 1
const generatedDir = config.generatedDir
const ts = FileHelper.getTemplateContent(Constants.SERVICE_TEMPLATE_PATH, pageList)
loggerNode(ts)
if (!fs.existsSync(generatedDir)) {
fs.mkdirSync(generatedDir, {recursive: true});
}
const zServiceFileName = pageList[0].buildFileName
const zServiceFilePath = `${generatedDir}${zServiceFileName}${Constants.ETS_SUFFIX}`
writeFileSync(zServiceFilePath, ts)
// 2
const etsIndexPath = `${config.indexDir}/Index.ets`
const importPath = getImportPath(config.indexDir, getEtsRelativePathByGeneratedDir(config, zServiceFileName))
const fileContent: string = `export * from './${importPath}'`
if (modName != Constants.DEF_MODULE_NAME) FileHelper.insertContentToFile(etsIndexPath, fileContent)
}
/**
* 删除旧的生成文件
* @param config
* @deprecated 此方法已废弃,clean任务已经实现了
*/
function deleteGeneratedFiles(config: PluginConfig) {
const generatedDir = config.generatedDir
if (!fs.existsSync(generatedDir)) return
const contents = readdirSync(generatedDir, {withFileTypes: true})
contents.forEach((value, index) => {
const filePath = path.join(generatedDir, value.name)
// logger('deleteGeneratedFiles: ', value.path)
// logger('deleteGeneratedFiles: ', value.parentPath)
if (value.isFile() && value.name.endsWith(Constants.ETS_SUFFIX) && !value.name.startsWith(prefixZR)) {
fs.unlinkSync(filePath)
}
})
}
/**
* 返回的格式:src/main/ets/....
* @param fullPath
* @param modDir
*/
function getRelativeModPath(fullPath: string, modDir: string): string {
const relativePath = fullPath.replace(modDir, '')
logger('===========================================')
logger('fullPath: ', fullPath)
logger('relativePath: ', relativePath)
logger('===========================================')
return relativePath.substring(1).replace(/\\/g, '/')
}
function getImportPath(from: string, to: string): string {
let importPath = path.relative(from, to).replace(/\\/g, '/')
logger('===========================================')
logger('from: ', from)
logger('to: ', to)
logger('importPath: ', importPath)
logger('===========================================')
return importPath.replace(Constants.ETS_SUFFIX, '')
}
function generateRouterRegisterFile(config: PluginConfig, pageList: PageInfo[]) {
logger('generateRouterRegisterFile: ', pageList)
pageList.forEach((item) => {
generate(item.buildFileName)
})
function generate(fileName: string) {
const generatedDir = config.generatedDir
const registerBuilderFilePath = `${generatedDir}${fileName}`
const registerBuilderFilePathOld = `${generatedDir}${builderRegisterFunFileName}`
if (fs.existsSync(registerBuilderFilePathOld)) {
fs.unlinkSync(registerBuilderFilePathOld)
}
if (fs.existsSync(registerBuilderFilePath) && pageList.length <= 0) {
fs.unlinkSync(registerBuilderFilePath)
return
}
const ts = FileHelper.getTemplateContent(Constants.ROUTER_TEMPLATE_PATH, pageList)
loggerNode(ts)
if (!fs.existsSync(generatedDir)) {
fs.mkdirSync(generatedDir, {recursive: true});
}
writeFileSync(registerBuilderFilePath, ts)
}
}
function generateRouterMap(config: PluginConfig, routeMap: RouteMap) {
logger('generateRouterMap: ', JSON.stringify(routeMap))
// if (routeMap.routerMap.length <= 0) return
writeFileSync(config.routerMapPath, JSON.stringify(routeMap, null, 2), {encoding: "utf8"})
}
/**
* 获取生成文件的相对路径 ./src/main/ets/_generated/fileName
* @param config
* @param fileName
* @returns {string} 返回的是相对路径
*/
function getEtsRelativePathByGeneratedDir(config: PluginConfig, fileName: string): string {
return path.join(config.generatedDir, fileName)
}
/**
* 历史问题,删除index.ets的导出
* @param config
* @deprecated 此方法已废弃,clean任务已经实现了
*/
function deleteIndexImport(config: PluginConfig) {
// logger('generateIndex page length: ', pageList.length)
const indexPath = `${config.indexDir}/Index.ets`
const importPath = getImportPath(config.indexDir, getEtsRelativePathByGeneratedDir(config, builderRegisterFunFileName))
const data: string = `export * from './${importPath}'`
if (!fs.existsSync(indexPath)) {
return
}
const content = fs.readFileSync(indexPath, {encoding: "utf8"})
const lines = content.split('\n').filter((item) => {
return item !== ''
})
const target = lines.find((item) => item === data)
if (!isEmpty(target)) {
// logger('generateIndex splice ')
const index = lines.indexOf(target!)
lines.splice(index, 1)
fs.writeFileSync(indexPath, lines.join('\n'), {encoding: "utf8"})
}
}
function checkIfModuleRouterMapConfig(config: PluginConfig, routeMap: RouteMap) {
logger("===========================================")
logger('checkIfModuleRouterMapConfig')
if (!fs.existsSync(config.moduleJsonPath) || routeMap.routerMap.length <= 0) return;
const content = readFileSync(config.moduleJsonPath, "utf8")
const module = JSON5.parse(content)
if (module.module.routerMap) return;
module.module.routerMap = "$profile:route_map"
fs.writeFileSync(config.moduleJsonPath, JSON.stringify(module, null, 2), {encoding: "utf8"})
logger('checkIfModuleRouterMapConfig ok')
logger("===========================================")
}