1 Star 3 Fork 0

Jay_Ohhh / front-end notes

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
前端工作流.md 50.94 KB
一键复制 编辑 原始数据 按行查看 历史
Jay_Ohhh 提交于 2022-06-26 00:10 . update

ESlint、Prettier、TypeScript

npm i -D eslint @typescript-eslint/parser  eslint-plugin-react
npm i -D prettier eslint-config-prettier eslint-plugin-prettier
  • eslint: ESLint的核心代码

  • @typescript-eslint/parser:ESLint的解析器,用于解析typescript,从而检查和规范Typescript代码

  • @typescript-eslint/eslint-plugin:这是一个ESLint插件,包含了各类定义好的检测Typescript代码的规范,eslint-plugin-prettier已覆盖,无需安装

  • eslint-plugin-react:支持React语法的ESlint插件

  • prettier:prettier 插件的核心代码

  • eslint-config-prettier:解决ESLint中的样式规范和prettier中样式规范的冲突,以prettier的样式规范为准,使ESLint中的样式规范自动失效。

  • eslint-plugin-prettier:将prettier的规则作为ESLint规则来使用,因此需要安装 prettier。

    该插件先使用prettier对你的代码进行格式化,然后与格式化之前的代码进行对比,如果过出现了不一致,这个地方就会被prettier进行标记。

    接下来,我们需要在rules中添加,"prettier/prettier": "error",表示被prettier标记的地方抛出错误信息。然后借助eslint --fix将抛出error的地方进行fix。

    // 如果已在extends添加了插件的配置方案,则不需要重复添加 "prettier/prettier": "error"
    extends: [ 
        'plugin:prettier/recommended',
    ],
    rules:{
        // "prettier/prettier": "error"
    }
  • eslint-config-prettier

    Changed: All configs have been merged into one!

    To upgrade, change:

    {
        "extends": [
           "some-other-config-you-use",
           "prettier",
           "prettier/@typescript-eslint",
           "prettier/babel",
           "prettier/flowtype",
           "prettier/react",
           "prettier/standard",
           "prettier/unicorn",
           "prettier/vue"
        ]
    }

    Into:

    {
        "extends": [
           "some-other-config-you-use",
           "prettier"
        ]
    }

    If you use eslint-plugin-prettier, all you need is plugin:prettier/recommended:

    {
        "extends": [
           "some-other-config-you-use",
           "plugin:prettier/recommended"
        ]
    }

.eslintrc.js 常用配置

rules

module.exports = {
    parser:  '@typescript-eslint/parser', //定义ESLint的解析器
    // extends可省略前缀 eslint-config-
    // plugins可省略前缀 eslint-plugin-
    // extends使用插件导出的config,删掉插件前缀后加上前缀plugin:
    extends: [ // 继承的规则,后面覆盖前面
      'plugin:react/recommended', 
      'plugin:prettier/recommended',
    ],
    plugins: [
      // plugin:prettier/recommended 的 config 已包含以下plugins
      // "react",
      // '@typescript-eslint',
    ],
      parserOptions: {  // 解析器选项
      "ecmaVersion": 2019, // 支持 ES6 语法并不意味着同时支持新的 ES6 全局变量或类型(比如 Set 等新类型),需{ "env":{ "es6": true } }
      "sourceType": "module", //  "script" (默认),如果代码是 ECMAScript 模块,设置为"module"
      "ecmaFeatures": {
        "jsx": true
      }
    },
    env:{                          // 指定代码的运行环境
        browser: true,
          es6:true,
        node: true,
    },
    // 全局变量
    globals:{
       "var1": "writable",
       "var2": "readonly",
       "var3": "off", // 禁用
    },
  settings: {             
      // eslint-plugin-react要求:https://www.npmjs.com/package/eslint-plugin-react
      "react": {
        "pragma": "React",
        "version": "detect"
      }
    }, 
    // 扩展(或覆盖)规则
    rules: {},
    // 覆盖指定文件类型的rules
    overrides: [
      {
            "files": ["./src/**/*.html"],
            "rules": {

            }
        }
    ]
}

.prettierrc.js 常用配置

https://prettier.io/docs/en/options.html

可以查看vscode 设置->扩展-> prettier,中文解释

module.exports = {
  printWidth: 120, // 行长
  tabWidth: 2, // 缩进长度
  useTabs: false, // 使用空格代替tab缩进
  semi: true, // 结尾分号
  singleQuote: true, // 单引号,jsx忽略此选项,请使用 jsxSingleQuote
  quoteProps: 'preserve', // <as-needed|consistent|preserve> 是否给对象属性加引号 preserve:保留编程时的状态
  jsxSingleQuote: false, // jsx中使用单引号
  trailingComma: 'all', // <es5|none|all> 在对象或数组最后一个元素后面是否加逗号。all 尽可能尾随逗号
  bracketSpacing: true, // 在对象前后添加空格-eg: { foo: bar }
  bracketSameLine: false, // 多属性html、jsx标签的‘>’折行放置
  arrowParens: 'avoid', // <always|avoid> avoid:单参数箭头函数参数不使用圆括号
  endOfLine: 'lf', // 结束行形式
  embeddedLanguageFormatting: 'auto', // 对引用代码进行格式化
  overrides: [
    // {
    //   files: '*.test.js',
    //   options: {
    //     semi: true,
    //   },
    // },
    // {
    //   files: ['*.html', 'legacy/**/*.js'],
    //   options: {
    //     tabWidth: 4,
    //   },
    // },
  ],
};

.eslintignore

glob模式

ESLint总是忽略 /node_modules/*/bower_components/* 中的文件。

dist
!build/index.js

如果相比于当前工作目录下 .eslintignore 文件,你更想使用一个不同的文件,你可以在命令行使用 --ignore-path 选项指定它。例如,你可以使用 .jshintignore 文件,因为它有相同的格式:

eslint --ignore-path .jshintignore file.js

你也可以使用你的 .gitignore 文件:

eslint --ignore-path .gitignore file.js

任何文件只要满足标准忽略文件格式都可以用。记住,指定 --ignore-path 意味着任何现有的 .eslintignore 文件将不被使用。请注意,.eslintignore 中的匹配规则比 .gitignore 中的更严格。****

.prettierignore

如果没有用 prettier 或 插件去修改,可以不用该文件

dist
!build/index.js
node_moduls

extends和plugins的区别

plugins

plugin插件主要是为eslint新增一些检查规则,引入 plugin 可以理解为只是加载了插件,引入了额外的 自定义的rules。需要在 rules、extends 中定义后才会生效,如果没有则不生效。

plugin类似配置好的.eslintrc.js,因此这部分可以在extends里配置:plugin:config名称。

extends

extends可以看做是集成一个个配置方案的最佳实践。它允许配置多份别人配置好的.eslintrc.js。

允许 extends 配置多个模块,如果规则冲突,位置靠后的包将覆盖前面的。rules 中的规则相同,并且优先级恒定高于 extends,类似class的extends,支持多继承。

命名规范

extends 的模块名称以 eslint-config- 开头,例如 eslint-config-myconfig。也支持 npm 作用域模块,例如 : @scope/eslint-config 或 @scope/eslint-config-myconfig) 。使用的时候可以用全称(slint-config-myconfig),也可以用缩写(myconfig)。

如果 extends 引用了 plugin 的config,plugins 中会自动引入该plugin,即使 plugins 没有显式声明。

You have done it correctly.

For your example, there are 2 ways to do add typescript-eslint...

  • 1st way:
{
  parser: "@typescript-eslint/parser",
  parserOptions: { sourceType: "module" },
  plugins: ["@typescript-eslint"],
  extends: [],
  rules: {
    "@typescript-eslint/explicit-function-return-type": [
      "error",
      {
        allowExpressions: true
      }
    ]
  }
}
  • 2nd way:
{
  plugins: [],
  extends: ["plugin:@typescript-eslint/recommended"],
  rules: {
    "@typescript-eslint/explicit-function-return-type": [
      "error",
      {
        allowExpressions: true
      }
    ]
  }
}

The difference is...

  • 1st way:
    • parser, parserOptions and plugins are manually added,
    • Only @typescript-eslint/explicit-function-return-type is enforced.
  • 2nd way:
    • plugin:@typescript-eslint/recommended has automatically added parser, parserOptions and plugins. extends引用了plugin的config,即使plugins中没有显式引入,也会自动引入
    • @typescript-eslint/explicit-function-return-type is augmented and enforced.

rules

优先级最高

  • "off"0 - 关闭规则
  • "warn"1 - 开启规则,使用警告级别的错误:warn (不会导致程序退出)
  • "error"2 - 开启规则,使用错误级别的错误:error (当被触发的时候,程序会退出)

rules 属性可以做下面的任何事情以扩展(或覆盖)规则:

  • 启用额外的规则
  • 改变继承的规则级别而不改变它的选项:
    • 基础配置:"eqeqeq": ["error", "allow-null"] 三等于号
    • 派生的配置:"eqeqeq": "warn"
    • 最后生成的配置:"eqeqeq": ["warn", "allow-null"]
  • 覆盖基础配置中的规则的选项
    • 基础配置:"quotes": ["error", "single", "avoid-escape"]
    • 派生的配置:"quotes": ["error", "single"]
    • 最后生成的配置:"quotes": ["error", "single"]

基础配置:数组类型,派生配置:字符串类型,最后类型:覆盖数组第一项,"error"和"warn"是同一类型,都是放在数组第一位。如果派生配置是"eqeqeq": "always",则最后类型:"eqeqeq": ["warn", "always"]

基础配置:数组类型,派生配置:数组类型,最后类型:覆盖数组。

eslint文件内忽略规则

https://eslint.org/docs/user-guide/configuring/rules#using-configuration-comments-1

将/* eslint-disable */放置于文件最顶部
/* eslint-disable */
alert('foo');
在文件中临时禁止规则出现警告

将需要忽略的代码块用注释包裹起来

/* eslint-disable */
alert('foo');
/* eslint-enable */
对指定规则的启用或者禁用警告

将需要忽略的代码块用注释包裹起来

/* eslint-disable no-alert, no-console */
alert('foo');
console.log('bar');
/* eslint-enable no-alert, no-console */
对指定行禁用规则警告

此方法,有两种形式

alert('foo'); // eslint-disable-line

// or

// eslint-disable-next-line
alert('foo');
在指定行上禁用指定的某个规则
alert('foo'); // eslint-disable-line no-alert

// eslint-disable-next-line no-alert
alert('foo');
在某个特定的行上禁用多个规则
alert('foo'); // eslint-disable-line no-alert, quotes, semi

// eslint-disable-next-line no-alert, quotes, semi
alert('foo');

prettier 文件内忽略

https://prettier.io/docs/en/ignore.html

A JavaScript comment(注释) of prettier-ignore will exclude the next node in the abstract syntax tree from formatting.

package.json

{
	"script":{
		"lint": "eslint src --fix --ext .js,.ts,.jsx,.tsx"
	}
}

.vscode/settings.json

下载 vscode的ESlint、prettier 插件

添加 eslint、prettier 规则

vscode的ESlint 插件会优先执行.eslintrc.js文件的配置。prettier 同理。

editorconfig

vscode下载EditorConfig for VS Code插件

通配符

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

配置选项

root = true                         # 根目录的配置文件,编辑器会由当前目录向上查找,如果找到 `roor = true` 的文件,则不再查找

[*]                                 # 匹配所有的文件
indent_style = space                # 空格缩进
indent_size = 4                     # 缩进空格为4个
end_of_line = lf                    # 文件换行符是 linux 的 `\n`
charset = utf-8                     # 文件编码是 utf-8
trim_trailing_whitespace = true     # 不保留行末的空格
insert_final_newline = true         # 文件末尾添加一个空行
curly_bracket_next_line = false     # 大括号不另起一行
spaces_around_operators = true      # 运算符两遍都有空格
indent_brace_style = 1tbs           # 条件语句格式是 1tbs
spaces_around_brackets              # 表示括号和括号之间应有空格:无空格,仅在括号内,仅在括号外或在括号的两侧 (none,inside,outside,both)
max_line_length                     # 在指定的字符数后强制换行。off关闭此功能
[*.js]                              # 对所有的 js 文件生效
quote_type = single                 # 字符串使用单引号

[*.{html,less,css,json}]            # 对所有 html, less, css, json 文件生效
quote_type = double                 # 字符串使用双引号

[package.json]                      # 对 package.json 生效
indent_size = 2                     # 使用2个空格缩进

常用配置

# http://editorconfig.org
# root: 表明是最顶层的配置文件,发现设为true时,才会停止查找.editorconfig文件。
root = true

[*]
# 文件编码
charset = utf-8
# 使用单引号
quote_type = single
# 换行符格式
end_of_line = lf
# 缩进数量
indent_size = 2
# 缩进类型
indent_style = tab
# 行最大长度,要和.prettierrc.js保持一致
max_line_length = 120
# 删除末尾空格
trim_trailing_whitespace = true
# 结尾插入新行
insert_final_newline = true

[*.md]
trim_trailing_whitespace = false

[*.{html,css,less,scss,json}]       # 对所有 html,css,less,scss,json 文件生效
quote_type = double                 # 字符串使用双引号

# Makefile文件中要用tab缩进,这个确实是历史遗留问题,用空格缩进会出错。
[Makefile]
indent_style = tab

stylelint

CSS语法规则和风格检测工具

ignore文件

支持 glob 模式

同类型文件有顺序之分

例如 .gitignore

*.zip
!xxx.zip

xxx.zip不会被忽略

!xxx.zip
*.zip

xxx.zip会被忽略

git workflow

https://www.yuque.com/arvinxx-fe/workflow

https://juejin.cn/post/6934292467160514567

husky 或 yorkie 可以让我们向项目中方便添加git hooks。
lint-staged 是一个在git暂存文件上运行linters的工具,支持glob模式,只针对 add 的文件,不是所有文件。
Lint 这个单词的本意就是衣服上的棉球,通过 Linting 这个单词的意思就是去除代码中可能存在问题的意思。

Git Hooks

完整git hooks

整体的hooks非常多,但是我们用的比较多的其实只有两个

commit-msg

  • git commitgit merge 调用
  • 可以使用 git commit --no-verify 绕过

pre-commit

  • git commit 调用
  • 可以使用 git commit --no-verify 绕过
  • 在获取建议的提交日志消息和进行提交之前被调用

hook 脚本置于目录 ~/.git/hooks 中,以可执行文件的形式存在。

$ ls -lah .git/hooks
applypatch-msg.sample     pre-merge-commit.sample
commit-msg.sample         pre-push.sample
fsmonitor-watchman.sample pre-rebase.sample
post-update.sample        pre-receive.sample
pre-applypatch.sample     prepare-commit-msg.sample
pre-commit.sample         update.sample

另外 git hooks 可使用 core.hooksPath 自定义脚本位置。

# 可通过命令行配置 core.hooksPath
$ git config 'core.hooksPath' .husky

# 也可通过写入文件配置 core.hooksPath
$ cat .git/config
[core]
  ignorecase = true
  precomposeunicode = true
  hooksPath = .husky

在前端工程化中,husky 即通过自定义 core.hooksPath 并将 npm scripts 写入其中的方式来实现此功能。

安装配置husky

  1. 安装依赖 npm install husky --save-dev

  2. npm set-script prepare "husky install"
    npm run prepare
    // package.json 生成 "prepare":"husky install"
    {
        "scripts":{
            "prepare":"husky install"
        }
    }

    husky install运行时需要在同一目录下存在 .git ,即有一个git仓库,运行成功后根目录会新建一个 .husky 目录

    注意:set-script 需要 npm > 7.0 版本 可以使用 npm install -g npm 升版本。

  3. npx husky add .husky/pre-commit "npm test"

卸载uninstall

npm uninstall husky && git config --unset core.hooksPath

pre-commit + lint-staged自动修复格式

lint-staged 可以让你当前的代码检查只检查本次修改更新的代码,并在出现错误的时候,自动修复并推送

很多人不用 lint-staged,直接在 pre-commit 钩子里执行 npm run lint,这样有个问题,如果项目大了,你只修改一个文件,但它仍然会校验src下所有文件,就会导致提个代码慢的要死等半天。而 lint-staged 就能解决这个问题,它只会校验你修改的那部分文件。

每次它在你本地 commit 之前,校验你所提的内容是否符合你本地配置的 eslint 规则

  1. 符合规则,提交成功
  2. 不符合规则,他会自动执行 eslint --fix 尝试帮你自动修复 2.1 修复成功,则会自动帮你把修复好的代码提交; 2.2 修复失败,提示你错误,让你修复好才可以提交代码;
npm install lint-staged --save-dev
npx husky add .husky/pre-commit "npx lint-staged"

ESlint命令行

--ext 指定JavaScript文件扩展名-默认值:.js
--fix 自动修复问题

package.json

"scripts":{
    "lint": "eslint src --fix --ext .js,.jsx,.ts,.tsx",
},
"lint-staged": {
  "src/**":[
    "npm run lint"
  ]
}

"scripts":{
    "lint": "lint-staged",
}
"lint-staged": {
   "**/*.js": "eslint --ext .js",  
   "**/*.vue": "eslint --ext .vue",  // 可能需要安装eslint-plugin-vue
   "src/**/*.{js,vue}": [
     "prettier --write",
     "eslint  --fix",
     // "git add"  // 从v10.0.0开始,对最初暂存文件的任何新修改都将自动添加到提交中。如果您的任务以前包含git add步骤,请删除此步骤。自动行为可确保竞争条件较少,因为尝试同时运行多个git操作通常会导致错误。
   ]
},

或使用yorkie,而不使用husky

npm install yorkie --save-dev
"scripts":{
    "lint": "eslint src --fix --ext .js,.jsx,.ts,.tsx",
},
"gitHooks": {
    "pre-commit": "lint-staged"
},
"lint-staged": {
  "src/**":[
    "npm run lint"
  ]
}

Augular Commit 规范

<type>(<scope>): <subject> #header部分
// 空一行
<body>
// 空一行
<footer> 

Header是必需,BodyFooter 可以省略

Header

Header部分只有一行,包括三个字段:type(必需)、scope(可选)和subject(必需)。

type

用于说明本次commit的类型

允许使用下面个标识

  • feat:新功能(feature)
  • fix:修补bug
  • docs:文档(documentation)
  • style: 格式(不影响代码运行的变动)
  • refactor:重构(即不是新增功能,也不是修改bug的代码变动)
  • perf: 性能提升(提高性能的代码改动)
  • test:测试
  • build:影响构建系统或外部依赖项的更改(gulp,npm等),Changes to the build process or auxiliary tools and libraries
  • ci:更改CI配置文件和脚本,CI configuration related e.g. changes to k8s,docker configuration files.
  • chore:更改构建过程或辅助工具和库,例如文档生成
  • revert:撤退之前的commit。 Reverts a previous commit
Type Description
feat A new feature
fix A bug fix
docs Documentation only changes
style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
refactor A code change that neither fixes a bug nor adds a feature
perf A code change that improves performance
test Adding missing or correcting existing tests
chore Changes to the build process or auxiliary tools and libraries such as documentation generation

如果type为feat、fix、perf、revert,则该 commit 将肯定出现在 Change log 之中。其他情况(docs、chore、style、test)由你决定,要不要放入 Change log。

Revert

还有一种特殊情况,如果当前 commit 用于撤销以前的 commit,则必须以revert:开头,后面跟着被撤销 Commit 的 Header。

Body部分的格式是固定的,必须写成This reverts commit . 其中的hash是被撤销 commit 的 SHA 标识符。

revert: feat(pencil): add 'graphiteWidth' option

This reverts commit 667ecc1654a317a13331b17617d973392f415f02.

其中的hash是被撤销 commit 的 SHA 标识符。

scope

用于说明本次commit影响的范围,比如首页、详情页等。当更改影响多个范围时,可以使用*。

subject

用于说明本次commit的简短描述

  • 使用祈使句,现在时态:"change" not "changed" nor "changes"
  • 首字母小写
  • 句末不要句号
ticket

ticket 是 commit 相关联的 issue 编号。每一次 commit 都可以选择性的与某个 issue 关联。

比如在 message 中添加 #n,就可以与第 n 个 issue 进行关联。这个提交会作为一个 comment ,出现在编号为1的 issue 记录中。

:gitmoji: type(scope): subject(#1)

如果添加:

  • close #n

  • closes #n

  • closed #n

  • fix #n

  • fixes #n

  • fixed #n

  • resolve #n

  • resolves #n

  • resolved #n

比如以下 Commit 可以自动关闭第 4 个 issue 。

:bug: fix: Fix bug(#4)
Body

用于本次commit的详细描述,可以分成多行

使用祈使句,现在时态:"change" not "changed" nor "changes"

<type>(<scope>): <subject> 

- first changes...  #body部分
- second changes... #body部分
Footer

只用于下面两种情况

是否产生了破坏性修改

如果当前代码与上一个版本出现重大变动或不兼容,如版本升级、接口参数减少、接口删除、迁移等就是破坏性修改。则 Footer 部分以BREAKING CHANGE开头,后面空一格或两个换行符,然后描述变动、以及变动理由和迁移方法。

BREAKING CHANGE: 变动的描述\理由\迁移方法
关闭Issue

如果当前 commit 针对某个 issue,那么可以在 Footer 部分关闭这个 issue,也可以关闭多个 issue。填写格式如下:

Close #ISSUE_ID
Closes #ISSUE_ID, #ISSUE_ID

commitizen

使用Commitizen提交时,系统会提示您在提交时填写所有必需的提交字段。

adapter(适配器) : 更换 commitizen 命令行工具的交互方式插件。

npm install -D commitizen cz-customizable

安装成功以后,这意味着你可以不再使用 git commit 的方式提交你的代码。

在你执行完 git add 后,执行 npx cznpm run commit 替代 git commit,commitizen 就可以一步步帮助你完成提交了。

// package.json
{
    "scripts":{
        "commit":"cz"
    }
}
cz-customizable 适配器
// package.json
{
  "config": {
    "commitizen": {
      "path": "./node_modules/cz-customizable"
    },
  }
}

根目录新建 .cz-config.js

module.exports = {
  types: [
    { value: 'feat', name: '✨ feat:       A new feature' },
    { value: 'fix', name: '🔧 fix:        A bug fix' },
    { value: 'docs', name: '📝 docs:       Documentation only changes' },
    { value: 'style', name: '🎨 style:      Changes that do not affect the meaning of the code' },
    { value: 'refactor', name: '♻️  refactor:   A code change that neither fixes a bug nor adds a feature' },
    { value: 'perf', name: '🚀 perf:       A code change that improves performance' },
    { value: 'test', name: '🧪 test:       Adding missing or correcting existing tests' },
    { value: 'ci', name: '👷 ci:         CI configuration related e.g. changes to k8s,docker configuration files' },
    { value: 'build', name: '📦‍ build:      Changes to the build process or auxiliary tools' },
    { value: 'chore', name: '🍻 chore:      chroe' },
    { value: 'revert', name: '💊 revert:     Reverts a previous commit' }
  ],
  scopes: [
    { name: 'docs' },
    { name: 'ci' },
    { name: 'style' },
  ],

  // allowTicketNumber: false,
  // isTicketNumberRequired: false,
  // ticketNumberPrefix: 'TICKET-',
  // ticketNumberRegExp: '\\d{1,5}',

  // it needs to match the value for field type. Eg.: 'fix'
  // 当 type 为 fix 时,重写 scopes 的值
  /*  scopeOverrides: {
    fix: [
      {name: 'merge'},
      {name: 'style'},
      {name: 'e2eTest'},
      {name: 'unitTest'}
    ]
  },  */
  // override the messages, defaults are as follows
  messages: {
    type: "Select the type of change that you're committing:",
    scope: 'Denote the SCOPE of this change (optional):',
    // used if allowCustomScopes is true
    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):\n',
    footer: 'List any ISSUES CLOSED by this change (optional). E.g.: #31, #34:\n',
    confirmCommit: 'Are you sure you want to proceed with the commit above?',
  },
  allowCustomScopes: true,
  allowBreakingChanges: ['feat', 'fix'],
  subjectLimit: 100,
  // skip any questions you want
  // skipQuestions: ['body'],
  // limit subject length
  // breaklineChar: '|', // It is supported for fields body and footer.
  // footerPrefix : 'ISSUES CLOSED:'
  // askForBreakingChangeFirst : true, // default is false
}
cz-emoji 适配器
// package.json
{
    "config": {
    "commitizen": {
      "path": "./node_modules/cz-emoji"
    },
    "cz-emoji": {
      "types": [
        {
          "emoji": "✨",
          "code": "✨ feat",
          "description": "A new feature",
          "name": "feature"
        },
        {
          "emoji": "🔧",
          "code": "🔧 fix",
          "description": " A bug fix",
          "name": "fix"
        },
        {
          "emoji": "📝",
          "code": "📝 docs",
          "description": "Documentation only changes",
          "name": "docs"
        },
        {
          "emoji": "🎨",
          "code": "🎨 style",
          "description": "Changes that do not affect the meaning of the code",
          "name": "style"
        },
        {
          "emoji": "♻️",
          "code": "♻️ refactor",
          "description": " A code change that neither fixes a bug nor adds a feature",
          "name": "refactor"
        },
        {
          "emoji": "🚀",
          "code": "🚀 perf",
          "description": "A code change that improves performance",
          "name": "perf"
        },
        {
          "emoji": "🧪",
          "code": "🧪 test",
          "description": "**Adding missing or correcting existing tests",
          "name": "test"
        },
        {
          "emoji": "👷",
          "code": "👷 ci",
          "description": "CI configuration related e.g. changes to k8s,docker configuration files",
          "name": "ci"
        },
        {
          "emoji": "📦‍",
          "code": "📦‍ build",
          "description": "Changes to the build process or auxiliary tools",
          "name": "build"
        },
        {
          "emoji": "🍻",
          "code": "🍻 chore",
          "description": "chore",
          "name": "chore"
        },
        {
          "emoji": "💊",
          "code": "💊 revert",
          "description": "Reverts a previous commit",
          "name": "revert"
        }
      ],
      "scopes": [
        "docs",
        "ci",
        "style"
      ],
      "subjectMaxLength": 50
    }
  }
}

我们也可以使用 vscode 插件 git-commit-plugin 实现上述的功能

Commitlint

Lint commit messages.

@commitlint/config-conventional
npm i -D @commitlint/cli @commitlint/config-conventional
module.exports = {
  extends: ['@commitlint/config-conventional'],
  /*
   * Any rules defined here will override rules from extends-config
   */
  // 0 disables the rule. For 1 it will be considered a warning for 2 an error.
  // always|never: never inverts(取反) the rule.
  rules: { // 要和 .cz-config.js 的 type 保持完全一致
    "type-enum": [2, 'always', ['feat', 'fix', 'docs', 'style', 'refactor', 'perf', 'test', 'ci', 'build', 'chore', 'revert']]
  },
}
emoji
npm i -D @commitlint/cli commitlint-config-git-commit-emoji

commitlint.config.js

数组里的type类型(包括emoji)要和 .cz-config.js 的 value 保持一致

module.exports = {
  extends: ['git-commit-emoji'], // commitlint-config-git-commit-emoji 可省略前缀 commitlint-config-
  /*
   * Any rules defined here will override rules from extends-config
   */
  rules: { // 要和 .cz-config.js 的 type 保持完全一致
    "type-enum": [2, 'always', ['✨ feat', '🔧 fix', '📝 docs', '🎨 style', '♻️  refactor', '🚀 perf', '🧪 test', '👷 ci', '📦‍ build', '🍻 chore', '⏪️ revert']]
  },
}

添加 commitlint 的 hook 到 husky 中,commit-msg 时进行校验

# Add hook
npx husky add .husky/commit-msg 'npx --no -- commitlint --edit "$1"'
# Sometimes above command doesn't work in some command interpreters
# You can try other commands below to write npx --no -- commitlint --edit $1
# in the commit-msg file.
npx husky add .husky/commit-msg \"npx --no -- commitlint --edit '$1'\"
# or
npx husky add .husky/commit-msg "npx --no -- commitlint --edit $1"

# or
yarn husky add .husky/commit-msg 'yarn commitlint --edit $1'
  • --no:如果本地项目依赖项中不存在任何请求的包,则将它们安装到npm缓存中的一个文件夹中,该文件夹将添加到执行过程中的PATH环境变量中。
  • commitlint --edit:read last commit message from the specified file or fallbacks to ./.git/COMMIT_EDITMSG.
  • $1:传递给该shell脚本的第一个参数。

CHANGELOG

semantic-release

semantic-release自动化了整个软件包发布工作流,包括:确定下一个版本号、生成release说明和发布软件包。(打tag、release、修改package.json version、生成CHANGLOG.md,npm publish)

npm i -D semantic-release @semantic-release/changelog @semantic-release/git conventional-changelog-conventionalcommits

semantic-release 的依赖包,无须再下载:

"@semantic-release/commit-analyzer"
"@semantic-release/release-notes-generator"
"@semantic-release/npm"
"@semantic-release/github"

文件夹根目录创建 .releaserc.js

// semantic-release 配置
module.exports = {
  branches: [ // 指定在哪个分支下要执行发布操作
    'master',
    {
      name: 'beta', prerelease: true // beta 分支为 预发布 分支 2.0.0-beta.1, 2.0.0-beta.2, etc...
    }
  ],
  plugins: [ // 插件有顺序要求
    [
      '@semantic-release/commit-analyzer', // semantic-release plugin to analyze commits with conventional-changelog
      {
        preset: "conventionalcommits", // 需安装 conventional-changelog-conventionalcommits
        // 没列在这里或者不在 default release rules(https://github.com/semantic-release/commit-analyzer/blob/master/lib/default-release-rules.js) 里的规则,不会进行 release
        "releaseRules": [
          { type: 'feat', release: 'minor' },
          { type: 'fix', release: 'patch' },
          { type: 'perf', release: 'patch' },
          { type: 'style', release: 'patch' },
          { type: 'revert', release: 'patch' },
          { breaking: true, release: 'major' }, // https://www.conventionalcommits.org/en a commit that has a footer BREAKING  CHANGE: 
        ],
        "parserOpts": {
          "noteKeywords": ["BREAKING CHANGE", "BREAKING CHANGES"] // the commits that contains BREAKING CHANGE, BREAKING CHANGES or BREAKING in their body will be considered breaking changes(by default the angular preset checks only for BREAKING CHANGE and BREAKING CHANGES)
        }
      }
    ],
    ['@semantic-release/release-notes-generator', // semantic-release plugin to generate changelog content(非文件) with conventional-changelog
      {
        preset: "conventionalcommits",
        presetConfig: { // Conventional Changelog Configuration Spec, https://github.com/conventional-changelog/conventional-changelog-config-spec
          "types": [
            { "type": "feat", "section": "✨ Features | 新功能" },
            { "type": "fix", "section": "🛠️ Bug Fixes | Bug 修复" },
            { "type": "docs", "section": "📝 Documentation | 文档" },
            { "type": "style", "section": "🎨 Styles | 风格" },
            { "type": "refactor", "section": "♻️ Code Refactoring | 代码重构" },
            { "type": "perf", "section": "🚀 Performance Improvements | 性能优化" },
            { "type": "test", "section": "📸 Tests | 测试" },
            { "type": "build", "section": "📦‍ Build System | 打包构建" },
            { "type": "ci", "section": "🐳 Continuous Integration | CI 配置" },
            { "type": "chore", "section": "🍮 Chore | 构建/工程依赖/工具" },
            { "type": "revert", "section": "💊 Revert | 回退" }
          ]
        }
      }
    ],
    [
      '@semantic-release/changelog', // semantic-release plugin to create or update a changelog file
      {
        changelogFile: 'CHANGELOG.md'
      }
    ],
    // 需要NPM_TOKEN,命令行cross-env、或 .npmrc 文件 或在 github actions中定义
    // 根据commit type,Update the package.json version
    '@semantic-release/npm',  // semantic-release plugin to publish a npm package.
    // 需要GITHUB_TOKEN
    [
      '@semantic-release/github', // 创建 github-release
      {
        labels: false // The labels to add to the issue created when a release fails. Set to false to not add any label.
      }
    ],
    [
      '@semantic-release/git', // semantic-release plugin to commit release assets to the project's git repository.
      {
        assets: ['CHANGELOG.md', 'package.json'], // release成功后,远程仓库的CHANGELOG.md、package.json 会更新,本地仓库需要 git pull 拉取
        message: "🍺 chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
      }
    ]
  ]
}

conventional-changelog-emoji-config 能够将 :emoji: type: … 表情 type: … 格式进行 type 分类。

但不支持 @semantic-release/release-notes-generator中的presetConfig,因为conventional-changelog-emoji-config的配置不符合conventional-changelog-config-spec 规范

根目录新建 .changelogrc.js

// conventional-changelog-emoji-config 配置
module.exports = {
  displayTypes: ['build', 'ci', 'feat', 'fix', 'docs', 'style', 'refactor', 'perf', 'test', 'chore', 'revert',]
}
standard-version

使用 semantic-release 就可以满足我们发布和changlog的需求。

standard-version 是一款遵循语义化版本( semver)和 commit message 标准规范 的版本和 changlog 自动化工具。通常情况下,我们会在 master 分支进行如下的版本发布操作:

  1. git pull origin master

  2. 根据 pacakage.json 中的 version 更新版本号,更新 changelog

  3. git add -A, 然后 git commit

  4. git tag 打版本操作

  5. push 版本 tag 和 master 分支到仓库

其中2,3,4则是 standard-version 工具会自动完成的工作,配合本地的 shell 脚本,则可以自动完成一系列版本发布的工作了。

npm i -D standard-version conventional-changelog-emoji-config
  • conventional-changelog-emoji-configconventional-changelog-gitmoji-config —— emoji 风格的 conventional-changelog 规则,后面会讲到

package.json

{
  "scripts": {
     "release": "standard-version",
     "release-major": "standard-version --release-as major",
     "release-minor": "standard-version --release-as minor",
     "release-patch": "standard-version --release-as patch"
  }
}

版本构成

https://semver.org/lang/zh-CN/

版本号 major.minor.patch

  • major: 当你发了一个含有 Breaking Change 的 API
  • minor: 当你新增了一个向后兼容的功能时
  • patch: 当你修复了一个向后兼容的 Bug

命令说明

  • release: 按照standard-version默认规则升级版本号
  • release-major: 升级major版本号
  • release-minor: 升级minor版本号
  • release-patch: 升级patch版本号

standard-version 默认版本升级规则

  • feature 会更新 minor
  • bug fix 会更新 patch
  • BREAKING CHANGES 会更新 minor

执行以下命令,就会根据你的commit信息自动生成 CHANGELOG.md 文件。

npm run release

当你的commit type是 feat和fix的时候执行这个命令,它会自增 package.json 版本号。

根目录新建 versionrc.js

module.exports = {
  "types": [
    { "type": "feat", "section": "✨ Features | 新功能" },
    { "type": "fix", "section": "🔧 Bug Fixes | Bug 修复" },
    { "type": "docs", "section": "📝 Documentation | 文档" },
    { "type": "style", "section": "🎨 Styles | 风格" },
    { "type": "refactor", "section": "♻️ Code Refactoring | 代码重构" },
    { "type": "perf", "section": "🚀 Performance Improvements | 性能优化" },
    { "type": "test", "section": "📸 Tests | 测试" },
    { "type": "build", "section": "📦‍ Build System | 打包构建" },
    { "type": "ci", "section": "🐳 Continuous Integration | CI 配置" },
    { "type": "chore", "section": "🍮 Chore | 构建/工程依赖/工具" }, 
    { "type": "revert", "section": "💊 Revert | 回退" }
  ]
}
配置说明
"types": [
    {"type": "feat", "section": "Features"},
    {"type": "fix", "section": "Bug Fixes"},
    {"type": "chore", "hidden": true},
    {"type": "docs", "hidden": true},
    {"type": "style", "hidden": true},
    {"type": "refactor", "hidden": true},
    {"type": "perf", "hidden": true},
    {"type": "test", "hidden": true}
]

types

name type required default description
type string ✔️ N/A A string used to match <type>s used in the Conventional Commits convention.
section string ✖️ N/A The section where the matched commit type will display in the CHANGELOG.
hidden boolean ✖️ N/A Set to true to hide matched commit types in the CHANGELOG.
预设

standard-version 默认的预设是 angular ,像之前自定义表情那种commmit,规范和angular不一样,它读不到对应的type生成的更改日志是没法分类的。根据 :emoji: type: … 格式生成 CHANGELOG 的工具:

conventional-changelog-emoji-config

conventional-changelog-gitmoji-config

npm i -D conventional-changelog-emoji-config
npx husky add .husky/pre-push "npm run release"
{
  "scripts": {
    "release": "standard-version --preset emoji-config",
    "release-major": "standard-version --preset emoji-config --release-as major",
    "release-minor": "standard-version --preset emoji-config --release-as minor",
    "release-patch": "standard-version --preset emoji-config --release-as patch"
  }
}

可以省略前缀 conventional-changelog-

根目录新建 .changelogrc.js

module.exports = {
  // changlog 可以显示的type
  displayTypes: ['build', 'ci', 'feat', 'fix', 'docs', 'style', 'refactor', 'perf', 'test', 'chore', 'revert',]
}
  • commitlint.config.js.cz-config.js type 类型要一致(包括emoji)
  • .versionrc.js.changelogrc.js type 类型要一致
  • 四个文件的type(例如 build ,但不包括emoji)要一致

检测依赖安全

如何确保所有 npm install 的依赖都是安全的?

当有一个库偷偷在你的笔记本后台挖矿怎么办?

比如,不久前一个周下载量超过八百万的库被侵入,它在你的笔记本运行时会偷偷挖矿。

Audit

Audit,审计,检测你的所有依赖是否安全。npm audit/yarn audit 均有效。

通过审计,可看出有风险的 package、依赖库的依赖链、风险原因及其解决方案。

https://docs.npmjs.com/cli/v6/commands/npm-audit

$ npm audit
┌───────────────┬──────────────────────────────────────────────────────────────┐
│ high          │ Regular Expression Denial of Service in trim                 │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Package       │ trim                                                         │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Patched in>=0.0.3                                                      │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Dependency of │ @mdx-js/loader                                               │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Path          │ @mdx-js/loader > @mdx-js/mdx > remark-mdx > remark-parse >
│               │ trim                                                         │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ More info     │ https://www.npmjs.com/advisories/1002775                     │
└───────────────┴──────────────────────────────────────────────────────────────┘
76 vulnerabilities found - Packages audited: 1076
Severity: 49 Moderate | 27 High
✨  Done in 4.60s.

仅针对生产依赖审计

$ npm audit production

$ yarn audit dependencies

通过 npm audit fix 可以自动修复该库的风险,原理就是升级依赖库,升级至已修复了风险的版本号。

$ npm audit fix

yarn audit 无法自动修复,需要使用 yarn upgrade 手动更新版本号,不够智能。

synk是一个高级版的 npm audit,可自动修复,且支持 CICD 集成与多种语言。

$ npx snyk

$ npx wizard

github dependabot

当dependabot检测到有问题时,会自动向该仓库提交 PR。

开启 Dependabot
  • 开启方式比较简单,仅需将 dependabot.yml 配置文件放入仓库的 .github 目录中即可开启。之后 Dependabot 就会自动提交 PR 来更新您项目中的依赖项了。

  • 您也可以在 GitHub 页面上进行操作,在仓库页面通过 Insights -> Dependency graph -> Dependabot -> Enable Dependabot 路径即可开启,之后就可以点击 Create config file 来创建配置文件了。

配置 dependabot.yml

官方文档

例子

# Basic dependabot.yml file with
# minimum configuration for two package managers

version: 2
updates:
  # Enable version updates for npm
  - package-ecosystem: "npm"
    # Look for `package.json` and `lock` files in the `root` directory
    directory: "/"
    # Check the npm registry for updates every day (weekdays)
    schedule:
      interval: "daily"

  # Enable version updates for Docker
  - package-ecosystem: "docker"
    # Look for a `Dockerfile` in the `root` directory
    directory: "/"
    # Check for updates once a week
    schedule:
      interval: "weekly"

自动更新版本号

使用 npm outdated 虽能发现需要升级版本号的 package,但仍然需要手动在 package.json 更改版本号进行升级。

此时推荐一个功能更强大的工具 npm-check-updates,比 npm outdated 强大百倍。

npm-check-updates -u,可自动将 package.json 中待更新版本号进行重写。

升级 [minor] 小版本号,有可能引起 Break Change,可仅仅升级到最新的 patch 版本。

$ npx npm-check-updates --target patch

patch-package

给npm包在本地环境打补丁

假设 lodash 有一个 Bug,影响线上开发,应该怎么办?

答: 三步走。

  1. 在 Github 提交 Pull Request,修复 Bug,等待合并
  2. 合并 PR 后,等待新版本发包
  3. 升级项目中的 lodash 依赖

很合理很规范的一个流程,但是它一个最大的问题就是,太慢了,三步走完黄花菜都凉了。

此时可直接上手修改 node_modules 中 lodash 代码,并修复问题!

新问题:node_modules 未纳入版本管理,在生产环境并没有用。请看流程

  1. 本地修改 node_modules/lodash,本地正常运行 ✅
  2. 线上 npm i lodash,lodash 未被修改,线上运行失败 ❌

此时有一个简单的方案,临时将修复文件纳入工作目录,可以解决这个问题

  1. 本地修改 node_modules/lodash,本地正常运行 ✅
  2. 将修改文件复制到 ${work_dir}/patchs/lodash 中,纳入版本管理
  3. 线上 npm i lodash,并将修改文件再度复制到 node_modules/lodash 中,线上正常运行 ✅

但此时并不是很智能,且略有小问题,演示如下:

  1. 本地修改 node_modules/lodash,本地正常运行 ✅
  2. 将修改文件复制到 ${work_dir}/patchs/lodash 中,纳入版本管理 ✅
  3. 线上 npm i lodash,并将修改文件再度复制到 node_modules/lodash 中,线上正常运行 ✅
  4. 两个月后升级 lodash,该问题得以解决,而我们代码引用了 lodash 的新特性
  5. 线上 npm i lodash,并将修改文件再度复制到 node_modules/lodash 中,由于已更新了 lodash,并且依赖于新特性,线上运行失败 ❌

此时有一个万能之策,那就是 patch-package

想要知道 patch-package 如何解决上述问题,请先了解下它的用法,流程如下

$ npm i -D patch-package
# 在 package.json scripts 增加命令 "postinstall":"patch-package"

# 修改 lodash 的一个小问题
$ vim node_modules/lodash/index.js

# 对 lodash 的修复,生成一个 patch 文件,位于 {workRoot}/patches/lodash+4.17.21.patch
$ npx patch-package lodash

# 将修复文件提交到版本管理之中
$ git add patches/lodash+4.17.21.patch
$ git commit -m "fix 一点儿小事 in lodash"

# 当其他同事拉到代码如何应用补丁呢?让同事执行 npm i

# 此后的命令在生产环境或 CI 中执行
# 此后的命令在生产环境或 CI 中执行
# 此后的命令在生产环境或 CI 中执行

# 在生产环境装包
$ npm i

# 大功告成!

patch-package只会当前版本生效

如果你装的包版本和你之前生成的补丁中记录的版本不一样,npx patch-package会直接报错**ERROR** Failed to apply patch for package xxxx at path

lock-file

web应用的生产环境构建需要 lock-file 锁定依赖版本

当我们在 npm i 某个依赖时,默认的版本号是最新版本号 ^1.2.3,以 ^ 开头可最大限度地使用新特性,但是某些库不遵循该依赖可能出现问题。

^1.2.3 指 >=1.2.3 <2.0.0

一个问题: 当项目中没有 lock 文件时,生产环境的风险是如何产生的?

演示风险过程如下:

  1. pkg 1.2.3: 首次在开发环境安装 pkg 库,为此时最新版本 1.2.3dependencies 依赖中显示 ^1.2.3,实际安装版本为 1.2.3
  2. pkg 1.19.0: 在生产环境中上线项目,安装 pkg 库,此时最新版本为 1.19.0,满足 dependencies 中依赖 ^1.2.3 范围,实际安装版本为 1.19.0但是 pkg 未遵从 semver 规范,在此过程中引入了 Breaking Change,如何此时 1.19.0 有问题的话,那生产环境中的 1.19.0 将会导致 bug,且难以调试

而当有了 lock 文件时,每一个依赖的版本号都被锁死在了 lock 文件,每次依赖安装的版本号都从 lock 文件中进行获取,避免了不可测的依赖风险。

配置文件为什么通常是rc结尾

https://stackoverflow.com/questions/11030552/what-does-rc-mean-in-dot-files

“rc”后缀可以追溯到Unix的祖父母CTSS。它有一个名为“runcom”的命令脚本功能。早期的Unix使用“rc”作为操作系统引导脚本的名称,以此向CTSS runcom致敬。

使用 TypeDoc 帮助我们自动生成文档并部署到 GitHub pages。

1
https://gitee.com/Jay_Ohhh/front-end-notes.git
git@gitee.com:Jay_Ohhh/front-end-notes.git
Jay_Ohhh
front-end-notes
front-end notes
master

搜索帮助