鸿蒙Flutter依赖审计:dart_dependency_checker_cli落地指南
鸿蒙 Flutter 开发走到今天团队最常争论的往往集中在 flutter impeller 的渲染表现、EventChannel 能不能复用、PlatformView 要不要重写这类问题上。坦白说这些是能不能用的问题真正拖垮工程效率的往往是没人注意的依赖管理问题。我最近接管一个鸿蒙 Flutter 项目第一件事不是看渲染性能而是把 pubspec.yaml 从第一行翻到最后一行——好家伙光完全没被代码引用的依赖就有十来个更别提那些被换成了本地鸿蒙适配版、原版还赖在 dev_dependencies 里的包。靠人眼审计这件事工程小的时候还能凑合一旦模块超过二十个、维护到第三代基本就是随缘。后来我把 dart_dependency_checker_cli 引入进来跑出一份完整的依赖审计报告很多遗留在历史里的包债才真正浮出水面。这篇文章要分享的不是什么黑科技而是一套我已经在鸿蒙 Flutter 工程里验证过的适配与落地方法dart_dependency_checker_cli 这个纯 Dart 工具在鸿蒙环境下怎么用、哪些地方需要针对鸿蒙工程特性做校准、如何把它沉淀成团队级的包健康审计机制。适合正在做鸿蒙 Flutter 应用、又每天被依赖树折磨的开发者也适合想给团队立规矩的技术负责人。1. 鸿蒙 Flutter 工程的依赖失控比想象中更普遍1.1 鸿蒙生态放大了依赖债的累积速度正常 Flutter 工程里三方库的来源相对集中pub.dev 官方、GitHub 热门仓库、公司内部镜像源。鸿蒙 Flutter 工程则完全不一样。鸿蒙生态的 Flutter 支持还在快速演进中大量成熟插件没有官方的鸿蒙实现于是会出现几个非常典型的依赖来源别人 fork 出来的鸿蒙适配版插件通过 git 依赖引入内部自研的鸿蒙桥接包通过 path 依赖挂在工程外的目录官方插件加鸿蒙扩展插件的组合比如 Android 端用某个原生插件、鸿蒙端用 ohos 版替代。这些依赖在 pubspec.yaml 里不会自动标注平台属性pub 字典里只有dependency: ^1.2.3、path: ../xxx、git:这样的记录。等到依赖树长大谁也不知道哪些包真的在跑哪些只是历史遗留。鸿蒙开发又往往伴随快速的平台适配迭代今天引入的桥接包明天可能就被新方案替代但 pubspec.yaml 里那一行经常被忘掉。而且鸿蒙侧的三方包质量参差不齐不少是个人维护、几个月不更新的状态。依赖它意味着把运行时稳定性和供应链安全都押在了别人的更新频率上。这种背景下过度依赖就不再是洁癖而是实打实的工程风险。1.2 四种典型的包债我在这类项目里几乎都遇到过我把实践中见到的依赖问题归纳成四类完全未使用的依赖代码里没有任何 import但一直挂在 pubspec.yaml。这种最好查也是 ddc 最擅长的。仅被工具链使用的依赖build_runner、json_serializable 这类包只在生成代码时用运行时完全不需要但它们经常被错误地放在 dependencies 而不是 dev_dependencies。被替换后残留的依赖鸿蒙端用了 fork 版或本地版后原插件那行还留着。从不升级的过时依赖lock 文件里某个包停在两年前的版本升级又要连带做一次回归。下面这张表是我排查时最常见的几类场景和它们对工程的影响包债类型典型表现主要危害未使用依赖无任何 import仍在 dependencies增加构建与安装体积混淆依赖关系工具链依赖放错位置build_runner 等放进 dependencies把开发期依赖打进生产包替换后残留原插件与 fork 版共存误导后来者可能触发重复初始化过时依赖长期滞留lock 锁在老版本安全补丁缺失升级难度增大这些如果都靠 PR 评审时人工盯再细心的负责人也会漏。所以需要一个自动化的审计员角色ddc 的作用就是这个。2. 先搞清楚 dart_dependency_checker_cli 到底查什么2.1 核心检查能力拆解dart_dependency_checker_cli 不是一个简单的扫一遍 import脚本。它先解析工程里的 Dart 源码统计所有 import、export、part 指令再结合 pubspec.yaml 与 pubspec.lock 建立完整的依赖图最后按规则做交叉比对。它提供的检查维度大致有以下几类检查维度含义典型发现unused dependencies声明了但没有被任何源码引用的依赖残留的无用包missing dependencies源码引用了但未在 pubspec 声明的依赖间接依赖被裸用outdated dependencies当前解析版本明显低于最新稳定版长期不更新的老依赖banished dependencies配置中明确禁用名单内的依赖被列入黑名单的包duplicate dependencies同一依赖以多种形式反复出现path、git、pub 三种来源并存以 unused 为例它的判断逻辑不是简单的 grep而是基于 Dart 的静态解析能识别条件导入、库前缀别名这类写法比人工搜索可靠得多。2.2 为什么不能靠 flutter pub deps 平替Flutter 自带的flutter pub deps能输出整棵依赖树很多团队也把它当审计工具用。但 pub deps 只解决看得清的问题不解决判得准的问题。举个例子flutter pub deps --stylecompact会告诉你某个包被谁传递依赖了但它不会告诉你这个包在你的代码里从来没有被 import 过也不会告诉你某个声明在 dependencies 里的包其实只该出现在 dev_dependencies。判断任务还是得人来做。ddc 这类工具的价值在于把判断规则沉淀成配置什么算未使用、什么算禁用、哪些路径豁免都由 dc.yaml 说了算。规则一旦进仓库团队所有人共享同一套审计标准新 PR 提交的时候自动执行这就是它在中台化路径上的核心意义。再补充一个容易混淆的工具dart pub outdated。它只管版本新旧对哪个依赖其实没在用一无所知。适合配合使用但不适合当审计主工具。3. 鸿蒙化适配的真实含义不是改插件而是理清边界3.1 纯 Dart CLI 在鸿蒙环境下为什么可以零修改运行首先要明确一点dart_dependency_checker_cli 是一个纯 Dart 实现的命令行工具不依赖任何 Android/iOS 原生代码也不依赖 Flutter 引擎的特定能力。鸿蒙 Flutter 工程本质上还是通过 Dart 的 pub 生态来管理依赖pubspec.yaml、pubspec.lock、.dart_tool 这些文件的结构和标准 Flutter 工程完全一致。所以严格来说这个工具不需要像插件那样改写原生代码、不需要注册 ohos 侧实现。鸿蒙化适配在 CLI 工具这里重点不是让它跑起来而是让它跑得对——也就是让它理解鸿蒙工程里特有的依赖组织方式不要误判、漏判。这一点很多人会误解以为要 fork 一个鸿蒙版 ddc 出来其实根本没那个必要。3.2 鸿蒙工程依赖的特殊性需要重新校准审计规则的三个点鸿蒙 Flutter 工程和标准 Flutter 工程相比依赖上有三个明显的差异ohos 目录与 hvigor 体系鸿蒙工程里有 ohos/ 目录、hvigorfile.ts、module.json5 等文件。这些不属于 Dart 代码正常不会被 ddc 当源码解析但如果你的工程把一些工具类放在 ohos/ 下的 JS/TS 目录里并且通过某种方式被 Dart 侧引用少见但存在就要小心误判。我的建议是把 ohos/ 明确加入忽略配置避免它被当成普通 Dart 目录扫描既防误报也能省一点扫描时间。平台替代依赖并存很多时候一个插件在 pubspec.yaml 里同时存在原版和鸿蒙 fork 版。比如第三方库 A 在 Android 上用原版鸿蒙用本地 path 指向的 fork。原版在 iOS/Android 目录下被原生代码引用但 Dart 层已经切到 fork 的 APIddc 可能会报告原版 unused。这其实是预期行为需要在配置里排除掉已知需要保留的平台依赖。依赖来源复杂化鸿蒙工程里 path 依赖、git 依赖的比例远高于普通 Flutter 项目。ddc 版本如果较老对 git 依赖的解析可能不完善。建议使用最新稳定版确保它能从 lock 文件里正确识别来源。这一点我实测过旧版本对本地 path 依赖的解析有概率漏报升级之后才正常。3.3 适配清单我把需要做的适配操作整理成一张对照表照着做就行适配项做法原因忽略 ohos 目录dc.yaml 中 ignoreohos/**避免扫描鸿蒙侧工程文件导致误报或拖慢速度豁免平台替代依赖将原版插件加入 unused 豁免清单它们实际被平台侧引用Dart 层扫不到确认 git/path 依赖解析使用最新版 ddc旧版对本地与 git 依赖支持不完整校准 dev_dependencies增加 dev 范围规则鸿蒙适配常引入一次性代码生成工具避免漏在开发依赖里4. 在鸿蒙 Flutter 工程里跑通审计的完整操作4.1 环境准备先保证你的鸿蒙 Flutter SDK 是能正常构建工程的版本。我建议在开始审计前跑一次flutter pub get这一步不是多余的。pub get 会刷新 pubspec.lock把当前解析到的版本锁下来。ddc 是从 lock 文件读依赖信息的lock 不新审计结果就是过期数据。同时确认 Dart 版本dart --versionddc 对 Dart 版本有最低要求太老的 SDK 可能导致它解析语法失败。我在鸿蒙环境里遇到过 flutter 与 dart 由鸿蒙 SDK 内置的情况直接用内置的 dart 即可。如果用的是自己装的独立 Dart SDK注意版本别低于工具要求。4.2 安装 ddc两种方式选一种方式一作为 dev dependency 安装适合只在当前工程使用、希望锁版本的场景。flutter pub add --dev dart_dependency_checker_cli然后运行dart run dart_dependency_checker_cli方式二全局激活适合多工程共用、想当日常排查工具的场景。dart pub global activate dart_dependency_checker_cli激活成功后终端会提示全局 bin 路径加入 PATH 后可以直接用命令名执行。两种方式我都试过团队协作我更推荐方式一版本在 pubspec.lock 里被锁住所有人跑出来的结果一致个人排查问题可以用方式二省得每个工程都装一遍。4.3 配置边界规则在项目根目录创建 dc.yaml。这是一个典型的鸿蒙 Flutter 工程配置示例presets: unused: # 完全不参与 unused 检查的包 exclude: - ^flutter_localizations$ # 即使检测到未使用也允许通过的包有豁免理由 ignore: - ^some_ohos_adapter$ # 只忽略 dev_dependencies 下的包 dev: ignore: - ^build_runner$ banished: ban: - old_package_name paths: ignore: - ohos/** - integration_test/** - test_resources/**字段名不同版本可能略有差异以你在命令行里dart run dart_dependency_checker_cli --help的输出为准。核心逻辑是exclude这类包不进未使用判断ignore判断到了但允许通过ban谁出现在依赖树里直接报错paths.ignore哪些目录完全不扫描。我特别建议鸿蒙工程把ohos/**加入 ignore因为鸿蒙工程的 ohos 目录里会有大量非 Dart 文件默认扫描即便不误报也会拖慢速度。4.4 运行与结果解读配置好之后执行dart run dart_dependency_checker_cli正常输出是以组为单位的检查报告大致长这样[✓] Finished checking unused dependencies [✓] Checking banished dependencies [!] Found 3 unused dependencies: - package_a - package_b - package_c看到[!]行就是需要处理的。建议拿到结果先不着急删包逐个确认一下是不是代码生成的文件才引用它如果是见第六节的处理方式是不是平台侧引用的那就加到 exclude如果确认什么都没引用删掉它然后重新flutter pub get。5. 把审计沉淀为中台CI 卡点与团队依赖基线5.1 一条最小可用的 CI 审计阶段要让审计不流于形式最好在合并前强制跑。下面是鸿蒙 Flutter 工程的 CI 里可以直接放进去的脚本片段bash#!/usr/bin/env bash set -euo pipefail cd ${PROJECT_ROOT:-.} # 1. 以 lock 文件为准刷新依赖 flutter pub get # 2. 执行依赖审计 if ! dart run dart_dependency_checker_cli; then echo 依赖审计未通过存在未使用/缺失/禁用依赖请检查 dc.yaml 与 pubspec.yaml exit 1 fi echo 依赖审计通过这段脚本的关键点是set -euo pipefail任何一步失败都会中断流水线不会出现报告打了一堆 warning 但 CI 照样绿的情况。如果你用的是 Jenkins、GitLab CI 或自建流水线把这段逻辑包进去即可。5.2 建立依赖基线防止新债悄悄进仓库依赖审计最容易出现的问题第一次跑出一堆旧账修不完于是有人把它关了或加了一堆 ignore等于没做。我的做法是分两步走第一步历史存量问题在配置里用带注释的 ignore 拉一个旧债白名单每一条都写明理由和负责人、预期清理日期。比如presets: unused: ignore: # TODO: 等当前鸿蒙桥接方案稳定后移除, owner: zhang-san - ^legacy_bridge_adaptor$第二步针对新增问题设置硬性卡点。ignore 白名单只减不增新的无用依赖一旦被 ddc 报告CI 直接红。这样团队既有消化存量债的空间又不会让新债无限叠加。5.3 让包健康成为可见指标如果团队有 dashboard 或者开发周报的习惯可以把 ddc 的报告落地成一份简化的 JSON 输出结合 CRON 定时跑一次记录趋势。某周新的无用依赖增加、某周存量债减少这些数据比我们最近比较注意依赖有说服力得多。所谓审计中台在我看来就是让这套检查能力成为团队公共基础设施而不是某个人偶尔想起来才跑一次。6. 实测高频问题误报、漏报与豁免策略6.1 代码生成类依赖一定会误报鸿蒙 Flutter 工程里几乎都有 json_serializable、freezed、build_runner 这类代码生成工具。代码生成前你在源码里的part xxx.g.dart;并不直接 import 这些包名但生成的代码可能依赖它们。ddc 在静态扫描时可能找不到直接引用关系于是把 build_runner、json_serializable 报成 unused。这类误报的正确处理不是删依赖而是在配置里把它识别为工具链依赖。如果你确定某个包只服务于代码生成阶段它就应该在 dev_dependencies 里然后在配置中通过 ignore 或 dev 范围豁免。我的习惯是build_runner 这类生成器一定放 dev_dependencies这样即使 ddc 判它 unused也能一眼看出它本来就不该在运行时存在。6.2 ohos 平台专属依赖的豁免鸿蒙工程经常出现原版插件在 Android 目录被 importDart 层没有直接引用的情况。ddc 报告 unused但实际上它被 iOS/Android 的原生代码引用着。这种不能删该做的是在配置里豁免。如果实在拿不准建议先全局搜索一下插件名在 android/、ios/、ohos/ 目录里有没有出现有就基本可以排除。6.3 动态 import 会造成漏报Dart 里可以通过字符串拼接执行动态导入虽然用的人少但存在还有反射、代码注入之类的手段。ddc 是静态分析识别不了这些。反过来讲如果你用的是import package:xxx/xxx.dart这种常规写法基本不会漏。我的兜底策略是ddc 报出来的问题只要有理由该豁免就豁免ddc 没报但你知道有人动态引用的包直接在 ignore 清单里显式写出来防止未来移除依赖时误伤。最后说一点我自己的体会。跑了好几个鸿蒙工程之后我最强的感受是依赖审计这件事工具只解决了发现这一半剩下的是机制问题。没有 CI 卡点报告发群里没人看没有基线旧债永远还不清。我现在每接手一个新工程第一件事就是把这套 ddc 配置放进仓库让它从一开始就跟着工程走。你会发现等到依赖债务不再积累的时候flutter pub get 的构建速度、HAP 包体积、Review pubspec.yaml 的时间都会慢慢给你回报。