zoukankan      html  css  js  c++  java
  • Webpack打包构建太慢了?试试几个方法

    Webpack是个很流行的打包工具,但其打包速度却一直被吐槽着

    如果不用上一些打包的优化建议,单单打包两三个文件就能花上好几秒,放上几十个入口文件依赖几百上千个包的话,几分钟十几分钟妥妥的

    本文整理了常见的一些方法,部分使用之后就看到了很大改善,部分没什么明显的变化,也可能是项目规模还不够大,先记录一下方法也好

    还是太慢了,快快使用Webpack4

    一、使用监听模式或热更新热替换

    webpack支持监听模式,此时需要重新编译时就可以进行增量构建,增量构建是很快的,基本不到一秒或几秒之内就能重新编译好

    注意区分一下开发环境和线上环境,开发环境启用热更新替换

    // 开发环境设置本地服务器,实现热更新
        devServer: {
            contentBase: path.resolve(__dirname, 'static'),
            // 提供给外部访问
            host: '0.0.0.0',
            port: 8388,
            // 允许开发服务器访问本地服务器的包JSON文件,防止跨域
            headers: {
                'Access-Control-Allow-Origin': '*'
            },
            // 设置热替换
            hot: true,
            // 设置页面引入
            inline: true
        },
    
        // 文件输出配置
        output: {
            // 设置路径,防止访问本地服务器相关资源时,被开发服务器认为是相对其的路径
            publicPath: 'http://localhost:8188/dist/js/',
        },
    
    
    // 插件配置
        plugins: [
            // 热更新替换
            new webpack.HotModuleReplacementPlugin()
        ]

    线上环境的编译,加个 --watch 参数就可以了

    二、开发环境不做无意义的操作

    很多配置,在开发阶段是不需要去做的,我们可以区分出开发和线上的两套配置,这样在需要上线的时候再全量编译即可

    比如说 代码压缩、目录内容清理、计算文件hash、提取CSS文件等

    三、选择一个合适的devtool属性值

    配置devtool可以支持使用sourceMap,但有些是耗时严重的,这个得多试试

    四、代码压缩用ParallelUglifyPlugin代替自带的 UglifyJsPlugin插件

    自带的JS压缩插件是单线程执行的,而webpack-parallel-uglify-plugin可以并行的执行,在我的小demo中使用后,速度直接从25s变成了14s

          new webpack.optimize.UglifyJsPlugin({
                sourceMap: true,
                compress: {
                    warnings: false
                }
            }),
    
    
    
    ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin')
    
    new ParallelUglifyPlugin({
               cacheDir: '.cache/',
               uglifyJS:{
                 output: {
                   comments: false
                 },
                 compress: {
                   warnings: false
                 }
               }
             }),

    五、css-loader使用0.15.0以下的版本

    听闻这个版本以上的速度会慢许多,不过在我的小demo中还没看到明显变化

    六、使用fast-sass-loader代替sass-loader

    fast-sass-loader可以并行地处理sass,在提交构建之前会先组织好代码,速度也会快一些

    七、babel-loader开启缓存

    babel-loader在执行的时候,可能会产生一些运行期间重复的公共文件,造成代码体积大冗余,同时也会减慢编译效率

    可以加上cacheDirectory参数或使用 transform-runtime 插件试试

    // webpack.config.js
    use: [{
                    loader: 'babel-loader',
                    options: {
                        cacheDirectory: true
                    }]
    
    
    // .bablerc
    {
        "presets": [
            "env",
            "react"
        ],
        "plugins": ["transform-runtime"]
    }

    八、不需要打包编译的插件库换成全局<script>标签引入的方式

    比如jQuery插件,react, react-dom等,代码量是很多的,打包起来可能会很耗时

    可以直接用标签引入,然后在webpack配置里使用 expose-loader  或 externalsProvidePlugin  提供给模块内部使用相应的变量

    // @1
    use: [{
                    loader: 'expose-loader',
                    options: '$'
                }, {
                    loader: 'expose-loader',
                    options: 'jQuery'
                }]
    
    
    // @2
    externals: {
            jquery: 'jQuery'
        },
    
    
    // @3
            new webpack.ProvidePlugin({
                $: 'jquery',
                jQuery: 'jquery',
                'window.jQuery': 'jquery'
            }),

    九、使用 DllPlugin 和 DllReferencePlugin 

    这种方式其实和externals是类似的,主要用于有些模块没有可以在<script>标签中引入的资源(纯npm包)

    Dll是动态链接库的意思,实际上就是将这些npm打包生成一个JSON文件,这个文件里包含了npm包的路径对应信息

    这两个插件要一起用

    首先,新建一个dll.config.js配置文件,先用webpack来打包这个文件

    const webpack = require('webpack');
    const path = require('path');
    
    module.exports = {
        output: {
            // 将会生成./ddl/lib.js文件
            path: path.resolve(__dirname, 'ddl'),
            filename: '[name].js',
            library: '[name]',
        },
        entry: {
            "lib": [
                'react',
                'react-dom',
                'jquery'
                // ...其它库
            ],
        },
        plugins: [
            new webpack.DllPlugin({
                // 生成的映射关系文件
                path: 'manifest.json',
                name: '[name]',
                context: __dirname,
            }),
        ],
    };

    manifest.json文件中就是相应的包对应的信息

    然后在我们的项目配置文件中配置DllReferencePlugin 使用这个清单文件

        // 插件配置
        plugins: [
            new webpack.DllReferencePlugin({
                context: __dirname,
                manifest: require('./manifest.json')
            }),

    十、提取公共代码

    使用CommonsChunkPlugin提取公共的模块,可以减少文件体积,也有助于浏览器层的文件缓存,还是比较推荐的

     // 提取公共模块文件
            new webpack.optimize.CommonsChunkPlugin({
                chunks: ['home', 'detail'],
                // 开发环境下需要使用热更新替换,而此时common用chunkhash会出错,可以直接不用hash
                filename: '[name].js' + (isProduction ? '?[chunkhash:8]' : ''),
                name: 'common'
            }),
    
    
    
    
    // 切合公共模块的提取规则,有时后你需要明确指定默认放到公共文件的模块
    // 文件入口配置
        entry: {
            home: './src/js/home',
            detail: './src/js/detail',
            // 提取jquery入公共文件
            common: ['jquery', 'react', 'react-dom']
        },

    十一、使用HappyPack来加速构建

    HappyPack会采用多进程去打包构建,使用方式还是蛮简单的,但并不是支持所有的loader

    首先引入,定义一下这个插件所开启的线程,推荐是四个,其实也可以直接使用默认的就行了

    HappyPack = require('happypack'),
        os = require('os'),
        happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length });

    然后在module的规则里改动一下,引入它,其中 id是一个标识符

    {
                test: /.jsx?$/,
                // 编译js或jsx文件,使用babel-loader转换es6为es5
                exclude: /node_modules/,
                loader: 'HappyPack/loader?id=js'
                // use: [{
                //     loader: 'babel-loader',
                //     options: {
    
                //     }
                // }]
            }

    然后我们调用插件,设置匹配的id,然后相关的配置可以直接把use:的规则部分套在loaders上

    new HappyPack({
                id: 'js',
                loaders: [{
                    loader: 'babel-loader',
                    options: {
                        // cacheDirectory: true
                    }
                }]
            }),

    要注意的第一点是,它对file-loaderurl-loader支持不好,所以这两个loader就不需要换成happypack了,其他loader可以类似地换一下

    要注意的第二点是,使用ExtractTextWebpackPlugin提取css文件也不是完全就能转换过来,所以需要小小的改动一下,比如

    module: {
            rules: [{
                test: /.css$/,
                // loader: 'HappyPack/loader?id=css'
                // 提取CSS文件
                use: cssExtractor.extract({
                    // 如果配置成不提取,则此类文件使用style-loader插入到<head>标签中
                    fallback: 'style-loader',
                    use: 'HappyPack/loader?id=css'
                    // use: [{
                    //         loader: 'css-loader',
                    //         options: {
                    //             // url: false,
                    //             minimize: true
                    //         }
                    //     },
                    //     // 'postcss-loader'
                    // ]
                })
            }, {
                test: /.scss$/,
                // loader: 'HappyPack/loader?id=scss'
                // 编译Sass文件 提取CSS文件
                use: sassExtractor.extract({
                    // 如果配置成不提取,则此类文件使用style-loader插入到<head>标签中
                    fallback: 'style-loader',
                    use: 'HappyPack/loader?id=scss'
                    // use: [
                    //     'css-loader',
                    //     // 'postcss-loader',
                    //     {
                    //         loader: 'sass-loader',
                    //         options: {
                    //             sourceMap: true,
                    //             outputStyle: 'compressed'
                    //         }
                    //     }
                    // ]
                })
            }

    因为它是直接函数调用的,我们就放到里层的use规则就行了,然后配置插件即可

    plugins: [
            new HappyPack({
                id: 'css',
                loaders: [{
                    loader: 'css-loader',
                    options: {
                        // url: false,
                        minimize: true
                    }
                }]
            }),
            new HappyPack({
                id: 'scss',
                loaders: [{
                    'loader': 'css-loader'
                }, {
                    loader: 'fast-sass-loader',
                    options: {
                        sourceMap: true,
                        outputStyle: 'compressed'
                    }
                }]
            }),

    十二、优化构建时的搜索路径

    在webpack打包时,会有各种各样的路径要去查询搜索,我们可以加上一些配置,让它搜索地更快

    比如说,方便改成绝对路径的模块路径就改一下,以纯模块名来引入的可以加上一些目录路径

    还可以善于用下resolve alias别名 这个字段来配置

    还有exclude等的配置,避免多余查找的文件,比如使用babel别忘了剔除不需要遍历的

    {
                test: /.jsx?$/,
                // 编译js或jsx文件,使用babel-loader转换es6为es5
                exclude: /node_modules/,
                 use: [{
                     loader: 'babel-loader',
                     options: {
    
                     }
                 }]
            }

    十三、(导出编译JSON文件)理一下打包构建涉及的模块,分析看有哪些包是不需要打包的,只打包需要的模块

    检查一下代码,看看是不是有不需要引入的模块出现在代码里

    webpack编译时加上参数 --json > stat.json 后,可以上传到 webpack-analyse webpack-visualizer 等分析站点上,看看打包的模块信息

    十四、使用ModuleConcatenationPlugin插件来加快JS执行速度

    这是webpack3的新特性(Scope Hoisting),其实是借鉴了Rollup打包工具来的,它将一些有联系的模块,放到一个闭包函数里面去,通过减少闭包函数数量从而加快JS的执行速度

     new webpack.optimize.ModuleConcatenationPlugin({
    
            })

    十五、使用noParse

    webpack打包的时候,有时不需要解析某些模块的依赖(这些模块并没有依赖,或者并根本就没有模块化),我们可以直接加上这个参数,直接跳过这种解析

    module: {
        noParse: /node_modules/(jquey.js)/
      }

    十六、使用异步的模块加载

    这个算是可以减小模块的体积吧,在一定程度上也是为用户考虑的,使用require.ensure来设置哪些模块需要异步加载,webpack会将它打包到一个独立的chunk中,

    在某个时刻(比如用户点击了查看)才异步地加载这个模块来执行

    $('.bg-input').click(() => {
        console.log('clicked, loading async.js')
    
        require.ensure([], require => {
    
            require('./components/async2').log();
            require('./components/async1').log();
            console.log('loading async.js done');
        });
    });

    十七、以模块化来引入

    有些模块是可以以模块化来引入的,就是说可以只引入其中的一部分,比如说lodash

    // 原来的引入方式
     import {debounce} from 'lodash';
    
    //按模块化的引入方式
    import debounce from 'lodash/debounce';

     主要是整理过来的,试用了几个方法,首次编译的速度可以从之前半分多钟减小到十秒左右了,当然,开启了热更新替换后简直美不可言

    当然还有很多方法没整理出,这些方法是有使用场景的,并不是每个都需要用,需要在自己的项目中尝试,结合配置它的复杂性和带来的效应来权衡。

  • 相关阅读:
    讲述Sagit.Framework解决:双向引用导致的IOS内存泄漏(上)
    Sagit.Framework For IOS 开发框架入门教程4:注册页布局-被消灭的变量
    Sagit.Framework For IOS 开发框架入门教程3:Start引导页及框架布局和隐藏事件的内幕
    Sagit.Framework For IOS 开发框架入门开发教程2:一行代码实现引导页
    Sagit.Framework For IOS 开发框架入门开发教程1:框架下载与环境配置
    CYQ.Data 正式支持 DotNET Core 版本发布
    IT连创业系列:App产品上线后,运营怎么搞?(中)
    IT连创业系列:App产品上线后,运营怎么搞?(上)
    分享:苹果APP更新上架被拒的另一种理由(Safety
    IT连创业系列:说说苹果商店AppStore上架App应用前后遇到的那些神坑
  • 原文地址:https://www.cnblogs.com/imwtr/p/7801973.html
Copyright © 2011-2022 走看看