首页
/ eslint-config-expo 演进全解:Expo 项目 ESLint 基础配置的规则、迁移与源码实现

eslint-config-expo 演进全解:Expo 项目 ESLint 基础配置的规则、迁移与源码实现

2026-09-08 14:09:27作者:庞队千Virginia

eslint-config-expo 是 Expo 官方为 React Native / Expo 应用提供的最小化 ESLint 基础配置包,负责开箱即用地支持 JSX、TypeScript、平台化文件后缀(.android.js.ios.js.web.js)以及 React Native 专属全局变量,同时承接 expo/use-dom-exports 等 DOM 组件规则。本文以 CHANGELOG.md 为骨架,逐版梳理该配置包从 2024 年 7.0.0 到 57.0.1 的功能演进与破坏性变更,并结合当前仓库中的 default.jsflat 配置utils 规则模块测试用例 展开源码级讲解。读完你将掌握:expo 配置由哪些最小规则构成、flat config 与传统 .eslintrc 两种接入方式、各版本升级时需要注意的兼容点,以及如何按需扩展属于自己的 Expo lint 规则集。

一、定位:一个"刻意保持最小"的 Base 配置

先明确 eslint-config-expo 的定位——它不是"开箱即用的全量规则大全",而是 Base 配置README.md 开篇即说明:

This is a minimal config that supports JSX and TypeScript, platform-specific global variables, and file extensions like .android.js, .ios.js and .web.js. You are intended to compose this base config with the linter rules of your choice in your own ESLint configuration.

即它只负责解决 Expo 工程中通用性最强的三件事

  1. 解析并识别 JSX 与 TypeScript 代码;
  2. 提供 React Native / Expo 运行时环境(__DEV__fetchFormDatanavigator 等全局变量);
  3. 理解平台化文件扩展名(.android.*.ios.*.web.*.native.*)的导入解析。

具体的编码风格、可读性、复杂度等"口味化"规则,官方建议由你(或你的团队)在 expo 配置之上自行叠加。

组成结构

当前包根目录(仓库路径 packages/eslint-config-expo)结构如下:

  • default.js:legacy(.eslintrc)模式的入口,聚合四份规则 + 全局变量;
  • flat.js / flat/default.js:flat config 模式的入口(推荐),通过 defineConfig 导出数组;
  • utils/:四类规则的模块化实现(core / typescript / react / expo)以及平台扩展名计算工具;
  • eslint.config.js:包自身 CI/lint 使用的自举配置;
  • __tests__/:针对 legacy 与 flat 两套模式的基线测试与规则测试。

二、核心规则构成:从源码看它到底 lint 什么

1. 平台化文件扩展名支持(extensions.js)

Expo 项目最常见的特性就是按平台拆分文件:Button.android.tsxButton.ios.tsxButton.web.tsx。若 ESLint 不认识这些后缀,导入解析与 import/no-unresolved 类规则会全部误报。

utils/extensions.js 给出了精确算法:

const jsExtensions = ['.js', '.jsx'];
const tsExtensions = ['.ts', '.tsx', '.d.ts'];
const platformSubextensions = ['.android', '.ios', '.web', '.native'];

function computeExpoExtensions(baseExtensions, platformSubextensions) {
  const expoExtensions = [];
  for (const platform of [...platformSubextensions, '']) {   // '' 代表无平台后缀
    for (const base of baseExtensions) {
      expoExtensions.push(`${platform}${base}`);
    }
  }
  return expoExtensions;
}

该函数把 4 个平台子扩展名与 5 个基础扩展名两两组合(含无平台后缀的情况),最终生成 20 个合法扩展名。default.jsflat/default.js 都会把它写入两份关键 settings:

settings: {
  'import/extensions': allExtensions,
  'import/resolver': { node: { extensions: allExtensions } },
},

这保证 eslint-plugin-import 在解析 import Foo from './Button' 时能命中任意平台变体文件。

2. TypeScript 与 React 最小规则集

utils/typescript.js 引入 @typescript-eslint parser/plugin 并提供针对 TS/TSX 的最小规则;utils/react.js 则是 JSX 相关的显式规则集合,注意它采用了逐条显式声明而非 plugin:react/recommended 全量继承(只有 react-hooks 通过 extends: ['plugin:react-hooks/recommended'] 引入),例如:

rules: {
  'react/display-name': 'warn',
  'react/jsx-no-duplicate-props': 'error',
  'react/jsx-no-undef': 'error',
  'react/jsx-uses-react': 'warn',
  'react/jsx-uses-vars': 'warn',
  'react/no-danger-with-children': 'warn',
  'react/no-deprecated': 'warn',
  'react/no-direct-mutation-state': 'warn',
  'react/no-string-refs': ['warn', { noTemplateLiterals: true }],
  'react/no-this-in-sfc': 'warn',
  'react/no-unknown-property': 'warn',
  'react/require-render-return': 'warn',
}
  • react/display-name 等以 warn 收尾的规则,是为了兼容默认模板不被"报错"打断;
  • react/require-render-return 强制 render() 必须有返回,属于 createClass 时代遗留但仍有防护价值的规则;
  • react/jsx-uses-vars 保证 JSX 中使用的组件变量不被 no-unused-vars 误删。

3. React Native / DOM 全局变量

Expo 运行环境既不是纯浏览器也不是纯 Node,所以 default.jsflat/default.js 手工声明了一批全局:

globals: {
  __DEV__: 'readonly',
  ErrorUtils: false,
  FormData: false,
  XMLHttpRequest: false,
  alert: false,
  cancelAnimationFrame: false,
  cancelIdleCallback: false,
  clearImmediate: false,
  fetch: false,
  navigator: false,
  process: false,
  requestAnimationFrame: false,
  requestIdleCallback: false,
  setImmediate: false,
  window: false,
  'shared-node-browser': true,
}
  • false 表示可写、readonly 表示只读(如 __DEV__);
  • navigatorwindowfetchXMLHttpRequest 等 Web 全局在 Expo/React Native 中确实可用,因此统一放行;
  • 值得注意的是 flat 版本额外 ...globals.browser(来自 globals 包)合并进基础 globals,并针对 *.web.* 文件再用独立 files 块锁定浏览器环境——这与 CHANGELOG 中 9.0.2 修复的 "Define browser globals correctly for flat config" 以及 8.0.1 的 "Enable node globals for metro.config.js" 一脉相承(后者在 legacy 模式里通过 *.web.* overrides + env: { browser: true } 实现,见 default.js)。

4. Expo 专属规则:DOM Components 与 env 安全

utils/expo.js 引入工作区内的 eslint-plugin-expo 插件(见 package.json"eslint-plugin-expo": "workspace:^"),并默认开启三条规则:

rules: {
  'expo/use-dom-exports': ['error'],
  'expo/no-env-var-destructuring': ['error'],
  'expo/no-dynamic-env-var': ['error'],
}

这三条规则是 CHANGELOG 多条记录的落点:

  • expo/use-dom-exports:校验 "use dom" 指令与 Expo DOM Components 的导出写法(对应 10.0.0 "Add lint rules for Expo DOM Components and the 'use dom' directive");
  • expo/no-dynamic-env-varexpo/no-env-var-destructuring:防止 process.env 被动态索引或解构,避免打包时环境变量内联失效(Expo 的 env 在构建期静态内联,动态访问无法被替换)。

该文件还包含一个经典的 ignorePatterns 示例:android/app/build——注释解释得很直白,"JS files can end up in build intermediates, eg: android/app/build/intermediates/assets/debug/EXDevMenuApp.android.js",即 Expo Go / dev 构建产物中的 JS 不应被当作源码 lint。

三、演进时间线:7.0.0 → 57.0.1 各版本解读

CHANGELOG 是本包最权威的演进记录。下面按时间正序(从早到晚)解读每条改动及其背后的动机,方便你在升级时对照检查。

起点:7.0.0(2024-04-03)

Breaking changes:Create a minimal ESLint config for Expo projects.

该版本本质上是包的"出生证明"。在此之前 Expo 生态的 lint 配置分散或依赖旧版,7.0.0 起确立设计原则:只提供基础、可组合的最小配置。此后凡是继承 expo 配置的用户,实际上就在消费这一最小集。

7.1.0(2024-04-18)

New features:Opt into explicit rules from eslint-plugin-react.

配置由"含糊地整包继承"改为逐条显式挑选 eslint-plugin-react 规则(即上文 utils/react.js 中那批手工枚举的规则)。显式规则的好处是升级插件大版本时规则名变更造成的破坏面可控,也便于读者/用户精确了解自己启用了什么。

7.1.1 / 7.1.2(2024-04-22 / 04-24)

两个 patch 版本均标注 This version does not introduce any user-facing changes.,属于纯内部构建/发布调整。

8.0.0(2024-10-22)

Breaking changes:Update @typescript-eslint dependencies to new major version, migrate rule set.

@typescript-eslint 主版本升级迫使规则集整体迁移——这也是 CHANGELOG 中首个真实的破坏性升级信号。若你在旧版本基础上自定义过基于 @typescript-eslint 老版规则名的配置,升级到 8.x 后需要按新规则名同步调整。同日发布的 8.0.1 修复了 metro.config.js 的 Node 全局变量缺失问题:Metro 配置文件跑在 Node 环境,理应可见 process__dirname 等全局。

9.0.0(2025-04-04)

New features:Support flat config.

Expo 官方正式跟随 ESLint 生态接入 flat config(自 ESLint v9 起 flat config 已是默认),见 flat/default.js 的实现——它用 defineConfig([...]) 输出由 core / typescript / react / expo 四段展开的数组,并把全局变量与 import settings 放入独立对象。

同一版本的 Others 记录了两项依赖升级:

  • Update @typescript-eslint dependencies for better compatibility with TypeScript;
  • Update eslint-plugin-react-hooks dependency to new major version。

9.0.1(2025-04-08)

Bug fixes:Wrap exported config in defineConfig.

9.0.0 刚发布 4 天即打补丁——flat 导出需要包裹在 defineConfig 中,以正确触发 ESLint 的配置校验与提示。这也解释了 flat.jsflat/default.jsconst { defineConfig } = require('eslint/config') 的来源。

9.0.2(2025-04-11)

Bug fixes:Define browser globals correctly for flat config.

修复 flat 模式下浏览器全局变量定义不完整的问题——当前实现通过在 flat 配置里 ...globals.browser 展开标准浏览器全局,确保 documentwindow 等只在 Web 文件里按预期生效。

9.0.3(2025-04-22)

New features:use react/recommended plugin.

一个方向性反转:之前 7.1.0 刻意逐条显式挑选 react 规则,而 9.0.3 改为引入 react/recommended 作为基础。不过从当前源码看,这一"推荐集"仍被收敛为"安全子集 + 少量显式覆盖",以保证对不同 React/React Native 版本与模板的普适性。

9.1.0(2025-04-23)

New features:Disallow require() for source files and continue to allow for assets.

规则语义:源码文件禁止 require()(统一使用 ESM import),而 assets 类导入(图片、字体、音频等)仍然允许 require()——因为 Metro 资源管线普遍依赖 require('./icon.png') 这种写法。落地时通过按文件类型(.js/.ts/.tsx vs 资源扩展名)区分实现,这是 Expo 工程非常"务实"的一条规则。

9.1.1(2025-04-25)

无用户可见变更的维护版本。

9.2.0(2025-04-30)

New features:Add no-var rule to disallow var.

no-var 加入规则集,配合 prefer-const 等习惯推动 const/let 的现代写法。

10.0.0(2025-08-13)

New features:Add lint rules for Expo DOM Components and the "use dom" directive.

版本号从 9.x 跳到 10.0.0 标志着一次能力跃迁:为 Expo DOM Components(在原生 App 中以 Web 组件形式渲染的 React 组件)以及文件顶部的 "use dom" 指令新增 lint 规则。其实现即 utils/expo.jsexpo/use-dom-exports 这条规则,规则集底层来自同仓库的 eslint-plugin-expo

55.0.0(2026-01-21)

Others:Fixed check-packages error on Windows.

从 10.0.0 跳到 55.0.0 是 Expo 各 SDK 相关 npm 包统一对齐 SDK 版本号(Expo SDK 55)的结果。此次改动解决 Windows 平台上 monorepo 内部 check-packages 脚本报错的问题,属于仓库工程化修复,不影响 lint 行为。

56.0.0(2026-05-05)

Bug fixes:Disable no-useless-return for TypeScript files.

TypeScript 中常见 if (cond) return; 的守卫式写法,其函数末尾若存在隐式 return 语义时 no-useless-return 可能产生噪音,故对 TS/TSX 文件显式关闭,避免与 TS 类型收窄(narrowing)习惯冲突。

56.0.1 / 56.0.2 / 56.0.3(2026-05-06 / 05-06 / 05-13)

连续三个 patch 均为 This version does not introduce any user-facing changes.

56.0.4(2026-05-13)

Others:Bump to eslint-plugin-react-hooks@^7.0.0.

将 React Hooks 规则插件升至 7.x 大版本,与 package.json 当前依赖 "eslint-plugin-react-hooks": "^7.0.0" 一致。该插件的 recommended 集(含 rules-of-hooksexhaustive-deps)正是 utils/react.js 中 extends 的部分。

57.0.0(2026-06-25)与 57.0.1(2026-07-29)

两个版本均标注无用户可见变更,只是随 SDK 57 对齐版本号并做发布管道维护。

未发布区(Unpublished)

CHANGELOG 顶部还保留了 ## Unpublished 小节,其下按 Breaking changes / New features / Bug fixes / Others 四类预留空位,这是 Expo 各包统一的变更记录规范——新合入的改动会先填到对应分类,随下一次发布归并到具体版本。

四、安装与两种接入方式实战

安装

README.md 说明,需要同时安装配置包与 ESLint 本体:

yarn add --dev eslint-config-expo
yarn add --dev eslint

当前 package.json 声明 "peerDependencies": { "eslint": ">=8.10" },即 ESLint 8.10 及以上均可,但若使用 flat config 推荐 ESLint 9+(仓库自身 devDependencies 为 eslint: ^9.18.0,同时保留了 eslint8 别名用于测试 legacy 兼容性)。

方式一:flat config(推荐)

创建 eslint.config.js,引入 flat 入口并展开到数组中:

// eslint.config.js
const expoConfig = require("eslint-config-expo/flat");
const { defineConfig } = require("eslint/config");

module.exports = defineConfig([
  expoConfig,
  // 你自己的其它规则配置
  {
    rules: {
      // 例如叠加团队风格规则
    },
  },
]);

flat 入口内部等价于直接展开 flat/default.js 导出的 defineConfig 数组(core → typescript → react → expo → globals/settings → *.web.*),所以你还可以用展开运算符把它嵌进已有数组:...require('eslint-config-expo/flat')

方式二:legacy .eslintrc(package.json 或 .eslintrc.js)

package.json

{
  "eslintConfig": {
    "extends": ["expo"]
  }
}

.eslintrc.js

module.exports = {
  extends: ["expo"],
};

legacy 入口 default.js 实际是四份 util 配置的组合:extends: ['./utils/core.js', './utils/typescript.js', './utils/react.js', './utils/expo.js'],再加全局变量、import settings 和 *.web.* overrides。因此你也可以在自有配置里只挑选某一份(例如仅需要 JSX 支持时 extends: ['eslint-config-expo/utils/react'])。

组合使用:Flat + 平台 / Node 环境

Expo 自己的 eslint.config.js 就是"在 base 之上组合"的教科书示例:它先展开 expoConfig,再叠加 globals.nodeglobals.jest(给源码测试文件提供 Node/Jest 全局)、引入 eslint-plugin-prettier/recommended 并将 prettier/prettier 降为 warn,最后忽略测试 fixtures 目录。这种分层写法正是 base 配置被设计成"最小可组合"的初衷。

五、版本策略与迁移指南

为什么版本号从 10 跳到 55、57?

从 CHANGELOG 能清晰读出两条版本线:

  • 功能演进线:7.x → 8.x → 9.x → 10.x,代表配置本身的实质功能迭代;
  • SDK 对齐线:10.0.0 之后直接 55.0.0、56.x、57.x,因为 Expo 各官方包在 SDK 55 之后采用与 SDK 主版本一致的版本号策略。因此 55/56/57 之间的升级不意味着有新的 lint 功能,只是随 SDK 同步;真正要看功能演进需回溯到 10.0.0 及更早。

各里程碑的破坏性变更清单

结合 CHANGELOG,跨大版本时重点检查以下三点:

版本 类型 对用户的影响
7.0.0 Breaking 首次确立最小化配置,若此前沿用旧配置需整体迁移
8.0.0 Breaking @typescript-eslint 升主版本,规则集迁移;自定义 TS 规则名可能失效
9.0.0 Feature(新增 flat) legacy 模式仍可用,但推荐切换到 flat;9.0.1 又补充 defineConfig 包裹,升级时注意入口写法
9.0.3 Feature 引入 react/recommended,react 相关告警基线可能变化
9.1.0 Feature 源码 require() 被禁止(资源文件除外),存量 require 代码需改写为 import
9.2.0 Feature 新增 no-varvar 声明会产生告警
10.0.0 Feature 新增 expo/use-dom-exports 等 DOM 规则,使用 "use dom" 的组件需符合新导出规范
56.0.0 Bug fix TS 文件中 no-useless-return 被关闭,告警数量可能减少

升级建议

  1. 保持 SDK 对齐:使用 Expo SDK 时优先跟随对应大版本(55/56/57),避免与 expo 主包、eslint-plugin-expo 的版本错位——三者版本同步才能确保 expo/* 规则加载正常;
  2. 自定义规则只做加法:尽量在 expo 之上叠加自有 rules,而不是覆写其内置告警级别,这样跨版本升级的冲突面最小;
  3. 关注 react-hooks 与 @typescript-eslint 主版本:这两个插件的大版本升级(8.0.0、56.0.4)最容易影响自研规则,升级后跑一次 eslint --fix 并 review 告警变化。

六、质量保障:测试是如何锁住行为的

配置类包的回归风险很高,因此仓库内配套了完整测试(package.json"test": "jest",pretest 会先构建 eslint-plugin-expo):

  • tests/baseline-test.jsbaseline-flat-test.js:分别针对 legacy 与 flat 两种模式,用 fixtures 中的典型 Expo 源码验证"基线无致命误报";
  • __tests__/rules-test.jsrules-flat-test.js:逐条断言关键规则是否被正确启用/关闭(例如第 56.0.0 条对 TS 文件禁用 no-useless-return 的改动必然对应测试中的规则覆盖);
  • 快照目录 __snapshots__ 锁定规则配置的解析结果,任何规则集意外变更都会在 CI 中失败。

这意味着 CHANGELOG 中几乎每条规则级改动(9.1.0 的 require 限制、9.2.0 的 no-var、56.0.0 的 TS 特例等)在合并时都伴随对应测试更新,可作为你观察"某个 lint 行为到底由哪次变更引入"的可靠索引。

七、小结

eslint-config-expo 的演进史清晰展示了 Expo 团队对 lint 配置的取舍:保持最小、按需组合、随 SDK 同步版本。无论你通过 extends: ['expo'] 走 legacy 路线,还是用 require('eslint-config-expo/flat') 走 flat 路线,核心都落在这四件事上——TypeScript/JSX 支持、平台化扩展名解析、React Native 全局变量、以及随 10.0.0 引入的 Expo DOM/环境变量专属规则。升级时对照本文里程碑表,重点审计 @typescript-eslinteslint-plugin-react-hooks 与新增/禁用规则带来的告警面变化,即可在 Expo SDK 迭代中平稳保持代码质量。

热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
docsdocs
暂无描述
Markdown
900
5.83 K
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.76 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
860
1.35 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
927
1.85 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.94 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
533
603
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.37 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
396
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.04 K
527