开发hvigor plugin

229 阅读2分钟

ohpm使用 开发hvigor插件 ohpm配置以及plugin仓库配置

import ts from 'typescript' , 一开始在 hvigorfile.ts 找不到,直接把,粘贴到鸿蒙项目中

image.png

import { hvigor, HvigorNode, HvigorPlugin, Json5Reader } from '@ohos/hvigor';
import { harTasks, OhosHapContext, OhosPluginId, Target, OhosHarContext, OhosHspContext } from '@ohos/hvigor-ohos-plugin';
import fs from 'fs'
import path from 'path'
import ts from 'typescript'
const appRouter = "AppRouter"
// 实现自定义插件
export function customPlugin(): HvigorPlugin {
    return {
        pluginId: 'customPlugin',
        context() {
            return {
                data: 'customPlugin'
            };
        },
        async apply(currentNode: HvigorNode): Promise<void> {
            hvigor.nodesEvaluated(async () => {
                // 注册模块级任务
                hapTask(currentNode);
            });
        }
    };
}

function hapTask(node: HvigorNode) {
    // hvigor向外部暴露的hvigor对象 通过此对象可以操作hvigor运行中的内容
    hvigor.nodesEvaluated(async () => {
        // let conf = readConfig(node)
        // 获取har模块上下文信息
        const context = node.getContext(OhosPluginId.OHOS_HAR_PLUGIN) as OhosHarContext;
        let packageJson = await Json5Reader.readJson5File(
            path.resolve(node.getNodePath(), './oh-package.json5'),
            'utf-8'
        )

        if (!context) {
            throw new Error('hspContext is null')
        }
        let pluginId = OhosPluginId.OHOS_HAR_PLUGIN
        node.getAllTasks().forEach((v,k)=>{
            logUtil(`allTasks = ${ v.getName()}`)
        })
        context.targets((target: Target) => {
            const targetName = target.getTargetName()
            node.registerTask({
                name: `${targetName}@MyPLugin`,
                run: () => {
                    MyPLugin(context, node)
                    let taskName = 'Package' + pluginId.split('.')[2].charAt(0).toUpperCase() + pluginId.split('.')[2].slice(1)
                    console.error(`nzy - >targetName ${targetName}`)
                    console.error(`nzy - >taskName ${taskName}`)
                    node.getTaskByName(`${targetName}@${taskName}`)?.afterRun(() => {

                        // deleteGeneratorFile(conf)
                    })
                },
                dependencies: [`${targetName}@PreBuild`],
                postDependencies: [`${targetName}@MergeProfile`]
            })
        })
    })
}
function MyPLugin(
    context: OhosHarContext,
    node: HvigorNode
) {
    let startTime = Date.now()
    console.log(node.getNodeName() + ' 开始分析' + startTime)
    // 遍历 node.getNodePath() 下面的所有文件
    let lists: string[] = getEtsFiles(node.getNodePath() + "/src/main/ets")

    node.getAllTasks().forEach((v,k)=>{
        logUtil(`allTasks = ${ v.getName()}`)
    })
    logUtil('找到了 ' + lists.length + " 个文件---\n" + lists)
    let filsPath: AppRouterFile[] = []
    lists.forEach(filePath => {
        const analyzer = new Analyzer(filePath)
        analyzer.start()
        for (let [key, value] of analyzer.analyzeResultMap) {
            console.log('analyzeResult=>' + key + "----" + JSON.stringify(value))
            // analyzeResult=>undefined----{"annotation":"AppRouter","path":"c1","name":"C1"}
            let record = value as Record<string, string>
            if (value.name) {
                filsPath.push({ filePath: filePath, path: record['path'] })
            }
        }
    })

    logUtil(node.getNodePath())
    // 判断是否有Index.ets
    let path = node.getNodePath() + "/Index1.ets"
    let exist = fs.existsSync(path)
    /**
     *
     import { BuilderNameConstants } from '@ohos/routermodule';
     export function harInit(builderName: string): void {
     // 动态引入要跳转的页面
     switch (builderName) {
     case BuilderNameConstants.HARC_C1:
     import("./src/main/ets/components/mainpage/C1");
     break;
     case BuilderNameConstants.HARC_C2:
     import("./src/main/ets/components/mainpage/C2");
     break;
     default:
     break;
     }
     }
     */

    let stringBuilder = new StringBuilder()
    stringBuilder.appendLine("import { BuilderNameConstants } from '@ohos/routermodule';")
    stringBuilder.appendLine("")
    stringBuilder.appendLine("export function harInit(builderName: string): void {")
    stringBuilder.appendLine("  switch (builderName) {")
    // 这里可以for循环遍历了
    filsPath.forEach((router) => {
        stringBuilder.append("    case ")
        stringBuilder.append(`"${router.path}"`)
        stringBuilder.appendLine(":")
        const srcIndex = router.filePath.indexOf('/src');

        // 截取路径
        const result = `.${router.filePath.substring(srcIndex, router.filePath.lastIndexOf('.ets'))}`;
        stringBuilder.appendLine(`      import("${result}")`)
        stringBuilder.appendLine("      break;")
    })
    stringBuilder.appendLine("  }")
    stringBuilder.appendLine("}")
    if (exist) {
        fs.unlinkSync(path)
    }
    fs.writeFileSync(path, stringBuilder.toString())
    let endTime = Date.now()
    console.log(node.getNodeName() + ' 结束耗时 ' + (endTime - startTime) + " ms")
}
// 遍历 AST
function findClasses(node: ts.Node) {
    ts.forEachChild(node, child => {
        console.log(`节点类型: ${ts.SyntaxKind[node.kind]}`); // 打印节点类型
        // resolveNode(child)
        // 检查节点是否是类声明
        // if (ts.isClassDeclaration(child)) {
        //     console.log(`Found class: ${child.name?.text}`);
        // }
        // // 递归遍历子节点
        findClasses(child);
        if (ts.isClassDeclaration(node)) {
            const className = node.name ? node.name.text : '匿名类';
            console.log(`找到类:${className}`);

            // 提取装饰器
            // node.decorators.forEach(decorator => {
            //     const decoratorName = decorator.expression.escapedText;
            //     console.log(`  装饰器:${decoratorName}`);
            // });

        }

    });
}



function getEtsFiles(dir: string): string[] {
    let files: string[] = [];

    // 读取目录内容
    const items = fs.readdirSync(dir);
    for (const item of items) {
        const itemPath = path.join(dir, item);
        const stat = fs.statSync(itemPath);

        // 如果是目录,则递归调用
        if (stat.isDirectory()) {
            files = files.concat(getEtsFiles(itemPath));
        } else if (item.endsWith('.ets')) {
            // 如果是以 .ets 结尾的文件,添加到数组中
            files.push(itemPath);
        }
    }

    return files;
}
export default {
    system: harTasks,  /* Built-in plugin of Hvigor. It cannot be modified. */
    plugins: [customPlugin()]         /* Custom plugin to extend the functionality of Hvigor. */
};

function logUtil(content: string) {
    console.log(`nzy-> ${content}`)
}

export type AnalyzerResultLike = HMRouterResult

export class BaseAnalyzeResult {
    name?: string // 类名
    module?: string // 模块名
    annotation?: string // 注解
}

export class HMRouterResult extends BaseAnalyzeResult {
    pageUrl?: string // 跳转路径
    dialog?: boolean // 是否弹窗
    singleton?: boolean // 是否单例
    interceptors?: string[] // 拦截器
    animator?: string // 动画
    lifecycle?: string // 生命周期
}
class NodeInfo {
    value?: any
}
export class Analyzer {
    private HMRouter = "AppRouter"
    customAnnotationExisted: boolean = false
    analyzeResultMap: Map<string, AnalyzerResultLike> = new Map()
    private sourcePath: string
    private pluginConfig:string[] = [this.HMRouter]

    private analyzeResult: AnalyzerResultLike = {}
    private keywordPos: number = 0

    constructor(sourcePath: string) {
        this.sourcePath = sourcePath

    }

    start() {
        const sourceCode = fs.readFileSync(this.sourcePath, 'utf-8')
        const sourceFile = ts.createSourceFile(
            this.sourcePath,
            sourceCode,
            ts.ScriptTarget.Latest,
            false
        )
        ts.forEachChild(sourceFile, node => {
            this.resolveNode(node)
            // 解析完成后赋值存进Map
            switch (this.analyzeResult.annotation) {
                case this.HMRouter:
                    this.analyzeResultMap.set(
                        (this.analyzeResult as HMRouterResult).pageUrl!,
                        this.analyzeResult
                    )
                    break
            }
        })
    }

    private resolveNode(node: ts.Node) {
        if (ts.isMissingDeclaration(node)) {
            this.resolveMissingDeclaration(node)
        } else if (ts.isClassDeclaration(node)) {
            this.resolveClass(node)
        } else if (ts.isDecorator(node)) {
            this.resolveDecorator(node)
        } else if (ts.isCallExpression(node)) {
            this.resolveCallExpression(node)
        } else if (ts.isExpressionStatement(node)) {
            this.resolveExpression(node)
        } else if (ts.isBlock(node)) {
            this.resolveBlock(node)
        } else if (ts.isPropertyAssignment(node)) {
            return this.resolvePropertyAccess(node)
        } else if (ts.isIdentifier(node)) {
            return this.resolveIdentifier(node)
        } else if (ts.isStringLiteral(node)) {
            return this.resolveStringLiteral(node)
        } else if (node.kind === ts.SyntaxKind.TrueKeyword) {
            let info = new NodeInfo()
            info.value = true
            return info
        } else if (node.kind === ts.SyntaxKind.FalseKeyword) {
            let info = new NodeInfo()
            info.value = false
            return info
        } else if (ts.isNumericLiteral(node)) {
            return this.resolveNumericLiteral(node)
        } else if (ts.isArrayLiteralExpression(node)) {
            let interceptors = this.resolveArrayLiteral(node)
            let info = new NodeInfo()
            info.value = interceptors
            return info
        }
    }

    private resolveMissingDeclaration(node: ts.MissingDeclaration) {
        this.analyzeResult = {}
        node.forEachChild(child => this.resolveNode(child))
    }

    private resolveClass(node: ts.ClassDeclaration) {
        // 解析到类声明,先清空一次返回结果
        this.analyzeResult = {}
        node.modifiers?.forEach(modifier => {
            // 遍历分析装饰器
            this.resolveNode(modifier)
        })
        if (this.customAnnotationExisted) {
            this.analyzeResult.name = node.name?.text
        }
    }

    private resolveDecorator(node: ts.Decorator) {
        if (ts.isCallExpression(node.expression)) {
            const callExpression = node.expression as ts.CallExpression
            if (ts.isIdentifier(callExpression.expression)) {
                this.switchIdentifier(callExpression)
            }
        }
    }

    private switchIdentifier(callExpression: ts.CallExpression) {
        const identifier = callExpression.expression as ts.Identifier
        if (this.pluginConfig.some(item => item === identifier.text)) {
            this.customAnnotationExisted = true
            // 区分是什么装饰器,构造不同的返回类
            switch (identifier.text) {
                case this.HMRouter:
                    this.analyzeResult = new HMRouterResult()
                    this.analyzeResult.annotation = this.HMRouter
                    break
            }
            if (callExpression.arguments.length > 0) {
                this.resolveCallExpression(callExpression)
            }
        }
    }

    private resolveCallExpression(node: ts.CallExpression) {
        let identifier = this.resolveNode(node.expression)
        this.parseAnnotation(node.arguments, identifier)
    }

    private resolveExpression(node: ts.ExpressionStatement) {
        let identifier = this.resolveNode(node.expression)
        if (identifier?.value === 'struct') {
            this.keywordPos = node.end
        }
        if (this.analyzeResult.annotation === this.HMRouter && this.keywordPos === node.pos) {
            this.analyzeResult.name = identifier?.value
        }
    }

    private resolveBlock(node: ts.Block) {
        node.statements.forEach(statement => {
            this.resolveNode(statement)
        })
    }

    private parseAnnotation(args: ts.NodeArray<ts.Expression>, nodeInfo?: NodeInfo) {
        if (this.pluginConfig.some(item => nodeInfo?.value === item)) {
            args
                .flatMap((e: ts.Expression) => (e as ts.ObjectLiteralExpression).properties)
                .forEach((e: ts.ObjectLiteralElementLike) => {
                    this.parseConfig(e, this.analyzeResult)
                })
        }
    }

    private parseConfig(node: ts.ObjectLiteralElementLike, result: AnalyzerResultLike) {
        let info = this.resolveNode(node)
        console.log(`-----${info?.value['key']}----${info?.value['value']}----`)
        Reflect.set(result, info?.value['key'], info?.value['value'])
    }

    private resolveArrayLiteral(node: ts.ArrayLiteralExpression) {
        return node.elements.map(e => this.resolveNode(e)?.value as string)
    }

    private resolvePropertyAccess(node: ts.PropertyAssignment) {
        let propertyName = this.resolveNode(node.name)?.value
        let propertyValue = this.resolveNode(node.initializer)?.value
        let info = new NodeInfo()
        info.value = { key: propertyName, value: propertyValue }
        return info
    }

    private resolveNumericLiteral(node: ts.NumericLiteral) {
        let info = new NodeInfo()
        info.value = Number(node.text)
        console.log(`-----${node.text}----`)
        return info
    }

    private resolveStringLiteral(node: ts.StringLiteral) {
        let info = new NodeInfo()
        info.value = node.text
        console.log(`-----${node.text}----`)
        return info
    }

    private resolveIdentifier(node: ts.Identifier) {
        let info = new NodeInfo()
        info.value = node.escapedText.toString()
        console.log(`-----${node.escapedText.toString()}----`)
        return info
    }
}


class StringBuilder {
    private strings: string[] = [];

    // 添加字符串
    append(str: string): void {
        this.strings.push(str);
    }

    // 添加字符串并换行
    appendLine(str: string): void {
        this.strings.push(str + '\n');
    }

    // 获取拼接后的字符串
    toString(): string {
        return this.strings.join('');
    }

    // 清空内容
    clear(): void {
        this.strings = [];
    }
}

interface AppRouterFile {
    filePath: string,
    path: string,
}