eslint-config-universe 完全指南:用 Expo 官方 ESLint 规则统一跨平台代码规范
在 GitHub_Trending/ex/expo 仓库中,Expo 团队既要保证 packages/expo、packages/expo-router、packages/expo-ui 等上百个原生与 Web 模块的代码风格高度一致,又要容忍不同平台(iOS / Android / Web / Node)与不同历史阶段代码的差异。支撑这一切质量基线的关键基础设施之一,就是位于 packages/eslint-config-universe 的共享 ESLint 配置包。它把自己的定位写得非常直白——"Shared ESLint configs for internal Expo projects"(Expo 内部项目共享的 ESLint 配置),既被 Expo 仓库自身使用,也随 eslint-config-universe 包发布到 npm 供外部项目引用。
本文将以 packages/eslint-config-universe/README.md 为主干,结合包内 default.js、native.js、web.js、node.js 以及 shared 下的共享配置源码,系统讲解这套配置的安装、接入、选型与定制方法,并深入剖析其"严重问题报 error、风格问题报 warn、整体偏向宽容"的设计哲学是如何在规则层面落地的。
一、配置包概览与版本基线
在开始使用前,先了解这个包在 npm 生态中的基本面貌。查看 packages/eslint-config-universe/package.json 可知:
- 包名:
eslint-config-universe,main入口指向default.js; - 许可证:MIT,作者为 Expo;
- 关键字:
eslint-config、expo、react-native; - 运行时依赖:
@typescript-eslint/eslint-plugin/@typescript-eslint/parser、eslint-config-prettier、eslint-plugin-import、eslint-plugin-node、eslint-plugin-prettier、eslint-plugin-react、eslint-plugin-react-hooks、globals等; - peer 依赖:
eslint >= 8.10,prettier >= 3(且 Prettier 被标记为 optional,即纯做 lint、不跑格式化时可以不装); - 发布文件清单(
files字段):default.js、native.js、node.js、web.js、shared目录以及flat目录(flat config 入口),同时排除了测试目录。
从该字段可以确认两件事:其一,配置以传统 .eslintrc 风格(legacy)为主体;其二,包内同时提供了 flat/ 目录,用于支持新版 ESLint 的 flat config(如 flat/default.js 通过 eslint/config 的 defineConfig 组合共享配置)。
二、安装:一条命令装齐 lint 全家桶
使用 npm 或 yarn 均可安装,README 以 yarn 为例:
yarn add --dev eslint-config-universe
由于该配置基于 ESLint 与 Prettier 工作,还需要把运行时自身一并装为开发依赖。参照 package.json 中的 peer 依赖声明,建议的安装命令为:
yarn add --dev eslint@8 prettier
两点版本提示:
- peer 依赖要求
eslint >= 8.10,因此使用 ESLint 8 可以放心接入;仓库自身的 devDependencies 中还通过eslint8(npm:eslint@^8.57.1)为测试保留了 ESLint 8 运行环境; prettier >= 3属于可选 peer。若你的团队只想要 lint、不需要它顺带强制格式化,可以省略 Prettier;反之若希望完整继承 Expo 的"格式化交给 Prettier"工作流,则建议安装。
三、快速接入:用 extends 一行引入
eslint-config-universe 的接入方式与传统 ESLint 配置完全一致:把它写进自己工程配置的 extends 字段。ESLint 会自动在 package.json 与 .eslintrc.* 文件中查找配置。
3.1 写在 package.json
在 package.json 中新增 eslintConfig 字段:
{
"eslintConfig": {
// Choose from universe/native, universe/node, universe/web
"extends": "universe"
}
}
注意:该字段是 JSON 格式,示例中的
//注释仅为说明占位,实际书写时请删除。
3.2 写在 .eslintrc.js
需要更多程序化逻辑时(例如后续启用 TypeScript typed linting),推荐独立的 .eslintrc.js:
module.exports = {
extends: 'universe',
};
由于 eslint-config-universe 遵循 ESLint 共享配置的命名解析约定,"extends": "universe" 会被自动解析为包内 default.js 入口。
四、按平台选型:universe 家族四条配置线
Expo 的最大特点是"一份代码跑多端",而不同运行环境拥有完全不同的全局对象与模块解析规则。为此,eslint-config-universe 按平台拆分出四条配置线,README 中的定位如下:
| 配置名 | 适用场景 | 源码入口 |
|---|---|---|
universe |
通用基础配置,适用于没有更具体平台归属的纯 JavaScript 工程 | default.js |
universe/native |
React Native 工程(含 Expo 工程),支持 React 与 JSX | native.js |
universe/web |
运行在浏览器中的 Web 代码,支持 React 与 JSX | web.js |
universe/node |
运行在 Node.js 环境中的代码 | node.js |
4.1 组合结构:所有配置共享 core + typescript + prettier
从源码看,四条配置并非各自为政,而是以"积木式"组合搭建。先看基础线 default.js:
module.exports = {
extends: ['./shared/core.js', './shared/typescript.js', './shared/prettier.js'],
};
也就是说 universe 由三块共享配置拼接而成:通用核心规则 shared/core.js + TypeScript 支持 shared/typescript.js + Prettier 协调 shared/prettier.js。Web 与 Node 线同样基于这三块,Native 线在此基础上再叠加 React/JSX 规则,形成"默认 = 通用 + TS + Prettier,各平台各取所需"的分层设计。
4.2 universe/native:Expo 工程的默认答案
对 Expo 工程而言,最常用的配置是 universe/native。README 给出的示例:
"eslintConfig": {
"extends": "universe/native"
}
打开 native.js 可以看到它做了三件 Native 特有的关键事情:
第一,补齐 React Native 运行时的全局变量。 由于 RN 代码跑在 Hermes/JSC 引擎中而非浏览器或标准 Node,需要显式声明 __DEV__、fetch、FormData、XMLHttpRequest、alert、navigator、requestAnimationFrame、requestIdleCallback、cancelIdleCallback、setImmediate、Atomics、SharedArrayBuffer、ErrorUtils、window、process 等一批全局标识(值均为 false,表示"只读、不允许赋值"),从而避免 no-undef 等规则对 RN 合法代码误报。
第二,为 React Native 的平台后缀文件扩展名定制 import 解析。 Native 代码经常出现 Button.ios.tsx、Button.android.tsx、Button.web.ts 这类按平台拆分的文件。结合 shared/extensions.js 中 computeExpoExtensions 的实现可以推断,它会遍历 .expo/空、平台子扩展 .android/.ios/.web/.native/空以及基础扩展(.js/.jsx/.ts/.tsx/.d.ts)做笛卡尔积,生成全部合法的 Expo 扩展名列表,再写入 settings['import/extensions'] 与 import/resolver.node.extensions,让 eslint-plugin-import 能正确识别并解析 Button.ios.tsx 这类导入。
第三,为 .web.* 文件单独开启浏览器环境。 配置末尾的 override 对 *.web.* 文件注入 env: { browser: true },正好呼应 Expo Web 支持:同一个 RN 组件树的 Web 副本在使用浏览器 API 时不会误报。
4.3 universe/web 与 universe/node:平台专项配置
浏览器线 web.js 在基础三件套上叠加了 React/JSX(即 shared/react.js),并声明 env: { browser: true, commonjs: true }:
module.exports = {
extends: ['./shared/core.js', './shared/typescript.js', './shared/react.js', './shared/prettier.js'],
env: { browser: true, commonjs: true },
};
Node 线 node.js 则不引入 React,而是激活 node 插件并打开 Node 环境与两条针对性规则:
module.exports = {
extends: ['./shared/core.js', './shared/typescript.js', './shared/prettier.js'],
plugins: ['node'],
env: { node: true },
rules: {
'no-buffer-constructor': 'warn', // 废弃的 Buffer 构造方式(new Buffer)
'node/no-path-concat': 'warn', // 禁止用字符串拼接 __dirname/__filename 构造路径
},
};
可见配置线的差异是"语义化"的:浏览器线关心浏览器全局对象与 DOM 相关的 React 代码,Node 线关心服务端/脚本代码的路径与缓冲安全问题。
4.4 同时扩展多个配置
当工程横跨多个平台(例如同时包含 Web 页面与 Node 脚本的 monorepo 子包)时,extends 可传入数组叠加多条配置:
"eslintConfig": {
"extends": ["universe/node", "universe/web"]
}
这样一份配置即可同时覆盖 Node 侧与浏览器侧代码的检查范围。
五、自定义 Prettier:团队格式化的总闸门
eslint-config-universe 对格式风格的处理思路是"风格交给 Prettier,ESLint 不做二次校验"。如果想定制格式细节,无需改动 ESLint 配置,只需在项目根目录创建 .prettierrc。README 给出了一个贴近 Expo 风格的示例:
{
"printWidth": 100,
"tabWidth": 2,
"singleQuote": true,
"bracketSameLine": true
}
各字段含义(均可在 Prettier 官方文档中进一步确认语义):
printWidth:单行最大宽度,默认 80,Expo 常用 100,超出即换行;tabWidth:缩进宽度,示例为 2 个空格;singleQuote:字符串统一使用单引号;bracketSameLine:多行 JSX/对象的花括号收尾与最后一个属性同行(即 Prettier 3 及以后版本的默认风格之一)。
从源码看,这一设计在 shared/prettier.js 中得到印证:
module.exports = {
extends: ['prettier'], // 引入 eslint-config-prettier,关闭所有与 Prettier 冲突的格式规则
plugins: ['prettier'],
rules: {
'prettier/prettier': 'off', // 不让 ESLint 代跑 Prettier 检查
},
};
也就是说:extends: ['prettier'] 负责把 core.js 中可能和 Prettier 打架的排版类规则(如缩进、空格、引号、逗号等)全部关闭;prettier/prettier 规则保持 off,意味着格式化统一由独立的 prettier 命令在 lint 之外执行,形成"ESLint 管正确性、Prettier 管排版"的清晰分工,避免双重校验带来的规则冲突与 CI 抖动。
六、进阶:启用 TypeScript 类型信息型 lint(typed linting)
universe 家族默认的 TypeScript 规则只做语法层与 AST 层检查(syntactic rules)。若要启用需要解析类型信息的规则(type-aware rules,例如基于类型流的数据竞争、隐式 any 误用等高阶检查),可额外挂载 universe/shared/typescript-analysis 这条扩展配置。README 特别提示:这会显著增加大型工程的 lint 耗时,因为需要让 TypeScript 编译器为被检查文件建立完整类型图。
启用它需要两处改动,完整示例见 shared/typescript-analysis.js 对应的接入说明。以 .eslintrc.js 为例:
module.exports = {
extends: [
'universe',
+ 'universe/shared/typescript-analysis',
],
+ overrides: [
+ {
+ files: [
+ '*.ts',
+ '*.tsx',
+ '*.d.ts'
+ ],
+ parserOptions: {
+ project: './tsconfig.json'
+ },
+ },
+ ],
};
关键点拆解:
extends新增'universe/shared/typescript-analysis':加载需要类型信息的补充规则集;overrides中把范围限定在*.ts、*.tsx、*.d.ts:typed linting 只应作用于真正的 TS 文件,避免把 JS 文件拖入类型检查流程;parserOptions.project: './tsconfig.json':告诉@typescript-eslint/parser使用哪个tsconfig.json来获取程序与类型信息。它要求工程必须存在可解析的 tsconfig,且被 lint 的文件需要被包含在该 tsconfig 的项目范围内,这是 typed linting 能否顺利运行的前提,接入前请确认工程已具备完整、可编译的 TS 项目配置。
这条可选项与默认 TypeScript 支持之间的区别在于:基础配置 shared/typescript.js 只负责把 .ts/.tsx/.d.ts 文件交给 @typescript-eslint/parser 解析,并注册 TS 专属规则与类型安全的 parser 设置(如 import/parsers、对 *.ts 文件关闭与 TS 机制冲突的 no-undef 等);而类型分析线才会真正消费类型图,两者定位互补、按需取用。
七、配置哲学:严重问题为 error,风格问题为 warn
README 用一节专门阐述设计哲学,这也是理解整套规则取舍的钥匙:
- 严重问题设为 error,风格问题设为 warn。 语法错误、明确的 bug 隐患必须阻断质量门禁;而写法风格则以告警呈现,允许在特定阶段存在。这样团队可以落地类似策略:"提交前保证零 error;若本次提交未引入新 warn,则允许历史 warn 通过。"
- 面向成熟团队刻意保持宽容。 它被设计成一套"偏宽松"的配置——适合决策能力强、擅长在 code review 中渗透学习编码规范、更需要灵活性而非硬性教条的团队。
这套哲学在 shared/core.js 中体现得相当鲜明,规则按严重度分层:
设为 error 的,几乎都是"语法或运行级错误"类别:
'no-const-assign': 'error', // 给 const 重新赋值
'no-delete-var': 'error', // delete 变量
'no-dupe-args': 'error', // 函数参数重名
'no-dupe-class-members': 'error',
'no-dupe-keys': 'error', // 对象字面量键重复
'no-duplicate-case': 'error', // switch 分支重复
'no-func-assign': 'error', // 给函数名赋值
'no-invalid-regexp': 'error', // 非法正则
'no-new-symbol': 'error',
'no-undef': 'error', // 使用了未定义变量(纯 JS 场景)
'no-unexpected-multiline': 'warn', // 注意:此项实为 warn
'use-isnan': 'error', // 用 isNaN() 判断而非 === NaN
'valid-typeof': 'error', // typeof 比较的写法合法性
此外 import 插件下的 import/export、import/namespace、import/no-duplicates 也以 error 级别把关模块引用的正确性。
设为 warn 的,则是风格、简洁性与潜在坏味道: 例如 semi(分号)、eqeqeq(严格相等,smart 模式)、no-var、object-shorthand、prefer-const、no-extra-bind、no-eval、no-useless-* 系列、curly(要求 if/for 一律使用花括号)、yoda(禁止 yoda 条件)等等。一个值得注意的细节是 no-unused-vars 在 core 中被配置为 warn 且 args: 'none'、caughtErrorsIgnorePattern: '^_'(以 _ 开头的 catch 变量不告警),体现"不打断功能开发、容忍渐进清理"的宽容取向。
import 分组排序规则也颇具工程实用性(shared/core.js 中 import/order):
'import/order': [
'warn',
{
groups: [['builtin', 'external'], 'internal', ['parent', 'index', 'sibling']],
'newlines-between': 'always', // 分组之间必须空行
alphabetize: { order: 'asc' }, // 组内按字母升序
},
],
它强制按"内置/外部依赖 → 内部模块 → 相对路径"的次序组织 import,并对 .d.ts 声明文件单独放行(override 中关闭 import/order),这类布局约束正是大型 monorepo 保持可读性的隐性契约。
八、Native 专属的 React 规则:宽容不等于放水
React/JSX 规则集中在 shared/react.js,由 universe/native 与 universe/web 共享。其中最值得注意的两条边界:
'react-hooks/rules-of-hooks': 'error', // Hook 调用规则:违反即为错误
'react-hooks/exhaustive-deps': 'off', // useEffect 依赖数组完整性:默认关闭
团队选择把"不许在条件/循环里调用 Hook"这条破坏 React 运行模型的铁律设为 error,却把"依赖数组是否写全"这类容易在大型 Native 工程中引发大量噪音的规则默认关闭——再次印证"错误归错误、风格归风格"的分层取舍。
其余规则以 warn 覆盖 JSX 写法风格:jsx-quotes 要求双引号、JSX 缩进统一为 2、jsx-boolean-value 简写布尔属性、jsx-no-bind 允许箭头函数/普通函数、react/no-unknown-property 检查未知 DOM 属性、react/self-closing-comp 推荐自闭合等。同时通过 settings.react.version = 'detect' 自动探测工程中的 React 版本,避免因版本漂移产生误报。
九、测试与质量保证:快照 + 真实 lint 夹具
作为一个将被上百个包引用的基础配置,eslint-config-universe 自带完善的回归测试体系,集中在 packages/eslint-config-universe/tests:
- 每个配置线(default / native / node / web / typescript-analysis)对应一个
*-test.js测试文件,并配套一份快照 snapshots,用于固化每次解析出的完整规则集; - fixtures 目录存放了
all-*.js、all-*.ts、web-native-*.js、typescript-analysis-*.ts等真实代码夹具,测试会真正用 ESLint 对夹具执行 lint 并断言输出(见 lintAsync.js 等测试工具),确保"规则变更不会悄悄破坏真实文件的检查结果"; - projects 下为每个平台线准备了带
index.ts的最小工程,验证配置在各典型目录结构下都能被 ESLint 正常解析; - tools/checkPrettierRulesAsync.js 用于核对共享规则与 Prettier 配置之间的一致性,防止新版 Prettier 引入冲突。
package.json 的 test 脚本即 jest,并在 jest.testMatch 中限定收集 **/__tests__/*-test.js。对使用者而言,这套测试传递的信号是:规则集是经过真实 lint 输出校验的,接入后行为可预期。
十、在 Expo 仓库中的落地方式:从文档到实践
eslint-config-universe 不是"文档里的参考实现",而是 Expo monorepo 自身的日常工具。从仓库结构可以看到两种实际用法:
- 顶层统一:仓库根目录的 eslint.config.js、各子包(如 packages/eslint-config-universe/eslint.config.js)中均引用了本配置或由它派生的规则;应用与测试工程也通过它统一口径。
- 配套指南:仓库内的 [Expo JavaScript Style Guide](https://gitcode.com/GitHub_Trending/ex/expo/blob/6c6bcb9aee43a1c7016db859b3293a410d0ef889/guides/Expo JavaScript Style Guide.md?utm_source=gitcode_repo_files) 与 lint 相关约定共同构成 Expo 的工程规范文档体系,eslint-config-universe 是其中规则的"可执行载体"。
因此,无论你是在维护一个纯 React Native 应用、一个 React Web 项目、一组 Node 脚本,还是一个横跨 Web 与 Node 的 monorepo 子包,都可以用"选一条平台线 + 按需叠加 typed linting + 用 .prettierrc 定制排版"的方式,在十几分钟内把 Expo 官方工程沉淀出的这套质量基线复刻到自己项目中。
总结
eslint-config-universe 的价值不在于"规则越严越好",而在于一套清晰可复用的取舍模型:通用核心(core)保底正确性,TypeScript 线补齐类型安全,React 线覆盖 JSX/Hook,Prettier 线关停格式冲突,平台线负责各自运行时的全局对象与模块解析,typed linting 作为可选的深度增强,error/warn 分层支撑宽松而可控的团队策略。 理解它的组合结构与哲学后,你既可以直接 extends: 'universe/native' 快速起步,也能参照它的 shared 分层思路,为自己的团队裁剪出一套专属的跨平台 lint 基线。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust4.21 K635- DDeepSeek-V4.1-FlashDeepSeek-V4.1-Flash 是一个多模态混合专家(MoE)模型,拥有 5520 亿骨干参数,并支持最多一百万 token 的上下文长度。该模型原生支持图像和文本输入,并以自回归方式生成文本Python40
jforgamejforgame是一个一站式游戏服务器开发框架。包含游戏服务器开发所需要的各种组件,比如网关,socket服务端与客户端,自定义高效消息编解码,游戏热更新,游戏通用工具等等。包含游戏服,跨服,匹配服,后台管理系统等实现,同时提供大量业务案例以供学习。亦可用于其他socket应用,例如及时聊天等。Java131
fizz-gateway-nodeAn Aggregation API Gateway in Java . FizzGate 是一个基于 Java开发的微服务聚合网关,是拥有自主知识产权的应用网关国产化替代方案,能够实现热服务编排聚合、自动授权选择、线上服务脚本编码、在线测试、高性能路由、API审核管理、回调管理等目的,拥有强大的自定义插件系统可以自行扩展,并且提供友好的图形化配置界面,能够快速帮助企业进行API服务治理、减少中间层胶水代码以及降低编码投入、提高 API 服务的稳定性和安全性。Java80
certd开源SSL证书管理工具;全自动证书申请、更新、续期;通配符证书,泛域名证书申请;证书自动化部署到阿里云、腾讯云、主机、群晖、宝塔;https证书,pfx证书,der证书,TLS证书,nginx证书自动续签自动部署JavaScript80
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python290