头图

1 What is ESLint

ESLint is a plug-in JavaScript/JSX code inspection tool for detecting and fixing problems in JavaScript code, with the goal of making the code more consistent and avoiding errors.

2 Introducing ESLint into the Vue project

The Vue2 project built with Vue CLI already comes with ESLint, so I won't go into details. Let's see how to introduce ESLint into the Vue3 project built by Vite.

Build a Vue3 project with the following command:

 npm create vite@latest vue3-project

After creation, start up:

 npm i
npm run dev

The effect is as follows:

image.png

2.1 Introducing ESLint

Execute the following command:

 npm init @eslint/config

Enter the interactive interface, which can be selected by the up and down arrow keys, and confirmed by pressing the Enter key.

The first question is:

  • What do you want to use ESLint for?
  • We choose the most comprehensive one: check syntax, spot problems, and enforce uniform code style
 $ 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

The second question is:

  • What module system is your project using?
  • Because it is running on the browser side, select ESModule
 ? What type of modules does your project use? … 
❯ JavaScript modules (import/export)
  CommonJS (require/exports)
  None of these

The third question is:

  • What framework are you using? (There is no Angular)
  • Select Vue
 ? Which framework does your project use? … 
  React
❯ Vue.js
  None of these

The fourth question is:

  • Are you using TypeScript?
  • Select Yes
 ? Does your project use TypeScript? › No / Yes

The fifth question is:

  • What environment is your code running in? (This can be multiple choice)
  • Select Browser Browser Environment
 ? Where does your code run? …  (Press <space> to select, <a> to toggle all, <i> to invert selection)
✔ Browser
✔ Node

The sixth question is:

  • What code style do you want to define?
  • Choose to use a popular coding style
 ? How would you like to define a style for your project? … 
❯ Use a popular style guide
  Answer questions about your style

The seventh question is:

  • Which style do you want to use?
  • Airbnb are many people who use it, so choose this one
 ? 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

The eighth question is:

  • What format is the configuration file in?
  • Just choose JavaScript (Generate eslintrc.js file)
 ? What format do you want your config file to be in? … 
❯ JavaScript
  YAML
  JSON

Finish! Isn't it super easy!

Take a look at what configurations we have chosen:

 ✔ 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

It mainly installs the following dependencies for us:

  • 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

and generated a eslintrc.cjs configuration file:

 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 configuration

2.3 Perform ESLint code inspection

Configure the lint script command in scripts in the package.json file:

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

Execute the lint script command:

 npm run lint

There are a bunch of errors:

 /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.

Most of them say that there is no semicolon at the end of the sentence, because we chose the Airbnb code specification, so there will be this error message, different code specifications, the built-in inspection rules are not necessarily the same.

2.4 Automatically fix ESLint issues

Add script commands to automatically fix ESLint problems in scripts:

 "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"
},

implement:

 npm run lint:fix

After executing the autofix command, all semicolons are added and unused variables are automatically removed.

Execute again:

 npm run lint

No more errors.

3 Configure husky and PR access control

3.1 Configure husky access control

To ensure that the code passes ESLint checks before each commit (git commit), we add a pre-commit gate.

  • Step 1: Install husky and lint-staged
 npm i lint-staged husky -D
  • Step 2: Add prepare script command to scripts in package.json
 "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"
},
  • Step 3: Execute the prepare script
 npm run prepare

After the command is executed, the .husky directory will be automatically generated in the project root directory.

  • Step 4: Add a pre-commit hook

Execute the following command, the .husky directory will automatically generate the pre-commit file hook.

 npx husky add .husky/pre-commit "npx lint-staged"
  • Step 5: Add lint-staged configuration
 "lint-staged": {
  "src/**/*.{vue,ts}": "eslint --fix"
},

Through the above five steps, every time you use the git commit command to submit the submitted code, it will:

  • Intercepted by pre-commit hook
  • Execute the npx lint-staged command
  • Then execute the eslint --fix command to check the files involved in the submitted modified code, and automatically repair the errors that can be repaired. The errors that cannot be repaired will be prompted. Only when all ESLint errors are repaired can the submission be successful.

3.2 Configure PR access control

If you are working on your own open source project, and you are very lucky, there are a group of like-minded partners who are willing to contribute together. At this time, in order to unify everyone's code style, let the contributors focus on feature development, without worrying about the code format specification, and With the ESLint tool prompting contributors, which code may pose potential risks, it is necessary for you to add ESLint gate to the submitted PR.

We've added native ESLint commands:

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

We need to create a .github/workflows/pull-request.yml file in this directory, and write the following in the file:

 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

In this way, as long as the PR is merged into the dev or main branch, the Github Actions workflow task will be run again. If the ESLint check fails, the PR's checks will be reported as red, and the merge of the PR will be blocked.

The submitter of the PR sees the red report, and can also click on the task to see where the error is reported. If these ESLint problems are corrected, the PR will turn green, and the project administrator can successfully merge the PR into the target branch 🎉

4 Common ESLint Problems and Repair Cases

Next, I will share with you the typical problems encountered during the repair process of ESLint, the open source Vue3 component library of Vue DevUI .

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

The frequency of this problem is relatively high, because some types are written any , which requires a clear type.

For example, in the unit test file of the Pagination component 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,>');

Among them ele: any belong to this kind of problem.

The solution is to add a clear type to ele , and see that the logic is <button> element, because it is a package element of @vue/test-utils library, so it needs to wrap a layer DOMWrapper :

 import { DOMWrapper } from '@vue/test-utils';

expect(btns.map((ele:  DOMWrapper<Element>) => ele.text()).join()).toEqual('<,1,...,4,5,6,...,16,>');

4.2 Case 2: 'xxx' was used before it was defined no-use-before-define

This is also a relatively common problem. If you use a variable or method before the declaration, the solution is also very simple. You only need to adjust the order of the code, and put the declaration of the variable or method before the calling statement.

For example, in the pagination.tsx of the Pagination component:

 // 极简模式下,可选的下拉选择页码
    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));

Among them, the declaration of totalPages is in a later position, but before the declaration, it is used in the totalPages litePageOptions and cursor variables totalPages , so prompt ESLint problem.

The solution is to put the declaration of totalPages 7ef2e70325d54a9331c0ffd80309b5ac--- before litePageOptions and cursor .

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

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

    // 当前页码
    const cursor = computed({ ... });

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

The problem is because the function lacks a return type, such as in the Fullscreen component utils.ts document launchImmersiveFullScreen method:

 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方法的参数问题, docElementany ,也缺失了返回类型, docElement其实就是document对象, HTMLElement类型, launchImmersiveFullScreen是用来启动沉浸式全屏的,为了实现浏览器兼容, docElement.mozRequestFullScreen with Firefox, and these methods are not available in HTMLElement, and will report a TS type error, so some modifications are required.

 interface CompatibleHTMLElement extends HTMLElement {
  mozRequestFullScreen?: () => void;
  webkitRequestFullScreen?: () => void;
  msRequestFullscreen?: () => void;
}

A type of CompatibleHTMLElement is defined here, which inherits HTMLElement and adds some custom methods.

 export const launchImmersiveFullScreen = async (docElement: CompatibleHTMLElement) => {
  ...
}

Let's take a look at the return type of the method launchImmersiveFullScreen .

 return await fullscreenLaunch.then(() => !!document.fullscreenElement);

This method returns a Promise object, its type is a generic type, we need to pass in a specific type:

 export const launchImmersiveFullScreen = async (docElement: CompatibleHTMLElement): Promise<boolean> => {
  ...
  return await fullscreenLaunch.then(() => !!document.fullscreenElement);
};

4.4 Case 4: 'xxx' is already declared in the upper scope @typescript-eslint/no-shadow

This problem is due to the same variable name defined in nested scopes, such as in the use-checked.ts file of the Tree component:

 export default function useChecked(...) {
  const onNodeClick = (item: TreeItem) => {
    // 这里定义了 id 变量
    const { id } = item;
    ...
    filter 里面又定义了一个 id 参数
    const currentSelectedItem = flatData.filter(({ id }) => currentSelected[id] && currentSelected[id] !== 'none');
    ...
  }
}

The modification method is to change the name of one of the ids, for example, change the id inside to itemId:

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

Welcome to share your problems in the project ESLint problems in the comment area👏👏

5 thanks

On the road to clearing the ESLint problem in the Vue DevUI component library, a friend I have to mention is @linxiang07 , who has repaired more than 100 components of more than 40 components for more than 4 months from March to July 2022. This is an ESLint issue. Until July 5th, ESLint will be cleared and ESLint access control will be added to the PR. If the subsequent PR does not pass the ESLint check, it will not be able to be merged.

Thank you linxiang for your contributions!

image.png

The following is part of the PR submitted by the classmates linxiang :

image.png

linxiang The classmate also became the contributor of Vue DevUI component library TOP3, and became our Committer and administrator🎉🎉

image.png

It is worth mentioning that linxiang the classmate is still our VirtualList virtual list component and Tree component and other components of the owner and contributor, and has improved a lot Unit testing of each component, very capable, and a very active and active contributor.

Welcome to join us and create high-quality open source component library projects with excellent developers! If you are interested, you can add assistant wx: devui-official


DevUI团队
714 声望810 粉丝

DevUI,致力于打造业界领先的企业级UI组件库。[链接]