從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…

相關文章
相關標籤/搜索