Vue項目中引入ESLint校驗代碼避免代碼錯誤
1 ESLint 是什么
ESLint 是一個插件式的 JavaScript / JSX 代碼檢查工具,用于檢測和修復(fù) JavaScript 代碼中的問題,目標(biāo)是讓代碼更一致并避免錯誤。
2 在 Vue 項目中引入 ESLint
使用 Vue CLI 搭建的 Vue2 項目已經(jīng)自帶 ESLint,就不贅述,我們看下 Vite 搭建的 Vue3 項目中怎么引入 ESLint。
使用以下命令搭建一個 Vue3 項目:
npm create vite@latest vue3-project
創(chuàng)建之后,啟動起來:
npm i npm run dev
效果如下:

2.1 引入 ESLint
執(zhí)行以下命令:
npm init @eslint/config
進入交互式界面,可通過上下方向鍵選擇,通過按回車鍵確定。
第一個問題是:
- 你希望用 ESLint 來干嘛?
- 我們選擇最全面的那個:檢查語法,發(fā)現(xiàn)問題,并強制統(tǒng)一代碼樣式
$ npm init @eslint/config ? How would you like to use ESLint? … To check syntax only To check syntax and find problems ? To check syntax, find problems, and enforce code style
第二個問題是:
- 你的項目用的是什么模塊系統(tǒng)?
- 因為是運行在瀏覽器端,選擇
ESModule
? What type of modules does your project use? … ? JavaScript modules (import/export) CommonJS (require/exports) None of these
第三個問題是:
- 你用的什么框架?(居然沒有 Angular)
- 選擇
Vue
? Which framework does your project use? … React ? Vue.js None of these
第四個問題是:
- 你是否使用 TypeScript?
- 選擇
Yes
? Does your project use TypeScript? ? No / Yes
第五個問題是:
- 你的代碼運行在什么環(huán)境?(這個可以多選)
- 選擇
Browser瀏覽器環(huán)境
? Where does your code run? … (Press <space> to select, <a> to toggle all, <i> to invert selection) ? Browser ? Node
第六個問題是:
- 你想定義怎樣的代碼風(fēng)格?
- 選擇使用一個流行的代碼風(fēng)格
? How would you like to define a style for your project? … ? Use a popular style guide Answer questions about your style
第七個問題是:
- 你想使用哪個樣式風(fēng)格?
Airbnb用的人比較多,就選這個吧
? Which style guide do you want to follow? … ? Airbnb: https://github.com/airbnb/javascript Standard: https://github.com/standard/standard Google: https://github.com/google/eslint-config-google XO: https://github.com/xojs/eslint-config-xo
第八個問題是:
- 配置文件用什么格式?
- 就選 JavaScript 吧(生成
eslintrc.js文件)
? What format do you want your config file to be in? … ? JavaScript YAML JSON
完成!是不是超級簡單!
看下我們都選了哪些配置:
? How would you like to use ESLint? · style ? What type of modules does your project use? · esm ? Which framework does your project use? · vue ? Does your project use TypeScript? · Yes ? Where does your code run? · browser ? How would you like to define a style for your project? · guide ? Which style guide do you want to follow? · airbnb ? What format do you want your config file to be in? · JavaScript
主要給我們安裝了以下依賴:
eslint-config-airbnb-base@15.0.0
eslint-plugin-import@2.26.0
eslint-plugin-vue@9.2.0
eslint@8.20.0
@typescript-eslint/parser@5.30.6
@typescript-eslint/eslint-plugin@5.30.6
并生成了一個 eslintrc.cjs 配置文件:
module.exports = {
env: {
browser: true,
es2021: true,
},
extends: [
'plugin:vue/vue3-essential',
'airbnb-base',
],
parserOptions: {
ecmaVersion: 'latest',
parser: '@typescript-eslint/parser',
sourceType: 'module',
},
plugins: [
'vue',
'@typescript-eslint',
],
// 自定義 rules 規(guī)則
rules: {
},
};
2.2 ESLint 配置
- parser 解析器
- extends 配置擴展
- plugins 插件
- rules 自定義規(guī)則 eslint.org/docs/latest…
- eslint-disable-next-line 禁用ESLint
2.3 執(zhí)行 ESLint 代碼檢查
在 package.json 文件的 scripts 中配置 lint 腳本命令:
"scripts": {
"dev": "vite",
"build": "vue-tsc --noEmit && vite build",
"preview": "vite preview",
// 配置 lint 腳本命令
"lint": "eslint --ext .vue,.ts src/"
},
執(zhí)行 lint 腳本命令:
npm run lint
出現(xiàn)了一堆報錯:
/vue3-project/src/App.vue 4:53 error Missing semicolon semi /vue3-project/src/components/HelloWorld.vue 2:26 error Missing semicolon semi 4:31 error Missing semicolon semi 6:21 error Missing semicolon semi /vue3-project/src/main.ts 1:32 error Missing semicolon semi 2:21 error Missing semicolon semi 3:28 error Missing semicolon semi 5:29 error Missing semicolon semi /vue3-project/src/vite-env.d.ts 4:3 error Expected 1 empty line after import statement not followed by another import import/newline-after-import 4:45 error Missing semicolon semi 5:48 error Missing semicolon semi 6:27 error Missing semicolon semi ? 12 problems (12 errors, 0 warnings) 12 errors and 0 warnings potentially fixable with the `--fix` option.
大部分都是說句尾沒有分號,因為我們選擇的是 Airbnb 代碼規(guī)范,所以會有這個報錯提示,不同的代碼規(guī)范,內(nèi)置的檢查規(guī)則不一定完全相同。
2.4 自動修復(fù) ESLint 問題
在 scripts 中增加自動修復(fù) ESLint 問題的腳本命令:
"scripts": {
"dev": "vite",
"build": "vue-tsc --noEmit && vite build",
"preview": "vite preview",
"lint": "eslint --ext .vue,.ts src/",
// 自動修復(fù) ESLint 問題腳本命令
"lint:fix": "eslint --ext .vue,.ts src/ --fix"
},
執(zhí)行:
npm run lint:fix
執(zhí)行自動修復(fù)的命令之后,所有分號都加上了,未使用的變量也自動移除了。
再次執(zhí)行:
npm run lint
沒有再報錯。
3 配置 husky 和 PR 門禁
3.1 配置 husky 門禁
為了確保每次提交(git commit)之前代碼都通過 ESLint 檢查,我們增加一個 pre-commit 門禁。
- 第一步:安裝 husky 和 lint-staged
npm i lint-staged husky -D
- 第二步:在 package.json 的 scripts 中增加 prepare 腳本命令
"scripts": {
"dev": "vite",
"build": "vue-tsc --noEmit && vite build",
"preview": "vite preview",
"lint": "eslint --ext .vue,.ts src/",
"lint:fix": "eslint --ext .vue,.ts src/ --fix",
// 在 npm install 之后自動執(zhí)行,生成`.husky`目錄。
"prepare": "husky install"
},
- 第三步:執(zhí)行 prepare 腳本
npm run prepare
該命令執(zhí)行完會在項目根目錄自動生成.husky目錄。
- 第四步:增加 pre-commit 鉤子
執(zhí)行以下命令,會在.husky目錄自動生成pre-commit文件鉤子。
npx husky add .husky/pre-commit "npx lint-staged"
- 第五步:增加 lint-staged 配置
"lint-staged": {
"src/**/*.{vue,ts}": "eslint --fix"
},
通過以上五個步驟,以后每次使用git commit命令提交提交代碼,都會:
- 被 pre-commit 鉤子攔截
- 執(zhí)行 npx lint-staged 命令
- 進而執(zhí)行 eslint --fix 命令,對本次提交修改的代碼涉及的文件進行代碼檢查,并自動修復(fù)能修復(fù)的錯誤,不能修復(fù)的錯誤會提示出來,只有所有 ESLint 錯誤都修復(fù)了才能提交成功
3.2 配置 PR 門禁
如果你在做自己的開源項目,并且非常幸運,有一群志同道合的小伙伴愿意一起參與貢獻,這時為了統(tǒng)一大家的代碼風(fēng)格,讓貢獻者們專注于特性開發(fā),不用擔(dān)心代碼格式規(guī)范問題,并通過 ESLint 工具提示貢獻者,哪些代碼可能帶來潛在的風(fēng)險,你就有必要給提交的 PR 加上 ESLint 門禁。
我們已經(jīng)增加了本地的 ESLint 命令:
"scripts": {
"lint": "eslint --ext .vue,.ts src/",
},
我們需要在本目錄創(chuàng)建一個.github/workflows/pull-request.yml文件,在該文件中寫入以下內(nèi)容:
name: Pull Request
on:
push:
branches: [ dev, main ]
pull_request:
branches: [ dev, main ]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [16.x]
name: "ESLint"
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Install pnpm
uses: npm/action-setup@v2
with:
version: 6
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node-version }}
- name: Install deps
run: npm i
- name: ESLint
run: npm run lint
這樣只要 PR 是往 dev 或 main 分支合入的,都會跑一遍這個 Github Actions 工作流任務(wù),ESLint 檢查不通過的話,PR 的 checks 里面會提示,攔截該 PR 的合入。
PR 的提交者看到提示,也可以點到任務(wù)里面去看是哪里報錯,修改掉這些 ESLint 問題,PR 就會變成綠色,項目的管理員就可以順利合入 PR 到目標(biāo)分支啦??
4 常見的 ESLint 問題及修復(fù)案例
接下來跟大家分享 Vue DevUI 開源 Vue3 組件庫 ESLint 問題修復(fù)過程中遇到的典型問題。
4.1 案例1:
warning Unexpected any. Specify a different type @typescript-eslint/no-explicit-any
該問題出現(xiàn)頻率比較高,原因是有些類型寫了any,需要明確的類型。
比如Pagination組件的單元測試文件pagination.spec.ts中:
const wrapper = mount({
components: {
DPagination
},
template: `<d-pagination ... />`
}, globalOption);
const btns = wrapper.findAll('a.devui-pagination-link');
expect(btns.map((ele: any) => ele.text()).join()).toEqual('<,1,...,4,5,6,...,16,>');
其中的ele: any就屬于這類問題。
解決辦法是給ele加上明確的類型,看邏輯是<button>元素,由于是@vue/test-utils庫的包裹元素,因此需要包一層DOMWrapper:
import { DOMWrapper } from '@vue/test-utils';
expect(btns.map((ele: DOMWrapper<Element>) => ele.text()).join()).toEqual('<,1,...,4,5,6,...,16,>');
4.2 案例2:
'xxx' was used before it was defined no-use-before-define
這也是一個比較常見的問題,在聲明之前使用變量或方法,解決辦法也很簡單,只需要調(diào)整下代碼的順序即可,將變量或方法的聲明放在調(diào)用的語句之前。
比如Pagination組件的pagination.tsx中:
// 極簡模式下,可選的下拉選擇頁碼
const litePageOptions = computed(() => liteSelectOptions(totalPages.value));
// 當(dāng)前頁碼
const cursor = computed({
get() {
// 是否需要修正錯誤的pageIndex
if (!props.showTruePageIndex && props.pageIndex > totalPages.value) {
emit('update:pageIndex', totalPages.value || 1);
return totalPages.value || 1;
}
return props.pageIndex || 1;
},
set(val: number) {
emit('update:pageIndex', val);
}
});
// 總頁數(shù)
const totalPages = computed(() => Math.ceil(props.total / props.pageSize));
其中的totalPages的聲明在比較靠后的位置,但是卻在聲明之前在litePageOptions和cursor變量中都使用了totalPages,所以提示 ESLint 問題。
解決的方法就是將totalPages的聲明放在litePageOptions和cursor之前。
// 總頁數(shù)
const totalPages = computed(() => Math.ceil(props.total / props.pageSize));
// 極簡模式下,可選的下拉選擇頁碼
const litePageOptions = computed(() => liteSelectOptions(totalPages.value));
// 當(dāng)前頁碼
const cursor = computed({ ... });
4.3 案例3:
warning Missing return type on function @typescript-eslint/explicit-module-boundary-types
該問題是因為函數(shù)缺少返回類型,比如Fullscreen組件utils.ts文件的launchImmersiveFullScreen方法中:
export const launchImmersiveFullScreen = async (docElement: any) => {
let fullscreenLaunch = null;
if (docElement.requestFullscreen) {
fullscreenLaunch = docElement.requestFullscreen();
} else if (docElement.mozRequestFullScreen) {
fullscreenLaunch = docElement.mozRequestFullScreen();
} else if (docElement.webkitRequestFullScreen) {
fullscreenLaunch = Promise.resolve(docElement.webkitRequestFullScreen());
} else if (docElement.msRequestFullscreen) {
fullscreenLaunch = Promise.resolve(docElement.msRequestFullscreen());
}
return await fullscreenLaunch.then(() => !!document.fullscreenElement);
};
先看下launchImmersiveFullScreen方法的參數(shù)問題,docElement用了any,也缺失了返回類型,docElement其實就是document對象,可以使用HTMLElement類型,但是launchImmersiveFullScreen這個方法是用來啟動沉浸式全屏的,為了實現(xiàn)瀏覽器兼容,比如使用了docElement.mozRequestFullScreen兼容火狐,而這些方法在HTMLElement中是沒有的,會報TS類型錯誤,所以需要做一些改造。
interface CompatibleHTMLElement extends HTMLElement {
mozRequestFullScreen?: () => void;
webkitRequestFullScreen?: () => void;
msRequestFullscreen?: () => void;
}
這里定義了一個CompatibleHTMLElement的類型,繼承了HTMLElement,并增加了一些自定義的方法。
export const launchImmersiveFullScreen = async (docElement: CompatibleHTMLElement) => {
...
}
再來看下launchImmersiveFullScreen方法的返回類型問題。
return await fullscreenLaunch.then(() => !!document.fullscreenElement);
該方法返回了一個Promise對象,它的類型是一個泛型,我們需要傳入具體的類型:
export const launchImmersiveFullScreen = async (docElement: CompatibleHTMLElement): Promise<boolean> => {
...
return await fullscreenLaunch.then(() => !!document.fullscreenElement);
};
4.4 案例4:
'xxx' is already declared in the upper scope @typescript-eslint/no-shadow
這個問題是由于嵌套的作用域中定義了相同的變量名,比如Tree組件的use-checked.ts文件中:
export default function useChecked(...) {
const onNodeClick = (item: TreeItem) => {
// 這里定義了 id 變量
const { id } = item;
...
filter 里面又定義了一個 id 參數(shù)
const currentSelectedItem = flatData.filter(({ id }) => currentSelected[id] && currentSelected[id] !== 'none');
...
}
}
修改方式就是將其中一個 id 的名字改了,比如把里面的 id 改成 itemId:
const currentSelectedItem = flatData.filter(({ id: itemId }) => currentSelected[itemId] && currentSelected[itemId] !== 'none');
歡迎在評論區(qū)分享你在項目中遇到的 ESLint 問題????
5 感謝
在 Vue DevUI 組件庫 ESLint 問題清零之路上,不得不提的一位小伙伴就是 @linxiang07 同學(xué),他從2022年3月到7月,持續(xù)4個多月,累計修復(fù)40多個組件的100多個 ESLint 問題,直到7月5日 ESLint 清零,并給 PR 添加 ESLint 門禁,后續(xù) PR 如果未通過 ESLint 檢查將無法合入。
感謝 linxiang 同學(xué)的付出!

以下是 linxiang 同學(xué)提交的部分PR:

linxiang 同學(xué)也因此成為 Vue DevUI 組件庫 TOP3 的貢獻者,并成為我們的 Committer 和管理員????

值得一提的是 linxiang 同學(xué)還是我們的 VirtualList 虛擬列表組件和 Tree 組件等多個組件的田主和貢獻者,并且完善了多個組件的單元測試,能力很強,是非?;钴S和積極的貢獻者。
以上就是Vue項目中引入ESLint校驗代碼避免代碼錯誤的詳細內(nèi)容,更多關(guān)于Vue引入ESLint代碼校驗的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
vue3配置router路由并實現(xiàn)頁面跳轉(zhuǎn)功能
這篇文章主要介紹了vue3配置router路由并實現(xiàn)頁面跳轉(zhuǎn),本文通過示例代碼給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-04-04
Vue+Express實現(xiàn)登錄狀態(tài)權(quán)限驗證的示例代碼
這篇文章主要介紹了Vue+Express實現(xiàn)登錄狀態(tài)權(quán)限驗證的示例代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2019-05-05
vue復(fù)雜表格單元格合并根據(jù)數(shù)據(jù)動態(tài)合并方式
這篇文章主要介紹了vue復(fù)雜表格單元格合并根據(jù)數(shù)據(jù)動態(tài)合并方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-02-02
Vue + Element-ui的下拉框el-select獲取額外參數(shù)詳解
這篇文章主要介紹了Vue + Element-ui的下拉框el-select獲取額外參數(shù)詳解,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-08-08
Vue學(xué)習(xí)筆記進階篇之vue-router安裝及使用方法
本篇文章主要介紹了Vue學(xué)習(xí)筆記進階篇之vue-router安裝及使用方法,具有一定的參考價值,有興趣的可以了解一下2017-07-07
解決el-tree數(shù)據(jù)回顯時子節(jié)點部分選中父節(jié)點都全選中的坑
本文主要介紹了解決el-tree數(shù)據(jù)回顯時子節(jié)點部分選中父節(jié)點都全選中的坑,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-08-08

