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
启动初始化流程步骤
流程函数调用链
调用node hvigor.js后,在 hvigor.js 中会首先会进行初始化命令行和日志等级。如果 daemon 模式,会先调用defaultDaemonServerFactory.createDaemonFork()在后台启动一个 daemon,否则直接调用cli.js#startHvigorBuild开始启动流程。
进入cli.js#startHvigorBuild,如果是 daemon 模式,会先创建 daemon 连接,否则直接调用index.js#boot开始启动流程。
进入index.js#boot,根据当前的 hvigor 版本信息、传入的参数等信息,开始初始化globalData,initEnvConfigProps等对象,然后调用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[];
}