拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Flutter依赖审计工具鸿蒙化:从dart_dependency_checker_cli到包健康中台

Flutter依赖审计工具鸿蒙化:从dart_dependency_checker_cli到包健康中台 1. dart_dependency_checker_cli 依赖审计原理与检测机制1.1 先看一个真实场景项目里的“僵尸依赖”有多离谱我去年接手了一个内部中台 App 的 Flutter 工程第一个打开的业务文件不是代码而是 pubspec.yaml。十几行到几十行的依赖声明看着不多可点开 pubspec.lock 一看传递依赖累计超过 400 个。再顺着代码库扫一遍lib 目录下将近一半的 import 语句指向的根本不是 pubspec 里声明的包而是靠别的包“顺手”拉进来的传递依赖。这种写法在项目早期还挺爽反正编译器不报错能跑就行。但等到要升级某个基础库时噩梦就来了。A 包依赖 B 包的旧版本C 包又依赖 B 包的新版本中间冲突一团乱麻。更要命的是有些包当初只为了一个工具函数引入后来函数被重构删掉了pubspec 里那行依赖却一直留着。它不报错但它让依赖树膨胀、安装时间变长、包体积变大还可能把某个旧版本的安全漏洞一直留在项目里。这种“声明了但没人用”的依赖圈里叫僵尸依赖。它属于过度依赖的一种同类问题还有代码里用了某些类但 pubspec 里压根没写缺失依赖、同样的功能在三个包里各来一份重复依赖、某个依赖版本落后太多但没人关心滞后依赖。这些问题用肉眼一个个查在中小项目里还能应付一旦项目过百个直接依赖就彻底失控了。dart_dependency_checker_cli 就是专门干这个的。它把 pubspec 里的依赖声明和代码里的 import 语句逐一对账自动把上述几类问题列成报告。我在自己的项目里跑了几轮之后基本离不开它了。尤其在 Flutter 工程迁往鸿蒙的背景下依赖治理这件原本靠人肉的事情必须变成自动化、可重复、能进门禁的工具能力。1.2 它不是一个简单的“查重脚本”而是一个静态分析规则引擎先拆解一下这个工具的工作原理方便后面讲鸿蒙化时对照改源码。它的核心流程分四步第一步解析 pubspec.yaml拿到直接依赖和开发依赖两份清单第二步遍历项目里 lib、test、integration_test、example 等目录下的所有 Dart 文件提取每条 import 指令把 import 跟包名做映射第三步用集合对比算法把“声明依赖”和“实际使用依赖”做差集第四步按检测规则输出报告标注未使用依赖、缺失依赖、可移除的依赖覆盖项以及版本滞后情况。它跟 dependency_validator 是同源的思路。技术上它依赖 analyzer、yaml、collection 这些纯 Dart 包所以整个工具本身可移植性相当好。这一点对鸿蒙化很关键——后面你会在源码里面对的主要是 dart:io 的 API 差异而不是一堆平台绑定代码。在具体参数设计上工具支持用--ignored维护一份豁免名单。因为有些包确实不该被自动扫描规则炸出来比如build_runner、json_serializable这类编译期代码生成器代码里不会直接 import再比如依赖注入、路由注册类框架可能只在配置文件里引用。默认都判成“未使用依赖”会误报一大堆所以豁免名单是必要的。还支持--no-default-packages等配置项可以从规则中剔除 Dart 内置的可直接可见库。这里有个细节值得注意扫描 import 时Dart 的part和part of机制会干扰统计。因为 part 文件会把多个源文件拼合成一个逻辑库如果工具按文件级统计 import很容易把一个库拆出来的十几个 part 文件误判成十几个孤立引用。我们在鸿蒙化适配时专门处理了这一点后面第 4 章会展开讲。1.3 鸿蒙化适配这件事为什么绕不开这个工具现在聊到鸿蒙很多 Flutter 团队的第一个想法是我们的 UI 层、业务逻辑层都是 Dart 写的理论上能复用迁移成本应该可控。这个判断大方向没错但有一个盲区——鸿蒙 Flutter 工程里依赖管理不再只有 pubspec 一套体系。一个标准的鸿蒙工程里ArkTS/ArkUI 侧的三方库依赖写在 oh-package.json5 文件里跟 pubspec.yaml 平行存在。原生侧代码又由 module.json5 做模块配置。换句话说同一份“包健康报告”要同时看懂两套依赖声明、两类源码文件.dart 和 .ets/.ts、两套构建产物规则。原版 dart_dependency_checker_cli 只会解析 pubspec完全不认识 oh-package.json5也不认 ArkTS 代码里的 import。所以鸿蒙化绝不是“把命令行工具编译一下就能跑”这么简单。要做的事至少有三件让工具能在鸿蒙环境下被正确构建和运行让工具能解析鸿蒙工程特有的依赖描述文件让工具能识别 ArkTS 源码里的依赖引用规则。做完这三件事工具才真正从“Flutter 专用审计器”变成“鸿蒙应用包健康审计器”。2. 鸿蒙化适配选型三条路线怎么挑2.1 先捋清鸿蒙环境里的 Dart/Flutter 生态现状选路线前得先知道鸿蒙上到底能怎么跑 Dart。HarmonyOS NEXT 已经不再兼容 APK原生应用主要用 ArkTS/ArkUI 来写但 Flutter 跨端代码可以通过 OpenHarmony 社区维护的 flutter_flutter 分支以及厂商提供的 Flutter OHOS SDK 运行起来。这些 SDK 实际上是 Flutter 官方仓库在 OpenHarmony 平台上的移植版本内置了完整的 Dart SDK并且保留了 dart:io 大部分的文件、网络与进程能力。这给 CLI 工具移植提供了基础一个以纯 Dart 编写的命令行程序理论上可以拿到鸿蒙版 Dart SDK 直接编译而不需要把逻辑重写成 ArkTS。但要注意这里说的“编译产物”和 Android/iOS 上常见的可执行文件不同鸿蒙侧会有自己的构建链、签名和打包约束后面实操部分会单独说。另外如果你的 App 原本用了大量 Flutter 与原生端的通信机制比如 MethodChannel、EventChannel迁移到鸿蒙平台时通道名称和实现路径都要重新对齐因为鸿蒙侧是用 ArkTS 实现原生端的。这一层的工作量不在依赖审计工具的范围内但会影响整体迁移排期。依赖审计应该放到迁移的前置步骤而不是等通道全通了再补做。2.2 三条路线对比直接编译、内嵌应用、服务端托管我在适配前把可行的路径整理成了三套方案分别针对不同的团队规模和工程形态。第一条路线是源码直接编译。办法是拿鸿蒙版 Dart SDK 把 dart_dependency_checker_cli 编译成鸿蒙环境下可执行的产物。优点是改动最小因为工具本身的依赖基本是纯 Dart 实现只需要处理 dart:io 的少数差异缺点是鸿蒙沙箱、权限和路径规则与常规 Linux 环境不完全一致得加一个适配层。第二条路线是做成鸿蒙应用的内嵌能力。把审计代码挂进一个已有的鸿蒙 Flutter 应用里通过 EntryAbility 或后台任务触发。优点是能接触到完整的 ArkTS 接口可以读工程文件、操作 UI 等缺点是打包复杂CLI 使用场景被重了。除非你本来就要做一个带审计界面的独立工具 App否则没必要。第三条路线是服务端托管。继续在常规 CI 机器上跑原版 CLI不碰鸿蒙设备。优点是零成本缺点也很明显完全无法覆盖鸿蒙工程特有的 oh-package.json5也扫描不到 .ets/.ts 源码里的依赖引用等于鸿蒙侧的包健康依然是个盲区。我选择的是第一条路线为主、外层包一个鸿蒙适配层。核心原因是 CLI 的使用场景决定了它应该是一个可以在构建前、提交时、流水线里随意调用的命令行工具而不是一个需要被手动打开的应用界面。2.3 方案选型的三个关键决策点决策点一确认依赖是否足够纯净。改造前我先把 dart_dependency_checker_cli 及其上游依赖逐个看了源码确认它们没有依赖 dart:ui、package:ffi、platform channels 等平台绑定能力。这个检查决定了后面要不要动大手术。结论是基本没有90% 以上的代码可以直接复用。决策点二定义“鸿蒙识别范围”。工具既要识别 pubspec.yaml 里声明的 Dart 依赖还要识别 oh-package.json5 里声明的 ArkTS 依赖。同时扫描代码引用时要新增对 .ets、.ts、.js 后缀文件的 import 处理。这个范围如果不提前定义好适配的时候会反复返工。决策点三明确报告口径。原版工具输出 JSON 和文本报告但鸿蒙化之后报告最好能区分“Dart 侧问题”和“ArkTS 侧问题”再混合成一个综合评分。如果只是简单拼接两份报告中台化的时候还要二次加工。所以我在早期就把这个口径定成了统一依赖模型后面 4.2 节会讲具体模型长什么样。3. 鸿蒙化实操全流程解析器扩展与沙箱适配3.1 环境准备版本不对齐后面全是坑先讲最基础的。我的推荐组合是DevEco Studio 自带的命令行 hvigorw 作为构建工具配合 OpenHarmony 社区的 flutter_flutter 分支作为 Flutter SDKDart SDK 直接用这个 Flutter 分支工具链里捆绑的版本不要在系统里单独装一套否则版本漂移会让人疯掉。在 CI 上运行的话建议把整套环境打成镜像。要包含的组件鸿蒙 SDK、Flutter OHOS SDK、JDK、Node.js、hvigorw。关键点是版本号必须对齐因为 Flutter OHOS 分支更新节奏快dart_dependency_checker_cli 依赖的 analyzer 版本对 Dart SDK 有最低要求一旦 SDK 版本太老编译直接挂。构建命令上最典型的是flutter build ohos --release这个命令会走完整套鸿蒙编译管线产出 HAP 包。CLI 工具的产物如果不需要界面可以考虑走“可执行快照”或 AOT 方案但实际落地时为了兼容各方操作习惯更稳妥的做法是保持 Dart/JIT 运行模式由 hvigor 负责把它打进构建产物。实测下来的体会是环境这块最容易翻车的地方不是函数报错而是“你以为在跑鸿蒙版 Dart实际还在用你机器上老版本的 Dart”。务必在 CI 脚本开头加一句dart --version的显式断言。3.2 解析器扩展让工具能读 oh-package.json5这是整个适配工作中我最想详细写的一块。鸿蒙工程的 ArkTS 三方依赖记录在 oh-package.json5 里它的结构类似于 npm 的 package.json但字段名和语义有差异而且用的是 JSON5 语法允许写注释、单引号、尾逗号Dart 的 jsonDecode 直接解析必挂。我的做法是先做一个轻量清洗函数把注释和尾逗号去掉降级成标准 JSON 再解析import dart:convert; MapString, dynamic parseOhPackage(String rawText) { final cleaned rawText .replaceAll(RegExp(r//[^\n]*), ) .replaceAll(RegExp(r/\*.*?\*/, dotAll: true), ) .replaceAll(RegExp(r,\s*([}\]])), r$1); return jsonDecode(cleaned) as MapString, dynamic; }清洗完后还不够因为 pubspec.yaml 里的依赖字段是 dependencies 和 dev_dependencies而 oh-package.json5 里是 dependencies 和 devDependencies。字段名大小写不一致后续规则引擎不好统一处理。所以更合理的方案是建一个中间模型把两套依赖都归一化class UnifiedDependency { final String name; final String versionRange; final bool isDirect; final bool isDev; final DepSource source; // fromPubspec / fromOhPackage }所有检测规则比如“僵尸依赖”“缺失依赖”“版本滞后”都跑在 UnifiedDependency 之上。这样无论后续鸿蒙体系再增加什么描述文件规则层都不用动。ArkTS 代码扫描是第二个大头。你在 .ets/.ts 文件里最常见的 import 形如import { BusinessError } from kit.BasicServicesKit; import axios from ohos/axios;这里kit.BasicServicesKit是系统内置包ohos/axios才是第三方依赖。如果工具把这些行全部算成依赖引用报告里会出现大量误报。我给适配层加了一组白名单映射凡是kit.开头、以及 ohos 侧的 SDK 内置模块都跳过只统计 oh-package.json5 里实际声明的三方库。同时名称要做归一化因为import axios from ohos/axios里的 axios 和 oh-package.json5 里的ohos/axios需要对上号。3.3 平台 API 替换与沙箱权限处理纯 Dart CLI 在鸿蒙环境里跑起来之后第二个障碍是运行时的路径与权限。常规的 Linux 命令行工具可以自由读写临时目录和用户主目录但鸿蒙沙箱环境下程序默认只能访问自己的数据目录。CLI 的使用方式是传入一个工程根目录作为参数于是适配层必须做三件事第一把外部传入的参数路径映射成沙箱内可见路径第二如果检测报告要落到固定目录得换到鸿蒙的临时目录或共享目录第三考虑报告导出否则沙箱里的文件在设备外不可见。权限方面如果工具需要联网拉取最新版本号来做“版本滞后”检测需要在 module.json5 的 requestPermissions 里声明网络权限{ module: { requestPermissions: [ { name: ohos.permission.INTERNET } ] } }如果你想把权限范围最小化我更推荐的做法是联网检测单独做成可关闭的开关默认只在本地做静态分析。这样工具在 CI 设备上的安全合规压力会小很多跑批速度也快。另外一个容易踩的坑是 Process.run。原版 CLI 在某些模式下会调用flutter pub get来重新拉取依赖树或执行解析。实测发现鸿蒙沙箱对子进程启动的限制比较严行为跟常规环境不一致。最稳的绕法是默认不触发任何外部命令行而是通过参数注入一份“已解析的依赖列表”让工具只做静态审计不做依赖安装。等于把工具的职责收窄反而更干净。3.4 构建产物与流水线集成所有适配代码完成之后最终要落到构建与集成。实践经验是不要把 CLI 当成一个孤立程序在设备上敲命令而是封装成一个自动化流水线步骤。推荐的最小集成方案是这样CI 收到 merge request 后拉取最新代码执行鸿蒙化后的审计可执行文件传入仓库根目录路径工具同时读取 pubspec.yaml、pubspec.lock、oh-package.json5扫描 .dart/.ets/.ts 源码生成一份 JSON 报告和一份 HTML 报告HTML 报告上传到内部静态服务JSON 报告作为 CI 门禁判断依据如果新增直接依赖没有被任何代码引用流水线直接标红。这里还需要注意 hdc 的路径问题。鸿蒙设备连接工具 hdc 类似 Android 时代的 adb但它的命令通道有时会被代理或防火墙干扰。CI 里我一般建议用 USB 直连或者局域网目标机不要跨广域网跑审计任务否则传输等待时间比审计本身还长。4. 从 CLI 到包健康审计中台落地架构与实践4.1 中台架构设计采集层、规则层、报告层CLI 是给单个开发者用的工具但标题里提到“包健康审计中台”这就意味着要把审计能力从“一个人手动跑命令”升级成“多个团队多仓库自动被治理”的平台能力。我的中台实现分成三层。采集层负责统一接收各仓库的 pubspec.lock、oh-package.json5、构建日志和源代码变更信息。不用一开始就搞实时监听定时拉取就够了。规则层在上面讲的 CLI 检测能力之上维护一套可配置的规则文件每个团队可以自定义--ignored名单、版本滞后阈值、警告级别。报告层负责把扫描结果落库、生成看板、输出趋势图和通知。技术选型上我不建议一上来就上重量级服务端框架。最早期的可行版本可以只是一个定时任务的 Git 拉取列表、一段调度脚本、一个静态站点生成器。报告自动生成后推到内网静态托管每个项目一个页面。等仓库数量超过一百个再考虑引入真正的服务端存储和 API。4.2 依赖健康评分卡把“过度依赖”量化成看得懂的分数“过度依赖”这个词很模糊如果不量化团队很难对一个指标形成统一认识。我设计了一套 0-100 分的依赖健康评分模型维度如下直接依赖数量超过基线扣分。基线和团队规模强相关我按小型团队直接依赖 30 个以内为优来设。僵尸依赖比例声明但未使用的依赖占用所有直接依赖的比例这一个权重最高。缺失依赖数量代码引用了但 pubspec/oh-package.json5 没声明每出现一个都是高危信号。传递依赖膨胀指数锁文件里的总包数除以直接依赖数。正常项目在 2-3 之间算健康超过 5 说明间接依赖失控。版本滞后度落后两个以上大版本的三方包数量。简化后的打分公式如下score 100 - W1 * directExcess - W2 * zombieRatio * 100 - W3 * missingCount - W4 * blowUpRatio - W5 * staleCount权重 W1-W5 建议按团队关注点来调如果近期重点是包体瘦身就提高 W2 和 W4如果近期重点是升级合规就提高 W5。评分本身不是为了制造焦虑而是为了在排期讨论时有依据低分项目优先治理。4.3 实际项目复盘把 200 依赖压到 120 的过程中发生了什么这套体系在我们自己工程里跑起来之后第一周的审计报告就让整个组坐到了会议室。当时 pubspec 里一共 130 个直接依赖代码里真正被 import 的只有 87 个锁文件里的传递依赖超过 400 个。逐条排查后问题集中在三类。第一类是状态管理全家桶bloc、cubit、equatable、hydrated_bloc 四个全引进来实际代码只用了 bloc 主库。这四个包加起来体积不小而且概念上互相重叠属于教科书级的过度依赖。第二类是工具库重复日期处理、字符串操作、网络请求几乎每个模块都有自己的封装但同时又引进了两三个通用工具库导致同一功能有三套实现。第三类是 part 机制对扫描的干扰这也是最容易误判的地方。关于 part 机制我在 1.2 节就提到过。审计工具如果按文件逐个统计 import一个用了 part 拆分的库主文件和十几个 part 文件都会被识别成不同的引用来源而它们实际上同属一个库。如果工具的统计口径不做去重报告里会出现一整排“某个库的多个子文件都未使用”的误报误导团队。 解决办法是扫描时只统计逻辑库层级遇到 part 文件跳过等看到主库顶部的 import 时统一计数。这一点在鸿蒙化后的增强里同样保留并且应用到 .ets 的 import 统计上效果很好。清理完第一轮直接依赖从 130 降到 110再把剩余冗余依赖同步移除后稳定在 83 个左右。包体缩减约 12%依赖安装时间下降约 30%。数字不算夸张但对于一个已经有三年历史的中台工程来说这个结果说明过度依赖问题在存量项目里真的很普遍。4.4 从 CLI 到中台的渐进落地路径如果团队现在还没有依赖审计能力我的建议非常直接先不要想着做中台先在一个仓库里把 CLI 跑起来把报告挂到 CI 上。这个阶段的收益已经很大因为每个 merge request 都能看到依赖健康变化。跑顺一个仓库后再抽象规则配置把不同仓库的白名单、豁免项收敛到统一配置中心。当接入的仓库超过十个才值得投入做一个聚合服务。因为在 10 个仓库以下聚合带来的洞察非常有限反而会增加运维成本。这个节奏把握住中台才不会变成空中楼阁。5. 鸿蒙化适配常见问题速查与避坑技巧5.1 问题速查表现象可能原因处理建议编译报 dart:io 相关符号找不到错用了普通 Dart SDK而非鸿蒙 Flutter 工具链自带 SDK改用 Flutter OHOS 分支捆绑的 Dart SDK调 Process.run 执行 flutter pub get 失败鸿蒙沙箱限制子进程行为绕过命令执行改为注入已解析依赖列表oh-package.json5 解析直接抛异常JSON5 语法含有注释、单引号、尾逗号先做正则清洗再交给 jsonDecode.ets 文件扫描出现大量误报系统 Kit 模块被当成三方依赖维护系统模块白名单跳过 kit. 前缀报告显示所有依赖都“未使用”part 成员文件被逐个统计扫描时跳过 part 文件按逻辑库去重鸿蒙设备上跑CI超时hdc 连接不稳或跨网络传输本地设备直连减少网络转发5.2 三个容易踩的坑每个都花掉过我半天时间第一个坑是 ignore 列表迁移。原版 CLI 的--ignored参数规则在鸿蒙适配版里不是天然兼容的——尤其是字段命名原版用的是 Dart 侧包名鸿蒙适配版还要收录 ArkTS 侧的ohos.包名。我在第一版适配时直接套用了原有规则文件结果鸿蒙侧的新增豁免项根本没生效导致误报率飙升。解决方式是在适配层做字段映射转换把两套依赖体系的豁免配置统一到同一份 YAML 里。第二个坑是报告文件被沙箱挡住。早期版本我把 HTML 报告直接写在当前工作目录本地 Linux 上没问题一放到鸿蒙设备上执行就找不到文件。后来才意识到沙箱对写入位置有约束需要显式指定到应用可写目录。如果你碰到“命令明明执行成功但文件不见了”的情况优先检查这个。第三个坑是版本号语义化比较。鸿蒙 SDK 的部分包版本号可能带 build 后缀比如1.2.3-rc1之类如果直接用 SemVer 库比较大小可能抛异常或者错判。比较前要做一次清洗把预发布标签单独提取出来。这也是为什么第 4 章的评分卡里版本滞后维度我在实现时单独做了一层版本号归一化而不是直接复用原工具方法。5.3 日常维护建议让依赖治理变成工程习惯工具终归是辅助真正的难点在于让团队形成依赖治理的纪律。我现在的建议是每个新增直接依赖都要过审PR 里如果新增了一行依赖声明而没有对应的 import 出现流水线直接拒绝合并。每周自动跑一次全量审计把评分趋势图发到组内看板谁也不想自己负责的模块评分持续走低。每季度做一次大的依赖整理把确定没用的包批量移除并把豁免名单中已失效的条目清理掉。这套节奏跑起来之后依赖健康不太会再出现“一次性重建地狱”的状况。它不需要一个专门的岗位只需要把工具放进工程设施里并制定几条简单的规则。说到底这些规则比各种架构评审都来得直观不过度依赖就是给未来的升级留活路。最后再讲一句个人体会。整个鸿蒙化改造做完我最深的感受是工具移植本身并没有想象中困难真正困难的是意识到“工程结构已经改变”原来的审计口径需要跟着变。dart_dependency_checker_cli 给了我们一个很好的起点但让它真正发挥价值的地方是在它被编译进鸿蒙 CI 流水线、被纳入团队日常研发节奏之后。如果你的团队也正在往鸿蒙迁移我建议把依赖审计放进迁移排期的前置步骤早一天让过度依赖无处遁形后面升级和瘦身的阻力就会小很多。
返回列表