阅读 2054

webpack优化不完全指南

基础篇

最基本的一个webpack配置

const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');

module.exports = {
        entry: __dirname + "/app/main.js", //已多次提及的唯一入口文件
        output: {
            path: __dirname + "/build",
            filename: "bundle-[hash].js"
        },
        devtool: 'none',
        devServer: {
            contentBase: "./public", //本地服务器所加载的页面所在的目录
            historyApiFallback: true, //不跳转
            inline: true,
            hot: true
        },
        module: {
            rules: [{
                    test: /(\.jsx|\.js)$/,
                    use: {
                        loader: "babel-loader"
                    }
                }, {
                    test: /\.css$/,
                    use: ExtractTextPlugin.extract({
                        fallback: "style-loader",
                        use: [{
                            loader: "css-loader",
                            options: {
                                modules: true,
                                localIdentName: '[name]__[local]--[hash:base64:5]'
                            }
                        }, {
                            loader: "postcss-loader"
                        }],
                    })
                }
            }
        ]
    },
    plugins: [
        new webpack.BannerPlugin(),
        new HtmlWebpackPlugin({
            template: __dirname + "/app/index.tmpl.html" //new 一个这个插件的实例,并传入相关的参数
        }),
        new webpack.optimize.OccurrenceOrderPlugin(),
        new webpack.optimize.UglifyJsPlugin(),
        new ExtractTextPlugin("style.css")
    ]
};

复制代码

接下来基于这份配置进行一个优化:
首先我们得区分开发环境、生产环境以及测试环境(这个在国内用的公司少) 实现思路(参考vue-cil),通用配置可以设置一个base文件然后exports,在其他的配置文件中require进来。

上面这个配置中new webpack.optimize.UglifyJsPlugin()就可以放入webpack.prod.conf.js中,因为代码压缩丑化只需要在生产环境中使用,在开发过程开启的话,不仅编译时间加长还不利于调试。

module与entry、output这个就可以放到webpack.base.cong.js文件中,这些配置是通用的不管在生产环境还是开发环境中都是用得到的。

devServer则是在开发过程中需要启动一个服务器然后再开启热替换功能。

  • 在module中也可进行些优化,例如在后缀为.js文件的处理中添加:

include: [resolve('src'), resolve('test')]

这样loader加载器就只会匹配src/与/test目录下的文件,至于node_modules目录下的文件则跳过,假如没加这一限制条件的话,node_modules下那么多的js文件,想想都觉得恐怖,并且别人发布出来的包文件一般都是经过babel转成ES5标准的。

  • 添加alias,如:

alias: {
'@': resolve('src')
}

这样当webpack遇到import('@xxx')的时候就会直接在src/目录下去寻找,能节省不少的目录查询时间

进阶篇

多页面优化

当我们的项目是多个页面的时候,我们需要用到HtmlWebpackPlugin插件,

for(let i = 0; i< html.length; i++) {
  plugins.push(
    new HtmlWebpackPlugin({
        filename:fileName,
        template: path.join('./src/html/', fileName),
        inject: true,
        minify: {
          removeComments: true,
          collapseWhitespace: false,
          removeAttributeQuotes: true
        },
        chunksSortMode: 'dependency',
        chunks: ['manifest', 'vendor', cur]
      }))
  )
}
复制代码

有多少个页面就push多少个HtmlWebpackPlugin进去,至于页面的获取我们可以通过walk-sync来做到,通过获取目录下的入口文件来判断有多少个页面存在。
我在优化过程中遇到个坑,当webpack从2.x升级为3.x的时候,

compilation.getStats().toJson()

这个函数的执行时间会大大加长,我看到其实在issue里头有人提出来了,不过作者并没有给合并到master中去。还有点就是:当页面是多页面的时候每一次小的改动都会把所有的html文件重新生成一次,针对这两个优化我在Githubfork了下html-webpack-plugin,并发布了html-webpack-plugin-multihtml。我们的项目一共是有49个页面之前rebuild是花费5-7s ,优化后保持在1s左右。

提取公共代码CommonsChunkPlugin

有些类库如utils, bootstrap之类的可能被多个页面共享,最好是可以合并成一个js,而非每个js单独去引用。这样能够节省一些空间。这时我们可以用到CommonsChunkPlugin,我们指定好生成文件的名字,以及想抽取哪些入口js文件的公共代码,webpack就会自动帮我们合并好。

new webpack.optimize.CommonsChunkPlugin({
      name: 'vendor',
      chunks: Object.keys(entrys),
      minChunks: function (module, count) {
        return (module.resource && /\.js$/.test(module.resource) && module.resource.indexOf(path.join(__dirname, '../node_modules')) === 0 && count > 8)
      }
}),
复制代码

这个是针对多页面优化的,CommonsChunkPlugin会自己计算当一个类库被引用的次数超过8次,就把它打包至vendor中,这样就不必全打包至一个index.js里头

externals提取常用库

externals 配置选项提供了「从输出的 bundle 中排除依赖」的方法。相反,所创建的 bundle 依赖于那些存在于用户环境(consumer's environment)中的依赖。
externals主要是防止将某些import的包打包到bundle中,而是在运行时(runtime)再去从外部获取这些扩展依赖

配置也挺简单的接下来贴下代码:

index.html中引入

<script src="https://code.jquery.com/jquery-3.1.0.js"></script>
复制代码
webpack.config.js中配置

externals: {
  jquery: 'jQuery'
}
复制代码

这样就能在代码中通过import引用了:

import $ from 'jquery';
复制代码

uglifyPlugin多线程压缩代码

在webpack的默认情况下,都是单线程跑的,有个叫webpack-uglify-parallel的插件,它能够充分调用计算机的资源,利用计算机的多核特性进行代码压缩。
使用方法也挺简单的, 把之前webpack.prod.conf.js中的

new webpack.optimize.UglifyJsPlugin({
  compress: {
    warnings: false,
    drop_console: true
  },
  comments: false
}),
复制代码

替换为

var os = require('os');
var UglifyJsParallelPlugin = require('webpack-uglify-parallel');

module.exports = {
    /// ... rest of config
    plugins: [
        new UglifyJsParallelPlugin({
            workers: os.cpus().length, // usually having as many workers as cpu cores gives good results
            // other uglify options
        })
    ]
}
复制代码

即可 GitHub地址:webpack-uglify-parallel

Happypack

happypack 是 webpack 的一个插件,目的是通过多进程模型,来加速代码构建。目前我们项目中已经配置并且上线了,效果还可以,从最初的40s减少到30s的编译时间。
配置按照GitHub上的来,也不难

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

// 省略其余配置
module: {
  loaders: [
      {
        test: /\.less$/,
        loader: ExtractTextPlugin.extract('style',path.resolve(__dirname,'./node_modules', 'happypack/loader') + '?id=less'
        )
      }
    ]
  },
  plugins: [
      new HappyPack({
        id: 'less',
        loaders: ['css!less'],
        threadPool: happyThreadPool,
        cache: true,
        verbose: true
      })
  ]
复制代码

我在优化我们项目的webpack遇到了两个坑,一个就是上面提到的,webpack2升级3编译时间增多,另一个就是devServer在低版本安卓机型上跑步起来。devServer这个使用"webpack-dev-server": "2.7.1"即可,因为2.9.x版本使用的是es6的写法安卓4.1以下不支持,ie9以下也不支持。