【每日一拳】打在 vue3+vite 新建项目添加 Prettier+ESLint+StyleLint+husky+Commitlint 等配置实践上

638 阅读13分钟

前言

众所周知,为了保证一个项目长久稳定的发展,前端工程代码规范代码的建设是非常重要的。

之前做的项目都没有一套好的规范去规范和统一我们的代码。正好最近接触了一个 vue3 + vite 的新项目,也想着趁这个机会好好学习用各种工具实现项目代码的规范化。

创建项目

首先使用 vite 初始化一个 vue3 新项目。

创建项目.jpg

配置 Prettier

Prettier 到底是什么,为什么要使用它呢,下面是 官网 的一段文字说明。简而言之,就是让我们的项目保持统一代码风格,便于多人协作开发。

Prettier.jpg

安装 prettier

pnpm install prettier -D

添加配置文件

在项目的根目录下创建一个 prettier 的配置文件 .prettierrc.js,并添加一些简单配置项,至于更多的配置可以详见官网。

module.exports = {
	// 指定每行的最大长度
	printWidth: 100,
	// 指定每个缩进级别的空格数
	tabWidth: 2,
	// 使用制表符而不是空格缩进行
	useTabs: true,
	// 在语句末尾使用分号(true有,false没有)
	semi: true,
	// 使用单引号代替双引号(true单双引号,false双引号)
	singleQuote: false,
	// 更改引用对象属性的时间 可选值"<as-needed|consistent|preserve>"
	quoteProps: "as-needed",
  // 在JSX中使用单引号而不是双引号
  jsxSingleQuote: false,
	// 多行时尽可能打印尾随逗号。(例如,单行数组永远不会出现逗号结尾。) 可选值"<none|es5|all>",默认none
	trailingComma: "none",
  // 在对象,数组括号与文字之间加空格 "{ foo: bar }"
	bracketSpacing: true,
	//  (x) => {} 箭头函数参数只有一个时是否要有小括号。avoid:省略括号 ,always:不省略括号
	arrowParens: "avoid",
};

当然为了加速编译,我们也可以创建一个 .prettierignore 文件来告诉编译器,哪些文件不需要格式化。

/dist/*
.local
.output.js
/node_modules/**

**/*.svg
**/*.sh

/public/*

安装插件

要想在 vscode 中更好的使用,可以安装 Prettier - Code formatter 这个插件插件,使用的人数还是很多的。 vscode插件.jpg

在根目录下的 .vscode 文件夹下创建一个 settings.json 文件,并添加下面代码,就可以让我们在保存代码的同时自动按照配置规则格式化代码。

{
  "editor.formatOnSave": true,
  "editor.defaultFormatter": "esbenp.prettier-vscode",
}

添加 scripts 命令

在根目录下的 package.json 中添加 scripts 命令

"scripts": {
    "lint:prettier": "prettier --write  \"src/**/*.{js,ts,json,tsx,css,less,scss,vue,html,md}\""
},

运行该命令 lint:prettier 就可以让指定的文件都按照 prettier 的配置规则文件格式化代码。

pnpm lint:prettier

如果运行命令后有报错如下图,可以把 package.json 里面的 "type": "module", 删了再重新运行。设置了"type": "module" 后你的所有 js 文件默认使用 ESM 模块规范,不支持 commonjs 规范了就会报错。

格式化报错.jpg

重新运行命令,就会发现整个项目文件都按照指定规则格式化了。

格式化代码.jpg

配置 ESLint

eslint 主要用于检测 JavaScript 的代码语法错误等,保证代码的可读性,以防止不必要的 bug 生成。

安装和初始化 eslint

pnpm install eslint -D

pnpm eslint --init

初始化eslint.jpg

初始化之后会在根目录生成一个 .eslintrc.js 文件,然后在 env 对象中增加一个 node: true 的配置,用来解决文件中 module.exports 的报错。

module.exports = {
    "env": {
        "browser": true,
        "es2021": true,
        "node": true,
    },
    "extends": [
        "eslint:recommended",
        "plugin:vue/vue3-essential",
        "plugin:@typescript-eslint/recommended"
    ],
    "overrides": [],
    "parser": "@typescript-eslint/parser",
    "parserOptions": {
        "ecmaVersion": "latest",
        "sourceType": "module"
    },
    "plugins": [
        "vue",
        "@typescript-eslint"
    ],
    "rules": {}
}

我们也可以创建一个 .eslintignore 文件来告诉编译器,哪些文件不需要花时间去校验。

*.sh
node_modules
*.md
*.woff
*.ttf
.vscode
.idea
dist
/public
/docs
.husky
.local
/bin
.eslintrc.js
.prettierrc.js
/src/mock/*

安装插件

同样的要想在 vscode 中更好的使用,可以安装 Eslint 插件,并在 .vscode 文件夹下的 settings.json 文件中添加下面代码,就可以让我们在保存代码的同时自动按照配置规则校验令和修复代码的错误。

ESlint插件.jpg

{
  "editor.codeActionsOnSave": {
    "source.fixAll": false,
    "source.fixAll.eslint": true
  }
}

添加 scripts 命令

package.json 中添加 scripts 命令

"scripts": {
    "lint:eslint": "eslint \"{src,mock,build}/**/*.{vue,js,ts,tsx}\" --fix",
 },

运行该命令 lint:eslint 就可以让指定的文件都按照 eslint 的配置规则文件进行错误检验。

pnpm lint:eslint

但是当我们运行命令会发现代码有报错,同时 vue 文件中也会有错误提示。这是因为 .eslintrc.js 文件中的 @typescript-eslint/parservue-eslint-parser 冲突了,@typescript-eslint 覆盖了解析 .vue 文件的配置,造成解析报错。

eslint报错.jpg

修改 .eslintrc.js 的配置如下图:

module.exports = {
    "env": {
        "browser": true,
        "es2021": true,
        "node": true,
    },
    "extends": [
        "eslint:recommended",
        "plugin:vue/vue3-essential",
        "plugin:@typescript-eslint/recommended"
    ],
    "overrides": [],
    "parser": "vue-eslint-parser",
    "parserOptions": {
        "ecmaVersion": "latest",
        "sourceType": "module",
        "parser": "@typescript-eslint/parser"
    },
    "plugins": [
        "vue",
        "@typescript-eslint"
    ],
    "rules": {}
}

重新运行命令,前面的 vue 文件的错误就没啦。但是依旧会有 TypeScript 报错,这是因为使用 TypeScript 时禁止使用 any 和 {} 等特定类型,需要关闭 TypeScript 相关配置,同样需要在 .eslintrc.js 文件的 rules 对象中配置规则。

ts报错.jpg

module.exports = {
    ……
    "rules": {
        "@typescript-eslint/no-explicit-any": "off", // 禁止使用 any 类型
        "@typescript-eslint/ban-types": "off", // 禁止使用特定类型
    }
}

此时,再执行 pnpm lint:eslint,就会发现没有报错,所有校验都通过了。

解决 Prettier 和 ESLint 的冲突

通常代码的检查工作是交给 eslint 和 prettier 共同完成的,其中 eslint 主要是完成对于代码语法的检查工作,而 prettier 主要专注于代码格式规范化。

但是当同时开启了自动化的 eslint 修复和自动化的 prettier 格式化,会发生 eslint 的部分规则 与 prettier 相冲突的问题,所以可以添加依赖来解决 prettier 与 eslint 的规则冲突,而 prettier 也会以 eslint 插件的形式使用。

  1. 安装依赖

    pnpm install eslint-config-prettier eslint-plugin-prettier -D
    
    • eslint-config-prettier:关掉所有和 Prettier 冲突的 eslint 的配置
    • eslint-plugin-prettier:将 Prettier 的 rules 以插件的形式加入到 eslint 里面,解决 prettier 与 eslint 规则冲突
  2. 修改 .eslintrc.json 文件

    { 
        extends: [
        'eslint:recommended',
        'plugin:vue/vue3-essential',
        'plugin:@typescript-eslint/recommended',
         // 新增,必须放在最后面
        'plugin:prettier/recommended' 
      ],
    }
    

配置 StyleLint

stylelint 可以用来帮助我们检查代码中 css 的语法的错误问题等。

安装 StyleLint

pnpm i stylelint postcss postcss-html stylelint-config-standard stylelint-config-prettier stylelint-config-recess-order stylelint-config-html stylelint-config-recommended-scss stylelint-config-standard-scss -D
  • stylelint:css 样式 lint 的核心库
  • postcss:postcss-html 的依赖包,可以用于转换 css 代码
  • postcss-html:用于解析 HTML(和类似 HTML)的 PostCSS 语法,可以用于识别 html 或者 vue 中的样式
  • stylelint-config-standard:Stylelint 的标准可共享配置,可以用额外的规则来执行在规范和一些 CSS 样式指南中发现的通用约定
  • stylelint-config-prettier:关闭所有不必要的或可能与 Prettier 冲突的规则
  • stylelint-config-recess-order:可以用于属性的排序(插件包)
  • stylelint-config-html:Stylelint 的可共享 HTML 配置。
  • stylelint-config-recommended-scss:扩展 stylelint-config-recommended 共享配置,并为 SCSS 配置其规则
  • stylelint-config-standard-scss:扩展 stylelint-config-standard 共享配置,并为 SCSS 配置其规则

添加配置文件

在根目录下增加 .stylelintrc.js 文件

module.exports = {
	extends: [
		"stylelint-config-standard", 
		"stylelint-config-html/vue", 
		"stylelint-config-standard-scss",
		"stylelint-config-recommended-vue/scss", 
		"stylelint-config-recess-order", 
		"stylelint-config-prettier"
	],
	overrides: [
		// 扫描 .vue/html 文件中的<style>标签内的样式
		{
			files: ["**/*.{vue,html}"],
			customSyntax: "postcss-html"
		}
	],
	/**
	 * null  => 关闭该规则
	 */
	rules: {
		"no-descending-specificity": null, // 禁止在具有较高优先级的选择器后出现被其覆盖的较低优先级的选择器
		"function-url-quotes": "always", // 要求或禁止 URL 的引号 "always(必须加上引号)"|"never(没有引号)"
		"string-quotes": "double", // 指定字符串使用单引号或双引号
		"unit-case": null, // 指定单位的大小写 "lower(全小写)"|"upper(全大写)"
		"color-hex-case": "lower", // 指定 16 进制颜色的大小写 "lower(全小写)"|"upper(全大写)"
		"color-hex-length": "long", // 指定 16 进制颜色的简写或扩写 "short(16进制简写)"|"long(16进制扩写)"
		"rule-empty-line-before": "never", // 要求或禁止在规则之前的空行 "always(规则之前必须始终有一个空行)"|"never(规则前绝不能有空行)"|"always-multi-line(多行规则之前必须始终有一个空行)"|"never-multi-line(多行规则之前绝不能有空行。)"
		"font-family-no-missing-generic-family-keyword": null, // 禁止在字体族名称列表中缺少通用字体族关键字
		"block-opening-brace-space-before": "always", // 要求在块的开大括号之前必须有一个空格或不能有空白符 "always(大括号前必须始终有一个空格)"|"never(左大括号之前绝不能有空格)"|"always-single-line(在单行块中的左大括号之前必须始终有一个空格)"|"never-single-line(在单行块中的左大括号之前绝不能有空格)"|"always-multi-line(在多行块中,左大括号之前必须始终有一个空格)"|"never-multi-line(多行块中的左大括号之前绝不能有空格)"
		"property-no-unknown": null, // 禁止未知的属性(true 为不允许)
		"no-empty-source": null, // 禁止空源码
		"declaration-block-trailing-semicolon": null, // 要求或不允许在声明块中使用尾随分号 string:"always(必须始终有一个尾随分号)"|"never(不得有尾随分号)"
		"selector-class-pattern": null, // 强制选择器类名的格式
		"scss/at-import-partial-extension": null, // 解决不能引入scss文件
		"value-no-vendor-prefix": null, // 关闭 vendor-prefix(为了解决多行省略 -webkit-box)
		"selector-pseudo-class-no-unknown": [
			true,
			{
				ignorePseudoClasses: ["global", "v-deep", "deep"]
			}
		]
	}
};

添加 scripts 命令

package.json 中添加 scripts 命令

"scripts": {
    "lint:stylelint": "stylelint --cache --fix \"**/*.{vue,less,postcss,css,scss,html}\""
},

运行该命令 lint:stylelint 就可以让指定的文件的样式都按照 stylelint 的配置规则文件进行自动格式化。

pnpm lint:stylelint

stylelint格式化.jpg

配置 husky

为了确保我们的代码在提交前先通过 eslint 和 preitter 的代码验证和格式化,可以使用 husky 来管理 git 的生命周期钩子来触发自动运行自动化 eslint 和 preitter 的命令。

安装依赖

pnpm install husky -D
npx husky install

package.json中的script中添加一条脚本命令

npm set-script prepare "husky install"

添加配置文件

添加一个 git 的生命周期勾子,在 commit 提交前必须执行 eslint 和 preitter 的代码校验和格式化。

npx husky add .husky/pre-commit "pnpm lint:eslint && pnpm lint:prettier && pnpm lint:stylelint"

会在根目录 .husky 文件夹下生成一个 pre-commit 文件

#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

pnpm lint:eslint && pnpm lint:prettier && pnpm lint:stylelint

当我们使用 git commit 提交代码的时候就会自动去触发生命周期勾子,进行 eslint、preitter、stylelint 的配置校验。

husky结果.jpg

添加 lint-staged

不仅如此,我们也可以添加 lint-staged 对于本地暂存代码的检查校验,将 eslint、preitter、stylelint 的配置校验合并在一起。

  1. 安装依赖

    pnpm install lint-staged -D
    
  2. package.json 中添加 scripts 命令

    "scripts": {
        "lint:lint-staged": "lint-staged -c ./.husky/lintstagedrc.js",
    },
    
  3. 在根目录 .husky 文件夹下创建一个 lintstagedrc.js 文件以及修改 pre-commit 文件

    // lintstagedrc.js
    
    module.exports = {
    	"*.{js,jsx,ts,tsx}": ["eslint --fix", "prettier --write"],
    	"{!(package)*.json,*.code-snippets,.!(browserslist)*rc}": ["prettier --write--parser json"],
    	"package.json": ["prettier --write"],
    	"*.vue": ["eslint --fix", "prettier --write", "stylelint --fix"],
    	"*.{css,scss,postcss,less,htm}": ["stylelint --fix", "prettier --write"],
    	"*.md": ["prettier --write"]
    };
    
    // pre-commit
    
    #!/usr/bin/env sh
    . "$(dirname -- "$0")/_/husky.sh"
    
    npm run lint:lint-staged
    
  4. 手动运行 ppm lint:lint-staged 或者 commit 提交后都会自动执行 eslint、preitter、stylelint 的配置校验。

    lint-staged结果.jpg

配置 Commitlint

提交规范标准主要是为了让开发者提交完整的更新信息,拥有清晰 commit 信息非常有助于查阅代码。

git 提交代码的标准格式包括三个部分:HeaderBodyFooter,其中的 Header 是必填的,而 Body 和 Footer 可以省略。

<type>(<scope>): <subject>
// 空一行
<body>
// 空一行
<footer>
  1. Header 部分只有一行,包括三个字段:type(必填)、scope(可选)、subject(必填)。type 代表你要提交的类型,scope 代表代码影响的一个范围,subject 指的是简短精炼的变更描述。目前主流的提交信息规范主要参考自 Angular 团队。
    • feat:新增功能
    • fix:修复缺陷
    • docs:文档修改
    • style:代码格式修改(不影响代码功能)
    • refactor:代码重构(不包括 bug 修复、功能新增)
    • perf:性能优化
    • test:新增和修改测试用例
    • build:构建流程、外部依赖变更(如升级 npm 包、修改 webpack 配置等)
    • ci:修改 CI 配置、脚本
    • chore:构建过程或辅助工具和库的变动(不影响源文件、测试用例)
    • revert:回滚提交
    • workflow:工作流相关文件修改
    • types:commit 的类型
    • release:发布新版本
  2. Body 是对于 subject 的补充,可以提交多行。
  3. Footer 主要是一些关联 issue 的操作。

安装 Commitlint

commitlint 是 git 信息提交校验工具,不符合则报错

pnpm i @commitlint/cli @commitlint/config-conventional -D

添加 Commitlint 配置文件

将 commitlint 脚本添加到 git 的生命周期勾子中, 让每次提交之前都验证信息是否符合上述标准。

npx husky add .husky/commit-msg 'npx --no-install commitlint --edit "$1"'

会在根目录 .husky 文件夹下生成一个 commit-msg 文件

#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

npx --no-install commitlint --edit "$1"

在根目录下增加 commitlint.config.js 文件

module.exports = {
	ignores: [commit => commit.includes("init")],
	extends: ["@commitlint/config-conventional"],
	rules: {
		// @see: https://commitlint.js.org/#/reference-rules
		"body-leading-blank": [2, "always"],
		"footer-leading-blank": [1, "always"],
		"header-max-length": [2, "always", 108],
		"subject-empty": [2, "never"],
		"type-empty": [2, "never"],
		"subject-case": [0],
		"type-enum": [
			2,
			"always",
			[
				"feat",
				"fix",
				"docs",
				"style",
				"refactor",
				"perf",
				"test",
				"build",
				"ci",
				"chore",
				"revert",
				"workflow",
				"types",
				"release"
			]
		]
	}
};

这样当我们提交不符合规范标准的信息就会报错。如下图提示 type 和 subject 报错

提交报错.jpg

安装 commitizen 和 cz-git

commitizen 是可以让我们撰写符合上面提交标准的一款工具,而 cz-git 是一款工程性更强,高度自定义,标准输出格式的 commitizen 适配器。

pnpm install commitizen -D
pnpm install cz-git -D

添加 scripts 命令

package.json 文件中添加配置,并在 commitlint.config.js 添加 prompt 字段。

// package.json

"scripts": {
  "commit": "git-cz",
},
"config": {
  "commitizen": {
    "path": "node_modules/cz-git"
  }
}
// commitlint.config.js

module.exports = {
  ……,
  prompt: {
    messages: {
      type: "Select the type of change that you're committing:",
      scope: "Denote the SCOPE of this change (optional):",
      customScope: "Denote the SCOPE of this change:",
      subject: "Write a SHORT, IMPERATIVE tense description of the change:\n",
      body: 'Provide a LONGER description of the change (optional). Use "|" to break new line:\n',
      breaking: 'List any BREAKING CHANGES (optional). Use "|" to break new line:\n',
      footerPrefixsSelect: "Select the ISSUES type of changeList by this change (optional):",
      customFooterPrefixs: "Input ISSUES prefix:",
      footer: "List any ISSUES by this change. E.g.: #31, #34:\n",
      confirmCommit: "Are you sure you want to proceed with the commit above?"
    },
    types: [{
        value: "feat",
        name: "feat:     🚀  A new feature",
        emoji: "🚀"
      },
      {
        value: "fix",
        name: "fix:      🧩  A bug fix",
        emoji: "🧩"
      },
      {
        value: "docs",
        name: "docs:     📚  Documentation only changes",
        emoji: "📚"
      },
      {
        value: "style",
        name: "style:    🎨  Changes that do not affect the meaning of the code",
        emoji: "🎨"
      },
      {
        value: "refactor",
        name: "refactor: ♻️   A code change that neither fixes a bug nor adds a feature",
        emoji: "♻️"
      },
      {
        value: "perf",
        name: "perf:     ⚡️  A code change that improves performance",
        emoji: "⚡️"
      },
      {
        value: "test",
        name: "test:     ✅  Adding missing tests or correcting existing tests",
        emoji: "✅"
      },
      {
        value: "build",
        name: "build:    📦️   Changes that affect the build system or external dependencies",
        emoji: "📦️"
      },
      {
        value: "ci",
        name: "ci:       🎡  Changes to our CI configuration files and scripts",
        emoji: "🎡"
      },
      {
        value: "chore",
        name: "chore:    🔨  Other changes that don't modify src or test files",
        emoji: "🔨"
      },
      {
        value: "revert",
        name: "revert:   ⏪️  Reverts a previous commit",
        emoji: "⏪️"
      }
    ],
    useEmoji: true,
    themeColorCode: "",
    scopes: [],
    allowCustomScopes: true,
    allowEmptyScopes: true,
    customScopesAlign: "bottom",
    customScopesAlias: "custom",
    emptyScopesAlias: "empty",
    upperCaseSubject: false,
    allowBreakingChanges: ["feat", "fix"],
    breaklineNumber: 100,
    breaklineChar: "|",
    skipQuestions: [],
    issuePrefixs: [{
      value: "closed",
      name: "closed:   ISSUES has been processed"
    }],
    customIssuePrefixsAlign: "top",
    emptyIssuePrefixsAlias: "skip",
    customIssuePrefixsAlias: "custom",
    allowCustomIssuePrefixs: true,
    allowEmptyIssuePrefixs: true,
    confirmColorize: true,
    maxHeaderLength: Infinity,
    maxSubjectLength: Infinity,
    minSubjectLength: 0,
    scopeOverrides: undefined,
    defaultBody: "",
    defaultIssues: "",
    defaultScope: "",
    defaultSubject: ""
  }
};

执行 pnpm commit 就可以按照配置文件配置的步骤进行 git 信息提交了。

提交成功.jpg

如果想要快速使用 czgit cz 命令进行启动全局使用, 可以 npm install -g commitizen 全局安装一下 commitizen ,然后就可以使用 czgit cz 来代替 pnpm commit,效果是一样的。


写在最后

用 Prettier 来格式化代码,ESLint 和 StyleLint 来检测语法,Commitlint 来规范提交信息,并使用 Husky 在提交代码时做自动检查,就可以形成一套目录、格式、编码以及提交的规范。一劳永逸,何乐而不为呢。