前端工程项目中需要用到三类工具来帮助我们进行代码开发, 分别是代码规范检查工具, 如 Eslint, 然后是代码格式化工具, 如 Prettier, 最后是Git Commit 检查工具, 如 Husky. 在项目初始化后, 需要完成相关配置, 方便项目代码可以以相同的风格展示在不同的 PC 上, 同时, 规范的 Git 提交信息有助于了解开发情况以及 bug 定位.

Git Commit 规范

参考 Angular 规范 定义 Commit Message 规范格式如下:

<type>(<scope>): <subject>
<BLANK LINE>
<body>
<BLANK LINE>
<footer>

其中 type 为必填项, 通常科定义内容如下:

'feat', // 新特性、需求
'fix', // bug 修复
'docs', // 文档内容改动
'style', // 不影响代码含义的改动,例如去掉空格、改变缩进、增删分号
'refactor', // 代码重构
'test', // 添加或修改测试
'chore', // 不修改 src 或者 test 的其余修改,例如构建过程或辅助工具的变动
'revert', // 执行 git revert 打印的 message
'build', //对构建系统或者外部依赖项进行了修改
'ci', // 对 CI 配置文件或脚本进行了修改
'pref' // 提高性能的代码更改

scope 为非必填项, 用于说明 commit 影响的范围, 比如数据层、控制层、视图层等. subject为必填项, 用于简要描述 commit 目的. body 用于详细描述 commit 目的. 详细内容请查看 官方文档. 一个简单提交例子如下:

feat: 添加商品浏览模态框组件

了解规范后, 在项目中进行 Husky 和 Commitlint 配置. 注意这里我以 Vue3 + Vite + TypeScript 项目为例, 所有工具都使用的最新版本, 一些旧版本的配置项将不再适用.

Husky 和 Commitlint 配置

1. 安装 Husky 和 Commitlint 依赖

npm install husky commitlint @commitlint/config-conventional -D

2. 配置 Husky
在 package.json 中的 script 中添加一条命令:

{
    "scripts": {
    	...
        "prepare": "husky install"
        ...
    },
}

接着执行

npm run prepare

这步会初始化 husky, 在根目录创建 .husky 文件夹. 注意事项.接着使用 husky 命令添加 pre-commit 钩子, 运行

npx husky add .husky/pre-commit "npm run lint && npm run format && npm run lint:style"

将在 .husky 目录下生成一个 pre-commit 文件, 这个 pre-commit 的主要作用就是在进行 git commit 之前进行代码规范检查和修复(避免提交不符合格式的代码, 如果校验不通过就会拒绝 commit 操作), 即这 3 个命令 npm lint ; npm format ; npm lint:style, 这 3 个命令的配置将在后面步骤中进行. 当然也可根据实际情况进行配置调整, 适用其他命令.

接着执行

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

将在 .husky 目录下生成一个 commit-msg 文件, 其用于标识采用什么规范来执行 commit 消息校验, 默认是 Angular 的提交规范.
3. 配置 commitlint.config.js 文件
在项目根目录下新建 commitlint.config.js 文件, 用于定义一些提交规范, 没有这个文件的话, 进行 git commit 时会报错提示:
在这里插入图片描述
commitlint.config.js 文件的内容可以只有:

module.exports = {extends: ['@commitlint/config-conventional']};

这时会采用默认配置. 这里我们可以添加一些自定义项:

module.exports = {
    extends: ['@commitlint/config-conventional'],
    rules: {
        'body-leading-blank': [2, 'always'], // body 开始于空白行
        'header-max-length': [2, 'always', 72], // header 字符最大长度为 72
        'subject-full-stop': [0, 'never'], // subject 结尾不加 '.'
        'type-empty': [2, 'never'], // type 不为空
        'type-enum': [
            2,
            'always',
            [
                'feat', // 新特性、需求
                'fix', // bug 修复
                'docs', // 文档内容改动
                'style', // 不影响代码含义的改动,例如去掉空格、改变缩进、增删分号
                'refactor', // 代码重构
                'test', // 添加或修改测试
                'chore', // 不修改 src 或者 test 的其余修改,例如构建过程或辅助工具的变动
                'revert', // 执行 git revert 打印的 message
                'build', //对构建系统或者外部依赖项进行了修改
                'ci', // 对 CI 配置文件或脚本进行了修改
                'pref' // 提高性能的代码更改
            ]
        ]
    }
}

至此一些基础的 Husky 配置便基本完成.

Prettier 配置

1. 安装 Prettier 依赖

npm i prettier eslint-config-prettier eslint-plugin-prettier -D

2. 配置 Prettier
在根目录下新建 .prettierrc.js 文件, 添加内容:

module.exports = {
    // 一个tab代表几个空格数,默认为80
    tabWidth: 4, 
    // 是否使用tab进行缩进,默认为false,表示用空格进行缩减
    useTabs: false, 
    // 字符串是否使用单引号,默认为false,使用双引号
    singleQuote: true, 
    // 行位是否使用分号,默认为true
    semi: false, 
    // 是否使用尾逗号,有三个可选值"<none|es5|all>"
    trailingComma: "none", 
    // 对象大括号直接是否有空格,默认为true,效果:{ foo: bar }
    bracketSpacing: true
}

更多详细配置见官方文档. 接着在 package.json 中的 script 中添加一条命令:

{
    "scripts": {
    	...
        "format": "prettier --write \"./**/*.{html,vue,ts,js,json,md,less,sass,scss,sty}\""
        ...
    },
}

这时我们运行

npm run format

就可进行代码格式化.

Eslint 配置

1. 安装 Eslint 依赖

npm install eslint -D

**2. 配置 Eslint **
运行

npx eslint --init

进行 eslint 配置文件初始化, 配置选项如下:
在这里插入图片描述

(1) How would you like to use ESLint?
选择: To check syntax and find problems

(2) What type of modules does your project use?
选择: JavaScript modules (import/export)

(3) Which framework does your project use?
选择: Vue.js (根据项目情况来选)

(4) Does your project use TypeScript?
选择: Yes

(5) Where does your code run?
选择: Browser

(6) What format do you want your config file to be in?
选择: JavaScript

(7) Would you like to install them now?
选择: Yes

(8) Which package manager do you want to use?
选择: npm

等待安装完成后, 会在根目录下生成 .eslintrc.js 文件:

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

但是这个默认文件的配置会和我们的其他工具发生冲突, 将其修改为如下配置:

module.exports = {
    "env": {
        "browser": true,
        "es2021": true,
        // 这里为新增项
        "node": true
    },
    "extends": [
        "eslint:recommended",
        "plugin:vue/vue3-essential",
        "plugin:@typescript-eslint/recommended",
        // 这里为新增项, 用于兼容 Prettier
        "plugin:prettier/recommended"
    ],
    // 添加额外 vue-eslint-parser
    "parser": "vue-eslint-parser",
    "parserOptions": {
        "ecmaVersion": "latest",
        "parser": "@typescript-eslint/parser",
        "sourceType": "module"
    },
    "plugins": [
        "vue",
        "@typescript-eslint"
    ],
    "rules": {
    	// 解决 Don‘t use `{}` as a type. `{}` actually means “any non-nullish value“
        "@typescript-eslint/ban-types": [
            "error",
            {
                "extendDefaults": true,
                "types": {
                    "{}": false
                }
            }
        ],
        // 忽略 Vue 中使用 any 时的 ts:warning
        "@typescript-eslint/no-explicit-any": ["off"]
    }
}

在 package.json 中的 script 中添加一条命令:

{
    "scripts": {
    	...
        "lint": "eslint . --ext .vue,.js,.ts,.jsx,.tsx --fix",
        ...
    },
}

这时我们运行

npm run lint

就可进行代码内容格式的检查和修改.

Stylelint 配置

Stylelint 为 css 的 lint 工具. 进行 css 代码格式化, 检查 css 语法错误与不合理的写法, 指定css书写顺序等.
1. 安装 Stylelint 的相关依赖

less

npm install stylelint stylelint-config-prettier stylelint-config-recommended-less stylelint-config-standard stylelint-config-standard-vue stylelint-less stylelint-order postcss-html postcss postcss-less -D

scss

npm install sass sass-loader stylelint stylelint-config-prettier stylelint-config-standard stylelint-config-html stylelint-order stylelint-scss postcss-html postcss-scss -D

**2. 配置 Stylelint **
在根目录下新建 .stylelintrc.js 文件:

less

module.exports = {
    extends: [
        'stylelint-config-standard',
        'stylelint-config-prettier',
        'stylelint-config-recommended-less',
        'stylelint-config-standard-vue'
    ],
    plugins: ['stylelint-order'],
    // 不同格式的文件指定自定义语法
    overrides: [
        {
            files: ['**/*.(less|css|vue|html)'],
            customSyntax: 'postcss-less'
        },
        {
            files: ['**/*.(html|vue)'],
            customSyntax: 'postcss-html'
        }
    ],
    ignoreFiles: [
        '**/*.js',
        '**/*.jsx',
        '**/*.tsx',
        '**/*.ts',
        '**/*.json',
        '**/*.md',
        '**/*.yaml'
    ],
    rules: {
        'no-descending-specificity': null, // 禁止在具有较高优先级的选择器后出现被其覆盖的较低优先级的选择器
        'selector-pseudo-element-no-unknown': [
            true,
            {
                ignorePseudoElements: ['v-deep']
            }
        ],
        'selector-pseudo-class-no-unknown': [
            true,
            {
                ignorePseudoClasses: ['deep']
            }
        ],
        // 指定样式的排序
        'order/properties-order': [
            'position',
            'top',
            'right',
            'bottom',
            'left',
            'z-index',
            'display',
            'justify-content',
            'align-items',
            'float',
            'clear',
            'overflow',
            'overflow-x',
            'overflow-y',
            'padding',
            'padding-top',
            'padding-right',
            'padding-bottom',
            'padding-left',
            'margin',
            'margin-top',
            'margin-right',
            'margin-bottom',
            'margin-left',
            'width',
            'min-width',
            'max-width',
            'height',
            'min-height',
            'max-height',
            'font-size',
            'font-family',
            'text-align',
            'text-justify',
            'text-indent',
            'text-overflow',
            'text-decoration',
            'white-space',
            'color',
            'background',
            'background-position',
            'background-repeat',
            'background-size',
            'background-color',
            'background-clip',
            'border',
            'border-style',
            'border-width',
            'border-color',
            'border-top-style',
            'border-top-width',
            'border-top-color',
            'border-right-style',
            'border-right-width',
            'border-right-color',
            'border-bottom-style',
            'border-bottom-width',
            'border-bottom-color',
            'border-left-style',
            'border-left-width',
            'border-left-color',
            'border-radius',
            'opacity',
            'filter',
            'list-style',
            'outline',
            'visibility',
            'box-shadow',
            'text-shadow',
            'resize',
            'transition'
        ]
    }
}

scss

module.exports = {
  root: true,
  plugins: ['stylelint-order', 'stylelint-scss'],
  customSyntax: 'postcss-html',
  extends: ['stylelint-config-standard', 'stylelint-config-prettier', 'stylelint-config-html'],
  rules: {
    'selector-class-pattern': null,
    'selector-pseudo-class-no-unknown': [
      true,
      {
        ignorePseudoClasses: ['deep', 'global'],
      },
    ],
    'selector-pseudo-element-no-unknown': [
      true,
      {
        ignorePseudoElements: ['v-deep', 'v-global', 'v-slotted'],
      },
    ],
    'at-rule-no-unknown': [
      true,
      {
        ignoreAtRules: [
          'tailwind',
          'apply',
          'variants',
          'responsive',
          'screen',
          'function',
          'if',
          'each',
          'include',
          'mixin',
        ],
      },
    ],
    'no-empty-source': null,
    'named-grid-areas-no-invalid': null,
    'unicode-bom': 'never',
    'no-descending-specificity': null,
    'font-family-no-missing-generic-family-keyword': null,
    'declaration-colon-space-after': 'always-single-line',
    'declaration-colon-space-before': 'never',
    // 'declaration-block-trailing-semicolon': 'always',
    'rule-empty-line-before': [
      'always',
      {
        ignore: ['after-comment', 'first-nested'],
      },
    ],
    'unit-no-unknown': [true, { ignoreUnits: ['rpx'] }],
    'keyframes-name-pattern': null,
    'order/order': [
      [
        'dollar-variables',
        'custom-properties',
        'at-rules',
        'declarations',
        {
          type: 'at-rule',
          name: 'supports',
        },
        {
          type: 'at-rule',
          name: 'media',
        },
        'rules',
      ],
      { severity: 'warning' },
    ],
    'order/properties-order': [
      'position',
      'top',
      'right',
      'bottom',
      'left',
      'z-index',
      'display',
      'float',
      'width',
      'height',
      'max-width',
      'max-height',
      'min-width',
      'min-height',
      'padding',
      'padding-top',
      'padding-right',
      'padding-bottom',
      'padding-left',
      'margin',
      'margin-top',
      'margin-right',
      'margin-bottom',
      'margin-left',
      'margin-collapse',
      'margin-top-collapse',
      'margin-right-collapse',
      'margin-bottom-collapse',
      'margin-left-collapse',
      'overflow',
      'overflow-x',
      'overflow-y',
      'clip',
      'clear',
      'font',
      'font-family',
      'font-size',
      'font-smoothing',
      'osx-font-smoothing',
      'font-style',
      'font-weight',
      'hyphens',
      'src',
      'line-height',
      'letter-spacing',
      'word-spacing',
      'color',
      'text-align',
      'text-decoration',
      'text-indent',
      'text-overflow',
      'text-rendering',
      'text-size-adjust',
      'text-shadow',
      'text-transform',
      'word-break',
      'word-wrap',
      'white-space',
      'vertical-align',
      'list-style',
      'list-style-type',
      'list-style-position',
      'list-style-image',
      'pointer-events',
      'cursor',
      'background',
      'background-attachment',
      'background-color',
      'background-image',
      'background-position',
      'background-repeat',
      'background-size',
      'border',
      'border-collapse',
      'border-top',
      'border-right',
      'border-bottom',
      'border-left',
      'border-color',
      'border-image',
      'border-top-color',
      'border-right-color',
      'border-bottom-color',
      'border-left-color',
      'border-spacing',
      'border-style',
      'border-top-style',
      'border-right-style',
      'border-bottom-style',
      'border-left-style',
      'border-width',
      'border-top-width',
      'border-right-width',
      'border-bottom-width',
      'border-left-width',
      'border-radius',
      'border-top-right-radius',
      'border-bottom-right-radius',
      'border-bottom-left-radius',
      'border-top-left-radius',
      'border-radius-topright',
      'border-radius-bottomright',
      'border-radius-bottomleft',
      'border-radius-topleft',
      'content',
      'quotes',
      'outline',
      'outline-offset',
      'opacity',
      'filter',
      'visibility',
      'size',
      'zoom',
      'transform',
      'box-align',
      'box-flex',
      'box-orient',
      'box-pack',
      'box-shadow',
      'box-sizing',
      'table-layout',
      'animation',
      'animation-delay',
      'animation-duration',
      'animation-iteration-count',
      'animation-name',
      'animation-play-state',
      'animation-timing-function',
      'animation-fill-mode',
      'transition',
      'transition-delay',
      'transition-duration',
      'transition-property',
      'transition-timing-function',
      'background-clip',
      'backface-visibility',
      'resize',
      'appearance',
      'user-select',
      'interpolation-mode',
      'direction',
      'marks',
      'page',
      'set-link-source',
      'unicode-bidi',
      'speak',
    ],
  },
  ignoreFiles: ['**/*.js', '**/*.jsx', '**/*.tsx', '**/*.ts'],
}

然后在 package.json 中的 script 中添加一条命令:

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

这时我们运行

npm run lint:style

就可进行 相关 css 文件的代码检查及修改.

结语

配置完成后, package.json 文件中的主要相关内容如下:
在这里插入图片描述
项目文件结构如下:
在这里插入图片描述

注意:
script 中的 prepare 命令会在 npm install 后自动运行, 即当我们将线上项目 clone 到本地后, 直接运行 npm install 后就不用管其他操作了. 由于本文中是在一个初始化的本地项目中进行配置, 所以需要手动运行 npm run prepare.

补充 scss 在 Vue、React 中的配置

React 18.2.0,Vue 2.6.14,Vue 3.2.37

npm install sass sass-loader -D

全局 scss 使用变量问题
当我们把一系列变量或 mixin 抽离到不同的 scss 文件中后,在每个自定义 view 中想要使用这些变量时,需要在相应 view 中 import 这些 scss 文件。
在这里插入图片描述
为避免每个 view 抖重复 import 这些文件,可以在 vite.config.js 中进行配置(如果项目使用 Vite构建):

import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';

export default defineConfig({
  plugins: [vue()],
  css: {
    preprocessorOptions: {
      scss: {
      	// sass 已经推荐使用@use方法引入
        additionalData: `@use "@/assets/styles/index.scss" as *;`
      }
    }
  }
});

这样,在单独的 view 文件中就能不用引入外部 scss 文件,就可使用定义的变量或方法。如上图所示,一些公共属性抽离出几个 scss 文件,这些文件通过 index.scss 再组织,最后通过 vite 配置引入。

在非 Vite 构建的 Vue 项目中,配置 vue.config.js

module.exports = {
  css: {
    loaderOptions: {
      sass: {
		//sass-loader v8-中,关键字为 “ data ”  
		//sass-loader v8中,关键字为 “ prependData ”  
		//sass-loader v10+中,关键字为 “ additionalData ”
        additionalData: `@use "@/assets/styles/index.scss" as *;`
      }
    }
  }
};
Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐