webpack优化

509 阅读4分钟

之前写过webpack的基本用法,用到了一些loader plugin 来处理js ,css ,图片,字体等模块,本文主要是对于项目优化,以及打包优化,我们知道随着项目的增大,打包速度也会越来越慢,所有我们可以靠一些优化手段提高效率

1.工具

我们想要知道如何优化就得知道优化哪些东西,我们可以通过一些工具去判断哪些文件需要优化,或者哪些打包过程需要优化

1. webpack-bundle-analyzer

这个是一个可视化打包的分析工具,使用这个插件之后会自动启动一个本地8888服务,用来查看打包后的包的信息

let BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
plugins: [
    new BundleAnalyzerPlugin()
]

我们页面代码,可以看出引入了 react、react dom、jquery、lodash 包

可视化页面如下,我们可以看到打包之后的index.js的信息 和每个包大小分布情况

从这上面我们就可以看到几个问题,都会增加打包后的包的大小和打包时间

  1. jquery 我们没有使用,但是还是打包了
  2. lodash 我们只用了 map 方法,整个lodash 都打包进去了

后面我们会提到如何优化这些,这里先介绍一下这个工具

2. speed-measure-webpack-plugin

这个插件可以测试出每个plugin 和 loader 所花费的时间

let SpeedMeasurePlugin = require('speed-measure-webpack-plugin')
let config = {
//...webpack 配置
}
module.exports = new SpeedMeasurePlugin().wrap(config)

这样配置之后打包后就可以看到打包信息

2.include/exculde

可以缩小解析的文件范围,include指定解析某个文件夹,exclude排除某个文件夹,这个是最简单并且效果最明显的优化,一般做webpack配置的时候都会配置上

上面的代码目录结构未配置前

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

打包速速  Time: 7595ms

配置后,缩小范围到  src目录下

module: {
 rules: [
   { test: /\.js$/, 
    loader: 'babel-loader', 
    include: [path.resolve(__dirname, 'src')]  
   } 
 ]
},

打包速度  Time: 1558ms

效果还是很明显的,exclude 优先级高于 include 我一般只配置上include

3.cache-loader

cache-loader会在首次打包时候加上一些缓存,再次打包的时候会读取缓存节省打包时间

module: {
 rules: [
   { test: /\.js$/, 
    loader: ['chche-loader', 'babel-loader'], 
    include: [path.resolve(__dirname, 'src')]  
   } 
 ]
},

加入cache-loader 之前速度是 Time: 1594ms , 再次构建加入缓存之后是 Time: 818ms,速度提升还是很明显

4. externals

防止将某些 import 的包打包出去,而是在运行时候再去从外部获取这些依赖,一般都是使用CDN缓存,打包的时候不会将这些打包进去,我们再模板文件(index.html)中引入jquery 和 lodash cdn

<script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script><script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script>

webpack上配置上externals属性

externals: {    'jquery': '$',    'lodash': '_',  },

这样js中再引入 jquery 和 lodash的时候就不会将两个文件打包了

设置上  externals 打包速度从加入****Time: 818ms,缩减到了  Time: 606ms 速度又提升了一些

5.noParse

如果已经知道某个包没有依赖项目,设置不去解析某个包中的依赖,比如上面的jquery ,lodash,我们先还原会不用externals 和 cdn,并设置noPase, module中 noPase属性,接收正则

module: {
    noParse: /jquery|lodash/
}

经过测试打包速度确实是快了一些

6. happypack

这个项目已经不再维护了,这个插件是多线程处理,小型项目其实还用不到,项目越大效果约明显

loader检测js时候配置如下

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

plugin处配置如下, 与loader中配置的 id一一对应,用了多个loader就根据id配置多个 plugin

new Happypack({
    id: 'js',
    use: ['babel-loader']
})

7.tree-shaking

webpack@4自带了tree-shaking功能

tree-shaking 用于消除引用了但是没有被使用的模块,它得益于ES6模块的特性,依赖关系是确定的,和运行时候的状态无关,可以进行可靠的静态分析,

但是tree-shaking也有问题,没有用到的类的方法不能消除

8.code spliting

也就是我们常说的代码分割,那什么是代码分割呢?

  • 我们项目会引入很多第三方库,因为第三方库几乎不会改动,我们想把这些库抽离出去可以给浏览器做缓存(一般是vendor)
  • 给webpack自己的runtime的代码单独打包(一般是manifest)
  • 为公共业务代码单独打包(一般是common)
  • 异步加载的module 单独打包(一般都是模块的编号)

webpack 4 之前都是用 CommonsChunkPlugin 来抽取公共代码,webpack4 只有使用splitChunks插件

1. 首先我们先抽取vendor   打包会生成一个vendor.js文件

optimization: {
    splitChunks: {
      cacheGroups: {
        vendor: {
          chunks: 'initial',
          test: /node_modules/,
          name: 'vendor',
          minChunks: 1,
        },
      }
    }
},

chunks 默认是async ,一定要改成all ,或者 initial 才能抽取node_modules下的库

2. 提取 webpack runtime 代码  打包会会多出一个manifest文件

optimization: {
    runtimeChunk: {
      name: 'manifest'
    },
    splitChunks: {
      cacheGroups: {
        vendor: {
          chunks: 'initial',
          test: /node_modules/,
          name: 'vendor',
          minChunks: 1,
        },
      }
    }
},

3. 提取公共业务代码  打包后会多出一个 common.js

optimization: {
    runtimeChunk: {
      name: 'manifest'
    },
    splitChunks: {
      cacheGroups: {
        commons: {
          chunks: 'initial',
          test: path.resolve(__dirname, 'src'),
          name: 'commons',
          minSize: 0,
          minChunks: 2,
        },
        vendor: {
          chunks: 'initial',
          test: /node_modules/,
          name: 'vendor',
          minChunks: 1,
        },
      }
    }
  },

4. 抽取异步加载代码

webpack 提供两种异步加载方式

  • import() //ES的提案,返回一个promise,导入的模块在then中获取到
  • require.ensure() 

上面的异步方式都依赖于promise,旧浏览器中使用就要用es6-promise polyfill

babel7 默认就有  syntax-dynamic-import 所以页面中直接使用 import()就可以打包出按需加载块了,vue-router的按需加载就是很好的应用

import(/* webpackChunkName: "打包后的模块名称" */'路径' ).then( res => {})

9. DllPlugin

我用了两个项目来做优化,感觉DllPlugin对打包速度优化的不是很明显,最多也就几秒钟,感兴趣的可以自己试试,这里就不多说了

下篇文章将会聊聊webpack的源码并手写一些loader 和 plugins