问题描述:
在前端工程化的道路上,Husky + lint-staged + ESLint 是保证代码质量的“黄金三角”。但是配置好后,执行 git commit 命令 却失败了,并且明明在 eslint.config.js文件 中关闭了 no-undef 规则,ESLint 却依然报错。



分析原因:
一切始于一个简单的 git commit 操作。终端输出了如下错误信息:

核心矛盾点:明明在 eslint.config.js文件 中写着 'no-undef': 'off',为什么 ESLint 视而不见?
排查问题如下:
第一步:排除外部干扰,锁定内部问题
既然使用的不是旧版 .eslintrc.js,那么 package.json 中的 eslintConfig 冲突自然不存在。同时,手动执行 npx eslint –fix src/main.js 也能复现问题。
结论:问题出现在 eslint.config.js 文件内部。
第二步:审视配置
在自己的 eslint.config.js 文件 发现结构混乱。自定义的 rules 对象放在了最前面,而 js.configs.recommended 等预设配置却放在了后面。
这意味着:
ESLint 首先加载了我的 { rules: { 'no-undef': 'off' } }。然后,它加载了 js.configs.recommended,这个预设内部包含了 { rules: { 'no-undef': 'error' } }。后面的配置覆盖了前面的!自己辛辛苦苦设置的 'off' 就这样被无情地覆盖了。
结论:不是自己的配置没生效,而是它生效了,但 立刻被后面的预设配置给覆盖了。
关键知识点:ESLint 9.x 引入了一个重大变革——它默认采用新的“Flat Config”格式(eslint.config.js),而不再自动支持旧版的 .eslintrc.js 文件。且 Flat Config 的一个核心原则是“数组中的配置对象按顺序合并,后面的会覆盖前面的!”
解决办法:
重构 Flat Config,建立秩序
找到了根源,解决方法就是 重新组织配置数组的顺序,并清理冗余。
清理 Prettier 配置:只保留 eslintConfigPrettier,这是最标准的做法。调整配置顺序:将预设配置(js.configs.recommended 等)放在前面,自定义配置放在最后,确保自定义规则拥有最高优先级。合并自定义规则:将所有 rules 集中到一个配置对象中。
最终,一个健壮、清晰的 eslint.config.js 诞生了。
原 eslint.config.js 代码:
import globals from 'globals'
import js from '@eslint/js'
import pluginVue from 'eslint-plugin-vue'
import skipFormatting from '@vue/eslint-config-prettier/skip-formatting'
import eslintConfigPrettier from '@vue/eslint-config-prettier' //添加这一行代码
export default [
{
name: 'app/files-to-lint',
files: ['**/*.{js,mjs,jsx,vue}'],
// rules添加到这里,上面写的该规则应用于js,mjs,jsx,vue
rules: {
'prettier/prettier': [
'warn',
{
singleQuote: true, // 单引号
semi: false, // 无分号
printWidth: 100, // 每行宽度至多100字符
trailingComma: 'none', // 不加对象|数组最后逗号
endOfLine: 'auto' //换行符号不限制(win mac 不一致)
}
],
// vue组件名称多单词组成(忽略index.vue)
'vue/multi-word-component-names': ['warn', { ignores: ['index'] }],
// 关闭 props 解构的校验
'vue/no-setup-props-destructure': ['off'],
// 💡 添加未定义变量错误提示,create-vue@3.6.3 关闭,这里加上是为了支持下一个章节演示。
'no-undef': 'off' //这里是为了 测试 lint-staged 将 error 改为 off
}
},
{
name: 'app/files-to-ignore',
ignores: ['**/dist/**', '**/dist-ssr/**', '**/coverage/**'],
//rules添加到这里
rules: {
'prettier/prettier': [
'warn',
{
singleQuote: true,
semi: false,
printWidth: 100,
trailingComma: 'none',
endOfLine: 'auto'
}
],
'vue/multi-word-component-names': ['warn', { ignores: ['index'] }],
'vue/no-setup-props-destructure': ['off'],
'no-undef': 'off' // 这里是为了 测试 lint-staged 将 error 改为 off
}
},
{
languageOptions: {
globals: {
...globals.browser
}
}
},
js.configs.recommended,
...pluginVue.configs['flat/essential'],
skipFormatting,
eslintConfigPrettier //添加这一行代码
]
问题一:重复的 Prettier 配置(严重问题)

同时引入了两个 Prettier 相关的配置:
skipFormatting (来自 @vue/eslint-config-prettier/skip-formatting)eslintConfigPrettier (来自 @vue/eslint-config-prettier)
这是错误的!
eslintConfigPrettier 的作用是 禁用所有与 Prettier 冲突的 ESLint 规则。
skipFormatting 的作用是 完全禁用 prettier/prettier 规则,只保留冲突禁用的功能。
同时使用两者会导致混乱。只能选择一个。


问题二:配置结构不合理(关键问题)
原 eslint.config.js 文件配置结构是混乱的,将自定义规则和预设配置(js.configs.recommended等)分开放置。在 Flat Config 中,数组的顺序非常重要,后面的配置会覆盖前面的。
一个更清晰、更不容易出错的写法是:
先定义全局配置(如 ignores)。然后按顺序应用各种预设配置(js.configs.recommended, pluginVue.configs 等)。最后,在数组的末尾添加自己的自定义规则,这样可以确保你的规则具有最高优先级,能覆盖预设中的规则。
原 eslint.config.js 文件:

问题三:rules 重复定义
原 eslint.config.js 文件在两个不同的配置对象里都定义了完全相同的 rules。这是不必要的,并且容易导致维护困难。应该将所有自定义规则统一放在一个地方。

问题四:缺少 globals 依赖
原 eslint.config.js 文件使用了 import globals from 'globals',虽然 由项目中 package.json文件知,globals 包是已安装的(自己看自己项目),但需要确保它被正确地应用到了 languageOptions 中。
修正后的写法:
// eslint.config.js
import js from '@eslint/js'
import pluginVue from 'eslint-plugin-vue'
import globals from 'globals'
import eslintConfigPrettier from '@vue/eslint-config-prettier'
export default [
// 1. 全局忽略文件(必须放在最前面)
{
name: 'app/files-to-ignore',
ignores: ['**/dist/**', '**/dist-ssr/**', '**/coverage/**']
},
// 2. 应用 ESLint 官方推荐规则
js.configs.recommended,
// 3. 应用 Vue 插件的推荐规则
...pluginVue.configs['flat/essential'],
// 4. 应用 Prettier 配置,禁用冲突规则(必须放在最后)
eslintConfigPrettier,
// 5. 你的项目自定义配置(放在最后,优先级最高)
{
name: 'app/custom-rules',
files: ['**/*.{js,mjs,jsx,vue}'], // 指定文件范围
languageOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
globals: {
...globals.browser // 注入浏览器全局变量
}
},
rules: {
// 在这里集中定义所有自定义规则
'prettier/prettier': [
'warn',
{
singleQuote: true, //单引号
semi: false, // 无分号
printWidth: 100, // 每行宽度至多100字符
trailingComma: 'none', // 不加对象|数组最后逗号
endOfLine: 'auto' //换行符号不限制(win mac 不一致)
}
],
// vue组件名称多单词组成(忽略index.vue)
'vue/multi-word-component-names': ['warn', { ignores: ['index'] }],
// 关闭 props 解构的校验,prop解构会影响响应式
'vue/no-setup-props-destructure': 'off',
// 💡 添加未定义变量错误提示,create-vue@3.6.3 关闭,这里加上是为了支持下一个章节演示。
'no-undef': 'off' //这里是为了 测试 lint-staged 将 error 改为 off,测试完记得改回error
}
}
]


主要修改点说明
只保留 eslintConfigPrettier:这是最标准的选择,它会确保 Prettier 的格式化规则与 ESLint 的规则不冲突,同时保留 prettier/prettier 规则来检查代码风格。优化了配置顺序:ignores 在前,然后是预设配置,最后是自定义规则。这是 Flat Config 的最佳实践。合并了 rules:所有自定义规则都集中在一个配置对象中,清晰明了。将 globals 放入 languageOptions:这是 Flat Config 的标准语法,用于定义全局变量。修正了可能的拼写错误:将 vue/^-setup-props-destructure 修正为 vue/no-setup-props-destructure。
重新在终端中提交:

成功!!
参考资料:
ESLint Flat Config 文档 – 配置对象
ESLint Flat Config 文档 – 如何配置规则