美文网首页让前端飞Web前端之路技术干货
react+webpack4搭建前端项目(三)打包优化

react+webpack4搭建前端项目(三)打包优化

作者: 小猿_Luck_Boy | 来源:发表于2019-10-11 15:00 被阅读0次

    本编文章接着前两篇文章进行项目webpack打包的优化

    1、react+webpack4搭建前端项目(一)
    2、react+webpack4搭建前端项目(二)react全家桶的使用

    主要从以下几个方面进行:

    • react路由的异步加载
    • css处理
      • 使用mini-css-extract-plugincssbundle包中抽取
      • 使用optimize-css-assets-webpack-plugin压缩css代码
      • 使用postcss-loader,autoprefixer对浏览器兼容性的css代码加前缀
    • js的处理
      • 使用uglifyjs-webpack-plugin代码压缩
      • 拆包,jsbundle包的提取(拆包)

    前言

    注意antd版本"antd": "^3.8.3",,高版本的antd官方把图标库也构建到release包,所以导致打包变得很大,仅仅icon图标库就有几百KB,请看下图。如果遇到这个问题,请降低antd的使用版本到3.8.3以前

    QQ截图20191010161431.png

    下边打包优化的基础代码请点击 源码1.0.3。有不熟悉的同学可以看一下,下载该版本1.0.3,在项目根目录执行 npm run dev;同时切换到mock目录,执行 npm run dev,打开http://localhost:8081即可看到效果
    主要实现的功能如下图:

    简历管理的查询,删除,修改:

    1567163086(1).jpg

    用户模块的查询,修改:


    QQ截图20190830190459.png

    用户模块的添加:

    QQ截图20190830190510.png

    首先我们看一下没有优化前的js包大小,执行npm run build

    QQ截图20190830160102.png

    这时候打包出的文件只有三个
    index.html模板文件
    reset.min.css是从静态目录copy进去的
    app.1a9adec2b6012290869f.js是我们利用webpack打包生成的。这里边包括项目中的所有js代码,css代码以及图片data资源

    工欲善其事必先利其器,我们先安装两个非常有用的webpack插件

    npm install -D clean-webpack-plugin webpack-bundle-analyzer
    
    • clean-webpack-plugin 在打包的时候会删除之前的打包目录
    • webpack-bundle-analyzer 在打包结束的时候,会启动启动一个服务在浏览器查看打包的大小和包含的内容等

    修改webpack.prod.config.js,在plugins属性下添加

    new CleanWebpackPlugin(),
    new BundleAnalyzerPlugin(),
    

    开始打包优化

    路由的异步加载

    我们知道想文件的异步加载需要使用import("xxx"),或者require.ensure这种方法适用webapck1.x 2.x。所以这里采用import("xxx")

    在vue中实现路由的异步加载很简单,通过()=>import("xxx")就可以,那么在react中我们也可以这样异步加载

    我们这里实现路由的异步加载借助react-loadable插件

    详细使用请点击 react-loadable使用方法

    1、首页编写一个loadable.js实现异步加载组件

    import Loadable from 'react-loadable';
    
    const LoadableComponent = (component) => Loadable({
      loader: component,
      loading: ()=>null,
    });
    
    export default LoadableComponent;
    

    2、修改路由组件的加载方式

    container/index.js文件组件的直接导入

    import BlogIndex from "@/blog"
    import ResumeIndex from "@/resume"
    import UserIndex from "@/user"
    

    改成使用react-loadable插件包装一层加载组件的方式

    import LoadableComponent from "@/loadable"
    const BlogListPage = LoadableComponent(()=>import("./pages/list"))
    const AddBlogPage = LoadableComponent(()=>import("./pages/add"))
    

    接着修改user/index.jsblog/index.js,把路由组件改成异步加载,改完之后测试一下打包如下图

    QQ截图20190830191951.png

    这时候已经把异步加载的路由组件单独打包到其它单独的文件。从922k减小到487k。

    你会发现1.a64085be1c517b7e1ef2.js单独打包出来的200多k,可以看到下图包含了antd的组件,这也证明antd按需加载的使用成功

    QQ截图20190830193807.png

    css处理

    把css从bundle包中的抽取

    在webpack4.x之前我们使用extract-text-webpack-plugin压缩抽取css。

    在webpack4.x我们需要使用mini-css-extract-plugin插件进行抽取css,mini-css-extract-plugin详细使用文档
    修改webpack.base.config.jscssless文件的处理,如下

    {
        test: /\.css$/,
        use:[
            {
                loader:MiniCssExtractPlugin.loader,
                options:{
                    hmr: utils.isDev(), // 开发的时候,修改css热更新,但是试了下不起作用
                    reloadAll:true,
                }
            },
            // {
            //     loader: 'style-loader', // 创建 <style></style>  // MiniCssExtractPlugin 有冲突,所以删掉
            // },
            { 
                loader: 'css-loader',  // 转换css
                options: { importLoaders: 1 } 
            }
        ]
    },
    {
        test: /\.less$/,
        use: [
            {
                loader:MiniCssExtractPlugin.loader,
                options:{
                    hmr: utils.isDev(), // 开发的时候,修改less热更新但是试了下不起作用
                    reloadAll:true,
                }
            },
            // {
            //     loader: 'style-loader', 
            // },
            {
                loader: 'css-loader',
            },
            {
                loader: 'less-loader', // 编译 Less -> CSS
            }
        ],
    },
    

    因为style-loaderMiniCssExtractPlugin.loader有冲突,在配置的时删除了style-loader对样式的处理,测试打包结果如下

    QQ截图20190830202044.png

    此时已经成功把css样式从bundle抽离出。bundle包从487k减小到381k。

    压缩css代码

    我们打开任意一个打包后的css文件,发现css代码没有压缩。所以我们需要对css压缩。安装optimize-css-assets-webpack-pluginoptimize-css-assets-webpack-plugin详细使用文档

    npm install -D optimize-css-assets-webpack-plugin
    

    在webpack.prod.config.js添加optimization`属性(webpack4.x的代码压缩和拆包都在这里处理,这是和webpack3.x的不同)

    optimization: {
        // 压缩css
        minimizer: [
            new OptimizeCSSAssetsPlugin({
                cssProcessorOptions: { 
                    discardComments: { removeAll: true } // 移除注释
                } 
            })
        ]
    }
    

    测试打包:


    QQ截图20190902093438.png

    对比一下两次打包的css文件大小,已经有一定的减小或者打开打包后的css文件代码也压缩了,这就代表压缩成功。

    但是我们发现js的bundle包变大了,这是为什么呢? 因为我们重写了optimization属性的minimizer,会把webpack自带的压缩方式给覆盖掉,这里需要我们自己定义js的压缩方式。

    js代码压缩

    这里和webpack3.x一样使用uglifyjs-webpack-plugin插件,uglifyjs-webpack-plugin详细使用文档,安装

    npm install -D uglifyjs-webpack-plugin
    

    然后在minimizer属性下添加下边代码

    // 自定义js优化配置,将会覆盖默认配置
    new UglifyJsPlugin({
        parallel: true,  //使用多进程并行运行来提高构建速度
        sourceMap: false,
        uglifyOptions: {
            warnings: false,
            compress: {
                unused: true,
                drop_debugger: true,
                drop_console: true, 
            },
            output: {
                comments: false // 去掉注释
            }
        }
    })
    

    重新测试打包比较和之前的js文件的大小一样!到此我们对css的处理告一段落!

    js处理

    代码压缩上边已经讲过啦,这里不在赘述
    拆包

    在webpack3.x的时候我们都是用webpack内置的CommonsChunkPlugin来拆包。webpack4.x发生了很大变化。
    webpack4.x要想进行拆包,需要先对splitChunks有一定的了解。splitChunks就算你什么配置都不做它也是生效的,源于webpack有一个默认配置,这也符合webpack4的开箱即用的特性。

    splitChunks的默认配置如下

    splitChunks: {
        // async表示只从异步加载得模块(动态加载import())里面进行拆分
        // initial表示只从入口模块进行拆分
        // all表示以上两者都包括
        chunks: "async",
        minSize: 30000,   // 大于30k会被webpack进行拆包
        minChunks: 1,     // 被引用次数大于等于这个次数进行拆分
        // import()文件本身算一个
        // 只计算js,不算css
        // 如果同时有两个模块满足cacheGroup的规则要进行拆分,但是maxInitialRequests的值只能允许再拆分一个模块,那尺寸更大的模块会被拆分出来
        maxAsyncRequests: 5,  // 最大的按需加载(异步)请求次数
        // 最大的初始化加载请求次数,为了对请求数做限制,不至于拆分出来过多模块
        // 入口文件算一个
        // 如果这个模块有异步加载的不算
        // 只算js,不算css
        // 通过runtimeChunk拆分出来的runtime不算在内
        // 如果同时又两个模块满足cacheGroup的规则要进行拆分,但是maxInitialRequests的值只能允许再拆分一个模块,那尺寸更大的模块会被拆分出来
        maxInitialRequests: 3,
        automaticNameDelimiter: '~', // 打包分隔符
        name:true,
        cacheGroups: {
            // 默认的配置
            vendors: {
                test: /[\\/]node_modules[\\/]/,
                priority: -10
            },
            // 默认的配置,vendors规则不命中的话,就会命中这里
            default: {
                minChunks: 2, // 引用超过两次的模块 -> default
                priority: -20,
                reuseExistingChunk: true
            },
        },
    }
    

    打包测试,和不添加splitChunks打包结果一致

    QQ截图20190830202044.png

    因为默认是async,只从异步加载的模块拆分。可以看到只有app.js是入口文件(同步加载)没有对app.js进行拆分。这个项目使用react-loadable异步加载,本项目中有7个组件采用这种方式加载。但是从打包结果可以看异步加载的组件拆分出来10个chunk。那么是为什么呢?

    使用webpack-bundle-analyzer分析可以看出有三个chunk是异步组件引用antd中的组件进行拆分出来的chunk

    假如我们把chunks:async改成chunks: "initial"进行打包测试:

    打包结果如下图

    QQ截图20191011111415.png

    打包结果完全不同,因为app.js是同步加载,app.js被拆分,此时发现app.js非常小。相反拆分出来的vendors~app.4fd9181b8f618e9fcac6.js比较大,这是因为这个chunk包含项目入口文件包含的所有第三方库。而异步加载的7个组件最终打包出来的还是7个chunk,这些异步加载的组件打包出来的每个chunk包含除了vendors~app.4fd9181b8f618e9fcac6.js打包进去的第三方库以外的代码和组件本身代码。

    那么同学们会想了,把chunks:initial改成chunks: "all"会是什么结果呢?那么我们进行测试一下

    打包结果如下图

    QQ截图20191011112433.png

    我们知道all不仅从同步组件拆分,还从异步加载中拆分。

    从打包结果看,是对initialasync的合并。即把异步组件拆分,也把同步组件拆分。

    那么结论来了,因为本项目包含异步加载,需要对异步组件和同步组件同时拆分,所以此项目采用chunks: "all"进行bundle的拆分。如果项目中同步加载的组件chunk不大,可以不对同步加载组件进行拆分,使用chunks:async。当然如果项目中异步加载的组件chunk不大,也可以不对异步加载组件进行拆分,使用chunks:initial。当然也可以混用,对于缓存组单独设置

    既使采用chunks: "all"的方式我们发现。拆分出来venders~app.jschunk(node_modules的第三方库)也比较大。随着第三方插件使用的增多这个chunk会变的越来越大。所以我们这里对他进行拆分。也就是把node_modules中使用的插件也拆分成不同的chunk

    我们拆包的策略是按照体积大小、共用率、更新频率重新划分我们的包,使其尽可能的利用浏览器缓存。

    分析项目的插件,可以按几下分类插件

    • UI组件库(antd)
    • 基础插件(react,react-dom,react-router-dom,mobx,axios等等)

    这些都是更新频率非常低,公用率高,提及大,所以单独抽取。只要这些包不更新,拆包的chunk文件名就不会变。就一直缓存在浏览器

    接下来我们根据这个分类拆包,增加cacheGroups茶包的规则

    antdui: {
        priority: 2,  
        test: /[\\/]node_modules[\\/](antd)[\\/]/,  //(module) => (/antd/.test(module.context)),
    },
    // 拆分基础插件
    basic: {
        priority: 3, 
        test: /[\\/]node_modules[\\/](moment|react|react-dom|react-router|react-router-dom|mobx|mobx-react|axios)[\\/]/,
    }
    

    打包测试:

    QQ截图20191011140206.png

    这里已经成功拆分出来antdui~app.jsbasic~app.js。使用webpack-bundle-analyzer分析可以精确的看到antd,moment|react|react-dom|react-router|react-router-dom|mobx|mobx-react|axios插件被拆分。

    不知道同学们有没有发现问题?
    1、打包的hash都是一样的,而且每次打包的hash还都不一样。
    解决方法,
    output添加

    chunkFilename: utils.assetsPath("js/[name].[chunkhash].js")
    

    new MiniCssExtractPlugin(options)初始化参数

    chunkFilename: utils.assetsPath('css/[id].[chunkhash].css'),
    

    2、venders~app.js不见了
    antdui~app.jsbasic~app.js只是拆分了antdui|moment|react|react-dom|react-router|react-router-dom|mobx|mobx-react|axios这些插件。还有其它的node_modules的插件会被拆分到venders~app.js。这里需要知道maxInitialRequests这个属性的作用了。

    maxInitialRequests:最大的初始化加载请求次数,为了对请求数做限制,不至于拆分出来过多模块

    • 入口文件算一个
    • 如果这个模块有异步加载的不算
    • 只算js,不算css
    • 通过runtimeChunk拆分出来的runtime不算在内
    • 如果同时又两个模块满足cacheGroup的规则要进行拆分,但是maxInitialRequests的值只能允许再拆分一个模块,那尺寸更大的模块会被拆分出来

    由此发现,加载app.js的时候有三个文件会同步加载app.jsantdui~app.jsbasic~app.js

    需要把maxInitialRequests的值修改成更大,修改成5

    maxInitialRequests: 5,
    

    打包测试如下图:

    QQ截图20191011145144.png

    查看结果venders~app.jsapp.js中拆分出来啦。

    github源码

    react+webpack4+react-router5+react-loadable+mobx系列文章

    1、react+webpack4搭建前端项目(一)基础项目搭建
    2、react+webpack4搭建前端项目(二)react全家桶的使用
    3、react+webpack4搭建前端项目(三)打包优化

    相关文章

      网友评论

        本文标题:react+webpack4搭建前端项目(三)打包优化

        本文链接:https://www.haomeiwen.com/subject/emkwectx.html