依赖包
|
描述
|
@babel/core
|
调用babel api进行转码的核心库,babel-loader的核心依赖
|
babel-loader
|
webpack编译js文件的loader
|
3. html文件输出
npm i html-webpack-plugin --save-dev
根目录新建index.html和favicon.ico
src目录新增入口文件(index.js)
document.querySelector('#root').innerHTML='<h1>Hello,Javascript</h1>';
2.webpack相关基础配置
根目录下新建config目录
统一管理node环境所有目录(paths.js)
const path = require('path');
const fs = require('fs');
const appDirectory = fs.realpathSync(process.cwd());
const resolveApp = (relativePath) => path.resolve(appDirectory, relativePath);
module.exports = {
rootDir: appDirectory,
appIndex: resolveApp('src/index'),
appSrc: resolveApp('src'),
appDist: resolveApp('dist'),
appHtml: resolveApp('index.html'),
appPages: resolveApp('src/pages'),
appStatic: resolveApp('src/static'),
appUtil: resolveApp('src/util'),
appInterfaces: resolveApp('src/interfaces'),
apis: resolveApp('src/apis'),
appNodeModules: resolveApp('node_modules'),
基础配置文件(webpack.config.js)
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const paths = require('./paths');
module.exports = function (webpackEnv) {
const host = process.env.HOST || '0.0.0.0';
const isEnvDevelopment = webpackEnv === 'development';
const isEnvProduction = webpackEnv === 'production';
let webpackConfig = {
mode: isEnvProduction ? 'production' : 'development',
target: 'web',
entry:{
app: paths.appIndex
output: {
path: paths.appDist,
publicPath: '/',
filename: `static/js/[name]${isEnvProduction ? '.[contenthash:8]' : ''}.js`,
clean: true,
module: {
rules: [
test: /\.jsx?$/,
loader: 'babel-loader',
include: paths.appSrc,
plugins: [
new HtmlWebpackPlugin(
inject: true,
filename: 'index.html',
template: paths.appHtml,
favicon: 'favicon.ico',
devServer: {
host,
allowedHosts: 'all',
compress: true,
port: 9006,
historyApiFallback: true,
open: false,
hot: true,
return webpackConfig;
用于启动开发环境的配置文件(start.js)
const configFactory = require('./webpack.config');
const config = configFactory('development');
module.exports = config;
至此,最基础的版本已完成,目录如下:
├─ .gitignore
├─ config
│ ├─ paths.js
│ ├─ start.js
│ └─ webpack.config.js
├─ favicon.ico
├─ index.html
├─ package-lock.json
├─ package.json
└─ src
└─ index.js
运行webpack server --config config/start.js
可在浏览器看到
3.加入React
1. react相关依赖
npm i react react-dom --save
依赖包 | 描述 |
---|
react | react的核心库 |
react-dom | 从react中剥离的涉及DOM操作的部分 |
react
:react的核心思想是虚拟DOM;主要包括:React.createElement生成虚拟DOM,Component相关的React.createClass,React.Component,React.Children
react-dom
:v0.14+从react核心库中拆离;负责浏览器和DOM操作。还有一个兄弟库react-native,用来编写原生应用。
react-dom主要包括方法有:
方法 | 描述 |
---|
render | 将虚拟DOM渲染到真是DOM中 |
hydrate | 服务端渲染,避免白屏 |
unmountComponentAtNode | 从 DOM 中移除已装载的 React 组件 |
findDOMNode | 访问原生浏览器DOM |
createPortal | 渲染react子元素到制定的DOM中 |
2. react根组件
项目入口文件(index.js)
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(<App />, document.getElementById('root'));
项目根组件(App.js)
import { hot } from 'react-hot-loader/root';
import React, { Component } from 'react';
class App extends Component {
render() {
return <h1>hello-react</h1>;
export default hot(App);
3. babel预设解析react
什么是preset,可以理解为是一系列plugin的集合
npm i @babel/preset-env @babel/preset-react @babel/plugin-proposal-class-properties --save-dev
presets 使用babel需要安装的一组插件(也就是支持哪些语法转换成es5)
presets | 描述 |
---|
@babel/preset-env | ES语法分析包,根据运行环境对代码做相应的编译 |
@babel/preset-react | 编译react语法 |
@babel/plugin-proposal-class-properties | 支持class语法插件 |
像上面的react语法如果没有@babel/preset-react
,则会报错↓
babel7发布后的变化:
弃用年份preset和babel-preset-stage-x(stage-0/1/2/3/4)
@babel/preset-env
替换之前所有的babel-prese-es20xx
和babel-preset-stage-x,提案的5个阶段,0表示只是一个想法,4表示已完成
重命名:Scoped Packages(@babel/x)
解决:命名困难;是否被他人占用;区分官方包名
重命名:-proposal-
任何提案都将被以 -proposal- 命名来标记他们还没有在 JavaScript 官方之内。
所以 @babel/plugin-transform-class-properties 变成 @babel/plugin-proposal-class-properties,当它进入 Stage 4 后,会把它命名回去。
Babel 几乎可以编译所有最新的 JavaScript 语法(例如箭头函数、const、对象解构),但对于 APIs 来说却并非如此。例如: Promise、Set、Map 等新增对象,Object.assign、Object.entries等静态方法。这时候就需要polyfill来解决了。
APIs-polyfill方案
为了达成使用这些新API的目的,社区又有2个实现流派:@babel/polyfill和babel-runtime+babel-plugin-transform-runtime
@babel/polyfill用来处理APIs的兼容问题;通过修改全局变量的方式实现。
如:[1,2,3].includes(1)
如果不进行兼容处理,打包出来就是直接是调用Array原型includes
方法,在低版本浏览器可能会有兼容问题。
安装和配置:
npm install --save core-js@3 @babel/polyfill
"presets": [
"@babel/preset-env",
"modules": false,
+ "useBuiltIns": "usage",
+ "corejs": 3
下面两张图可以看出,core-js通过修改Array的原型对象对数组扩展;
这样会带来一个问题,如果第三方库也修改了原型方法,可能会导致冲突
注意:如果没有安装core-js的话,打包会报错
问题2:babel转义js代码需要,会在每个bundle
中生成工具函数,如果bundle
较多时,会额外增加代码体积(这个问题不是使用core-js导致的问题,是babel转义时产生的)
辅助函数代码如下:
为了解决以上两个问题,有了方案2
@babel/plugin-transform-runtime
@babel/plugin-transform-runtime
插件作用是,如发现源码中有Promise、Map等新类型时自动按需加入合适的polyfill以解决兼容问题(不需要每个模块手动导入import Promise from 'babel-runtime/core-js/xxx
')。同时解决了使用@babel/polyfill
导致的污染全局变量和辅助代码复用的问题。需要用到两个依赖包@babel/runtime-corejs3
:加载必要的新API;@babel/runtime
:提供帮助程序。
npm install --save-dev @babel/plugin-transform-runtime
npm install --save @babel/runtime @babel/runtime-corejs3
"plugins": [
"@babel/plugin-transform-runtime",
"corejs": 3
这里的路径最好用绝对路径,在项目中就不会存在替换后的路径问题了
import bridge from '@util/bridge';
extensions(扩展名)
在导入路径中如果没有文件后缀,webpack会自动加上后缀尝试查找文件是否存在,resolve.extensions用于配置在尝试过程中用到的后缀列表,默认是:
extensions: ['.js', '.json']
这里由于新加了react,所以需要修改默认配置为
extensions: ['.js', '.jsx', '.json']
4.资源文件配置
1.样式(less,scss,css)文件配置
项目实际开发中还需要样式文件
细节可以参考另一篇webpack配置- 样式类,下面是loader配置
1. 安装依赖包
npm i node-sass sass-loader postcss-loader autoprefixer css-loader style-loader mini-css-extract-plugin --save-dev
2.新增loader和plugins
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const getStyleLoaders = ({modules = true}) =>
isEnvProduction ? MiniCssExtractPlugin.loader : 'style-loader',
loader: 'css-loader',
options: {
modules: modules ? {
localIdentName: '[name]-[local]-[hash:base64:5]',
} : false,
'postcss-loader',
'sass-loader',
].filter(Boolean);
module: {
rules:[
test: /\.(sc|c)ss$/,
use: getStyleLoaders({ modules:true }),
include: paths.appSrc,
plugins:[
isEnvProduction && new MiniCssExtractPlugin({
filename: 'static/css/[name].[contenthash:10].css',
].filter(Boolean)
3.postcss配置
//postcss.config.js
module.exports = {
plugins: autoprefixer: {}
4.添加兼容浏览器列表(package.json)
"browserslist": ["iOS >= 8","> 1%","Android > 4","last 5 versions"]
5.项目入口引入全局样式文件
import './assets/styles/app.less';
重新启动项目,可以看到样式已经插入head中
2.图片和字体类
参考:webpack5内置静态资源构建能力
loader新增:
test: /\.(gif|png|jpe?g|svg)(\?.*)?$/,
type: 'asset',
generator: {
filename: 'static/img/[name][ext]?[hash]',
parser: {
dataUrlCondition: {
maxSize: 10 * 1024,
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
type: 'asset/resource',
include: [paths.appSrc],
generator: {
filename: 'fonts/[name].[hash:7][ext]',
3.模块热替换
参考:模块热替换方案-HotModuleReplacementPlugin(HMR)
npm install react-router-dom --save
首先当然是依赖包,为什么是react-router-dom
而不是react-router
;
在react-router
4.0.0+版本;官方提供了一套基于react-router
封装的(包含react-router
所有内容)用于运行在浏览器端的react-router-dom
和用于开发reactNative
应用的react-router-native
。
React Router中有三类组件
router组件(BrowserRouter,HashRouter)
route matching组件(Route,Routes)
navigation组件(Link)
Router设置
基于React Router的web应用,根组件应该是一个router组件;react-router-dom提供了两种路由模式;
<BrowserRouter>
:使用HTML5 提供的 history API (pushState, replaceState 和 popstate 事件),动态展示组件
<HashRouter>
:通过监听window.location.hash的改变,动态展示组件
最直观的感受就是BrowserRouter不会再浏览器URL上追加#,为了地址的优雅当然首选这种模式,但如果是静态服务器,那就只能使用备选方案HashRouter了。
Route路由匹配
React Router
提供两个匹配路由的组件<Route>
和 <Routes>
路由匹配是通过将组件的path属性与当前location
的pathname
进行匹配,当一个组件匹配了,则展示
我们可以在组件树的任何位置放置<Route>
组件。但是更常见的情况是将几个<Route>
写在一起。<Routes>
组件可以用来将多个<Route>
“包裹”在一起。
useRoutes JavaScript 对象的配置
新建路由组件(src/routes/index.js)。
<BrowserRouter>
<Routes>
<Route path="/" element={<Home/>} />
<Route path="/shopping" element={<Shopping/>} />
<Route path="/detail/:id" element={<Detail/>} />
{/* 如果上面的Route的路径都没有匹配上,则 <NoMatch>被渲染,我们可以在此组件中返回404 */}
<Route path="*" element={<NoMatch/>} />
</Routes>
</BrowserRouter>
useSearchParams
获取路由参数
Link 导航组件
//to: string
<Link to="/about?tab=name" />
//to: object
to={{
pathname: "/courses",
search: "?sort=name",
hash: "#the-hash",
state: { fromDashboard: true } //传入下一个页面额外的state参数
useNavigate
以编程方式导航
在不同的React版本中,使用方法稍有差异,下面总结了各版本的使用方法
ReactRouter 6 (using hooks and React >16.8)
你可以使用 useNavigate
hook 在函数组件中使用编程时导航,ReactRouter5使用useHistory
import { useNavigate } from "react-router-dom";
function HomeButton() {
let navigate = useNavigate();
navigate('/some/path', { replace: true })
React-Router 4.0.0+
在4.0+, 在组件中使用props中的 history 对象
class Example extends React.Component {
React-Router 3.0.0+
在3.0+, 在组件中使用props中的 router.
class Example extends React.Component {
5. 状态管理
npm install redux react-redux --save
redux
是一个“可预测的状态容器”,参考了flux
的设计思想,
Redux三大原则
单一数据源
一个应用只有唯一的数据源,好处是整个应用的状态都保存在一个对象中,这样可以随时去除整个应用的状态进行持久化;当然如果一个复杂项目也可以用Redux
提供的工具函数combineReducers
对数据进行拆分管理。
状态是只读的
React
并不会显示定义store,而使用Reducer返回当前应用的状态(state),这里并不是修改之前的状态,而是返回一个全新的状态。
React提供的createStore方法会根据Reducer生成store,最后可以用store.disputch方法修改状态。
状态修改均由纯函数完成
这使得Reducer里对状态的修改变得简单、纯粹
Redux核心API
Redux的核心是一个store,这个store由Redux提供的createStore(reducers[,initalState])
方法生成。
reducers必传参数用来响应由用户操作产生的action,reducer本质是一个函数,其函数签名为reducer(previousState,action)=>newState
;reducer的职责就是根据previousState和action计算出新的state;在实际应用中reducer在处理previousState时,需要有一个非空判断。很显然,reducer第一次执行的时候没有任何previousState,而reducer的职责时返回新的state,因此需要在这种特殊情况返回一个定义好的initalState。
与React绑定
Redux 官方提供的 React 绑定-react-redux。这是一种前端框架或类库的架构趋势,即尽可能做到平台无关。
react-redux提供了一个组件和一个API,一个是React组件,接受一个store作为props,它是整个Redux应用的顶层组件;一个是connect(),它提供了在整个React应用的任意组件中获取store中数据的功能。
项目使用redux
入口文件加入Provider组件
import { Provider } from 'react-redux';
import store from './redux/index';
ReactDOM.render(<Provider store={store}><App /></Provider>, rootEl);
创建store文件
import reducers from './reducers/index'
export default createStore(reducers);
创建reducers文件
export default (state=[],action)=>{
switch (action.type){
case 'RECEIVE_PRODUCTS':
return action.products;
default:
return state;
容器组件中dispatch触发reducers改变state
import { connect } from 'react-redux'
const ProductsContainer = ({products,getAllProducts}) => (
<button onClick={getAllProducts}>获取数据</button>
const mapStateToProps = (state) => ({
products:state.products
const mapDispatchToProps = (dispatch, ownProps)=> ({
getAllProducts:() => {
dispatch({ type: 'RECEIVE_PRODUCTS', [1,2,3]})
export default connect(mapStateToProps, mapDispatchToProps)(ProductsContainer)
6.环境全局变量
项目中测试环境和生产环境常常有些全局变量是不同的;最典型的api接口域名部分、跳转地址域名部分;
我们可以在webpack的plugin中设置DefinePlugin:
new webpack.DefinePlugin({
'process.env': env
但在webpack node环境中还不能区分测试和生产环境,因为webpack build打包向node注入的NODE_ENV
都是produiction
,所以process.env.NODE_ENV
是相同的。
这里结合cross-env向node环境手动注入一个标记参数NODE_ENV_MARK
;package代码如下:
"scripts": {
"dev": "cross-env NODE_ENV_MARK=dev webpack-dev-server --config config/start.js",
"build:test": "cross-env NODE_ENV_MARK=test node config/build.js",
"build:prod": "cross-env NODE_ENV_MARK=production node config/build.js"
webpack.config.js中根据NODE_ENV_MARK
变量获取对应的文件:
const env = require(`../env/${process.env.NODE_ENV_MARK}.env`);
env目录下添加dev.env.js/test.env.js/production.env.js;文件内容根据实际情况进行编辑
module.exports = {
NODE_ENV: '"production"',
prefix: '"//api.abc.com"'
这样在浏览器环境中就可以使用process.env.prefix
变量了。
到此项目配置基本告一段落,一下是对项目进行的一些优化。
7.项目优化
webpack相关优化可以参考优化
8.项目规范和风格配置
eslint:代码风格和语法规则检查工具
ESLint 是一种检验JavaScript代码格式的工具,目标是使代码更加一致并避免错误, ESLint是一个代码限制的工具
注意:这里的格式不仅包含风格,还包括一些最佳实践,(比如for in循环对象时,eslilt就建议用Object.keys(obj),然后循环数组,这样避免for in循环出原型对象)
安装 npm install eslint --save-dev
初始化配置文件 npx eslint --init
(根据项目情况选择)
module.exports = {
"env": {
"browser": true,
"es2021": true
"extends": [
"plugin:react/recommended",
"airbnb"
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaFeatures": {
"jsx": true
"ecmaVersion": 13,
"sourceType": "module"
"plugins": [
"react",
"@typescript-eslint"
"rules": {
初始化后会安装一下依赖
eslint@8.6.0
JavaScript检查器,eslint核心库
@typescript-eslint/parser@5.9.1
Typescript语法的解析器
eslint-config-airbnb@19.0.4
Airbnb的ESLint配置,作为一种扩展的共享配置
eslint-plugin-import@2.25.4
ES6+ import/export语法校验,防止文件路径和名称拼写错误的问题
eslint-plugin-react@7.28.0
分析react特性
eslint-plugin-jsx-a11y@6.5.1
检查JSX语法规范
eslint-plugin-react-hooks@4.3.0
@typescript-eslint/eslint-plugin@5.9.1
这时候运行eslint src/index.tsx 会报错
项目中使用了typescript,需要安装eslint-config-airbnb-typescript
,增强Airbnb的ESLint,其实就是针对ts的一些配置和关闭一些rules
eslintrc.js更新
extends: [
'airbnb',
+ 'airbnb-typescript'
+ parserOptions: {
+ project: './tsconfig.json'
+ ignorePatterns: ['.eslintrc.js'],
根目录下新建.eslintignore,配置的目录或文件将不进行eslint格式校验
**/dist/**
**/node_modules/**
**/config/**
如果是新项目加入eslint,extends建议使用airbnb
,这样会约束你编写出更加优雅的代码,这样渐渐的也就会成为你的编码风格
如果是老项目加入eslint,extends建议使用"eslint:recommended"
和"plugin:react/recommended"
这里项目中使用airbnb;这时候运行npx eslint src
,会发现有很多类似这种的报错
大部分报错都是编码风格的报错
可以使用npx eslint src --fix
;可以自动修复编码风格问题,(比如使用单双引号singleQuote
、行位是否加分号semi
);在我理解自动修复不会新增行或者移动代码。
运行之后,发现还剩下类似这种的报错,剩下的就需要手动修复了
如果是vscode编辑器,可以安装eslint插件并配置,开启保存自动格式化,方便查看报错提示和修改
"eslint.validate": [
"javascript",
"javascriptreact"
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
老版本的项目如果配置了resolve.alias,还会出现一下问题:
eslint报错找不到路径;安装插件并新增以下配置即可解决
npm install eslint-plugin-import eslint-import-resolver-alias --save-dev
settings: {
'import/resolver': {
alias: {
map: [
['@redux', paths.appRedux]
['@pages', paths.appPages]
['@util', paths.util]
但这时你会发现ctrl/command
+鼠标左键无法识别路径,开发体验不是很好。
在根目录新建jsconfig.json
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@redux/*": ["src/redux/*"],
"@pages/*":["src/pages/*"],
"@util/*":["src/util/*"]
stylelint规范sass文件
npm install --save-dev stylelint stylelint-config-standard-scss
根目录新增.stylelintrc.js
module.exports = {
"extends": "stylelint-config-standard-scss",
"ignoreFiles": ["node_modules/**/*.scss"],
"rules": {
"selector-pseudo-class-no-unknown": [
true,
"ignorePseudoClasses": ["global"]
rules分三类
Possible errors 语法类错误
大部分不可 --fix修复
Limit language features 限制写法类
比如小数点后默认只能有4位、不允许0后面的单位、透明值不允许百分比,少部分可以--fix修复
Stylistic issues 编码风格类
比如16进制颜色指定大小写、url值是否需要引号、大括号开始/结束是否需要换行,此类基本都可以--fix修复
规则列表中如果带有(Autofixable)
的,--fix都是可以自动修复的,其他的需要手动修复。
VSCode 自动格式化配置
安装ESlint
和Stylelint
插件
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true,
"source.fixAll.stylelint": true
"eslint.validate": [ "vue","html","javascript","typescript","javascriptreact","typescriptreact" ],
"eslint.alwaysShowStatus": true,
"stylelint.validate": [ "css", "less", "postcss", "scss", "vue", "sass" ],
Prettier
一款用于格式化代码风格的工具,它的设计哲学是:Opinionated,就是强制性用官方的风格,只提供少量的配置选项(20个左右),目的在于停止关于代码风格的争论。
上面已经用了eslint,为什么还需要引入Prettier呢?在我理解eslint职责在于检测代码是否符合rules规则, 不符合的会给出提示,--fix还可以解决一些风格问题;prettier用于格式化代码风格避免这些报错;当然prettier无法格式化代码质量和语法类问题,这些还需要eslint来解决(手动解决)。
npm i -save-dev --save-exact prettier
npm i -save-dev eslint-config-prettier eslint-plugin-prettier
presets | 描述 |
---|
eslint-config-prettier | 让eslint 和prettier 兼容,关闭所有不必要或可能与prettier 冲突的规则 |
eslint-plugin-prettier | 运行eslint 会静默prettier 检查,反馈给eslint 显示错误 |
prettier-eslint | 将prettier 输出传递给eslint --fix |
stylelint-config-prettier | stylelint 和prettier 兼容 |
eslint修改配置
extends: [
'airbnb',
+ 'plugin:prettier/recommended'
配置package.json命令
"scripts": {
"format": "prettier --write \"src/**/*.{js,jsx,scss}\""
运行 npm run format 发现文件已经被格式化,但语法错误和一些代码质量问题还是需要手动修改
如果出现保存格式化两次,是eslint和prettier冲突了,需在extends中加入覆盖规则
husky 添加git hooks
上面配置了检测代码的eslint和stylelint,如何让每次提交的代码都符合规范,还需要借助自动化工具
npm i -D husky
npx husky install
执行husky install
将git hooks
的目录指定为.husky/
npm install后自动启用hooks
npm set-script prepare "husky install"
prepare
是 NPM
操作生命周期中的一环,在执行 install
的时候会按生命周期顺序执行相应钩子: NPM7: preinstall -> install -> postinstall -> prepublish -> preprepare -> prepare -> postprepare
向.husky/
中添加hook
pre-commit 提交前验证eslint规则
"scripts": {
"prepare": "husky install",
"lint": "eslint src --ext .jsx && stylelint \"./src*.scss\""
npx husky add .husky/pre-commit "npm run lint"
执行commit提交会发现报错,并阻止了代码提交,这样可以避免把错误代码提交到线上导致线上报错。
代码报错修复完,即可提交。
这个方式虽然可以解决提交前校验的问题,但项目庞大后,修改一个文件后,会校验所有的文件,比较费时,lint-staged可以解决这个问题
npm i -D lint-staged
"lint-staged": {
"src/**/*.scss": [
"stylelint --fix"
"src/**/*.{ts,tsx,js,jsx}": "eslint --fix"
在.kusky文件夹内修改pre-commit
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx --no-install lint-staged
这样commit文件后就只校验修改的文件了
commit-msg 规范提交信息
npm install --save-dev @commitlint/config-conventional @commitlint/cli
在根目录新建 commitlint.config.js
module.exports = { extends: ["@commitlint/config-conventional"] };
.husky
创建commit-msg
hook
npx husky add .husky/commit-msg 'npx --no -- commitlint --edit "$1"'
Commit message格式,注意冒号后面有空格
<type>(<scope>): <subject>
type:用于说明 commit 的类别,只允许使用下面7个标识,也可以自己在配置文件中更改或者扩展。
type | 说明 |
---|
feat | 新功能(feature) |
fix | 修补bug |
docs | 文档变更(documentation) |
style | 代码格式(不影响功能,例如空格、分号等格式修正) |
refactor | 代码重构 |
perf | 性能优化 |
build | 变更项目构建或外部依赖(例:scopes: webpack、gulp、npm) |
test | 测试 |
ci | 更改持续集成软件的配置 |
chore | 构建过程或辅助工具的变动 |
revert | 代码回滚 |
scope:(可选)说明commit
影响的范围;在业务项目中可以根据菜单或功能木块划分
subject: commit 的简短描述,不能超过72个字符,且结尾不加英文句号。
如果type为feat和fix,则该 commit 将肯定出现在 Change log 之中
交互式commit
npm i -D commitizen cz-conventional-changelog
package.json
"config":{
"commitizen":{ "path":"node_modules/cz-conventional-changelog" }
"scripts": { "commit": "git-cz",}
提交代码可以执行
npm run commit
github.com/futurewan/r…
文章有什么问题欢迎评论区讨论~