从npm init 搭建React企业级项目

写在前面

这篇文章致力于从npm init 一步一步搭建react企业级项目,主要包含一下几点:javascript

  • webpack配置和优化
  • react-router 基本配置和使用
  • react-redux 基本配置和使用
  • 项目规范和风格配置

首先奉上项目地址react-base-projectcss

0.基础文件

  1. mkdir react-pro && cd react-pro
  2. npm init
  3. 新建.gitignore
.DS_Store
.vscode
node_modules/
dist/
npm-debug.log
yarn.lock
package-lock.json
复制代码

1.安装依赖项

1. webpack4相关依赖

npm i webpack webpack-cli webpack-dev-server --save-dev
复制代码

2. babel7

npm i @babel/core @babel/preset-env @babel/preset-react babel-loader @babel/plugin-proposal-class-properties -D
复制代码

presets 使用babel须要安装的插件(也就是支持哪些语法转换成es5)html

presets 描述
babel javascript语法编译器
@babel/core 调用babel api进行转码的核心库
@babel/preset-env 根据运行环境为代码作相应的编译
@babel/preset-react 编译react语法
@babel/plugin-proposal-class-properties 支持class语法插件
babel-preset-stage-x(stage-0/1/2/3/4) 提案的5个阶段,0表示只是一个想法,4表示已完成

babel7发布后的变化:前端

  1. 弃用年份preset

@babel/preset-env替换以前全部的babel-prese-es20xxpreset,java

  1. 重命名:Scoped Packages(@babel/x

解决:命名困难;是否被他人占用;区分官方包名node

  1. 重命名:-proposal-

任何提案都将被以 -proposal- 命名来标记他们尚未在 JavaScript 官方以内。react

因此 @babel/plugin-transform-class-properties 变成 @babel/plugin-proposal-class-properties,当它进入 Stage 4 后,会把它命名回去。webpack

polyfill便是在当前运行环境中用来复制(意指模拟性的复制,而不是拷贝)尚不存在的原生 api 的代码。能让你提早使用还不可用的 APIsgit

Babel 几乎能够编译全部时新的 JavaScript 语法,但对于 APIs 来讲却并不是如此。例如: Promise、Set、Map 等新增对象,Object.assign、Object.entries等静态方法。es6

为了达成使用这些新API的目的,社区又有2个实现流派:babel-polyfill和babel-runtime+babel-plugin-transform-runtime

3. react和开发环境热更新

npm i react react-dom --save
复制代码

react-dom:v0.14+从react核心库中拆离;负责浏览器和DOM操做。还有一个兄弟库react-native,用来编写原生应用。

react-dom主要包括方法有:

presets 描述
render 渲染react组件到DOM中
hydrate 服务端渲染,避免白屏
unmountComponentAtNode 从 DOM 中移除已装载的 React 组件
findDOMNode 访问原生浏览器DOM
createPortal 渲染react子元素到制定的DOM中

react:React的核心库;主要包括:React.createElement,React.createClass,React.Component,React.PropTypes,React.Children

4. 生成html文件和开发环境热更新

npm i html-webpack-plugin -D
npm i react-hot-loader -S
复制代码

2.基础工程文件和配置

  • 根目录下新建config目录,并新建webpack.config.js基础配置文件
const paths = require('./paths');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = function(webpackEnv){
  const isEnvDevelopment = webpackEnv === 'development';
  const isEnvProduction = webpackEnv === 'production';
  let entry = [paths.appIndex];
  return {
    mode:isEnvProduction ? 'production' : isEnvDevelopment && 'development',
    entry:entry,
    output:{
      path:paths.appDist,
      publicPath:'/',
      filename:`static/js/[name]${isEnvProduction ? '.[contenthash:8]':''}.js`
    },
    module:{
      rules:[
        {
          test: /\.jsx?$/,
          loader: 'babel-loader'
        }
      ]
    },
    plugins:[
      new HtmlWebpackPlugin({
        filename: 'index.html',
        template: paths.appHtml,
        favicon: 'favicon.ico'
      }),
      isEnvDevelopment && new webpack.HotModuleReplacementPlugin()//开启HRM
    ],
    devServer: {
      publicPath: '/',
      host: '0.0.0.0',
      disableHostCheck: true,
      compress: true,
      port: 9001,
      historyApiFallback: true,
      open: true,
      hot:true,
    }
  }
}
复制代码
  • 用于启动开发环境的配置文件
const configFactory = require('./webpack.config');
const config = configFactory('development');

module.exports = config;
复制代码
  • 为统一管理路径,新建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 = {
  appIndex:resolveApp('src/index'), //入口文件
  appSrc:resolveApp('src'), //项目代码主目录
  appDist:resolveApp('dist'), //打包目录
  appHtml:resolveApp('index.html'), //模板文件
}
复制代码
  • 根目录下新建src目录并新建项目入口文件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);
复制代码
  • 根目录下新建.babelrc
{
    "presets": [
        [
            "@babel/preset-env",
            {
                "modules": false
            }
        ],
        "@babel/preset-react"
    ],
    "plugins": ["react-hot-loader/babel","@babel/plugin-proposal-class-properties"]
}
复制代码

webpack-dev-server默认会开启livereload功能(俗称热更新),监听文件有改动,自动刷新页面;但须要实现react组件改动不刷新页面,还须要配合 react-hot-loader 实现(配置可参考官方文档),俗称热替换Hot Module Replacement

目前为止,项目就能够运行了 在package.json文件 scripts添加脚本命令webpack-dev-server --config config/start.js并执行;浏览器会打开网页 http://0.0.0.0:9001/ 第一阶段目录结构以下:

.
├── README.md
├── config
│   ├── build.js
│   ├── paths.js
│   ├── start.js
│   └── webpack.config.js
├── favicon.ico
├── index.html
├── package-lock.json
├── package.json
├── src
│   ├── App.js
│   └── index.js
└── yarn.lock
复制代码

2.资源文件配置

项目实际开发中还须要样式文件,本项目以scss为例进行配置

2.1首先是样式文件的配置

  1. 安装依赖包 npm i style-loader css-loader postcss-loader sass-loader node-sass autoprefixer -D
presets 描述
style-loader 将css文件插入到html中
css-loader 编译css文件
sass-loader 编译scss文件
postcss-loader 使用javascript插件转换css的工具
autoprefixer 根据用户的使用场景来解析CSS和添加vendor prefixes
  1. webpack新增loader
{
  test: /\.(sc|c)ss$/,
  use: [
      isEnvDevelopment && 'style-loader',
      'css-loader', 'postcss-loader', 'sass-loader'
    ].filter(Boolean)
}
复制代码

3.新建postcss.config.js文件

const autoprefixer = require('autoprefixer');
module.exports = {
  plugins: [autoprefixer]
};
复制代码

4.package.json添加兼容浏览器列表

![](https://user-gold-cdn.xitu.io/2020/5/7/171ef7b25122c989?w=862&h=692&f=png&s=263915)
"browserslist": ["iOS >= 8","> 1%","Android > 4","last 5 versions"]
复制代码

5.项目入口文件index.js引入全局scss文件
import './assets/styles/app.scss';
6.从新启动项目,能够看到样式已经插入head中

3.静态文件处理

npm i file-loader url-loader -D

presets 描述
file-loader 解决项目中引用本地资源(图片、字体、音视频等)相对路径问题;这里我测试了下,绝对路径不会出现路径问题
url-loader 对资源文件作转dataURI处理
{
    test: /\.(gif|png|jpe?g|svg)(\?.*)?$/,
    use: [
      {
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: 'img/[name].[ext]?[hash]'
        }
      }
    ]
},
{
    test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
    loader: 'url-loader',
    options: {
      limit: 10000,
      name: 'fonts/[name].[hash:7].[ext]'
    }
}
复制代码

4.路由

首先固然是安装依赖,为何是react-router-dom而不是react-router; 在react-router4.0.0+版本;官方提供了一套基于react-router封装的用于运行在浏览器端的react-router-dom;react-router-native是用于开发react-native应用。

npm install react-router-dom --save
复制代码

React Router中有三类组件

  • router组件(BrowserRouter,HashRouter)
  • route matching组件(Route,Switch)
  • navigation组件(Link)

Routers

基于React Router的web应用,根组件应该是一个router组件;react-router-dom提供了两种路由模式;
<BrowserRouter>:使用HTML5 提供的 history API (pushState, replaceState 和 popstate 事件),动态展现组件
<HashRouter>:经过监听window.location.hash的改变,动态展现组件 最直观的感觉就是BrowserRouter不会再浏览器URL上追加#,为了地址的优雅固然首选这种模式,但若是是静态服务器,那就只能使用备选方案HashRouter了。

Route 路由匹配

react-router-dom中有两个匹配路由的组件: 和 路由匹配是经过将组件的path属性与当前location的pathname进行匹配,当一个组件匹配了,则展现;不然渲染null。若是一个没有path属性,他的组件对应内容将一直被渲染出来

// 当 location = { pathname: '/about' }
<Route path='/about' component={About}/> // 路径匹配成功,渲染 <About/>组件
<Route path='/contact' component={Contact}/> // 路径不匹配,渲染 null
<Route component={Always}/> // 该组件没有path属性,其对应的<Always/>组件会一直渲染
复制代码

咱们能够在组件树的任何位置放置<Route>组件。可是更常见的状况是将几个<Route>写在一块儿。<Switch>组件能够用来将多个<Route>“包裹”在一块儿。 多个组件在一块儿使用时,并不强制要求使用<Switch>组件,可是使用<Switch>组件倒是很是便利的。<Switch>会迭代它下面的全部<Route>子组件,并只渲染第一个路径匹配的<Route>
这里是在根组件引入新建路由组件(src/routes/index.js)。

<BrowserRouter basename="/">
    <Switch>
      <Route exact path="/" component={Home} /> //exact彻底匹配
      <Route path="/shopping" component={Shopping} />
      <Route path="/contact" component={Contact} />
      <Route path="/detail/:id" component={Contact} />
      {/* 可经过this.props.match获取路由参数 */}
      {/* 若是上面的Route的路径都没有匹配上,则 <NoMatch>被渲染,咱们能够在此组件中返回404 */}
      <Route component={NoMatch} />
    </Switch>
</BrowserRouter>
复制代码

Link 导航组件

//to: string
<Link to="/about?tab=name" />

//to: object
<Link
  to={{
    pathname: "/courses",
    search: "?sort=name",
    hash: "#the-hash",
    state: { fromDashboard: true } //传入下一个页面额外的state参数
  }}
/>
复制代码

使用history控制路由跳转

在不一样的React版本中,使用方法稍有差别,下面总结了各版本的使用方法

  1. React-Router 5.1.0+ (using hooks and React >16.8) 你可使用 useHistory hook 在函数组件中使用编程时导航
import { useHistory } from "react-router-dom";

function HomeButton() {
  let history = useHistory();
  // use history.push('/some/path') here
};
复制代码
  1. React-Router 4.0.0+ 在4.0+, 在组件中使用props中的 history 对象
class Example extends React.Component {
   // use `this.props.history.push('/some/path')` here
};
复制代码
  1. React-Router 3.0.0+ 在3.0+, 在组件中使用props中的 router.
class Example extends React.Component {
   // use `this.props.router.push('/some/path')` here
};
复制代码

5. 状态管理

npm install redux react-redux --save
复制代码

redux是一个“可预测的状态容器”,参考了flux的设计思想,

Redux三大原则

  1. 单一数据源
    一个应用只有惟一的数据源,好处是整个应用的状态都保存在一个对象中,这样能够随时去除整个应用的状态进行持久化;固然若是一个复杂项目也能够用Redux提供的工具函数combineReducers对数据进行拆分管理。

  2. 状态是只读的
    React并不会显示定义store,而使用Reducer返回当前应用的状态(state),这里并非修改以前的状态,而是返回一个全新的状态。
    React提供的createStore方法会根据Reducer生成store,最后能够用store.disputch方法修改状态。

  3. 状态修改均由纯函数完成
    这使得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

  1. 入口文件加入Provider组件
import { Provider } from 'react-redux';
import store from './redux/index';
ReactDOM.render(<Provider store={store}><App /></Provider>, rootEl);
复制代码
  1. 建立store文件
import reducers from './reducers/index'
export default createStore(reducers);
复制代码
  1. 建立reducers文件
export default (state=[],action)=>{
 switch (action.type){
   case 'RECEIVE_PRODUCTS':
     return action.products;
   default:
     return state;
 }
}
复制代码
  1. 容器组件中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:

//向浏览器环境注入全局变量,非window下
new webpack.DefinePlugin({
    'process.env': 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变量了。

到此项目配置基本告一段落,一下是对项目进行的一些优化。

项目优化

如下都是基于webpack4作的优化配置

打包速度优化

  • clean-webpack-plugin -每次打包前删除上次打包内容 webpack.config.js 中新增以下配置:
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
plugins: [
    !isEnvDevelopment && new CleanWebpackPlugin()
]
复制代码
  • 减少文件搜索范围
{
    test: /\.jsx?$/,
    loader: 'babel-loader',
    include: paths.appSrc,
    exclude: /node_modules/
}
复制代码
  • 缓存loader执行结果
loader: 'babel-loader?cacheDirectory=true' 
复制代码
  • noParse
    用了noParse的模块将不会被loaders解析,当肯定这个库没有第三方依赖,可使用该配置来优化性能
noParse: /lodash/
复制代码
  • happypack 多线程打包
const os = require("os");
const HappyPack = require("happypack");
const happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length });
plugins:[
    new HappyPack({
        id: "happyBabel",
        loaders: ["babel-loader?cacheDirectory=true"],
        threadPool: happyThreadPool,
        verbose: true
     })
]
复制代码
  • sourcemap

为了方便调试线上的问题,sourcemap就是对应打包后代码和源码的一个映射文件。

devtool: isEnvDevelopment ? 'cheap-module-eval-source-map' : 'source-map',
复制代码

输出体积优化

  • webpack-bundle-analyzer
    首先能够用这个插件分析下打包引用各三方库模块的大小,依次做出优化方案。
1.package.json新增scripts脚本
"analyze": "cross-env NODE_ENV_REPORT=true npm run build:prod"
2.webpackage.config.js
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
plugins:[
    process.env.NODE_ENV_REPORT && new BundleAnalyzerPlugin()
]
3.浏览器会自动打开 http://127.0.0.1:8888
复制代码

分析报告以下图:

  • mini-css-extract-plugin提取css
const MiniCssExtractPlugin=require('mini-css-extract-plugin');
plugins:[
    isEnvProduction && new MiniCssExtractPlugin({
        filename:'static/css/[name].[contenthash:10].css'
    })
]
//loader修改
{
  test: /\.(sc|c)ss$/,
  use: [
    -  isEnvDevelopment && 'style-loader',
    +  MiniCssExtractPlugin.loader,
      'css-loader', 'postcss-loader', 'sass-loader'
    ].filter(Boolean)
}
复制代码
  • optimize-css-assets-webpack-plugin 压缩css
  • uglifyjs-webpack-plugin 压缩js
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');

optimization: {
  minimizer: [
    new UglifyJsPlugin({
    cache:true,
    parallel:true,
    sourceMap:true
  }),
  new OptimizeCSSAssetsPlugin()
],
复制代码

对项目进行拆包

Loadable

在对项目拆包前,先对对页面路由进行懒加载

- import Home from '../pages/home/Home';
+ import Loadable from 'react-loadable';
+ const loading = () => { return (  <div> loading... </div> ) }
+ const Home = Loadable({loader: () => import(/* webpackChunkName: "Home" */ '../pages/home/Home'),loading:loading});
复制代码

import()函数是es6的语法,是一种动态引入的方式,返回一个Promise

splitChunks

对项目的拆包主要是如下几个方面:

  • react|react-dom|react-redux|react-router-dom|redux,项目的基本框架
  • lodash项目中用到比较大的三方库
  • 项目中的公共组件和方法
splitChunks:{
    cacheGroups:{
      dll: { //项目基础框架库
        chunks:'all',
        test: /[\\/]node_modules[\\/](react|react-dom|react-redux|react-router-dom|redux)[\\/]/,
        name: 'dll',
        priority:100, //权重
        enforce: true,// 为此缓存组建立块时,告诉webpack忽略minSize,minChunks,maxAsyncRequests,maxInitialRequests选项
        reuseExistingChunk: true // 可设置是否重用已用chunk 再也不建立新的chunk
      },
      lodash: { //经常使用的比较大的三方库
        chunks:'all',
        test: /[\\/]node_modules[\\/](lodash)[\\/]/,
        name: 'lodash',
        priority: 90,
        enforce: true, 
        reuseExistingChunk: true
      },
      commons: { //项目中使用的其余公共库
        name: 'commons',
        minChunks: 2, //Math.ceil(pages.length / 3), 当你有多个页面时,获取pages.length,至少被1/3页面的引入才打入common包
        chunks:'all',
        reuseExistingChunk: true
        }
    },
    chunks: 'all',   
    name: true,
}
复制代码

下面图是通过拆包后的结果

再用speed-measure-webpack-plugin分析下webpack各环节的打包速度

  • speed-measure-webpack-plugin
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
 
const smp = new SpeedMeasurePlugin();
//用smp.wrap方法把webpack配置文件处理下
webpackConfig = smp.wrap({
  //webpack配置对象
});
复制代码

打包后以下图:

开发体验优化

  • resolve.extensions
    当引入模块时不带文件后缀 webpack会根据此配置自动解析肯定的文件后缀
import Cart from '../components/Cart.jsx' //配置前
import Cart from '../components/Cart' //配置后
resolve: {
  extensions:['.js','.jsx','.json']
}
复制代码
  • resolve.alias
    方便项目中引入其余目录的文件
import product from '../../../redux/reducers/products' //配置前
import product from '@redux/reducers/products' //配置后

alias:{
    '@redux':paths.appRedux
}
复制代码
  • 拷贝静态文件
//这是6.0+的语法
new CopyWebpackPlugin({
    patterns:[{
      from:paths.appStatic,
      to:'static/',
    }]
})
复制代码

项目规范和风格配置

eslint:代码风格和语法规则检查工具

  1. 安装 npm install eslint --save-dev
  2. 生成.eslintrc.js eslint --init(根据项目状况选择) 这时候运行eslint src/index.js会报错React version not specified in eslint-plugin-react settings ,是没有配置react的版本
"settings":{
    "react": {
        "version": "detect", // React version. "detect" automatically picks the version you have installed.
    }
}
复制代码
  1. 忽略文件 根目录下新建.eslintignore,配置的目录或文件将不进行eslint格式校验
**/dist/**
**/node_modules/**
**/config/**
复制代码

若是是新项目加入eslint,extends建议使用airbnb,这样会约束你编写出更加优雅的代码,这样渐渐的也就会成为你的编码风格

npm i eslint-plugin-react eslint-config-airbnb eslint-plugin-import eslint-plugin-jsx-a11y -D
extends: [
    'airbnb'
]
复制代码

若是是老项目加入eslint,extends建议使用"eslint:recommended""plugin:react/recommended"

npm i eslint-plugin-react -D
extends: [
    "eslint:recommended",
    "plugin:react/recommended"
]
复制代码

这里我项目中使用airbnb;这时候运行eslint src,会发现有不少相似这种的报错

大部分报错都是编码风格的报错

可使用eslint src --fix;能够自动修复编码风格问题,在我理解自动修复不会新增行或者移动代码。 运行以后,发现还剩下相似这种的报错,剩下的就须要手动修复了

若是是vscode(1.46.1)编辑器能够加入一下配置,开启保存自动格式化

"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文件

  1. 安装
npm i -D stylelint stylelint-config-standard stylelint-scss
复制代码
  1. 配置
    根目录新增.stylelintrc.js
module.exports = {
  extends: 'stylelint-config-standard', // 这是官方推荐的方式
  processors: [],
  plugins: ['stylelint-scss'],
  ignoreFiles: ['node_modules/**/*.scss'],
  rules: {
    'rule-empty-line-before': 'never-multi-line',
  },
};
复制代码

Prettier

一款用于格式化代码的工具

上面已经用了eslint,为何还须要引入Prettier呢?在我理解eslint职责在于检测代码是否符合rules规则,prettier用于格式化代码避免这些报错;固然prettier没法格式化代码质量和语法类的问题。

  1. 安装
npm i eslint prettier eslint-config-prettier eslint-plugin-prettier -D
复制代码
presets 描述
eslint-plugin-prettier 在eslint rules中扩展规则
eslint-config-prettier 让eslint和prettier兼容,关闭prettier和eslint冲突的部分
  1. eslint修改配置
extends: [
    'airbnb',
    + 'plugin:prettier/recommended'
]
复制代码
  1. 配置package.json命令
"scripts": {
    "format": "prettier --write \"src/**/*.{js,jsx}\""
}
复制代码

运行 npm run format 发现文件已经被格式化,但语法错误和一些代码质量问题仍是须要手动修改

husky 前脚代码前的检测

上面配置了检测代码的eslint和stylelint,如何让每次提交的代码都符合规范,还须要借助自动化工具

  1. 安装
npm i -D husky
复制代码
  1. 配置到package.json中
"scripts": {
  "lint": "eslint src --ext .jsx && stylelint \"./src/**/*.scss\""  
},
"husky": {
    "hooks": {
      "pre-commit": "npm run lint"
    }
  }
复制代码

执行commit提交会发现报错,并阻止了代码提交,这样能够避免把错误代码提交到线上致使线上报错。

代码报错修复完,便可提交。
文章有什么问题欢迎评论区讨论~

项目地址

github.com/futurewan/r…

相关文章
相关标签/搜索