Hvigor 启动流程

1,332 阅读4分钟

hvigor 启动流程

hvigor 4.0.4 版本

启动命令

node ~/.hvigor/.../node_modules/@ohos/hvigor/bin/hvigor.js --mode module -p product=default -p module=entry@default assembleHap --analyze --parallel --incremental --daemon

启动初始化流程步骤

whiteboard_exported_image.png

流程函数调用链

whiteboard_exported_image (1).png

调用node hvigor.js后,在 hvigor.js 中会首先会进行初始化命令行和日志等级。如果 daemon 模式,会先调用defaultDaemonServerFactory.createDaemonFork()在后台启动一个 daemon,否则直接调用cli.js#startHvigorBuild开始启动流程。

进入cli.js#startHvigorBuild,如果是 daemon 模式,会先创建 daemon 连接,否则直接调用index.js#boot开始启动流程。

进入index.js#boot,根据当前的 hvigor 版本信息、传入的参数等信息,开始初始化globalDatainitEnvConfigProps等对象,然后调用hvigor-process.js#hvigorProcess.init()对进程进行初始化(主要是创建一系列事件的监听),最后调用index.js#start方法进行最后的准备和最终启动。

进入index.js#start,首先创建 init 构建度量事件,然后初始化 hvigor 的数据模型、初始化工程构建配置、注册系统、自定义插件、构建 TASK DAG,最终组装构建参数,创建AbstractExecutePipeline,并调用AbstractExecutePipeline#startPipeline()触发整个任务流的执行。至此,整个初始化和启动流程结束,开始进入执行流程。

关键函数分析

执行请求启动

// src/base/boot/index.js
async function start() {
    // MetricFactory 创建 init 埋点
    const r = MetricFactory.createDurationEvent(
        'init',
        "Initialize and build task graph.",
        HvigorTaskGroupType.INIT_TASK_GROUP
    );
    // 开始事件
    r.start();
    // 获取cli执行参数
    const o = globalData.cliOpts
    const s = globalData.cliEnv
    // 初始化hvigor的数据模型 src/base/internal/lifecycle/init.js#init()
    init()
    // 初始化工程构建配置,注册系统、自定义插件 src/base/internal/lifecycle/configuration.js#configuration
    // 返回根工程模型
    const t = await configuration();
    // 构建TASK DAG src/base/internal/lifecycle/build-task-graph.js
    buildTaskGraph(t)
    // 组装构建参数
    const i = { toRunTasks: o._, isNeedSync: !!o.sync };
    // 获取启动 task 名称数组
    const _ = s.configProps.get(modeAlias);
    // 结束init事件
    // 创建并开始执行任务流水线
    await new ExecuteModeFactory(t, i)
        // 根据参数信息获取具体ExecutePipeline模型,由 --mode 参数决定,目前有 module 和 project
        .getExecutePipeline(_) 
        // 开始执行任务流水线,后续就是从TASK DAG中找到启动task以及依赖,按依赖顺序执行每一个Task
        .startPipeline();
}

工程信息配置流程

// src/base/internal/lifecycle/configuration.js
async function configuration() {
    // 记录当前时间
    // 获取globalData.cliEnv
    const cliEnv = globalData.cliEnv
    // hvigor.getProject()
    const t = hvigor.getProject()
    // 寻找hvigorfile.ts文件路径
    const o = path.resolve(cliEnv.cwd, HvigorCommonConst.BUILD_FILE_NAME)
    // 给根工程增加tasks和tasktree任务
    configNodeTask(t)
    // 给根工程增加PruneTask任务
    configProjectTask(t)
    // 加载hvigorfile.ts文件
    await evaluateNodeVigorFile(t, findRealHvigorFilePath(o, log))
    // 触发 HvigorCoreNode afterEvaluate中注册的回调
    await executeAfterNodeEvaluated(t);
    // 遍历子模块
    for (const e of t.getAllSubModules()) {
        // 给模块增加tasks和tasktree任务
        configNodeTask(e)
        // 加载hvigorfile.ts文件
        await evaluateNodeVigorFile(e, findRealHvigorFilePath(e.getBuildFilePath(), log))
        // 触发 HvigorCoreNode afterEvaluate中注册的回调
        await executeAfterNodeEvaluated(e);
    }
    
    if (cluster_1.default.isWorker) {
        // 在worker进程将hvigorfile.ts以及它的依赖加入到监控列表中
        // ...
    }
    // 触发 hvigor.beforeNodesEvaluated 注册的监听
    await executeBeforeAllNodesEvaluated()
    // 触发 hvigor.nodesEvaluated 注册的监听
    await executeAfterAllNodesEvaluated()
    
    return t
}

// e - HvigorCoreNode
// i - hvigorfile.ts文件路径
async function evaluateNodeVigorFile(e, i) {
    let t;
    // 前置处理
    // 加载文件
    try {
        t = require(i)
    } catch (e) {
        // 报错
    }
    // 判断数据是否存在
    if (t.default && t.default.system) {
        // 绑定hvigor-ohos-plugin
        await bindSystemPlugins(t.default.system, e, i)
        // 绑定自定义插件,这里并没有使用await
        bindCustomPlugins(t.default.plugins, e, i)
    } else {
        // 其他绑定方式
        await bindSystemPlugins(t, e, i)
    }
    // 将文件路径加入监控
    cluster_1.default.isWorker && watch_config_file_js_1.addModuleDependency(i)
}

// 绑定自定义插件
// e - 插件对象数组
// i - HvigorCoreNode
// t - hvigorfile.ts文件路径
function bindCustomPlugins(e, i, t) {
    // 对e进行数据校验
    // 根据vigorfile.ts文件路径获取HvigorCoreNode
    const o = hvigor.getHvigorNodeByScriptPath(t.substring(0, t.lastIndexOf(".")));
    // 遍历插件数组
    for (let s = 0; s < e.length; s++) {
        const n = e[s];
        // 对象字段校验
        if ("object" == typeof n && "string" == typeof n.pluginId && "function" == typeof n.apply) {
            // 如果插件有context,向node绑定信息
            n.context && i.bindPluginContextFunc(n.pluginId, n.context.bind(n)
            // 在 hvigor 添加 beforeNodesEvaluated 回调,用于执行apply方法
            hvigor.beforeNodesEvaluated(async () => {
                await n.apply(o);
            }))
        }
    }
}

从上面的源码,可以知道 hvigor 工程的根 project 是如何被初始化的,并且代码中也展示了 hvigor 和 project 对象中几个钩子回调的时机,根据回调时机点可以做一些自定义逻辑。

指的关注的是自定义插件的加载和 apply 流程,同时也了解到了 hvigorfile.ts 文件在 hvigor 初始化过程中的作用。

和 SystemPlugin 不同的是,CustomPlugin 并没有被工程对象调用bindPlugin方法注册到自身的 pluginContainer,猜测是 SystemPlugin 在框架其他地方还有更复杂的处理逻辑。但是 CustomPlugin 也支持通过bindPluginContextFunc方法将一些插件信息存储到工程对象里面,方便后续查询。

总结

  • 了解了调用 hvigor 命令之后 hvigor 的启动流程

  • 了解了 hvigor 工程模型的初始化流程(configuration.js)

  • 了解了系统插件和自定义插件的绑定注册流程(hvigorfile.ts)

附录

启动参数模型

HvigorCliOptions {
    _: string[]; // 启动任务
    help?: boolean;
    cwd?: string;
    require?: string;
    prop?: string | string[];
    mode?: string;
    sync?: boolean;
    error?: boolean;
    warn?: boolean;
    info?: boolean;
    debug?: boolean;
    parallel?: boolean;
    incremental?: boolean;
    stacktrace?: boolean;
    enableBuildScriptTypeCheck?: boolean;
    daemon?: boolean;
    watch?: boolean;
    stopDaemon?: boolean;
    stopDaemonAll?: boolean;
    startDaemon?: boolean;
    statusDaemon?: boolean;
    nodeHome?: string;
    hvigorUserHome?: string;
    typeCheck?: boolean;
    pnpmFrozenLockfile?: boolean;
    analyze?: boolean;
    verboseAnalyze?: boolean;
    completeCommand?: string;
    config?: string[];
}