从0到1搭建webpack4.0+react全家桶(下)

1,992 阅读2分钟

本篇写的是优化方案,查看基础配置的话可以前往这里juejin.cn/post/684490…

webpack优化

缓存配置

  • babel-loader自带的缓存

{
    test:/\.js$/,
    use:[
            'babel-loader?cacheDirectory=true'
        ]
}

  • 使用cache-loader

下载包 cnpm install cache-loader --save

{
    test:/\.js$/,
    use:[
        'cache-loader'
    ]
}

多进程打包(1)happypck

下载包 cnpm install happypack os --save

happy启用进程池,设置数量一般为设备cup总数,在loader处指定id作为一个实例,在plugin处进行单独处理

const HappyPack = require('happypack');
const os = require('os');

module.exports = {

module:{
    rules:[{
        test:/\.js$/,
        use:['happypack/loader?id=js']
    }]
},

plugins:[
    new HappyPack({
        id:'js',
        //这里loaders配置与之前的loader一致
        loaders:['babel-loader'],
        //构建共享进程池,进程池里有多个子进程去处理任务
        ThreadPool: HappyPack.ThreadPool({size: os.cups().length })
    })
]

}

多进程打包(2)thread-loader(推荐)

下载包 cnpm install thread-loader --save

把这个loader放在其他loader前面,放置在这个loader之后的loader就会在一个单独的worker池中运行,尽量只在耗时的loader上面使用,如果项目体积较小打包时间可能还会延长。

可以通过预热worker池将制定的loader模块载入node模块缓存里防止启动worker时的高延时。

const ThreadLoader = require('thread-loader');
const os = require('os');

const JSWorkPool  = {
    //产生work数量
    workers: os.cups().length,
    //闲置时定时删除worker进程
    poolTimeout: 2000,
}

//预热babel-loader模块
ThreadLoader.warmup(JSWorkPool,['babel-loader']);

module.exports = {
    module:{
        rules:[
            {
                test:/\.js$/,
                use:[
                    'thread-loader',
                     'babel-loader'
              ]
            }
        ]
    }
}

缩小文件寻找路径

在resolve里使用路径重定向、include、exclude和loader的匹配规则

const path = require('path');

module.export = {
    module:{
        rules:[
            {
                test:/\.js$/,
                exclude: /node_modules/,
                include: path.join(__dirname,'./src'),
                use:['babel-loader']
            }
        ]
    },
    resolve:{
        modules:['node_modules'],
        extensions:['js','jsx','json'],
        alias:{
            '@src': path.join(__dirname,'./src')
        }
    }
}

打包压缩

使用官方推荐的terser-webpack-plugin插件,实际使用能极大提升打包速度

const TerserWebpackPlugin = require('terser-webpack-plugin');

module.export = {
    optimization:{
        minimizer:[
            new TerserWebpackPlugin({
                parallel:true
            })
        ]
    }
}

React优化

路由懒加载

使用动态import()、React.lazy、React.Suspense加载路由

在用lazy加载组件时,使用Suspense包裹加载组件,正在等待加载组件时可以用Suspense的fallback返回一个Loading组件

//Loading组件
import React, { Component } from 'react';
import { Spin } from 'antd';

class Loading extends Component {
    render() {
        return (
            <div style={{ width: '100%', height: '100vh', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
                <Spin size='large' />
            </div>
        );
    }
}

export default Loading;

//index.js
import React, {lazy,Suspense} from 'react';
import ReactDOM from 'react-dom';
import {Router,Route,Switch} from 'react-router-dom';
import Loading from './Loading'

const Page1 = lazy(()=>import('@src/page1'));
const Page2 = lazy(()=>import('@src/page2'));
const Page3 = lazy(()=>import('@src/page3'));

ReactDOM.render(
    <Router>
        <Suspense fallback = {<Loading/>}>
            <Switch>
                <Route path='/1' component={Page1} />
                <Route path='/2' component={Page2} />
                <Route path='/' component={Page3} />
            </Switch>
        </Suspense>
    </Router>
)

避免调停

使用React.PureComponent代替shouldComponentUpdate作浅层对比避免重复渲染

使用shouldComponentUpdate情况,对比父组件传来的color值和当前组件的num值变化来判断是否重新渲染组件。

class Child extends React.Component{
    constructor(props){
        super(props);
        this.state={
            num: 0
        }
    }

    shouldComponentUpdate(nextProps,nextState){
        if(this.props.color !== nextProps.color){
            return true;
        }
        
        if(this.state.num !== nextState.num){
            return true;
        }

        return false;
    }

    render(){
        return(
            <div>
                {this.state.num}
                <button 
                    color={this.props.color}
                    onClick={()=>this.setState(state => {num: state.num + 1})}
                >
                点击+1
                </button>
            </div>
        )
    }
}

换用PureComponent浅层渲染,它会对比所有的prop和state的值来判断是否更新组件,上面例子换作PureComponent来写

class Child extends React.PureComponent{
    constructor(props){
        super(props);
        this.state={
            num: 0
        }
    }

    render(){
        return(
            <div>
                {this.state.num}
                <button 
                    color={this.props.color}
                    onClick={()=>this.setState(state => ({num: state.num + 1}))}
                >
                点击+1
                </button>
            </div>
        )
    }
}

但这只是浅层对比,上面例子里state.num和props.color都是基本数据类型,举个例子

 1 === 1’red‘ === ’red‘结果都是true

[1,2,3] === [1,2,3] 、{a:1} === {a:1} 结果都是false

因为后面例子属于引用数据类型里的Array和Object,对比的是引用地址而不是值,所以遇到props或者state的值是引用数据类型时,PureComponent就会失去对比意义。

官方提出了一个解决方案就是使用浅拷贝,使用Array.concat、Array.slice、Object.assign来拷贝原数据

handleClick(n){
    this.setState(state=>({
        arr: state.arr.concat(['n'])
        //使用es6扩展符
        //arr: [state.arr, 'n']
    }))
}

handleClick2(obj){
    return Object.assign({},obj,{a:1})
    //使用扩展符
    return {...obj,{a:1}};
}

参考文档

www.webpackjs.com/    webpack官方文档
react.docschina.org/   react官方文档