乐趣区

关于前端:前端Vuer请给你的项目加上-ESLint

1 ESLint 是什么

ESLint 是一个插件式的 JavaScript / JSX 代码查看工具,用于检测和修复 JavaScript 代码中的问题,指标是让代码更统一并防止谬误。

2 在 Vue 我的项目中引入 ESLint

应用 Vue CLI 搭建的 Vue2 我的项目曾经自带 ESLint,就不赘述,咱们看下 Vite 搭建的 Vue3 我的项目中怎么引入 ESLint。

应用以下命令搭建一个 Vue3 我的项目:

npm create vite@latest vue3-project

创立之后,启动起来:

npm i
npm run dev

成果如下:

2.1 引入 ESLint

执行以下命令:

npm init @eslint/config

进入交互式界面,可通过高低方向键抉择,通过按回车键确定。

第一个问题是:

  • 你心愿用 ESLint 来干嘛?
  • 咱们抉择最全面的那个:查看语法,发现问题,并强制对立代码款式
$ 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

第二个问题是:

  • 你的我的项目用的是什么模块零碎?
  • 因为是运行在浏览器端,抉择 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

第五个问题是:

  • 你的代码运行在什么环境?(这个能够多选)
  • 抉择 Browser 浏览器环境
? Where does your code run? …  (Press <space> to select, <a> to toggle all, <i> to invert selection)
✔ Browser
✔ Node

第六个问题是:

  • 你想定义怎么的代码格调?
  • 抉择应用一个风行的代码格调
? How would you like to define a style for your project? … 
❯ Use a popular style guide
  Answer questions about your style

第七个问题是:

  • 你想应用哪个款式格调?
  • 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 规定
  rules: {},};

2.2 ESLint 配置

  • parser 解析器
  • extends 配置扩大
  • plugins 插件
  • rules 自定义规定 https://eslint.org/docs/latest/rules/
  • eslint-disable-next-line 禁用 ESLint

2.3 执行 ESLint 代码查看

在 package.json 文件的 scripts 中配置 lint 脚本命令:

"scripts": {
  "dev": "vite",
  "build": "vue-tsc --noEmit && vite build",
  "preview": "vite preview",
  
  // 配置 lint 脚本命令
  "lint": "eslint --ext .vue,.ts src/"
},

执行 lint 脚本命令:

npm run lint

呈现了一堆报错:

/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 代码标准,所以会有这个报错提醒,不同的代码标准,内置的查看规定不肯定完全相同。

2.4 主动修复 ESLint 问题

在 scripts 中减少主动修复 ESLint 问题的脚本命令:

"scripts": {
  "dev": "vite",
  "build": "vue-tsc --noEmit && vite build",
  "preview": "vite preview",
  "lint": "eslint --ext .vue,.ts src/",
  
  // 主动修复 ESLint 问题脚本命令
  "lint:fix": "eslint --ext .vue,.ts src/ --fix"
},

执行:

npm run lint:fix

执行主动修复的命令之后,所有分号都加上了,未应用的变量也主动移除了。

再次执行:

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 之后主动执行,生成 `.husky` 目录。"prepare": "husky install"
},
  • 第三步:执行 prepare 脚本
npm run prepare

该命令执行完会在我的项目根目录主动生成 .husky 目录。

  • 第四步:减少 pre-commit 钩子

执行以下命令,会在 .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 钩子拦挡
  • 执行 npx lint-staged 命令
  • 进而执行 eslint –fix 命令,对本次提交批改的代码波及的文件进行代码查看,并主动修复能修复的谬误,不能修复的谬误会提醒进去,只有所有 ESLint 谬误都修复了能力提交胜利

3.2 配置 PR 门禁

如果你在做本人的开源我的项目,并且十分侥幸,有一群气味相投的小伙伴违心一起参加奉献,这时为了对立大家的代码格调,让贡献者们专一于个性开发,不必放心代码格局标准问题,并通过 ESLint 工具提醒贡献者,哪些代码可能带来潜在的危险,你就有必要给提交的 PR 加上 ESLint 门禁。

咱们曾经减少了本地的 ESLint 命令:

"scripts": {"lint": "eslint --ext .vue,.ts src/",},

咱们须要在本目录创立一个 .github/workflows/pull-request.yml 文件,在该文件中写入以下内容:

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 工作流工作,ESLint 查看不通过的话,PR 的 checks 外面会报红,拦挡该 PR 的合入。

PR 的提交者看到报红,也能够点到工作外面去看是哪里报错,批改掉这些 ESLint 问题,PR 就会变绿,我的项目的管理员就能够顺利合入 PR 到指标分支啦🎉

4 常见的 ESLint 问题及修复案例

接下来跟大家分享 Vue DevUI 开源 Vue3 组件库 ESLint 问题修复过程中遇到的典型问题。

4.1 案例 1:warning Unexpected any. Specify a different type @typescript-eslint/no-explicit-any

该问题呈现频率比拟高,起因是有些类型写了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

这也是一个比拟常见的问题,在申明之前应用变量或办法,解决办法也很简略,只须要调整下代码的程序即可,将变量或办法的申明放在调用的语句之前。

比方 Pagination 组件的 pagination.tsx 中:

    // 极简模式下,可选的下拉抉择页码
    const litePageOptions = computed(() =>  liteSelectOptions(totalPages.value));

    // 以后页码
    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);
      }
    });

    // 总页数
    const totalPages = computed(() => Math.ceil(props.total / props.pageSize));

其中的 totalPages 的申明在比拟靠后的地位,然而却在申明之前在 litePageOptionscursor变量中都应用了totalPages,所以提醒 ESLint 问题。

解决的办法就是将 totalPages 的申明放在 litePageOptionscursor之前。

    // 总页数
    const totalPages = computed(() => Math.ceil(props.total / props.pageSize));

    // 极简模式下,可选的下拉抉择页码
    const litePageOptions = computed(() =>  liteSelectOptions(totalPages.value));

    // 以后页码
    const cursor = computed({...});

4.3 案例 3:warning Missing return type on function @typescript-eslint/explicit-module-boundary-types

该问题是因为函数短少返回类型,比方 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 办法的参数问题,docElement用了 any,也缺失了返回类型,docElement 其实就是 document 对象,能够应用 HTMLElement 类型,然而 launchImmersiveFullScreen 这个办法是用来启动沉迷式全屏的,为了实现浏览器兼容,比方应用了 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 参数
    const currentSelectedItem = flatData.filter(({id}) => currentSelected[id] && currentSelected[id] !== 'none');
    ...
  }
}

批改形式就是将其中一个 id 的名字改了,比方把外面的 id 改成 itemId:

const currentSelectedItem = flatData.filter(({id: itemId}) => currentSelected[itemId] && currentSelected[itemId] !== 'none');

欢送在评论区分享你在我的项目中遇到的 ESLint 问题👏👏

5 感激

在 Vue DevUI 组件库 ESLint 问题清零之路上,不得不提的一位小伙伴就是 @linxiang07 同学,他从 2022 年 3 月到 7 月,继续 4 个多月,累计修复 40 多个组件的 100 多个 ESLint 问题,直到 7 月 5 日 ESLint 清零,并给 PR 增加 ESLint 门禁,后续 PR 如果未通过 ESLint 查看将无奈合入。

感激 linxiang 同学的付出!

以下是 linxiang 同学提交的局部 PR:

linxiang 同学也因而成为 Vue DevUI 组件库 TOP3 的贡献者,并成为咱们的 Committer 和管理员🎉🎉

值得一提的是 linxiang 同学还是咱们的 VirtualList 虚构列表组件和 Tree 组件等多个组件的田主和贡献者,并且欠缺了多个组件的单元测试,能力很强,是十分沉闷和踊跃的贡献者。

欢送退出咱们,和优良的开发者一起打造高质量的开源组件库我的项目!感兴趣能够加助手 wx:devui-official

退出移动版