前端代码工作流

工欲善其事,必先利其器。对于写代码而言,也是需要有一套完善的工作流(工具和流程)。

ESLint

ESLint 是一款插件化的 JavaScript 代码静态检查工具,其核心是通过对代码解析得到的 AST(Abstract Syntax Tree,抽象语法树)进行模式匹配,来分析代码达到检查代码质量和风格问题的能力。

安装

安装并初始化 eslint:

# 全局安装
npm install -g eslint

# cd到项目根目录下
# 强制初始化package.json
npm init -force

# 初始化ESLint
eslint --init

使用方式

写注释

下面这行注释表示在当前文件中禁用 console 关键字

/* eslint no-console: "error" */

写文件

ESLint 支持 eslint.js , eslintrc.yaml , eslintrc.json 类型的配置文件。

如 eslint.js 配置文件:

module.exports = {
    env: {
        // 环境
        browser: true,
        es2021: true,
    },
    extends: [
        // 拓展
        "eslint:recommended",
        "plugin:@typescript-eslint/recommended",
    ],
    parser: "@typescript-eslint/parser", // 解析器,本解析器支持Ts
    parserOptions: {
        // 解析器配置选项
        ecmaVersion: 12, // 指定es版本
        sourceType: "module", // 代码支持es6,使用module
    },
    plugins: [
        // 插件
        "@typescript-eslint",
    ],
    rules: {
        // 规则
    },
};
const { defineConfig } = require("eslint-define-config");

module.exports = defineConfig({
    root: true,
    env: {
        browser: true,
        node: true,
        jest: true,
        es6: true,
    },
    plugins: ["vue"],
    parser: "vue-eslint-parser",
    parserOptions: {
        ecmaVersion: "latest",
        sourceType: "module",
        allowImportExportEverywhere: true,
        ecmaFeatures: {
            jsx: true,
        },
    },
    extends: [
        "airbnb-base",
        "eslint:recommended",
        "plugin:vue/vue3-essential",
        "plugin:vue/vue3-recommended",
        "plugin:prettier/recommended",
    ],
    rules: {
        // 禁止使用多余的包
        "import/no-extraneous-dependencies": 0,
        // 确保在导入路径内一致使用文件扩展名
        "import/extensions": 0,
        // 确保导入指向可以解析的文件/模块
        "import/no-unresolved": 0,
        // 首选默认导出导入/首选默认导出
        "import/prefer-default-export": 0,
        // 要求使用 let 或 const 而不是 var
        "no-var": "error",
        // 禁止使用 new 以避免产生副作用
        "no-new": 1,
        // 禁止变量声明与外层作用域的变量同名
        "no-shadow": 0,
        // 禁用 console
        "no-console": 0,
        // 禁止标识符中有悬空下划线
        "no-underscore-dangle": 0,
        // 禁止在可能与比较操作符相混淆的地方使用箭头函数
        "no-confusing-arrow": 0,
        // 禁用一元操作符 ++ 和 --
        "no-plusplus": 0,
        // 禁止对 function 的参数进行重新赋值
        "no-param-reassign": 0,
        // 禁用特定的语法
        "no-restricted-syntax": 0,
        // 禁止在变量定义之前使用它们
        "no-use-before-define": 0,
        // 禁止直接调用 Object.prototypes 的内置属性
        "no-prototype-builtins": 0,
        // 禁止可以在有更简单的可替代的表达式时使用三元操作符
        "no-unneeded-ternary": "error",
        // 禁止重复模块导入
        "no-duplicate-imports": "error",
        // 禁止在对象中使用不必要的计算属性
        "no-useless-computed-key": "error",
        // 强制使用一致的缩进
        indent: ["error", 2],
        // 强制使用骆驼拼写法命名约定
        camelcase: 0,
        // 强制类方法使用 this
        "class-methods-use-this": 0,
        // 要求构造函数首字母大写
        "new-cap": 0,
        // 强制一致地使用 function 声明或表达式
        "func-style": 0,
        // 强制一行的最大长度
        "max-len": 0,
        // 要求 return 语句要么总是指定返回的值,要么不指定
        "consistent-return": 0,
        // 强制switch要有default分支
        "default-case": 2,
        // 强制剩余和扩展运算符及其表达式之间有空格
        "rest-spread-spacing": "error",
        // 要求使用 const 声明那些声明后不再被修改的变量
        "prefer-const": "error",
        // 强制箭头函数的箭头前后使用一致的空格
        "arrow-spacing": "error",
    },
    overrides: [
        {
            files: ["*.vue"],
            rules: {
                // 要求组件名称总是多个单词
                "vue/multi-word-component-names": 0,
            },
        },
    ],
});

配置项

  • parser - 解析器
  • parserOptions - 解析器选项
  • env 和 globals - 环境和全局变量
  • rules - 规则
    • off 或 0,关闭规则
    • warn 或 1,开启规则
    • error 或 2,开启规则,并会出错阻止代码运行
  • plugins - 插件
  • extends - 拓展

配置优先级

规则是使用离要检测的文件最近的 .eslintrc 文件作为最高优先级。

  • 行内配置
  • 命令行选项
  • 项目级配置
  • IDE 环境配置

Stylelint

在项目根目录创建 .stylelintrc.js 文件,并填入以下内容:

module.exports = {
    root: true,
    defaultSeverity: "error",
    extends: ["stylelint-config-standard", "stylelint-config-prettier"],
    plugins: ["stylelint-order"],
    rules: {
        // 不允许未知函数
        "function-no-unknown": null,
        // 指定类选择器的模式
        "selector-class-pattern": null,
        // 禁止空源码
        "no-empty-source": null,
        // 指定字符串使用单引号
        "string-quotes": "single",
        // 禁止未知的@规则
        "at-rule-no-unknown": [
            true,
            {
                ignoreAtRules: [
                    "tailwind",
                    "apply",
                    "variants",
                    "responsive",
                    "screen",
                    "function",
                    "if",
                    "each",
                    "include",
                    "mixin",
                ],
            },
        ],
        // 指定@规则名的大小写
        "at-rule-name-case": "lower",
        // 指定缩进
        indentation: [
            2,
            {
                severity: "warning",
            },
        ],
        // 禁止未知的伪类选择器
        "selector-pseudo-class-no-unknown": [
            true,
            {
                ignorePseudoClasses: ["global"],
            },
        ],
        // 禁止未知的伪元素选择器
        "selector-pseudo-element-no-unknown": [
            true,
            {
                ignorePseudoElements: ["v-deep"],
            },
        ],
        "order/properties-order": [
            "position",
            "top",
            "right",
            "bottom",
            "left",
            "z-index",
            "display",
            "justify-content",
            "align-items",
            "float",
            "clear",
            "overflow",
            "overflow-x",
            "overflow-y",
            "margin",
            "margin-top",
            "margin-right",
            "margin-bottom",
            "margin-left",
            "padding",
            "padding-top",
            "padding-right",
            "padding-bottom",
            "padding-left",
            "width",
            "min-width",
            "max-width",
            "height",
            "min-height",
            "max-height",
            "font-size",
            "font-family",
            "font-weight",
            "border",
            "border-style",
            "border-width",
            "border-color",
            "border-top",
            "border-top-style",
            "border-top-width",
            "border-top-color",
            "border-right",
            "border-right-style",
            "border-right-width",
            "border-right-color",
            "border-bottom",
            "border-bottom-style",
            "border-bottom-width",
            "border-bottom-color",
            "border-left",
            "border-left-style",
            "border-left-width",
            "border-left-color",
            "border-radius",
            "text-align",
            "text-justify",
            "text-indent",
            "text-overflow",
            "text-decoration",
            "white-space",
            "color",
            "background",
            "background-position",
            "background-repeat",
            "background-size",
            "background-color",
            "background-clip",
            "opacity",
            "filter",
            "list-style",
            "outline",
            "visibility",
            "box-shadow",
            "text-shadow",
            "resize",
            "transition",
        ],
    },
};

Prettier

Prettier 是一个代码格式化的工具。

安装使用

npm install --save-dev --save-exact prettier

# 格式化所有文件,npx命令是使用当前项目下的prettier
npx prettier --write .

配置文件

Prettier 支持 .prettierrc 为名称,以 .yaml .yml .json .js 为后缀的的配置文件,当然你也可以使用 package.json 文件中的 Prettier 属性来配置。

module.exports = {
    printWidth: 80, //一行的字符数,如果超过会进行换行,默认为80
    tabWidth: 2, //一个tab代表几个空格数,默认为80
    useTabs: false, //是否使用tab进行缩进,默认为false,表示用空格进行缩减
    singleQuote: false, //字符串是否使用单引号,默认为false,使用双引号
    semi: true, //行位是否使用分号,默认为true
    trailingComma: "none", //是否使用尾逗号,有三个可选值"
};
module.exports = {
    // 一行最多 120 字符
    printWidth: 120,
    // 使用 2 个空格缩进
    tabWidth: 2,
    // 不使用缩进符,而使用空格
    useTabs: false,
    // 行尾需要有分号
    semi: true,
    // 使用单引号
    singleQuote: true,
    // 对象的 key 仅在必要时用引号
    quoteProps: "as-needed",
    // jsx 不使用单引号,而使用双引号
    jsxSingleQuote: false,
    // 末尾需要有逗号
    trailingComma: "all",
    // 大括号内的首尾需要空格
    bracketSpacing: true,
    // jsx 标签的反尖括号需要换行
    jsxBracketSameLine: false,
    // 箭头函数,只有一个参数的时候,也需要括号
    arrowParens: "always",
    // 每个文件格式化的范围是文件的全部内容
    rangeStart: 0,
    rangeEnd: Infinity,
    // 不需要写文件开头的 @prettier
    requirePragma: false,
    // 不需要自动在文件开头插入 @prettier
    insertPragma: false,
    // 使用默认的折行标准
    proseWrap: "preserve",
    // 根据显示样式决定 html 要不要折行
    htmlWhitespaceSensitivity: "css",
    // vue 文件中的 script 和 style 内不用缩进
    vueIndentScriptAndStyle: false,
    // 换行符使用 lf
    endOfLine: "lf",
    // 格式化嵌入的内容
    embeddedLanguageFormatting: "auto",
    // html, vue, jsx 中每个属性占一行
    singleAttributePerLine: false,
};

EditorConfig

EditorConfig 有助于维护跨多个编辑器和 IDE 从事同一项目的多个开发人员的一致编码风格,团队必备神器。

.editorconfig 文件

# EditorConfig is awesome: https://EditorConfig.org

# top-most EditorConfig file 表示是最顶层的配置文件,发现设为true时,才会停止查找.editorconfig文件
root = true

# Unix-style newlines with a newline ending every file 对于所有的文件  始终在文件末尾插入一个新行
[*]
end_of_line = lf
insert_final_newline = true

# Matches multiple files with brace expansion notation
# Set default charset  对于所有的js,py文件,设置文件字符集为utf-8
[*.{js,py}]
charset = utf-8

# 4 space indentation 控制py文件类型的缩进大小
[*.py]
indent_style = space
indent_size = 4

# Tab indentation (no size specified) 设置某中文件的缩进风格为tab Makefile未指明
[Makefile]
indent_style = tab

# Indentation override for all JS under lib directory  设置在lib目录下所有JS的缩进为
[lib/**.js]
indent_style = space
indent_size = 2

# Matches the exact files either package.json or .travis.yml 设置确切文件 package.json/.travis/.yml的缩进类型
[{package.json,.travis.yml}]
indent_style = space
indent_size = 2
# 表示是最顶层的 EditorConfig 配置文件
root = true

# 表示所有文件适用
[*]
# 缩进风格(tab | space)
indent_style = space
# 控制换行类型(lf | cr | crlf)
end_of_line = lf
# 设置文件字符集为 utf-8
charset = utf-8
# 去除行首的任意空白字符
trim_trailing_whitespace = true
# 始终在文件末尾插入一个新行
insert_final_newline = true

# 表示仅 md 文件适用以下规则
[*.md]
max_line_length = off
trim_trailing_whitespace = false

# 表示仅 ts、js、vue、css 文件适用以下规则
[*.{ts,js,vue,css}]
indent_size = 2

通配符

*                匹配除/之外的任意字符串
**               匹配任意字符串
?                匹配任意单个字符
[name]           匹配name中的任意一个单一字符
[!name]          匹配不存在name中的任意一个单一字符
{s1,s2,s3}       匹配给定的字符串中的任意一个(用逗号分隔)
{num1..num2}    匹配num1到num2之间的任意一个整数, 这里的num1和num2可以为正整数也可以为负整数

属性

indent_style    设置缩进风格(tab是硬缩进,space为软缩进)
indent_size     用一个整数定义的列数来设置缩进的宽度,如果indent_style为tab,则此属性默认为tab_width
tab_width       用一个整数来设置tab缩进的列数。默认是indent_size
end_of_line     设置换行符,值为lf、cr和crlf
charset         设置编码,值为latin1、utf-8、utf-8-bom、utf-16be和utf-16le,不建议使用utf-8-bom
trim_trailing_whitespace  设为true表示会去除换行行首的任意空白字符。
insert_final_newline      设为true表示使文件以一个空白行结尾
root           表示是最顶层的配置文件,发现设为true时,才会停止查找.editorconfig文件

VSCode 集成

我使用的是 VSCode ,来给它添加魔法,加 EditorConfig , Eslint , Prettier , Git 扩展。

配置全局工作区 setting.json 文件,在文件中加入下面配置:

// 设置全部语言在保存时自动格式化
"editor.formatOnSave": ture,
// 设置特定语言在保存时自动格式化
"[javascript]": {
    "editor.formatOnSave": true
}

prettier 配置

{
    // 设置全部语言的默认格式化程序为prettier
    "editor.defaultFormatter": "esbenp.prettier-vscode",
    // 设置特定语言的默认格式化程序为prettier
    "[javascript]": {
        "editor.defaultFormatter": "esbenp.prettier-vscode"
    }
}

ESLint 配置

{
    // 保存时自动修复"editor.codeActionsOnSave": {
        // For ESLint
        "source.fixAll.eslint": true,
    }
}

husky/lint-staged

在提交 git 之前,我们需要校验我们的代码是否符合规范,如果不符合,则不允许提交代码。

安装依赖

npm install -D husky

# lint-staged 可以让husky只检验git工作区的文件,不会导致你一下出现成百上千个错误
npm install -D lint-staged

然后修改 package.json,增加配置

"scripts": {
	"precommit": "eslint src/**/*.js"
}
"husky": {
  "hooks": {
    "pre-commit": "lint-staged"
  }
},
"lint-staged": {
  "src/**/*.{js,vue}": ["prettier --write", "eslint --cache --fix", "git add"]
}

在 git commit 之前会进入 工作区文件的扫描,执行 prettier 脚本,修改 eslint 问题,然后重要提交到工作区。

Commitizen

  • 好处:
    • 提供更多的历史信息,方便快速浏览
    • 可以过滤某些 commit,便于筛选代码 review
    • 可以追踪 commit 生成更新日志
    • 可以关联 issues

安装

npm install -g commitizen

安装符合 AngularJS 规范的提交说明,初始化 cz-conventional-changelog 适配器:

commitizen init cz-conventional-changelog --save --save-exact

然后使用 git cz 命令 代替 git comit 来提交 git 说明:

git cz

定制化项目提交说明

上面的提交说明都是英文的,如果想自定义,可以试试 cz-customizable,先安装:

npm install cz-customizable --save-dev

修改 package.json 文件:

"config": {
  "commitizen": {
    "path": "node_modules/cz-customizable"
  }
}

在项目根目录下创建 .cz.config.js 文件:

"use strict";

module.exports = {
    types: [
        { value: "特性", name: "特性:    一个新的特性" },
        { value: "修复", name: "修复:    修复一个Bug" },
        { value: "文档", name: "文档:    变更的只有文档" },
        { value: "格式", name: "格式:    空格, 分号等格式修复" },
        { value: "重构", name: "重构:    代码重构,注意和特性、修复区分开" },
        { value: "性能", name: "性能:    提升性能" },
        { value: "测试", name: "测试:    添加一个测试" },
        { value: "工具", name: "工具:    开发工具变动(构建、脚手架工具等)" },
        { value: "回滚", name: "回滚:    代码回退" },
    ],

    scopes: [
        { name: "模块1" },
        { name: "模块2" },
        { name: "模块3" },
        { name: "模块4" },
    ],

    // it needs to match the value for field type. Eg.: 'fix'
    /*
  scopeOverrides: {
    fix: [
      {name: 'merge'},
      {name: 'style'},
      {name: 'e2eTest'},
      {name: 'unitTest'}
    ]
  },
  */
    // override the messages, defaults are as follows
    messages: {
        type: "选择一种你的提交类型:",
        scope: "选择一个scope (可选):",
        // used if allowCustomScopes is true
        customScope: "Denote the SCOPE of this change:",
        subject: "短说明:\n",
        body: '长说明,使用"|"换行(可选):\n',
        breaking: "非兼容性说明 (可选):\n",
        footer: "关联关闭的issue,例如:#31, #34(可选):\n",
        confirmCommit: "确定提交说明?",
    },

    allowCustomScopes: true,
    allowBreakingChanges: ["特性", "修复"],

    // limit subject length
    subjectLimit: 100,
};

然后运行

git cz

Commitizen 校验

检验提交的说明是否符合规范,不符合则不可以提交

npm install --save-dev @commitlint/cli

# 安装符合Angular风格的校验规则
npm install --save-dev @commitlint/config-conventional

在根目录下创建 commitlint.config.js 并配置检验:

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

然后在 package.json 中配置 husky ,之前我们已经安装过了,直接添加配置:

"husky": {
  "hooks": {
    "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
  }
}

给 commit 加表情

安装:

npm i -g gitmoji-cli

使用: 你可以在这个 gitmojiopen in new window 网站找到更多的表情来丰富你的提交记录,只需要在提交记录中加上类型 🐛 的代码就可以显示表情了。

如下示例

🎨 (调色板)    :art:    改进代码结构/代码格式
⚡️ (闪电)    :zap:    提升性能
🔥 (火焰)    :fire:    移除代码或文件
🐛 (bug)    :bug:    修复 bug
🚑 (急救车)    :ambulance:    重要补丁
✨ (火花)    :sparkles:    引入新功能
📝 (备忘录)    :memo:    撰写文档
🚀 (火箭)    :rocket:    部署功能
💄 (口红)    :lipstick:    更新 UI 和样式文件
🎉 (庆祝)    :tada:    初次提交
✅ (复选框)    :white_check_mark:    增加测试
🔒 (锁)    :lock:    修复安全问题
🍎 (苹果)    :apple:    修复 macOS 下的问题
🐧 (企鹅)    :penguin:    修复 Linux 下的问题
🏁 (旗帜)    :checkered_flag:    修复 Windows 下的问题
🤖 (机器人)    :robot:    修复 Android 下的问题
🍏 (苹果)    :green_apple:    修复 IOS下的问题
🔖 (书签)    :bookmark:    发行/版本标签
🚨 (警车灯)    :rotating_light:    移除 linter 警告
🚧 (施工)    :construction:    工作进行中
💚 (绿心)    :green_heart:    修复 CI 构建问题
⬇️ (下降箭头)    :arrow_down:    降级依赖
⬆️ (上升箭头)    :arrow_up:    升级依赖
📌 (图钉)    :pushpin:    依赖固定到特定版本
👷 (工人)    :construction_worker:    添加 CI 构建系统
📈 (上升趋势图)    :chart_with_upwards_trend:    添加分析或跟踪代码
♻️ (回收)    :recycle:    重构代码
🐳 (鲸鱼)    :whale:    Docker 相关工作
🔨 (锤子)    :hammer:    重大重构
➕ (加号)    :heavy_plus_sign:    增加一个依赖
➖ (减号)    :heavy_minus_sign:    减少一个依赖
🔧 (扳手)    :wrench:    修改配置文件
🌐 (地球)    :globe_with_meridians:    国际化与本地化
✏️ (铅笔)    :pencil2:    修复错别字
💩 (粑粑)    :poop:    编写需要改进的错误代码
⏪ (后退)    :rewind:    还原更改
🔀    :twisted_rightwards_arrows:    Merge 分支
📦 (包裹)    :package:    更新编译文件或Package
👽 (外星人)    :alien:    由于外部API的更改而更新了代码
🚚 (卡车)    :truck:    移动或重命名文件
📄 (文件)    :page_facing_up:    添加或更新 Licence
💥 (隆隆声)    :boom:    引入重大变化
🍱 (便当)    :bento:    添加或更新 Assets
👌(OK)    :ok_hand:    由于代码评审更改而更新代码
♿️ (轮椅)    :wheelchair:    提高可访问性
💡 (电灯泡)    :bulb:    记录源代码
🍻 (啤酒)    :beers:    醉醺醺地编写代码
💬 (发言)    :speech_balloon:    更新文本和文字
🗃 (文件盒)    :card_file_box:    执行与数据库相关的更改
🔊 (巨大声响)    :loud_sound:    添加日志
🔇 (静音)    :mute:    移除日志
👥 (轮廓半身像)    :busts_in_silhouette:    添加贡献者
🚸 (儿童通过)    :children_crossing:    提高用户体验/可用性
🏗 (房屋)    :building_construction:    使建筑变化
📱 (苹果手机)    :iphone:    致力于响应式设计
🤡 (小丑)    :clown_face:    Mock 相关
🥚 (彩蛋)    :egg:    加入一个复活节彩蛋
🙈 (非礼勿视)    :see_no_evil:    添加或更新 .gitignore 文件
📸 (相机)    :camera_flash:    添加或更新快照
⚗️ (蒸馏器)    :alembic:    尝试新事物
🔍 (放大镜)    :mag:    SEO 提升
☸️ (达摩车轮)    :wheel_of_dharma:    Kubernetes 相关工作
🏷 (标签)    :label:    添加或更新 types (Flow, TypeScript)
🌱 (种子)    :seedling:    添加或更新种子文件
🚩 (旗帜)    :triangular_flag_on_post:    添加、更新或删除功能标志
💫 (头昏眼花)    :dizzy:    添加或更新动画和转换
贡献者: mankueng