背景
公司前端目前有 30 多个项目,其中一部分采用 React 技术栈,另一部分采用 Vue 技术栈。由于这些项目最初由不同的团队成员分别创建,导致即便是同一技术栈,不同项目的 ESLint 规则配置差异比较大。
比如:某些 React 项目的 ESLint 配置规则非常严格,包含大量规则集;而另一些 React 项目仅配置了最基础的规则,只是为了应付公司检查。于是就出现了这样的情况:
- 在 A 项目提交代码时,husky 触发的 ESLint 检查很宽松,几乎不用改多少代码。
- 在 B 项目提交代码时,ESLint 检查非常严格,往往需要花很长时间修改。
这种差异让部分成员觉得缺乏公平性:同样写代码,有人几乎没成本,有人却要反复调整。为了兼顾 公平性和代码质量的有效检查,有必要在公司内部统一 ESLint 配置规则。
那么问题来了:如何统一? 思路并不唯一,本文采用的方案是:针对 Vue 和 React 项目分别定制一套 ESLint 规则集,并发布到公司私有 npm 仓库,在各项目中统一安装和使用。下面我们分步骤介绍这一方案的实现
第一步 创建私有npm仓库
一说起创建私有npm仓库, 一搜技术文章,几乎清一色的都会说用Verdaccio实现。其实gitlab也能做。这里有极狐gitlab为例,说一下如何创建公司私有的npm仓库。在要开发和使用公司私有npm包的项目下, 新建一个.npmrc文件, 配置这两行内容,这两行内容的含义是,分别指明去哪下载/发布 和 怎么鉴权。
bash
## 指定 @{公司英文名} 作用域的仓库地址
@{公司名}:registry=https://{gitlab仓库域名}/api/v4/projects/project/{项目id}/packages/npm/
# 使用 token 自动认证
//{gitlab仓库域名}/api/v4/projects/{项目id}/packages/npm/:_authToken={gitlab中配置的个人访问凭证}
- 第一行:仓库地址
指定当安装/发布@公司名/*
的包时,不去默认的 npmjs.org,而是去 GitLab 中对应项目下的 npm Registry。 - 第二行:鉴权信息
这里的写法只能是//host/path
,而不能写成https://
,因为.npmrc
的语法要求协议在 registry 中定义,鉴权部分只写//...
即可。
配置完成后,执行 npm publish
或 pnpm add
时,就会自动完成鉴权。
接下来,可以写一个简单的 npm 包进行验证,确认上传和安装都正常。
npm测试包目录结构如下:

- .npmrc里面配置的内容就是上面的两行
- index.js中写了一个简单的hello,world导出函数
- package.json中定义了包名和入口文件
js
{
"name": "@juejin/my-test-package",
"version": "0.0.1",
"description": "A simple test npm package",
"main": "index.js",
"scripts": {
"test": "node index.js"
},
"author": "Your Name",
"license": "MIT"
}
执行npm publish
之后,去极狐Gitlab特定仓库的部署==>软件包库菜单查看,可以看到包已经成功上传。

然后把.npmrc的配置添加到业务项目的.npmrc文件中,在业务项目下执行
perl
pnpm add @xxx/my-test-package
在node_modules文件夹下查看, 可以发现正常安装。
第二步 制作ESLint规则包
首先ESLint该选择ESLint8还是ESLint9呢?
ESLint v8
- 稳定性:目前大多数项目(特别是 Vue CLI、Create React App、Vite 脚手架)默认还是用 v8,生态支持最好。
- 插件兼容性 :几乎所有插件(
eslint-plugin-vue
、eslint-plugin-react
、@typescript-eslint
等)都第一时间支持 v8。 - 配置方式 :传统的
.eslintrc.*
文件(JSON/JS/YAML)为主,比较熟悉。 - 生命周期:虽然 v9 已经发布,但 v8 仍然会维护一段时间(安全补丁)。
适合:已有项目、依赖多、团队追求稳定性。
ESLint v9
- 新配置系统 :完全切换到 Flat Config,用
eslint.config.js
代替.eslintrc.*
,更清晰、支持按文件匹配配置。 - 内置配置 :提供
@eslint/js
,替代eslint:recommended
。 - 更现代的设计 :从根本上解决了
.eslintrc
扩展层层嵌套、难以管理的问题。 - 生态迁移中:多数插件已支持 v9,但仍有一些插件和脚手架没完全更新。比如老项目如果依赖某些工具链,可能会遇到兼容性问题。
适合:新项目、想尝鲜或愿意跟进最新标准的团队。
考虑了一下,还是要着眼未来, 选择ESLint9,好处是配置更清晰、未来趋势(避免未来二次迁移),而且最新的eslint v9.34.0版本, 支持多线程linting, 执行速度比较快。对同一项目, 使用单线程,和开启2核和4核的平均耗时分别是
- 单线程:
45.234 s ± 1.123 s
- 多线程(2核):
24.567 s ± 0.891 s
- 多线程(4核):
15.123 s ± 0.567 s
js
Benchmark 1: npx eslint src/ --concurrency off
Time (mean ± σ): 45.234 s ± 1.123 s [User: 44.1 s, System: 1.2 s]
Range (min ... max): 43.891 s ... 47.234 s 10 runs
Benchmark 2: npx eslint src/ --concurrency 2
Time (mean ± σ): 24.567 s ± 0.891 s [User: 47.2 s, System: 1.8 s]
Range (min ... max): 23.234 s ... 25.987 s 10 runs
Benchmark 3: npx eslint src/ --concurrency 4
Time (mean ± σ): 15.123 s ± 0.567 s [User: 58.4 s, System: 2.1 s]
Range (min ... max): 14.234 s ... 16.123 s 10 runs
因为项目有两种技术栈, 所以对每种技术栈, 各自搜集一套ESLint规则
React版:
选择一些社区与生态认可度比较高的规则集
eslint:recommended
/@eslint/js
→ ESLint 官方团队提供,保证基础语法错误都能被检测(未定义变量、重复定义等)。
📊 NPM 下载量:超过 每周 3000 万,几乎所有项目必备。@typescript-eslint/recommended
→ TypeScript 官方维护的 ESLint 插件,专门覆盖 TS 类型相关的最佳实践。
📊 NPM 下载量:每周 1500 万+ ,在所有 TS 项目中广泛使用。plugin:react/recommended
→ 由 React 官方团队成员维护 (eslint-plugin-react),包含 propTypes、JSX 语法错误等检测。
📊 下载量:每周 1700 万+ 。几乎所有 React 项目都会装。plugin:react-hooks/recommended
→ Facebook React 团队自己写的规则集,强制遵守 Hooks 规则(useEffect 依赖完整性、Hook 调用顺序)。
📊 React 官方文档明确要求安装这个插件。plugin:import/recommended
→ 解决 import/export 顺序、模块未解析的问题,保证跨项目依赖更稳健。
📊 在 Node.js + React 项目里极常见。plugin:prettier/recommended
→ 结合 Prettier 格式化,避免 ESLint 与 Prettier 冲突。社区里几乎是 标配。
这些插件和规则集由 React 官方 或 业界主流公司维护的,不是个人爱好,所以可靠性、流行度都比较高。
js
import js from "@eslint/js";
import globals from "globals";
import tseslint from "typescript-eslint";
import react from "eslint-plugin-react";
import reactHooks from "eslint-plugin-react-hooks";
import importPlugin from "eslint-plugin-import";
import prettier from "eslint-plugin-prettier";
/**
* 配置全局变量
*/
const browserGlobalsConfig = {
languageOptions: {
globals: {
...globals.browser,
/** 追加一些其他自定义全局规则 */
wx: true
}
}
};
// TypeScript 文件配置
const tsFiles = {
files: ["src/**/*.{ts,tsx}"],
languageOptions: {
parser: tseslint.parser,
parserOptions: {
ecmaVersion: 2024,
sourceType: "module",
ecmaFeatures: { jsx: true }
}
},
rules: {
"no-undef": "off"
}
};
// React 配置
const reactConfig = {
files: ["src/**/*.tsx", "src/**/*.jsx"],
plugins: { react, "react-hooks": reactHooks }, // 应用全局规则,并覆盖特定的规则
rules: {
...reactHooks.configs.recommended.rules,
...react.configs.recommended.rules,
"react/react-in-jsx-scope": "off",
"no-undef": "off"
},
settings: {
react: { version: "detect" }
}
};
const importConfig = {
files: ["src/**/*.{ts,tsx,js,jsx}"],
plugins: { import: importPlugin },
rules: {
...importPlugin.configs.recommended.rules,
"@typescript-eslint/no-require-imports": "off"
},
settings: {
"import/resolver": {
alias: {
map: [
["@", "./src"],
["@@", "./src/.umi"],
["@@test", "./src/.umi-test"]
],
extensions: [".ts", ".tsx", ".js", ".jsx", ".json", ".less"]
}
}
}
};
const prettierConfig = {
files: ["src/**/*.{ts,tsx,js,jsx}"],
plugins: { prettier },
rules: { "prettier/prettier": "warn" }
};
// 全局规则
const commonRules = {
rules: {
"no-unused-vars": "off",
"react/prop-types": "off",
"no-useless-escape": "off",
"react/display-name": "off",
"react-hooks/exhaustive-deps": "off",
"@typescript-eslint/no-unused-vars": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/ban-ts-comment": "off"
}
};
// 导出最终配置
export default [
// 全局忽略
{
ignores: [
"**/node_modules/**",
"**/.umi*/**",
"**/dist/**",
"**/build/**",
"scripts/**",
"config/**",
"public/**",
"mock/**",
"**/*.d.ts"
]
},
// js推荐配置
js.configs.recommended,
// ts推荐配置
...tseslint.configs.recommended,
// 浏览器全局变量
browserGlobalsConfig,
tsFiles,
reactConfig,
importConfig,
prettierConfig, // 将通用规则放在最后,确保它能覆盖之前的所有配置
commonRules
];
Vue3版
1. eslint-plugin-vue: flat/recommended
推荐理由:
- Vue 官方维护,覆盖最核心的 Vue 3 代码风格、语法规则。
- 包含对
script setup
、组合式 API 的支持。 - 能帮你避免常见的坑,比如未注册的组件、未使用的 ref、无效的 v-for key 等。
2. @vue/eslint-config-typescript
推荐理由:
- Vue 官方团队维护,专门适配 TypeScript + Vue 3。
- 内置了
@typescript-eslint
,并帮你处理.vue
文件里<script lang="ts">
的解析。 - 避免了你手动折腾 parser 配置,拿来即用。
3. @vue/eslint-config-prettier/skip-formatting
推荐理由:
- 禁用掉和 Prettier 冲突的风格类规则(比如缩进、换行)。
- 避免 ESLint 和 Prettier 打架,让 ESLint 专注语法/逻辑问题,Prettier 专注格式化。
- 如果你团队里已经统一用 Prettier,就必加。
4. eslint-plugin-import (可选)
推荐理由:
- 帮你检查 import/export 是否正确。
- 避免未使用的 import、重复导入、错误的相对路径。
- 在 Vue3 + Vite 项目里尤其有用,结合 alias (
@/
) 可以避免路径拼错。
js
import js from "@eslint/js";
import globals from "globals";
import vuePlugin from "eslint-plugin-vue";
import skipFormatting from "@vue/eslint-config-prettier/skip-formatting";
import { defineConfigWithVueTs, vueTsConfigs } from "@vue/eslint-config-typescript";
/**
* 配置全局变量
*/
const browserGlobalsConfig = {
languageOptions: {
globals: {
...globals.browser,
/** 追加一些其他自定义全局规则 */
wx: true
}
}
};
export default defineConfigWithVueTs(
js.configs.recommended,
// TypeScript 推荐规则
vueTsConfigs.recommended,
browserGlobalsConfig,
...vuePlugin.configs["flat/recommended"],
{
files: ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"],
languageOptions: {
parserOptions: {
parser: "@typescript-eslint/parser"
}
},
rules: {
"vue/event-name-casing": "off",
"vue/prop-name-casing": "off",
"vue/singleline-html-element-content-newline": "off",
"vue/max-attributes-per-line": "off",
"vue/multi-word-component-names": "off",
"vue/html-closing-bracket-newline": [
"warn",
{
singleline: "never",
multiline: "never"
}
],
"vue/first-attribute-linebreak": [
"warn",
{
singleline: "ignore",
multiline: "ignore"
}
],
"vue/html-self-closing": [
"error",
{
html: { void: "always", normal: "never", component: "always" }
}
]
}
},
skipFormatting
);
规则集ts类型定义文件types.d.ts内容如下:
js
// 从 eslint 包导入官方的 Config 类型
type EslintFlatConfig = import("eslint").Config;
// 声明一个常量,它的类型是 EslintFlatConfig 类型的数组
declare const createVue3TsEslintConfig: EslintFlatConfig[];
// 将这个常量作为模块的默认导出
export default createVue3TsEslintConfig;
第三步 一键发版
我们采用 pnpm workspace + Changesets 来管理和发布多个 ESLint 配置包。
目录结构大致如下:
js
repo-root/
├─ package.json # 根配置(workspace、脚本)
├─ pnpm-workspace.yaml
├─ .npmrc # 指定 scope 的 registry + 鉴权(本地或 CI)
├─ .changeset/ # changesets 配置 & 变更文件
└─ npm-packages/
├─ eslint-config-vue3/
│ ├─ package.json
│ └─ index.js
└─ eslint-config-react/
├─ package.json
└─ index.js
3.1 根配置
- pnpm-workspace.yaml 采用pnpm的workspace方式对多个npm包进行管理。
js
packages:
- 'npm-packages/*'
- 根 package.json
version-packages
:根据 changeset 生成版本号并更新依赖与 lockfilerelease
:按依赖拓扑顺序构建并发布所有变更包(Changesets 会自动先发被依赖的包)
js
{
"name": "eslint-configs-monorepo",
"private": true,
"packageManager": "pnpm@10.12.4",
"scripts": {
"changeset": "changeset",
"version-packages": "changeset version && pnpm install --lockfile-only",
"release": "pnpm -r build --if-present && changeset publish"
}
}
pnpm -r build --if-present
的含义是给每个子包跑build(如果有)
- 原则上,根 .npmrc不能把含 token 的 .npmrc 提交到仓库,但考虑到公司的gitlab仓库只能内网访问, 为了使用方便,提交上去也无妨。
ruby
@公司名:registry=https://git.example.com/api/v4/projects/项目ID/packages/npm/
//git.example.com/api/v4/projects/项目ID/packages/npm/:_authToken=YOUR_PERSONAL_ACCESS_TOKEN
3.2 包配置
-
"main": "index.mjs"
包的入口文件路径。当别人
import
或require
这个包时,会默认从这里加载。 -
"types": "types.d.ts"
TypeScript 类型声明文件路径。用于在编辑器/IDE 中获得类型提示。
-
"files": [ "index.mjs", "types.d.ts", "README.md" ]
指定 发布到 npm 仓库时包含的文件。这样可以避免把无关文件(如源码、测试文件)上传,保持包体积小。只会发布列出的文件。
-
"dependencies"
: 运行这些规则,就需要这些依赖, 将所有用的依赖包,都列举出来 -
peerDependencies
: 明确工具包的依赖关系, 避免与业务项目的ESLint版本冲突 -
"publishConfig": { "access": "restricted" }
控制包发布的访问级别:
"public"
→ 可公开发布到 npmjs.org(所有人可安装);"restricted"
→ 只能发布到私有仓库(如 GitLab npm Registry / npm 企业版),不能上传到公网。在公司私有仓库场景下,通常用"restricted"
,避免意外泄露。
1. vue3规则集的package.json配置
npm-packages/eslint-config-vue3/package.json 内容如下:
js
{
"name": "@company/eslint-config-vue3",
"version": "1.7.6",
"description": "vue3 eslint config",
"author": "xxx Frontend Team",
"license": "MIT",
"main": "index.mjs",
"types": "types.d.ts",
"files": [
"index.mjs",
"types.d.ts",
"README.md"
],
"dependencies": {
"@eslint/js": "^9.34.0",
"@vue/eslint-config-prettier": "^10.2.0",
"@vue/eslint-config-typescript": "^14.6.0",
"eslint-plugin-vue": "^10.4.0",
"globals": "^16.3.0"
},
"peerDependencies": {
"eslint": ">=9.34.0"
},
"keywords": [
"eslint9+",
"eslint-config",
"vue3"
],
"publishConfig": {
"access": "restricted"
}
}
2. react规则集package.json
npm-packages/eslint-config-react/package.json的内容如下
js
{
"name": "@company/eslint-config-react",
"version": "1.4.0",
"main": "index.mjs",
"types": "types.d.ts",
"files": [
"index.mjs",
"types.d.ts"
],
"license": "MIT",
"publishConfig": {
"access": "restricted"
},
"dependencies": {
"@eslint/js": "^9.34.0",
"eslint-import-resolver-alias": "^1.1.2",
"eslint-import-resolver-typescript": "^4.4.4",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-prettier": "^5.5.4",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^5.2.0",
"globals": "^16.3.0",
"prettier": "^3.6.2",
"typescript-eslint": "^8.42.0"
},
"peerDependencies": {
"eslint": ">=9.34.0"
}
}
3.3 初始化 Changesets(一次性)
js
pnpm add -D @changesets/cli
pnpm changeset init
会生成 .changeset/config.json
,可以保持默认;如果你希望内部依赖只要上游变更就给下游自动打补丁号,可加入:updateInternalDependencies: "patch"
:当 eslint-config-base
升级时,eslint-config-react
也会自动至少 bump 一个 patch,避免依赖范围不匹配。
js
{
"$schema": "https://unpkg.com/@changesets/config/schema.json",
"changelog": "@changesets/changelog-github",
"commit": false,
"linked": [],
"access": "restricted",
"baseBranch": "main",
"updateInternalDependencies": "patch"
}
3.4.写变更(每次发版)
当你修改了包后,创建一个 changeset:
js
pnpm changeset
交互式选择受影响的包(先按空格再按回车),选择变更等级(patch/minor/major),写一句描述。
3.5 一键发布
-
version-packages
:会把两个包的version
改掉,并把workspace:^
解析为实际 semver。 -
release
(changeset publish
):自动安装依赖顺序排序,成功后会打印每个包的发布结果。
js
# 生成新版本号 & 更新依赖与 lockfile
pnpm version-packages
# 构建并发布(会按顺序先发 base 后发 react)
pnpm release
单独指定子包发布的命令是:
ruby
cd npm-packages/eslint-config-vue3
pnpm publish --access public --registry=https://gitlab.xxx.com/api/v4/projects/xxx/packages/npm/
cd ../eslint-config-react
pnpm publish --access public --registry=https://gitlab.xxx.com/api/v4/projects/xxx/packages/npm/
第四步 安装使用
1. 配置项目使用私有仓库
在你要使用这些 ESLint 配置的项目根目录下创建或修改 .npmrc
文件:
ruby
@公司名:registry=https://git.example.com/api/v4/projects/项目ID/packages/npm/
//git.example.com/api/v4/projects/项目ID/packages/npm/:_authToken=YOUR_PERSONAL_ACCESS_TOKEN
这样 npm / pnpm 就会从你的 GitLab 私有仓库拉取 @公司名
作用域下的包。
2. 安装 ESLint 规则包
假设你已经发布了:
@公司名/eslint-config-vue3
@公司名/eslint-config-react
要配置一个 Vue3 项目规则集, 在项目中执行:
arduino
pnpm add -D @公司名/eslint-config-vue3
3. 配置 ESLint 使用这些规则集
在你的项目根目录下 eslint.config.ts
中:
js
import vue3ESLintRules from "@公司名/eslint-config-vue3";
export default [
...vue3ESLintRules,
{
rules: {
"vue/no-v-html": "off",
"vue/no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": "off",
"@typescript-eslint/no-explicit-any": "off",
"vue/singleline-html-element-content-newline": "off",
"vue/html-self-closing": [
"error",
{
html: { void: "always", normal: "never", component: "always" }
}
]
}
}
];
如果是 React 项目,使用 @公司名/eslint-config-react
,配置方法类似。
最后
文章末尾,对本文做一下总结,本文介绍了一种在公司内统一 ESLint 规则的完整方案:
- 使用 GitLab npm Registry 搭建私有仓库。
- 基于 ESLint v9,分别为 React 和 Vue3 项目设计规则集。
- 借助 pnpm workspace + Changesets,实现规则包的管理和一键发布。
- 在业务项目中通过安装和引入统一规则包,最终实现公司内部的 ESLint 配置统一。
这样一来,不同团队成员在不同项目中都能遵循相同的规则,不仅避免了"不公平"的问题,也能整体提升公司代码质量和一致性。如果你有同样的业务需求,可以参考一下本文的技术思路和实现方案。本文完