前端工程化之Lint工具链

当下,在项目开发的过程中,前端工程师们越来越离不开构建工具了,可以说构建工具已经成为了前端工程项目的标配。

不过,如今的前端构建工具有很多且更新速度极快,有远古时代的browserifygrunt,有传统的WebpackRollupParcel,也有现代的EsbuildVite 等等,不仅种类繁多,更新也很快。

本文将基于Vite工具的周边Lint工具链进行讲解,帮助大家了解一下什么是前端Lint,以及如何做好前端Lint工作。

Lint工具链主要从如下两个方面入手:

  • 现代化的CSS工程化。
  • 现代化的代码风格和质量检查。

一、现代化的CSS工程化

样式方案的意义

对初学者来说,谈到开发前端的样式,首先想到的便是直接写原生 CSS。但时间一长,难免会发现原生 CSS 开发的各种问题。那么,如果我们不用任何 CSS 工程方案,又会出现哪些问题呢?

  1. 开发体验欠佳。比如原生 CSS 不支持选择器的嵌套:
css 复制代码
// 选择器只能平铺,不能嵌套
.container .header .nav .title .text {
  color: blue;
}

.container .header .nav .box {
  color: blue;
  border: 1px solid grey;
}
  1. 样式污染问题。如果出现同样的类名,很容易造成不同的样式互相覆盖和污染。
css 复制代码
// a.css
.container {
  color: red;
}

// b.css
// 很有可能覆盖 a.css 的样式!
.container {
  color: blue;
}
  1. 浏览器兼容 问题。为了兼容不同的浏览器,我们需要对一些属性(如transition)加上不同的浏览器前缀,比如 -webkit--moz--ms--o-,意味着开发者要针对同一个样式属性写很多的冗余代码。 4. 打包后的代码体积问题。如果不用任何的 CSS 工程化方案,所有的 CSS 代码都将打包到产物中,即使有部分样式并没有在代码中使用,导致产物体积过大。

针对如上原生 CSS 的痛点,社区中诞生了不少解决方案,常见的有 5 类。

  1. CSS 预处理器:主流的包括Sass/ScssLessStylus。这些方案各自定义了一套语法,让 CSS 也能使用嵌套规则,甚至能像编程语言一样定义变量、写条件判断和循环语句,大大增强了样式语言的灵活性,解决原生 CSS 的开发体验问题
  2. CSS Modules:能将 CSS 类名处理成哈希值,这样就可以避免同名的情况下样式污染的问题。
  3. CSS 后处理器PostCSS,用来解析和处理 CSS 代码,可以实现的功能非常丰富,比如将 px 转换为 rem、根据目标浏览器情况自动加上类似于--moz---o-的属性前缀等等。
  4. CSS in JS 方案,主流的包括emotionstyled-components等等,顾名思义,这类方案可以实现直接在 JS 中写样式代码,基本包含CSS 预处理器CSS Modules 的各项优点,非常灵活,解决了开发体验和全局样式污染的问题。
  5. CSS 原子化框架,如Tailwind CSSWindi CSS,通过类名来指定样式,大大简化了样式写法,提高了样式开发的效率,主要解决了原生 CSS 开发体验的问题。

不过,各种方案没有孰优孰劣,各自解决的方案有重叠的部分,但也有一定的差异,大家可以根据自己项目的痛点来引入。接下来,我们进入实战阶段,在 Vite 中应用上述常见的 CSS 方案。

CSS 预处理器

Vite 本身对 CSS 各种预处理器语言(Sass/ScssLessStylus)做了内置支持。也就是说,即使你不经过任何的配置也可以直接使用各种 CSS 预处理器。我们以 Sass/Scss 为例,来具体感受一下 Vite 的零配置给我们带来的便利。

由于 Vite 底层会调用 CSS 预处理器的官方库进行编译,而 Vite 为了实现按需加载,并没有内置这些工具库,而是让用户根据需要安装。因此,我们首先安装 Sass 的官方库,安装命令如下:

css 复制代码
pnpm i sass -D

然后,在上一节初始化后的项目中新建 src/components/Header 目录,并且分别新建index.tsxindex.scss文件,代码如下:

javascript 复制代码
// index.tsx
import './index.scss';
export function Header() {
  return <p className="header">This is Header</p>
};

// index.scss
.header {
  color: red;
}

这样就完成了一个最简单的 demo 组件。接着我们在 App.tsx 应用这个组件:

javascript 复制代码
import { Header } from "./components/Header";

function App() {
  return (
    <div>
      <Header />
    </div>
  );
}

export default App;

现在你可以执行pnpm run dev,然后到浏览器上查看效果:

内容比较简单,如果页面出现红色的文字部分,就说明 scss 文件中的样式已经成功生效。好,现在我们封装一个全局的主题色,新建src/variable.scss文件,内容如下:

css 复制代码
// variable.scss
$theme-color: red;

然后,我们在原来 Header 组件的样式中应用这个变量:

css 复制代码
@import "../../variable";

.header {
  color: $theme-color;
}

回到浏览器访问页面,可以看到样式依然生效。你可能会注意到,每次要使用$theme-color属性的时候我们都需要手动引入variable.scss文件,那有没有自动引入的方案呢?这就需要在 Vite 中进行一些自定义配置了,在配置文件中增加如下的内容:

javascript 复制代码
// vite.config.ts
import { normalizePath } from 'vite';
// 如果类型报错,需要安装 @types/node: pnpm i @types/node -D
import path from 'path';

// 全局 scss 文件的路径
// 用 normalizePath 解决 window 下的路径问题
const variablePath = normalizePath(path.resolve('./src/variable.scss'));


export default defineConfig({
  // css 相关的配置
  css: {
    preprocessorOptions: {
      scss: {
        // additionalData 的内容会在每个 scss 文件的开头自动注入
        additionalData: `@import "${variablePath}";`
      }
    }
  }
})

现在你可以直接在文件中使用全局文件的变量,相当于之前手动引入的方式显然方便了许多:

css 复制代码
.header {
  color: $theme-color;
}

同样的,你可以对 lessstylus进行一些能力的配置,如果有需要你可以去下面的官方文档中查阅更多的配置项:

CSS Modules

CSS Modules 在 Vite 也是一个开箱即用的能力,Vite 会对后缀带有.module的样式文件自动应用 CSS Modules。接下来我们通过一个简单的例子来使用这个功能。

首先,将 Header 组件中的index.scss更名为index.module.scss,然后稍微改动一下index.tsx的内容,如下:

javascript 复制代码
// index.tsx
import styles from './index.module.scss';
export function Header() {
  return <p className={styles.header}>This is Header</p>
};

现在打开浏览器,可以看见 p 标签的类名已经被处理成了哈希值的形式:

说明现在 CSS Modules 已经正式生效了!同样的,你也可以在配置文件中的css.modules选项来配置 CSS Modules 的功能,比如下面这个例子:

arduino 复制代码
// vite.config.ts
export default {
  css: {
    modules: {
      // 一般我们可以通过 generateScopedName 属性来对生成的类名进行自定义
      // 其中,name 表示当前文件名,local 表示类名
      generateScopedName: "[name]__[local]___[hash:base64:5]"
    },
    preprocessorOptions: {
      // 省略预处理器配置
    }

  }
}

再次访问页面,我们可以发现刚才的类名已经变成了我们自定义的形式:

这是一个 CSS Modules 中很常见的配置,对开发时的调试非常有用。其它的一些配置项不太常用,大家可以去这个地址进行查阅。

PostCSS

一般你可以通过 postcss.config.js 来配置 postcss ,不过在 Vite 配置文件中已经提供了 PostCSS 的配置入口,我们可以直接在 Vite 配置文件中进行操作。

首先,我们来安装一个常用的 PostCSS 插件------autoprefixer:

css 复制代码
pnpm i autoprefixer -D

这个插件主要用来自动为不同的目标浏览器添加样式前缀,解决的是浏览器兼容性的问题。接下来让我们在 Vite 中接入这个插件:

javascript 复制代码
// vite.config.ts 增加如下的配置
import autoprefixer from 'autoprefixer';

export default {
  css: {
    // 进行 PostCSS 配置
    postcss: {
      plugins: [
        autoprefixer({
          // 指定目标浏览器
          overrideBrowserslist: ['Chrome > 40', 'ff > 31', 'ie 11']
        })
      ]
    }
  }
}

配置完成后,我们回到 Header 组件的样式文件中添加一个新的 CSS 属性:

xml 复制代码
.header {
  <!-- 前面的样式省略 -->
  text-decoration: dashed;
}

你可以执行pnpm run build命令进行打包,可以看到产物中自动补上了浏览器前缀,如:

xml 复制代码
._header_kcvt0_1 {
  <!-- 前面的样式省略 -->
  -webkit-text-decoration: dashed;
  -moz-text-decoration: dashed;
  text-decoration: dashed;
}

由于有 CSS 代码的 AST (抽象语法树)解析能力,PostCSS 可以做的事情非常多,甚至能实现 CSS 预处理器语法和 CSS Modules,社区当中也有不少的 PostCSS 插件,除了刚刚提到的autoprefixer插件,常见的插件还包括:

  • postcss-pxtorem: 用来将 px 转换为 rem 单位,在适配移动端的场景下很常用。
  • postcss-preset-env: 通过它,你可以编写最新的 CSS 语法,不用担心兼容性问题。
  • cssnano: 主要用来压缩 CSS 代码,跟常规的代码压缩工具不一样,它能做得更加智能,比如提取一些公共样式进行复用、缩短一些常见的属性值等等。

关于 PostCSS 插件,这里还给大家推荐一个站点:www.postcss.parts/ ,你可以去里面探索更多的内容。

CSS In JS

社区中有两款主流的CSS In JS 方案: styled-componentsemotion

对于 CSS In JS 方案,在构建侧我们需要考虑选择器命名问题DCE(Dead Code Elimination 即无用代码删除)、代码压缩生成 SourceMap服务端渲染(SSR)等问题,而styled-componentsemotion已经提供了对应的 babel 插件来解决这些问题,我们在 Vite 中要做的就是集成这些 babel 插件。

具体来说,上述的两种主流 CSS in JS 方案在 Vite 中集成方式如下:

javascript 复制代码
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    react({
      babel: {
        // 加入 babel 插件
        // 以下插件包都需要提前安装
        // 当然,通过这个配置你也可以添加其它的 Babel 插件
        plugins: [
          // 适配 styled-component
          "babel-plugin-styled-components"
          // 适配 emotion
          "@emotion/babel-plugin"
        ]
      },
      // 注意: 对于 emotion,需要单独加上这个配置
      // 通过 `@emotion/react` 包编译 emotion 中的特殊 jsx 语法
      jsxImportSource: "@emotion/react"
    })
  ]
})

CSS 原子化框架

在目前的社区当中,CSS 原子化框架主要包括Tailwind CSSWindi CSS。Windi CSS 作为前者的替换方案,实现了按需生成 CSS 类名的功能,开发环境下的 CSS 产物体积大大减少,速度上比Tailwind CSS v2快 20~100 倍!当然,Tailwind CSS 在 v3 版本也引入 JIT(即时编译) 的功能,解决了开发环境下 CSS 产物体积庞大的问题。接下来我们将这两个方案分别接入到 Vite 中,在实际的项目中你只需要使用其中一种就可以了。我个人比较喜欢 Windi CSS 本身的attributifyshortcuts等独有的特性,因此首先从 windicss 开始说起。

1. Windi CSS 接入

首先安装 windicss 及对应的 Vite 插件:

css 复制代码
pnpm i windicss vite-plugin-windicss -D

随后我们在配置文件中来使用它:

javascript 复制代码
// vite.config.ts
import windi from "vite-plugin-windicss";

export default {
  plugins: [
    // 省略其它插件
    windi()
  ]
}

接着要注意在src/main.tsx中引入一个必需的 import 语句:

arduino 复制代码
// main.tsx
// 用来注入 Windi CSS 所需的样式,一定要加上!
import "virtual:windi.css";

这样我们就完成了 Windi CSS 在 Vite 中的接入,接下来我们在 Header 组件中来测试,组件代码修改如下:

javascript 复制代码
// src/components/Header/index.tsx
import { devDependencies } from "../../../package.json";

export function Header() {
  return (
    <div className="p-20px text-center">
      <h1 className="font-bold text-2xl mb-2">
        vite version: {devDependencies.vite}
      </h1>
    </div>
  );
}

启动项目可以看到如下的效果,说明样式已经正常生效:

除了本身的原子化 CSS 能力,Windi CSS 还有一些非常好用的高级功能,在此我给大家推荐自己常用的两个能力: attributifyshortcuts

要开启这两个功能,我们需要在项目根目录新建windi.config.ts,配置如下:

arduino 复制代码
import { defineConfig } from "vite-plugin-windicss";

export default defineConfig({
  // 开启 attributify
  attributify: true,
});

首先我们来看看attributify,翻译过来就是属性化,也就是说我们可以用 props 的方式去定义样式属性,如下所示:

ini 复制代码
<button 
  bg="blue-400 hover:blue-500 dark:blue-500 dark:hover:blue-600"
  text="sm white"
  font="mono light"
  p="y-2 x-4"
  border="2 rounded blue-200"
>
  Button
</button>

这样的开发方式不仅省去了繁琐的 className 内容,还加强了语义化,让代码更易维护,大大提升了开发体验。

不过使用attributify的时候需要注意类型问题,你需要添加types/shim.d.ts来增加类型声明,以防类型报错:

typescript 复制代码
import { AttributifyAttributes } from 'windicss/types/jsx';

declare module 'react' {
  type HTMLAttributes<T> = AttributifyAttributes;
}

shortcuts 用来封装一系列的原子化能力,尤其是一些常见的类名集合,我们在 windi.config.ts来配置它:

arduino 复制代码
//windi.config.ts
import { defineConfig } from "vite-plugin-windicss";

export default defineConfig({
  attributify: true,
  shortcuts: {
    "flex-c": "flex justify-center items-center",
  }
});

比如这里封装了flex-c的类名,接下来我们可以在业务代码直接使用这个类名:

xml 复制代码
<div className="flex-c"></div>
<!-- 等同于下面这段 -->
<div className="flex justify-center items-center"></div>

如果你也有过 Windi CSS 的开发经历,欢迎把你用到的高级功能分享到评论区,让大家一起来见识见识。

2. Tailwind CSS

接下来我们来接入 Tailwind CSS 方案,为了避免和之前的 Windi CSS 混淆,这里我建议你新起一个 Vite 项目。

小册中对应的 GitHub 代码地址

首先安装 tailwindcss 及其必要的依赖:

复制代码
pnpm install -D tailwindcss postcss autoprefixer

然后新建两个配置文件tailwind.config.jspostcss.config.js:

java 复制代码
// tailwind.config.js
module.exports = {
  content: [
    "./index.html",
    "./src/**/*.{vue,js,ts,jsx,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}

// postcss.config.js
// 从中你可以看到,Tailwind CSS 的编译能力是通过 PostCSS 插件实现的
// 而 Vite 本身内置了 PostCSS,因此可以通过 PostCSS 配置接入 Tailwind CSS 
// 注意: Vite 配置文件中如果有 PostCSS 配置的情况下会覆盖掉 post.config.js 的内容!
module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
}

接着在项目的入口 CSS 中引入必要的样板代码:

less 复制代码
@tailwind base;
@tailwind components;
@tailwind utilities;

现在,你就可以在项目中安心地使用 Tailwind 样式了,如下所示:

javascript 复制代码
// App.tsx

import logo from "./logo.svg";
import "./App.css";

function App() {
  return (
    <div>
      <header className="App-header">
        <img src={logo} className="w-20" alt="logo" />
        <p className="bg-red-400">Hello Vite + React!</p>
      </header>
    </div>
  );
}

export default App;

当你启动项目之后可以看到 Tailwind CSS 的样式已经正常生效:

小结

OK,本小节的内容到这里就结束了。这一节我们完成了脚手架项目样式部分的搭建,你需要重点掌握前端工程中各种样式方案在 Vite 的接入方法。这些样式方案包括,包括CSS 预处理器CSS ModulesPostCSSCSS In JSCSS 原子化框架(Windi CSS)。与此同时,你应该明白了各种样式方案的含义以及背后所解决的问题。接下来,我们将会进入项目规范搭建的部分,让我们下一节再见!

二、现代化的代码风格和质量检查

在真实的工程项目中,尤其是多人协作的场景下,代码规范就变得非常重要了,它可以用来统一团队代码风格,避免不同风格的代码混杂到一起难以阅读,有效提高代码质量 ,甚至可以将一些语法错误 在开发阶段提前规避掉。但仅有规范本身不够,我们需要自动化的工具 (即Lint 工具)来保证规范的落地,把代码规范检查(包括自动修复)这件事情交给机器完成,开发者只需要专注应用逻辑本身。

本节,我们将一起来完成 Lint 工具链在项目中的落地,实现自动化代码规范检查及修复的能力。学完本节内容后,你不仅能熟悉诸如ESLintPrettierStylelintCommitlint 等诸多主流 Lint 工具的概念和使用,还能配合huskylint-stagedVSCode 插件Vite 生态在项目中集成完整的 Lint 工具链,搭建起完整的前端开发和代码提交工作流,这部分内容虽然和 Vite 没有直接的联系,但也是 Vite 项目搭建中非常重要的一环,是前端工程化的必备知识。

小节示例项目仓库: 点击直达

JS/TS 规范工具: ESLint

简介

ESLint 是在 ECMAScript/JavaScript 代码中识别和报告模式匹配的工具,它的目标是保证代码的一致性和避免错误。

Eslint 是国外的前端大牛Nicholas C. Zakas在 2013 年发起的一个开源项目,有一本书被誉为前端界的"圣经",叫《JavaScript 高级程序设计》(即红宝书),他正是这本书的作者。

Nicholas 当初做这个开源项目,就是为了打造一款插件化的 JavaScript 代码静态检查工具,通过解析代码的 AST 来分析代码格式,检查代码的风格和质量问题。现在,Eslint 已经成为一个非常成功的开源项目了,基本上属于前端项目中 Lint 工具的标配。

ESLint 的使用并不复杂,主要通过配置文件对各种代码格式的规则(rules)进行配置,以指定具体的代码规范。目前开源社区也有一些成熟的规范集可供使用,著名的包括Airbnb JavaScript 代码规范Standard JavaScript 规范Google JavaScript 规范等等,你可以在项目中直接使用这些成熟的规范,也可以自己定制一套团队独有的代码规范,这在一些大型团队当中还是很常见的。

初始化

接下来我们来利用 ESLint 官方的 cli 在现有的脚手架项目中进行初始化操作,首先我们需要安装 ESLint:

css 复制代码
pnpm i eslint -D

接着执行 ESLint 的初始化命令,并进行如下的命令行交互:

csharp 复制代码
npx eslint --init

接着 ESLint 会帮我们自动生成.eslintrc.js配置文件。需要注意的是,在上述初始化流程中我们并没有用 npm 安装依赖,需要进行手动安装:

java 复制代码
pnpm i eslint-plugin-react@latest @typescript-eslint/eslint-plugin@latest @typescript-eslint/parser@latest -D

核心配置解读

大家初次接触配置文件可能会有点不太理解,接下来我来为你介绍一下几个核心的配置项,你可以对照目前生成的.eslintrc.js一起学习。

1. parser - 解析器

ESLint 底层默认使用 Espree来进行 AST 解析,这个解析器目前已经基于 Acron 来实现,虽然说 Acron 目前能够解析绝大多数的 ECMAScript 规范的语法,但还是不支持 TypeScript ,因此需要引入其他的解析器完成 TS 的解析。

社区提供了@typescript-eslint/parser这个解决方案,专门为了 TypeScript 的解析而诞生,将 TS 代码转换为 Espree 能够识别的格式(即 Estree 格式),然后在 Eslint 下通过Espree进行格式检查, 以此兼容了 TypeScript 语法。

2. parserOptions - 解析器选项

这个配置可以对上述的解析器进行能力定制,默认情况下 ESLint 支持 ES5 语法,你可以配置这个选项,具体内容如下:

  • ecmaVersion: 这个配置和 AcronecmaVersion 是兼容的,可以配置 ES + 数字(如 ES6)或者ES + 年份(如 ES2015),也可以直接配置为latest,启用最新的 ES 语法。
  • sourceType: 默认为script,如果使用 ES Module 则应设置为module
  • ecmaFeatures: 为一个对象,表示想使用的额外语言特性,如开启 jsx
3. rules - 具体代码规则

rules 配置即代表在 ESLint 中手动调整哪些代码规则,比如禁止在 if 语句中使用赋值语句这条规则可以像如下的方式配置:

java 复制代码
// .eslintrc.js
module.exports = {
  // 其它配置省略
  rules: {
    // key 为规则名,value 配置内容
    "no-cond-assign": ["error", "always"]
  }
}

在 rules 对象中,key 一般为规则名value 为具体的配置内容,在上述的例子中我们设置为一个数组,数组第一项为规则的 ID,第二项为规则的配置

这里重点说一说规则的 ID,它的语法对所有规则都适用,你可以设置以下的值:

  • off0: 表示关闭规则。
  • warn1: 表示开启规则,不过违背规则后只抛出 warning,而不会导致程序退出。
  • error2: 表示开启规则,不过违背规则后抛出 error,程序会退出。

具体的规则配置可能会不一样,有的是一个字符串,有的可以配置一个对象,你可以参考 ESLint 官方文档

当然,你也能直接将 rules 对象的 value 配置成 ID,如: "no-cond-assign": "error"

4. plugins

上面提到过 ESLint 的 parser 基于Acorn实现,不能直接解析 TypeScript,需要我们指定 parser 选项为@typescript-eslint/parser才能兼容 TS 的解析。同理,ESLint 本身也没有内置 TypeScript 的代码规则,这个时候 ESLint 的插件系统就派上用场了。我们需要通过添加 ESLint 插件来增加一些特定的规则,比如添加@typescript-eslint/eslint-plugin 来拓展一些关于 TS 代码的规则,如下代码所示:

java 复制代码
// .eslintrc.js
module.exports = {
  // 添加 TS 规则,可省略`eslint-plugin`
  plugins: ['@typescript-eslint']
}

值得注意的是,添加插件后只是拓展了 ESLint 本身的规则集,但 ESLint 默认并没有开启这些规则的校验!如果要开启或者调整这些规则,你需要在 rules 中进行配置,如:

java 复制代码
// .eslintrc.js
module.exports = {
  // 开启一些 TS 规则
  rules: {
    '@typescript-eslint/ban-ts-comment': 'error',
    '@typescript-eslint/no-explicit-any': 'warn',
  }
}
5. extends - 继承配置

extends 相当于继承另外一份 ESLint 配置,可以配置为一个字符串,也可以配置成一个字符串数组。主要分如下 3 种情况:

  1. 从 ESLint 本身继承;
  2. 从类似 eslint-config-xxx 的 npm 包继承;
  3. 从 ESLint 插件继承。
java 复制代码
// .eslintrc.js
module.exports = {
   "extends": [
     // 第1种情况 
     "eslint:recommended",
     // 第2种情况,一般配置的时候可以省略 `eslint-config`
     "standard"
     // 第3种情况,可以省略包名中的 `eslint-plugin`
     // 格式一般为: `plugin:${pluginName}/${configName}`
     "plugin:react/recommended"
     "plugin:@typescript-eslint/recommended",
   ]
}

有了 extends 的配置,对于之前所说的 ESLint 插件中的繁多配置,我们就不需要手动一一开启了,通过 extends 字段即可自动开启插件中的推荐规则:

perl 复制代码
extends: ["plugin:@typescript-eslint/recommended"]
6. env 和 globals

这两个配置分别表示运行环境全局变量,在指定的运行环境中会预设一些全局变量,比如:

arduino 复制代码
// .eslint.js
module.export = {
  "env": {
    "browser": "true",
    "node": "true"
  }
}

指定上述的 env 配置后便会启用浏览器和 Node.js 环境,这两个环境中的一些全局变量(如 windowglobal 等)会同时启用。

有些全局变量是业务代码引入的第三方库所声明,这里就需要在globals配置中声明全局变量了。每个全局变量的配置值有 3 种情况:

  1. "writable"或者 true,表示变量可重写;
  2. "readonly"或者false,表示变量不可重写;
  3. "off",表示禁用该全局变量。

jquery举例,我们可以在配置文件中声明如下:

java 复制代码
// .eslintrc.js
module.exports = {
  "globals": {
    // 不可重写
    "$": false, 
    "jQuery": false 
  }
}

相信有了上述核心配置部分的讲解,你再回头看看初始化生成的 ESLint 配置文件,你也能很好地理解各个配置项的含义了。

与 Prettier 强强联合

虽然 ESLint 本身具备自动格式化代码的功能(eslint --fix),但术业有专攻,ESLint 的主要优势在于代码的风格检查并给出提示,而在代码格式化这一块 Prettier 做的更加专业,因此我们经常将 ESLint 结合 Prettier 一起使用。

首先我们来安装一下 Prettier:

css 复制代码
pnpm i prettier -D

在项目根目录新建.prettierrc.js配置文件,填写如下的配置内容:

java 复制代码
// .prettierrc.js
module.exports = {
  printWidth: 80, //一行的字符数,如果超过会进行换行,默认为80
  tabWidth: 2, // 一个 tab 代表几个空格数,默认为 2 个
  useTabs: false, //是否使用 tab 进行缩进,默认为false,表示用空格进行缩减
  singleQuote: true, // 字符串是否使用单引号,默认为 false,使用双引号
  semi: true, // 行尾是否使用分号,默认为true
  trailingComma: "none", // 是否使用尾逗号
  bracketSpacing: true // 对象大括号直接是否有空格,默认为 true,效果:{ a: 1 }
};

接下来我们将Prettier集成到现有的ESLint工具中,首先安装两个工具包:

arduino 复制代码
pnpm i eslint-config-prettier eslint-plugin-prettier -D

其中eslint-config-prettier用来覆盖 ESLint 本身的规则配置,而eslint-plugin-prettier则是用于让 Prettier 来接管eslint --fix即修复代码的能力。

.eslintrc.js 配置文件中接入 prettier 的相关工具链,最终的配置代码如下所示,你可以直接粘贴过去:

java 复制代码
// .eslintrc.js
module.exports = {
  env: {
    browser: true,
    es2021: true
  },
  extends: [
    "eslint:recommended",
    "plugin:react/recommended",
    "plugin:@typescript-eslint/recommended",
    // 1. 接入 prettier 的规则
    "prettier",
    "plugin:prettier/recommended"
  ],
  parser: "@typescript-eslint/parser",
  parserOptions: {
    ecmaFeatures: {
      jsx: true
    },
    ecmaVersion: "latest",
    sourceType: "module"
  },
  // 2. 加入 prettier 的 eslint 插件
  plugins: ["react", "@typescript-eslint", "prettier"],
  rules: {
    // 3. 注意要加上这一句,开启 prettier 自动修复的功能
    "prettier/prettier": "error",
    quotes: ["error", "single"],
    semi: ["error", "always"],
    "react/react-in-jsx-scope": "off"
  }
};

OK,现在我们回到项目中来见证一下ESLint + Prettier强强联合的威力,在 package.json 中定义一个脚本:

json 复制代码
{
  "scripts": {
    // 省略已有 script
    "lint:script": "eslint --ext .js,.jsx,.ts,.tsx --fix --quiet ./src",
  }
}

接下来在命令行终端执行:

arduino 复制代码
pnpm run lint:script

这样我们就完成了 ESLint 的规则检查以及 Prettier 的自动修复。不过每次执行这个命令未免会有些繁琐,我们可以在VSCode中安装ESLintPrettier这两个插件,并且在设置区中开启Format On Save:

接下来在你按Ctrl + S保存代码的时候,Prettier 便会自动帮忙修复代码格式。

在 Vite 中接入 ESLint

除了安装编辑器插件,我们也可以通过 Vite 插件的方式在开发阶段进行 ESLint 扫描,以命令行的方式展示出代码中的规范问题,并能够直接定位到原文件。

首先我们安装 Vite 中的 ESLint 插件:

css 复制代码
pnpm i vite-plugin-eslint -D

然后在 vite.config.ts 中接入:

javascript 复制代码
// vite.config.ts
import viteEslint from 'vite-plugin-eslint';

// 具体配置
{
  plugins: [
    // 省略其它插件
    viteEslint(),
  ]
}

现在你可以试着重新启动项目, ESLint 的错误已经能够及时显示到命令行窗口中了。

由于这个插件采用另一个进程来运行 ESLint 的扫描工作,因此不会影响 Vite 项目的启动速度,这个大家不用担心。

样式规范工具: Stylelint

接下来我们进入Stylelint的部分,先来看看官方的定义:

Stylelint,一个强大的现代化样式 Lint 工具,用来帮助你避免语法错误和统一代码风格。

Stylelint 主要专注于样式代码的规范检查,内置了 170 多个 CSS 书写规则 ,支持 CSS 预处理器 (如 Sass、Less),提供插件化机制 以供开发者扩展规则,已经被 Google、Github 等大型团队投入使用。与 ESLint 类似,在规范检查方面,Stylelint 已经做的足够专业,而在代码格式化方面,我们仍然需要结合 Prettier 一起来使用。

首先让我们来安装 Stylelint 以及相应的工具套件:

arduino 复制代码
pnpm i stylelint stylelint-prettier stylelint-config-prettier stylelint-config-recess-order stylelint-config-standard stylelint-config-standard-scss -D

然后,我们在 Stylelint 的配置文件.stylelintrc.js中一一使用这些工具套件:

java 复制代码
// .stylelintrc.js
module.exports = {
  // 注册 stylelint 的 prettier 插件
  plugins: ['stylelint-prettier'],
  // 继承一系列规则集合
  extends: [
    // standard 规则集合
    'stylelint-config-standard',
    // standard 规则集合的 scss 版本
    'stylelint-config-standard-scss',
    // 样式属性顺序规则
    'stylelint-config-recess-order',
    // 接入 Prettier 规则
    'stylelint-config-prettier',
    'stylelint-prettier/recommended'
  ],
  // 配置 rules
  rules: {
    // 开启 Prettier 自动格式化功能
    'prettier/prettier': true
  }
};

可以发现 Stylelint 的配置文件和 ESLint 还是非常相似的,常用的pluginsextendsrules属性在 ESLint 同样存在,并且与 ESLint 中这三个属性的功能也基本相同。不过需要强调的是在 Stylelint 中 rules 的配置会和 ESLint 有些区别,对于每个具体的 rule 会有三种配置方式:

  • null,表示关闭规则。
  • 一个简单值(如 true,字符串,根据不同规则有所不同),表示开启规则,但并不做过多的定制。
  • 一个数组,包含两个元素,即[简单值,自定义配置],第一个元素通常为一个简单值,第二个元素用来进行更精细化的规则配置。

接下来我们将 Stylelint 集成到项目中,回到 package.json 中,增加如下的 scripts 配置:

json 复制代码
{
  "scripts": {
    // 整合 lint 命令
    "lint": "npm run lint:script && npm run lint:style",
    // stylelint 命令
    "lint:style": "stylelint --fix "src/**/*.{css,scss}""
  }
}

执行pnpm run lint:style即可完成样式代码的规范检查和自动格式化。当然,你也可以在 VSCode 中安装Stylelint插件,这样能够在开发阶段即时感知到代码格式问题,提前进行修复。

当然,我们也可以直接在 Vite 中集成 Stylelint。社区中提供了 Stylelint 的 Vite 插件,实现在项目开发阶段提前暴露出样式代码的规范问题。我们来安装一下这个插件:

bash 复制代码
# Vite 2.x
pnpm i @amatlash/vite-plugin-stylelint -D

# Vite 3.x 及以后的版本
pnpm i vite-plugin-stylelint -D

然后在 Vite 配置文件中添加如下的内容:

javascript 复制代码
import viteStylelint from '@amatlash/vite-plugin-stylelint';
// 注意: Vite 3.x 以及以后的版本需要引入 vite-plugin-stylelint

// 具体配置
{
  plugins: [
    // 省略其它插件
    viteStylelint({
      // 对某些文件排除检查
      exclude: /windicss|node_modules/
    }),
  ]
}

接下来,你就可以在命令行界面看到对应的 Stylelint 提示了:

Husky + lint-staged 的 Git 提交工作流集成

提交前的代码 Lint 检查

在上文中我们提到了安装 ESLintPrettierStylelint的 VSCode 插件或者 Vite 插件,在开发阶段提前规避掉代码格式的问题,但实际上这也只是将问题提前暴露,并不能保证规范问题能完全被解决,还是可能导致线上的代码出现不符合规范的情况。那么如何来避免这类问题呢?

我们可以在代码提交的时候进行卡点检查,也就是拦截 git commit 命令,进行代码格式检查,只有确保通过格式检查才允许正常提交代码。社区中已经有了对应的工具------Husky来完成这件事情,让我们来安装一下这个工具:

css 复制代码
pnpm i husky -D

值得提醒的是,有很多人推荐在package.json中配置 husky 的钩子:

json 复制代码
// package.json
{
  "husky": {
    "pre-commit": "npm run lint"
  }
}

这种做法在 Husky 4.x 及以下版本没问题,而在最新版本(7.x 版本)中是无效的!在新版 Husky 版本中,我们需要做如下的事情:

  1. 初始化 Husky: npx husky install,并将 husky install作为项目启动前脚本,如:
json 复制代码
{
  "scripts": {
    // 会在安装 npm 依赖后自动执行
    "prepare": "husky install"
  }
}
  1. 添加 Husky 钩子,在终端执行如下命令:
sql 复制代码
npx husky add .husky/pre-commit "npm run lint"

接着你将会在项目根目录的.husky目录中看到名为pre-commit的文件,里面包含了 git commit前要执行的脚本。现在,当你执行 git commit 的时候,会首先执行 npm run lint脚本,通过 Lint 检查后才会正式提交代码记录。

不过,刚才我们直接在 Husky 的钩子中执行 npm run lint,这会产生一个额外的问题: Husky 中每次执行npm run lint都对仓库中的代码进行全量检查,也就是说,即使某些文件并没有改动,也会走一次 Lint 检查,当项目代码越来越多的时候,提交的过程会越来越慢,影响开发体验。

lint-staged就是用来解决上述全量扫描问题的,可以实现只对存入暂存区的文件进行 Lint 检查,大大提高了提交代码的效率。首先,让我们安装一下对应的 npm 包:

css 复制代码
pnpm i -D lint-staged

然后在 package.json中添加如下的配置:

json 复制代码
{
  "lint-staged": {
    "**/*.{js,jsx,tsx,ts}": [
      "npm run lint:script",
      "git add ."
    ],
    "**/*.{scss}": [
      "npm run lint:style",
      "git add ."
    ]
  }
}

接下来我们需要在 Husky 中应用lint-stage,回到.husky/pre-commit脚本中,将原来的npm run lint换成如下脚本:

css 复制代码
npx --no -- lint-staged

如此一来,我们便实现了提交代码时的增量 Lint 检查

提交时的 commit 信息规范

除了代码规范检查之后,Git 提交信息的规范也是不容忽视的一个环节,规范的 commit 信息能够方便团队协作和问题定位。首先我们来安装一下需要的工具库,执行如下的命令:

bash 复制代码
pnpm i commitlint @commitlint/cli @commitlint/config-conventional -D

接下来新建.commitlintrc.js

ini 复制代码
// .commitlintrc.js
module.exports = {
  extends: ["@commitlint/config-conventional"]
};

一般我们直接使用@commitlint/config-conventional规范集就可以了,它所规定的 commit 信息一般由两个部分: typesubject 组成,结构如下:

go 复制代码
// type 指提交的类型
// subject 指提交的摘要信息
<type>: <subject>

常用的 type 值包括如下:

  • feat: 添加新功能。
  • fix: 修复 Bug。
  • chore: 一些不影响功能的更改。
  • docs: 专指文档的修改。
  • perf: 性能方面的优化。
  • refactor: 代码重构。
  • test: 添加一些测试代码等等。

接下来我们将commitlint的功能集成到 Husky 的钩子当中,在终端执行如下命令即可:

bash 复制代码
npx husky add .husky/commit-msg "npx --no-install commitlint -e $HUSKY_GIT_PARAMS"

你可以发现在.husky目录下多出了commit-msg脚本文件,表示commitlint命令已经成功接入到 husky 的钩子当中。现在我们可以尝试对代码进行提交,假如输入一个错误的 commit 信息,commitlint 会自动抛出错误并退出:

至此,我们便完成了 Git 提交信息的卡点扫描和规范检查。

小结

恭喜你,学完了本节的内容。本小节你应该了解前端的自动化代码规范工具的使用 以及在 Vite 中的接入方法

我主要给你介绍了 3 个方面的自动化代码规范工具:

  1. JavaScript/TypeScript 规范。主流的 Lint 工具包括 EslintPrettier
  2. 样式开发规范。主流的 Lint 工具包括StylelintPrettier
  3. Git 提交规范。主流的 Lint 工具包括Commitlint

我们可以通过编辑器的插件或者 Vite 插件在开发阶段暴露出规范问题,但也无法保证这类问题在开发时完全被解决掉,因此我们尝试在代码提交阶段来解决这个问题,通过Husky+lint-staged成功地拦截 git commit过程,只有在各项 Lint 检查通过后才能正常提交代码,这样就有效提高了线上代码和 Git 提交信息的质量。

相关推荐
猫猫不是喵喵.21 分钟前
Vue3 Props 属性
前端·javascript·vue.js
醉城夜风~1 小时前
CSS元素显示模式(display)
前端·css
AI大模型-小华2 小时前
Codex 三方充值快速入门指南
java·前端·数据库·chatgpt·ai编程·codex·chatgpt pro
做前端的娜娜子4 小时前
同一链接实现 PC Web 与移动 H5 自适应
前端·掘金·金石计划
小帅不太帅4 小时前
架构没变、规模没变,DeepSeek V4 Flash 正式版凭什么暴涨 47 分?
前端·aigc·deepseek
jarvisuni5 小时前
DeepSeekFlash前端依旧拉垮,而且变慢了很多!
前端·javascript·算法
卷福同学6 小时前
AI编程出海第二步:验证关键词能否做站
前端·人工智能·后端
赵庆明老师6 小时前
Vben精讲:21-详解web-antd:tsconfig.json
前端·json·vim
wc887 小时前
微软EDGE浏览器功能学习
前端·学习·edge