当我的项目越来越简单时,会面临着构建速度慢和构建进去的文件体积大的问题。webapck构建优化对于大我的项目是必须要思考的一件事,上面咱们就从速度和体积两方面来探讨构建优化的策略。

剖析工具

在优化之前,咱们须要理解一些量化剖析的工具,应用它们来帮忙咱们剖析须要优化的点。

webpackbar

webpackbar能够在打包时实时显示打包进度。配置也很简略,在plugins数组中退出即可:

const WebpackBar = require('webpackbar')module.exports = {  plugins: [    ...    new WebpackBar()  ]}

speed-measure-webpack-plugin

应用speed-measure-webpack-plugin能够看到每个loader和plugin的耗时状况。

和一般插件的应用略有不同,须要用它的wrap办法包裹整个webpack配置项。

const SpeedMeasurePlugin = require('speed-measure-webpack-plugin')const smp = new SpeedMeasurePlugin()module.exports = smp.wrap({  entry: './src/main.js',  ...})

打包后,在命令行的输入信息如下,咱们能够看出哪些loader和plugin耗时比拟久,而后对其进行优化。

webpack-bundle-analyzer

webpack-bundle-analyzer以可视化的形式让咱们直观地看到打包的bundle中到底蕴含哪些模块内容,以及每一个模块的体积大小。咱们能够依据这些信息去剖析我的项目构造,调整打包配置,进行优化。

在plugins数组中退出该插件。构建实现后,默认会在http://127.0.0.1:8888/展现剖析后果。

const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPluginmodule.exports = {  plugins: [    ...    new BundleAnalyzerPlugin()  ]}

webpack-bundle-analyzer会计算出模块文件在三种情景下的大小:

  • stat:文件未通过任何转换的原始大小
  • parsed:文件通过转换后的输入大小(比方babel-loader转换ES6->ES5、UglifyJsPlugin压缩等等)
  • gzip:parsed后的文件,通过Gzip压缩的大小
    应用speed-measure-webpack-pluginwebpack-bundle-analyzer自身也会减少打包工夫(webpack-bundle-analyzer特地耗时),所以倡议这两个插件在开发剖析时应用,而在生产环境去掉。

优化构建速度

多过程构建

运行在Node.js之上的 Webpack 是单线程的,就算有多个工作同时存在,它们也只能一个一个排队执行。当我的项目比较复杂时,构建就会比较慢。现在大多数CPU都是多核的,咱们能够借助一些工具,充沛开释 CPU 在多核并发方面的劣势。

比拟常见的有happypackthread-loader

happypack

happypack可能将构建工作合成给多个子过程去并发执行,子过程解决完后再把后果发送给主过程。应用配置如下,就是把原有的loader的配置转移到happyPack中去解决。

const Happypack = require('happypack')module.exports = {  module:{    rules:[      {        test: /\.js$/,        use: 'happypack/loader?id=babel'   //问号前面的查问参数指定了解决这类文件的HappyPack实例的名字      },    ]  },  plugins:[    new Happypack({      id: 'babel',     //HappyPack实例名,对应下面rules中的“id=babel”      use: ['babel-loader']     //本来要应用的loader    })  ]}

thread-loader

happypack的作者曾经没有这个我的项目进行保护了,在webpack4之后,能够应用thread-loader

thread-loader应用起来很简略,就是把它搁置在其它loader之前,如下所示。搁置在这个thread-loader之后的 loaders会运行在一个独自的worker池中。

module.exports = {  module:{    rules:[      {          test: /\.js$/,          use: ['thread-loader','babel-loader']      }    ]  },}
参考webpack视频解说:进入学习

如果是小我的项目,不倡议开启多过程构建,因为开启过程是须要破费工夫的,构建速度反而会变慢。

利用缓存

利用缓存能够晋升二次构建速度(上面的比照图都是二次构建的速度)。应用缓存后,在node_modules中会有一个.cache目录,用于寄存缓存的内容。

cache-loader

在一些性能开销较大的 loader 之前增加此cache-loader,以将后果缓存到磁盘中。

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

能够看出,应用cache-loader后,构建速度有非常明显的晋升。

babel-loader应用缓存,也能够不借助cache-loader,间接在babel-loader前面加上?cacheDirectory=true

module.exports = {  module: {    rules: [      {        test: /\.js$/,        use: ['babel-loader?cacheDirectory=true']      }    ]  }}

hard-source-webpack-plugin

hard-source-webpack-plugin用于开启模块的缓存。

const HardSourceWebpackPlugin = require("hard-source-webpack-plugin")module.exports = {  plugins:[    new HardSourceWebpackPlugin()  ]}

应用hard-source-webpack-plugin后,二次构建速度大略晋升了90%。

include/exclude

通常来说,loader会解决合乎匹配规定的所有文件。比方babel-loader,会遍历我的项目中用到的所有js文件,对每个文件的代码进行编译转换。而node_modules里的js文件基本上都是转译好了的,不须要再次解决,所以咱们用 include/exclude 来帮咱们防止这种不必要的转译。

module.exports = {  module:{    rules:[      {          test: /\.js$/,          use: ['babel-loader'],          exclude: /node_modules/          //或者  include: [path.resolve(__dirname, 'src')]      }    ]  },}

include间接指定查找文件夹,比exclude效率更高,更能晋升构建速度。

动态链接库

下面的babel-loader能够通过include/exclude,防止解决node_modules里的第三方库。

但如果将第三方库代码和业务代码都打包进一个bundle文件,那么解决这个bundle文件的插件,比方uglifyjs-webpack-plugin、terser-webpack-plugin等,就没方法不解决外面第三方库内容。

其实第三方库代码根本都是成熟的,不用作什么解决。因而,咱们能够将我的项目的第三方库代码分离出来。

常见的解决形式有三种:

  1. Externals
  2. SplitChunks
  3. DllPlugin

Externals能够防止解决第三方库,但每一个第三方库都得在html文档中减少一个script标签来引入,一个页面过多的js文件下载会影响网页性能,而且有时咱们只应用第三方库中的一小部分性能,用script标签全量引入不太正当。

SplitChunks在每一次构建时都会从新构建第三方库,不能无效晋升构建速度。

这里举荐应用DllPlugin和DLLReferencePlugin(配合应用),它们是webpack的内置插件。DllPlugin会将不频繁更新的第三方库独自打包,当这些第三方库版本没有变动时,就不须要从新构建。

应用办法:

  1. 应用DllPlugin打包第三方库
  2. 应用DLLReferencePlugin援用manifest.json,去关联第1步中曾经打好的包
  3. 首先,新建一个webpack配置文件webpack.dll.js用于打包第三方库(第1步)
const path = require('path')const webpack = require('webpack')module.exports = {  mode: 'production',  entry: {    three: ['three', 'dat.gui']   // 第三方库数组  },  output: {    filename: '[name].dll.js',    //[name]就是在entry    path: path.resolve(__dirname, 'dist/lib'),    library: '[name]'  },  plugins: [    new webpack.DllPlugin({      name: '[name]',      path: path.resolve(__dirname, 'dist/lib/[name].json') //manifest.json的寄存地位    })  ]}

打包好后,能够看到,在dist目录下减少了一个lib文件夹。

  • 而后,咱们在webpack.base.js做一下批改,去关联第1步中曾经打好的包(第2步)
module.exports = {  plugins:[    //批改CleanWebpackPlugin配置    new CleanWebpackPlugin({      cleanOnceBeforeBuildPatterns: [          '!lib/**'              //在每次分明dist目录时,不清理lib文件夹的内容      ]    }),    // dll相干配置    new webpack.DllReferencePlugin({          // 将manifest字段配置成咱们第1步中打包进去的json文件      manifest: require('./dist/lib/three.json')      })  ]}

再次打包后能够看到,相比于一开始整个我的项目的体积 9.11MB,体积减小了90%,因为这是一个多页面打包(多页面打包配置)的利用,每个页面都援用了体积宏大的three.js外围文件,咱们把体积最大的three.js外围文件从每个页面的bundle中抽离进去后,bundle的体积大大减小。

再来看看构建工夫:相比于应用DllPlugin之前,工夫缩小了30% 。

不仅仅是第三方库,业务代码中的根底库也能够通过进行DllPlugin拆散。

优化构建体积

代码宰割

拆散第三方库和业务代码中的根底库,能够防止单个bundle.js体积过大,加载工夫过长。并且在多页面构建中,还能缩小反复打包。

常见的操作是通过SplitChunks(之前的文章曾经具体地写过了:SplitChunks)和 动态链接库(如上所示),这里都不再赘述。

动静import

动静import的作用次要缩小首屏资源的体积,非首屏的资源在用到的时候再去申请,从而进步首屏的加载速度。一个常见的例子就是单页面利用的路由治理(比方vue-router

{  path: '/list',  name: 'List',  component: () => import('../views/List.vue')  },

不是间接import组件(import List from '../views/List.vue'),那样会把组件都打包进同一个bundle。而是动静import组件,但凡通过import()援用的模块都会打包到独立的bundle,应用到的时候再去加载。对于性能简单,又不是首屏必须的资源都举荐应用动静import。

<span @click="loadModal">show弹窗</span>/***methods: {  loadModal(){     import('../modal/index.js')  }}***/

treeShaking

应用ES6的import/export语法,并且应用上面的形式导入导出你的代码,而不要应用export default。

// util.js 导出export const a = 1export const b = 2export function afunc(){}或export { a, b, afunc }// index.js 导入import { a, b } from './util.js'console.log(a,b)

那么在mode:production生产环境,就会主动开启tree-shaking,移除没有应用到的代码,下面例子中的afunc函数就不会被打包到bundle中。

代码压缩

罕用的js代码压缩插件有:uglifyjs-webpack-pluginterser-webpack-plugin

在webpack4中,生产环境默认开启代码压缩。咱们也能够本人配置去笼罩默认配置,来实现更定制化的需要。

v4.26.0版本之前,webpack内置的压缩插件是uglifyjs-webpack-plugin,从v4.26.0版本开始,换成了terser-webpack-plugin。咱们这里也以terser-webpack-plugin为例,和一般插件应用不同,在optimization.minimizer中配置压缩插件

const TerserPlugin = require('terser-webpack-plugin');module.exports = {  optimization: {    minimizer: [        new TerserPlugin({        parallel: true,  //开启并行压缩,能够放慢构建速度        sourceMap: true, //如果生产环境应用source-maps,则必须设置为true      })    ]  }}

雪碧图

雪碧图将多张小图标拼接成一张大图,在HTTP1.x环境下,雪碧图能够缩小HTTP申请,减速网页的显示速度。

用于合成雪碧图的图标体积要小,较大的图片不倡议拼接成雪碧图;同时要是网站动态图标,不是通过ajax申请动静获取的图标。所以通常是作为网站logo、icon之类的图片。

开发时,能够是UI提供雪碧图,然而每新增一个图标,就要从新制作一次,从新计算偏移量,比拟麻烦。通过webpack插件合成雪碧图,就能够在开发时间接应用单个小图标,在打包时,自动合成雪碧图,并主动主动批改css中的background-position的值。

上面,咱们借助postcss-sprites来自动合成雪碧图。

首先,在webpack.base.js中配置postcss-loader

//webpack.base.jsmodule.exports = {  module: {    rules: [      {        test: /\.css$/,        use: ['vue-style-loader','css-loader', 'postcss-loader']  //配置postcss-loader      },      {        test: /\.less$/,        use: [          'vue-style-loader','css-loader', 'postcss-loader', 'less-loader']  //配置postcss-loader      }    ]  }};

而后在我的项目根目录下新建.postcssrc.js,配置postcss-sprites

module.exports = {  "plugins": [    require('postcss-sprites')({      // 默认会合并css中用到的所有动态图片      // 应用filterBy指定须要合并的图片,比方这里这里只合并images/icon文件夹下的图片      filterBy: function (image) {        if (image.url.indexOf('/images/icon/') > -1) {            return Promise.resolve();        }        return Promise.reject();      }    })  ]}

默认会把图片合并到名为sprite.png的雪碧图中。

在css中间接指定小图标当背景:

.star{  display: inline-block;  height: 100px;  width: 100px;  &.l1{    background: url('../icon/star.png') no-repeat;  }  &.l2{    background: url('../icon/star2.png') no-repeat;  }  &.l3{    background: url('../icon/star3.png') no-repeat;  }}

打包实现后能够看到,主动批改了background-imagebackground-position

gzip

开启gzip压缩,能够减小文件体积。在浏览器反对gzip的状况下,能够放慢资源加载速度。服务端和客户端都能够实现gzip压缩,服务端响应申请时压缩,客户端利用构建时压缩。但压缩文件这个过程自身是须要消耗工夫和CPU资源的,如果存在大量的压缩需要,会加大服务器的累赘。

所以能够在构建打包时候就生成gzip压缩文件,作为动态资源放在服务器上,接管到申请后间接把压缩文件返回。

应用webpack生成gzip文件须要借助compression-webpack-plugin,应用配置如下:

const CompressionWebpackPlugin = require("compression-webpack-plugin")module.exports = {  plugins: [     new CompressionWebpackPlugin({       test: /\.(js|css)$/,         //匹配要压缩的文件       algorithm: "gzip"     })  ]}

打包实现后除了生成打包文件外,还会额定生成 .gz后缀的压缩文件。能够看出,gzip压缩文件的体积比未压缩文件的体积小很多。