微信小程序 + Taro 从 ESLint 迁移到 Oxlint 让项目代码检查速度提升近 12 倍

微信小程序 + Taro 从 ESLint 迁移到 Oxlint 让项目代码检查速度提升近 12 倍

项目里有些问题,我希望在提交代码之前就能发现。比如变量声明后没有使用,条件判断里误写了赋值,或者 useEffect 少写了依赖。这些问题不一定马上影响页面运行,但等到出现异常再排查,往往要花更多时间。

我目前用 ESLint 做这层检查。项目是一个 pnpm Monorepo,包含 Web、微信小程序和公共业务包,在根目录执行一次 pnpm lint,就能把它们一起检查。

ESLint 的基础推荐规则主要处理常见的代码错误,例如重复定义对象属性、循环方向写反、switch 意外贯穿、无效正则,以及未使用的变量。它本身不会自动覆盖所有框架和语言特性,因此我又加了 TypeScript、React Hooks 和 Fast Refresh 相关配置。

这套检查能解决我关心的不少问题,只是全量运行需要大约十秒。我觉得这个等待有点久,想在保留现有检查能力的前提下,看看能不能换一个更快的工具。

解决方案

在尝试替换之前,我先把当前配置里每一部分负责什么理清楚。只有知道原来检查了什么,才能判断 Oxlint 有没有少检查东西。

我在 ESLint 里用了哪些规则

基础部分是 @eslint/js 的推荐配置。它检查的是 JavaScript 中比较通用的问题,比如 no-debugger、no-dupe-keys、no-constant-condition 和 use-isnan。

TypeScript 部分使用 typescript-eslint 的推荐配置,处理显式 any、未使用变量、不必要的类型约束等问题。它也会关闭或替换一些基础规则,避免重复检查。比如 TS、TSX 中的未使用变量由 @typescript-eslint/no-unused-vars 检查,no-undef 则被关闭,未定义变量交给 TypeScript 检查。

需要说明的是,我没有在 ESLint 中开启依赖类型信息的规则,所以这套配置不能代替 tsc。

React 相关检查分成两部分:

插件 我主要用它检查什么
eslint-plugin-react-hooks Hook 调用位置、依赖数组,以及渲染纯度、ref 使用、state 更新等问题
eslint-plugin-react-refresh 组件文件的导出方式是否符合 Fast Refresh 的要求

React Hooks 用的是 recommended-latest。项目安装的插件版本为 7.1.1,实际启用了 17 条规则:

规则,省略 react-hooks/ 前缀 检查内容 级别
rules-of-hooks Hook 调用是否符合规则 error
exhaustive-deps Effect、Memo、Callback 等依赖是否完整 warn
static-components 是否在渲染过程中动态定义组件 error
use-memo useMemo 的常见错误用法 error
void-use-memo useMemo 是否返回值、结果是否被使用 error
preserve-manual-memoization Compiler 能否保留已有的手动记忆化行为 error
incompatible-library 是否使用了与记忆化不兼容的库或 API warn
immutability 是否直接修改 props、state 等不可变数据 error
globals 是否在渲染期间修改全局变量 error
refs 是否在渲染期间不当读写 ref error
set-state-in-effect 是否在 effect 中同步更新 state,造成额外渲染 error
error-boundaries 是否错误地用 try/catch 捕获子组件渲染异常 error
purity 渲染期间是否调用已知的不纯函数 error
set-state-in-render 是否在渲染期间不当更新 state error
unsupported-syntax 是否使用 Compiler 不支持的语法 warn
config React Compiler 配置是否有效 error
gating React Compiler 条件启用配置是否有效 error

项目根目录的 eslint.config.js 完整配置如下:

javascript 复制代码
import js from "@eslint/js"
import globals from "globals"
import reactHooks from "eslint-plugin-react-hooks"
import reactRefresh from "eslint-plugin-react-refresh"
import tseslint from "typescript-eslint"
import { defineConfig, globalIgnores } from "eslint/config"

export default defineConfig([
  globalIgnores(["**/dist/**", "packages/common/src/generated/**"]),
  {
    files: ["**/*.{js,mjs,cjs}"],
    extends: [js.configs.recommended],
    languageOptions: { globals: globals.node },
  },
  {
    files: ["apps/**/*.{ts,tsx}", "packages/**/*.{ts,tsx}"],
    extends: [
      js.configs.recommended,
      tseslint.configs.recommended,
    ],
  },
  {
    files: ["apps/**/*.{ts,tsx}"],
    extends: [reactHooks.configs.flat["recommended-latest"]],
  },
  {
    files: ["apps/web/**/*.{ts,tsx}"],
    extends: [reactRefresh.configs.vite],
    languageOptions: {
      globals: globals.browser,
    },
  },
  {
    files: ["apps/web/src/components/ui/**/*.{ts,tsx}"],
    rules: {
      "react-refresh/only-export-components": "off",
    },
  },
])

这里忽略了构建产物和接口生成代码。Hooks 检查作用于 Web 和小程序,Fast Refresh 只检查 Web,UI 组件目录另外保留了导出规则的例外。

根目录 package.json 中的执行入口是:

json 复制代码
{
  "scripts": {
    "lint": "eslint apps packages eslint.config.js"
  }
}

348 个文件,每次检查约十秒

这次实际进入检查范围的代码是 348 个文件,不是整个仓库的文件总数,也没有把生成代码和构建产物算进去。

在 Node.js 24.15.0、ESLint 10.14.0 的环境下,我关闭 lint 缓存,直接调用 CLI,以 JSON 格式输出,连续运行五次:

次数 ESLint 耗时
第 1 次 10.012 秒
第 2 次 10.438 秒
第 3 次 10.294 秒
第 4 次 9.871 秒
第 5 次 10.034 秒
中位数 10.034 秒

十秒不至于让检查没法用,但对这样一个经常执行的命令,我还是希望它能更快一点。

于是我找到了 Oxlint。它基于 Rust 的 Oxc 工具链,重点优化运行性能,同时内置了许多常用 ESLint 插件的规则实现,包括 TypeScript 和 React 相关规则。它也提供 ESLint 配置迁移工具,并支持通过 JavaScript 插件保留部分原有插件能力。Oxlint 官方介绍、迁移指南

这让我觉得可以试一试。不过,支持迁移不等于所有规则和行为都一样,我还是需要拿当前项目的配置和结果来对照。

把现有配置转换到 Oxlint

项目已经使用 ESLint Flat Config,可以从根目录的 eslint.config.js 开始转换。操作前先提交或备份现有配置,然后在项目根目录执行:

bash 复制代码
pnpm add -Dw oxlint@1.93.0
pnpm dlx @oxlint/migrate@1.93.0 eslint.config.js --with-nursery --details

这里固定的是本次对比使用的版本。

--details 会列出无法迁移的规则;--with-nursery 则用来包含开发中的规则。当前配置里的 no-undef 和 no-useless-assignment 在这个 Oxlint 版本中属于这一类,不加参数会被跳过。

命令会将配置保存到根目录的 .oxlintrc.json。生成之后,我没有直接开始计时,因为迁移工具给出了部分嵌套文件匹配条件不受支持的警告。

核对配置时,还有几处需要处理:TypeScript 对基础规则的关闭、替换关系没有完整保留下来,preserve-caught-error 的部分参数也不能原样传给 Oxlint。Web 的 Fast Refresh 参数和 UI 目录例外同样需要确认。

我手动编辑了生成的 .oxlintrc.json,把这些内容尽量和原配置对齐。这里不适合拿另一份项目的配置直接覆盖,具体调整还是要以自己的迁移警告和原有规则为准。

保存 JSON 文件后,在项目根目录运行:

bash 复制代码
pnpm exec oxlint -c .oxlintrc.json apps packages eslint.config.js

CLI 每次执行都会重新读取配置,不需要重启服务。我先确认它检查的也是同一批 348 个文件,再对比耗时。

如果最终决定切换日常入口,可以编辑根目录的 package.json,将 lint 改为:

json 复制代码
{
  "scripts": {
    "lint": "oxlint apps packages eslint.config.js"
  }
}

保存后,执行 pnpm lint 就会调用 Oxlint。这里保留 eslint.config.js 作为检查对象,是为了与原来的范围一致;后续如果删除这个文件,也需要从命令里移除。

Oxlint 五次都跑进了一秒以内

Oxlint 使用 1.93.0,仍然不开 lint 缓存,也没有额外开启类型感知检查。两边都直接调用 CLI、输出 JSON,统计整个进程耗时,不包含安装依赖和转换配置的时间。

次数 ESLint Oxlint
第 1 次 10.012 秒 0.838 秒
第 2 次 10.438 秒 0.826 秒
第 3 次 10.294 秒 0.864 秒
第 4 次 9.871 秒 0.855 秒
第 5 次 10.034 秒 0.852 秒
中位数 10.034 秒 0.852 秒

按中位数计算,Oxlint 的速度约为 ESLint 的 11.8 倍 ,耗时减少 91.5% ,每次全量检查节省约 9.2 秒。

对我来说,这个收益已经足够明显。原来十秒左右的等待缩短到了不到一秒,值得继续评估迁移。当然,这只是本机、当前代码和这套规则下的结果,不能直接套到其他项目上。

原来的规则和报错能兼容多少

按当前启用的 ESLint 规则名称去重,一共有 106 条,其中 102 条在 Oxlint 中有对应实现,约占 96%。

规则来源 当前启用 Oxlint 有对应实现
ESLint 核心规则 68 66
TypeScript 20 20
React Hooks 17 15
React Refresh 1 1
合计 106 102

缺少对应项的是 no-dupe-args、no-octal、react-hooks/config 和 react-hooks/gating。

迁移工具认为,前两条涉及的语法可以由严格模式约束;后两条则与 React Compiler 的配置方式有关。再加上前面提到的开发中规则和配置参数差异,我不会把这个覆盖率理解成完全兼容。

实际代码的检查结果也需要一起看:

诊断结果 ESLint Oxlint
错误 9 9
警告 29 53

原来的九个错误,包括五个 ref 使用问题、两个渲染纯度问题,以及两个 effect 中同步更新 state 的问题,在 Oxlint 中全部检出,规则和位置也都能对应上。

警告则都是 Hooks 依赖检查,两边的报告方式有差异。有些地方 ESLint 将多个依赖问题合并在一条警告里,Oxlint 会拆开;有些地方两边都提示缺少依赖,Oxlint 还会额外指出某些 *DataVersion 依赖多余。

所以,多出的 24 条警告不能直接算成 24 个新缺陷。我能确认的是,ESLint 原来提示的 29 个依赖警告位置,在 Oxlint 中都有对应报告。

还有一点需要留意:Oxlint 的 React Compiler 原生规则目前仍被官方标为实验性。官方插件说明

对于当前项目,我认为性能收益值得推进迁移,但是否彻底移除 ESLint,还需要结合这些差异判断。如果希望保留官方 React Hooks 插件的检查行为,也可以先让 Oxlint 接管基础 JS、TS 等规则,ESLint 继续负责 Hooks。这种混合方案本次没有测量耗时,不能直接套用 0.852 秒的结果。

相关推荐
deli0076 分钟前
红绿灯配时实测:固定 60 秒 vs 自适应,2400 辆车差多少
前端
去伪存真6 分钟前
井道无信号、满手黑机油,我们如何用 DeepSeek V4 打造不“犯浑”的电梯维保 Agent?
前端·agent
deli0079 分钟前
60fps 为什么还是一顿一顿?帧时间曲线实测给你看
前端
时间的情敌37 分钟前
Cesium 流动管线、管道动画、管线流向仿真,管网、油气、水利孪生高频需求
前端·交互·cesium
wangxiaoxuan2172 小时前
SLAM 的基本结构:前端、后端、回环与地图优化
前端·机器人·科沃斯八界
Agent世界5 小时前
用 CDP 做发布自动化:四道机器闸门与产物验收
前端·javascript
FungLeo5 小时前
成为全栈·Vue 管理后台篇·编辑器图片上传:从文件选择到文章附件关联
前端·vue.js·编辑器·markdown·文件上传·formdata·附件管理
某哈压力大6 小时前
SSH 突然 Connection reset?不是密钥坏了,是 OpenSSL 把 sshd 干趴了
前端·node.js·命令行
程序员大阳6 小时前
副队长ES6+教程(3)--解构赋值
前端·javascript·es6·赋值·解构
神膘护体小月半6 小时前
css - 元素背景图由 hover 触发,初次加载时闪动的解决办法
前端·javascript·css