Webpack的几点性能优化

904 阅读3分钟

Webpack的性能优化主要从打包时间和打包出来的包的大小来考虑


减少 Webpack 打包时间

优化 Loader

对于打包来说,loader越多说明代码需要转换的越多,则效率就越低,因此在优化loader的时候,可以选择loader的搜索范围。

module.exports = {
  module: {
    rules: [
      {
        // js 文件才使用 babel
        test: /\.js$/,
        loader: 'babel-loader',
        // 选择范围,只在 src 文件夹下查找
        include: [resolve('src')],
        // 选择范围,不会去查找的路径
        exclude: /node_modules/
      }
    ]
  }
}

为什么不去查找node_modules路径下的文件呢?因为node_modules中使用的代码都是编译过的,因此无必要再去处理一下。

同时,还可以将Babel编译过的文件缓存起来,在下次需要编译更改过的代码文件就可以,这样可以大大减少打包的时间

loader: 'babel-loader?cacheDirectory=true'

提前打包后引入

DllPluginDllReferencePlugin可以将一些不做修改的依赖文件,提前打包,只有当类库更新版本才有需要重新打包,并且也实现了将公共代码抽离成单独文件的优化方案。

new webpack.DllPlugin({
  context: __dirname,
  name: "[name]_[hash]",
  path: path.join(__dirname, "manifest.json"),
})

new webpack.DllReferencePlugin({
  context: __dirname, //文件中请求的上下文
  manifest: require("./manifest.json"), //生成manifest.json文件
  name: "./my-dll.js", //暴露的名称(可选)
  scope: "xyz", //用于访问dll的内容(可选)
  sourceType: "commonjs2" //dll是如何暴露的 (可选)
})

//webpack.dll.conf.js

const path = require('path');
const webpack = require('webpack');
module.exports = {
  entry: {
    vendor: [
    'vue/dist/vue.esm.js',
    'vue-router',
    'vuex',
    'babel-polyfill' //提前打包一些基本不怎么修改的文件
    ]
  },
  output: {
    path: path.join(__dirname, '../static/js'), //放在项目的static/js目录下面
    filename: '[name].dll.js', //打包文件的名字
    library: '[name]_library' //可选 暴露出的全局变量名
    // vendor.dll.js中暴露出的全局变量名。
    // 主要是给DllPlugin中的name使用,
    // 故这里需要和webpack.DllPlugin中的`name: '[name]_library',`保持一致。
  },
  plugins: [
    new webpack.DllPlugin({
      path: path.join(__dirname, '.', '[name]-manifest.json'), //生成上文说到清单文件,放在当前build文件下面,这个看你自己想放哪里了。
      name: '[name]_library'
    }),  
    //压缩 只是为了包更小一点 
    new webpack.optimize.UglifyJsPlugin({
      compress: {
        warnings: false,
        drop_console:true,
        drop_debugger:true
      },
      output:{
        // 去掉注释内容
        comments: false,
      },
      sourceMap: true
    })
  ]
};

//webpack.pro.conf.js 

const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin');

  new webpack.DllReferencePlugin({
      context: path.resolve(__dirname, '..'), 
      manifest: require('./vendor-manifest.json')
    }),
    //这个主要是将生成的vendor.dll.js文件加上hash值插入到页面中。
    new AddAssetHtmlPlugin([{
      filepath: path.resolve(__dirname,'../dist/static/js/vendor.dll.js'),
      outputPath: utils.assetsPath('js'),
      publicPath: path.posix.join(config.build.assetsPublicPath, 'static/js'),
      includeSourcemap: false,
      hash: true,
    }]),

npm run build:dll  //这个命令在最初执行一次之后,之后发布都不需要再重复执行了,除非webpack.dll.conf.js里面的依赖文件有升级。
//发布之前的打包
npm run build 

代码压缩

使用UglifyJS 来压缩代码,因为这个是单线程运行的,为了加快效率,可以使用webpack-parallel-uglify-plugin来并行运行UglifyJS,从而提高效率。

在 Webpack4 中,我们就不需要以上这些操作了,只需要将 mode 设置为 production 就可以默认开启以上功能。

还有需要注意的是:

resolve.extensions:用来表明文件后缀列表,默认查找顺序是 ['.js', '.json'],如果你的导入文件没有添加后缀就会按照这个顺序查找文件。我们应该尽可能减少后缀列表长度,然后将出现频率高的后缀排在前面

resolve.alias:可以通过别名的方式来映射一个路径,能让 Webpack 更快找到路径


减少 Webpack 打包后的文件体积

Scope Hoisting

Scope Hoisting会分析出模块之间的的依赖关系,尽可能的把打包出来的模块合并到一个函数中

// test.js
export const a = 1
// index.js
import { a } from './test.js'

对于这种情况,我们打包出来的代码会类似这样

[
  /* 0 */
  function (module, exports, require) {
    //...
  },
  /* 1 */
  function (module, exports, require) {
    //...
  }
]

但是如果使用 Scope Hoisting 的话,代码就会尽可能的合并到一个函数中去,也就变成了这样的类似代码

[
  /* 0 */
  function (module, exports, require) {
    //...
  }
]

如果在 Webpack4 中你希望开启这个功能,只需要启用 optimization.concatenateModules 就可以了

odule.exports = {
  optimization: {
    concatenateModules: true
  }
}