记一次真实的webpack优化经历

前言

公司目前现有的一款产品是使用vue v2.0框架实现的,配套的打包工具为webpack v3.0。整个项目大概有80多个vue文件,也算不上什么大型项目。javascript

只不过每次头疼的就是打包所耗费的时间平均在一分钟左右,并且打包后有几个文件显示为【big】,也就是文件体积过大。css

最近就想着捣鼓一下,看能不能在此前的基础上作一些优化,顺带记录下来分享给你们。html

webpack打包优化

关于webpack的打包优化通常会从两个方面考虑:缩短打包时长下降打包后的文件体积,这两个方面也恰好是前面我须要解决的问题。前端

因此咱们先来了解一下这两个方面各自有什么具体的实现方式。vue

缩短打包时长

咱们都知道webpack的运行流程就像一条生产线同样,在这条生产线上会按顺序的执行每个流程。那很显然若是每个流程要干的事情越少或者每个流程有多我的来共同完成,那webpack打包的执行效率就会提升。java

1.减小loader搜索文件范围

咱们能够经过配置loaderexclude选项,告诉对应的loader能够忽略某个目录;或者经过配置loaderinclude选项,告诉loader只须要处理指定的目录。由于loader处理的文件越少,执行速度就会更快。node

通常比较常见的就是给babel-loader配置exclude选项。webpack

// webpack.config.js
module.exports = {
    entry: {},
    output: {},
    plugin: [],
    module: {
        rules:[
            {
                test: /\.js$/,
                loader: 'babel-loader',
                exclude: /node_modules/   // exclude的值是一个正则
            }
        ]
    }
}

以上配置即告诉babel-loader在转化JS代码的时候忽略node_modules目录,这么配置是由于咱们引用node_modules下的包基本都是编译过的,因此不须要在经过babel-loader处理。ios

2.利用缓存

关于webpack的缓存,官方的大体解释为:开启缓存之后,webpack构建将尝试从缓存中读取数据,以免每次运行时都须要运行代价高昂的从新编译过程。git

那如何在编译代码时开启缓存呢?

◕ cacheDirectory

第一种是配置babel-loadercacheDirectory选项,针对babel-loader开启缓存。

// webpack.config.js
module.exports = {
    entry: {},
    output: {},
    plugin: [],
    module: {
        rules:[
            {
                test: /\.js$/,
                loader: 'babel-loader?cacheDirectory',
                exclude: /node_modules/
            }
        ]
    }
}

◕ cache-loader

第二种是利用cache-loader

首先须要对其进行安装:npm install cache-loader --save-dev ;接着在webpack中进行配置:

// webpack.config.js
module.exports = {
    entry: {},
    output: {},
    plugin: [],
    module: {
        rules:[
            {
                test: /\.js$/,
                loader: [
                    'cache-loader',
                    'babel-loader'
                ]
                exclude: /node_modules/
            },
            {
                test: /\.ext$/,
                use: [
                  'cache-loader',
                  // 其余的loader
                  // ...
                ],
            }
        ]
    }
}

对于cache-loader官方给出的使用建议为:在一些性能开销较大的loader以前添加此loader,以将结果缓存到磁盘里;保存和读取这些缓存文件会有一些时间开销,因此请只对性能开销较大的loader使用此 loader

能够简单粗暴的认为若是一个loader在执行过程当中处理的任务较多,较为耗时,即断定此loader性能开销较大。咱们就能够尝试给该loader开启缓存,固然若是开启缓存之后实际的打包时长并无下降,则说明开启缓存对该loader的性能影响不大。

更多有关cache-loader的内容能够查看:https://www.webpackjs.com/loaders/cache-loader/

◕ hard-source-webpack-plugin

第三种是开启缓存的方式是使用hard-source-webpack-plugin。它是一个webpack插件,安装命令为:npm install --save-dev hard-source-webpack-plugin;最基础的配置以下:

// webpack.config.js
// 引入
const HardSourceWebpackPlugin  =  require('hard-source-webpack-plugin');

// 只在生产环境下开启HardSourceWebpackPlugin
if (process.env.NODE_ENV === "production") {
    module.exports.plugins = (module.exports.plugins || []).concat([
         new HardSourceWebpackPlugin()
    ])
}

更多有关hard-source-webpack-plugin的用法能够查看:https://github.com/mzgoddard/hard-source-webpack-plugin

以上三种开启缓存的方式虽然各不相同,但只要作了配置就能够在咱们的磁盘中看到它们的缓存结果。

3.多线程

多线程也就是将一件事情交给多我的去作,从理论上来说是能够提升一件事情的完成效率。

◕ happyhack

咱们都知道受限于node单线程模式,webpack的整个运行构建过程也是单线程模式的。

因此第一种开启多线程的方式就是将webpackloader的执行过程从单线程扩展到多线程。这种方式的具体实现依赖的是HappyPack插件。

使用happypack的第一步依然是安装:npm install --save-dev happypack;最简单的配置以下:

// webpack.config.js
// 引入
const HappyPack = require('happypack');

module.exports = {
    entry: {},
    output: {},
    plugin: [],
    module: {
        rules:[
            {
                test: /\.js$/,
                // 使用loader调起happypack
                loader: 'happypack/loader',
                exclude: /node_modules/
            }
        ]
    }
}
// 只有在生产环境下配置对应的happypack
if (process.env.NODE_ENV === "production") {
    module.exports.plugins = (module.exports.plugins || []).concat([
        new HappyPack({
            // re-add the loaders you replaced above in #1:
            loaders: 'babel-loader',
        })
    ])
}

这样的配置表示匹配到的.js源代码将被传递给HappyPackHappyPack将使用loaders指定的加载器(本例中是babel-loader)并行地转换它们。

这种最基础的配置,默认是3个线程并行处理。同时咱们也能够经过配置thread选项,自定义线程个数。

// webpack.config.js
new HappyPack({
    // re-add the loaders you replaced above in #1:
    loaders: 'babel-loader',
    // 自定义线程个数
    threads: 2,
})

关于线程的设置,官方推荐使用共享线程池的方式来控制线程个数However, if you're using more than one HappyPack plugin it can be more optimal to create a thread pool yourself and then configure the plugins to share that pool, minimizing the idle time of threads within it.(可是,若是您使用多个HappyPack插件,那么最好本身建立一个线程池,而后配置这些插件来共享该池,从而最大限度地减小其中线程的空闲时间。)

线程池的建立也很简单:

// webpack.config.js
const happyThreadPool = HappyPack.ThreadPool({ size: 4 });

除了能够经过上面的这种方式建立具体的线程数,也能够根据CPU的核数建立:

// webpack.config.js
const os = require('os');
const happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length-1 });

线程池建立好了之后,经过threadPool进行指定共享线程池

// webpack.config.js

// 此处省略一些代码

new HappyPack({
    // re-add the loaders you replaced above in #1:
    loaders: 'babel-loader',
    // 使用共享线程池
    threadPool: happyThreadPool
})

最后一个实用的配置项是verbose选项,可让happypack输出执行日志:

// webpack.config.js

// 此处省略一些代码

new HappyPack({
    // re-add the loaders you replaced above in #1:
    loaders: 'babel-loader',
    // 使用共享线程池
    threadPool: happyThreadPool,
    // 输出执行日志
    verbose: true
})

更多有关HappyPack的内容的能够查看:https://github.com/amireh/happypack

不过这里很遗憾的是该插件的做者在github上面宣布他本人不会在对该项目进行更新维护了。

◕ thread-loader

thread-loaderhappypack相似,也是经过扩展loader的处理线程来下降打包时间。安装命令:npm install thread-loader --save-dev;最简单的配置以下:

// webpack.config.js
module.exports = {
    entry: {},
    output: {},
    plugin: [],
    module: {
        rules:[
            {
                test: /\.js$/,
                loader: [
                    'thread-loader',
                    'babel-loader'
                ]
                exclude: /node_modules/
            }
        ]
    }
}

即将thread-loader配置到其余的loader以前便可。

更多有关thread-loader的内容能够查看:https://www.webpackjs.com/loaders/thread-loader

◕ webpack-parallel-uglify-plugin

通常咱们为了减小打包后的文件体积,会对文件进行压缩,好比删除换行删除中注释等。那常见的就是对JS进行压缩,最基本的就是使用webpack官方提供的uglifyjs-webpack-plugin插件;不过该插件是单线程压缩代码,效率相对来讲比较低。而webpack-parallel-uglify-plugin就是一款多线程压缩js代码的插件;

安装命令:npm install webpack-parallel-uglify-plugin;简单的配置以下:

// webpack.config.js
// 引入 ParallelUglifyPlugin 插件
const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin');
// 只在生产环境中配置ParallelUglifyPlugin
if (process.env.NODE_ENV === "production") {
    new ParallelUglifyPlugin({
      workerCount: 4,//开启几个子进程去并发的执行压缩。默认是当前运行电脑的cPU核数减去1
      cacheDir: './cache/',
      uglifyJs:{
          output:{
              beautify:false,//不须要格式化
              comments:false,//不保留注释
          },
          compress:{
              warnings:false,// 在Uglify]s除没有用到的代码时不输出警告
              drop_console:true,//删除全部的console语句,能够兼容ie浏览器
              collapse_vars:true,//内嵌定义了可是只用到一次的变量
              reduce_vars:true,//取出出现屡次可是没有定义成变量去引用的静态值
          }
      }
    }),
}

以后在进行打包,能够显著提高JS代码的压缩效率。

这个插件的使用必定要注意版本的问题,若是配置之后在构建代码时出现问题,能够尝试更换低版本。
本次个人webpack版本为v3.6,直接安装的webpack-parallel-uglify-plugin版本为v2.0.0。后面打包出现错误,因而将其版本下降为0.4.2后就能够正常打包。

关于多线程咱们特别须要注意,并非线程数量越多构建时间就越短。由于子线程处理完成后须要将把结果发送到主进程中,主进程在进行汇总处理,这个过程也是须要耗费时间的。因此最适合的线程数量能够尝试经过实践去得到。

4.动态连接库

通常咱们在打包一个vue项目时,会将vuevue-routeraxios等这些插件的代码跟咱们的代码打包到一个文件中,而这些插件的代码除非版本有变化,不然代码内容基本不会发生变化。因此每次在打包项目时,实际上都在重复打包这些插件的代码,很显然浪费了不少时间。

关于动态连接库这个词实际上借用的是操做系统中的动态连接库概念,webpack的具体实现也就是把前面咱们描述的那些插件分别打包成一个独立的文件。当有模块须要引用该插件时会经过生成的json文件连接到对应的插件。这样无论是咱们在开发环境仍是在生成环境下的打包构建,都不须要在对这些插件作重复的处理。那接下来咱们看看动态连接库的配置和使用。

首先咱们须要新建一个webpack.dll.config.js,该文件自己是一个webpack配置文件,主要用于分离第三方插件。

// webpack.dll.config.js

const path = require("path");
const webpack = require("webpack");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");

const DllPlugin = require('webpack/lib/DllPlugin');

// 分离出来的第三方库文件存放的目录
const dllPath = "webpackDLL";

module.exports = {
  // 入口文件   入口处配置须要分离的第三方插件
  entry: {
    echarts: ['echarts'], // 该配置表示分离echarts插件
  },
  // 输出文件
  output: {
    path: path.join(__dirname, dllPath), // 分离出来的第三方插件保存位置
    filename: "[name]-dll.[hash:8].js", // 分离出来的第三方插件文件名称
    library: '_dll_[name]'  // 第三方插件的名称,后续其余模块须要引用该插件,便用该名称进行引用
  },
  resolve: {
    extensions: ['.js', '.vue', '.json'],
    alias: {
      'vue$': 'vue/dist/vue.esm.js',
    }
  },
  plugins: [
    // 清除以前的dll文件
    new CleanWebpackPlugin(),
    // 使用DLLPlugin进行分离
    new webpack.DllPlugin({
      // 生成的 *.manfest.json 文件的路径
      path: path.join(__dirname, dllPath, "[name]-manifest.json"),
      // 这里的name须要和前面output.library配置一致
      // 以后生成的*.manfest.json 中有一个name字段,值就是咱们这里配置的值
      name: '_dll_[name]',
    })
  ]
};

关于上面各个配置项的含义已在注释中说明。接下来咱们先用这个配置项作一个打包,看一下结果。在这以前咱们须要在package.json中新增一个script脚本。

// package.json

{
  "scripts": {
    "dev": "cross-env NODE_ENV=development webpack-dev-server --hot --port 4500 --host 192.168.1.10",
    "build": "cross-env NODE_ENV=production  webpack --progress --hide-modules",
    "dll": "webpack -p --progress --config ./webpack.dll.config.js"
  }
}

新增的dll脚本会使用webpack.dll.config.js做为配置文件执行打包任务。

脚本执行成功之后,本地已经生成了对应的目录和文件。

其中echarts.dll.2a6026f8.js就是咱们分离出来的echarts插件,echarts-manifest.json就是前面咱们说的json文件。

第三方库文件分离后,当有模块须要引用echarts时,该如何引用到对应的echarts.dll.2a6026f8.js文件呢?

此时就须要DllReferencePlugin出场了:经过配置DllReferencePluginmanifest文件来把依赖的模块名称映射到对应的插件。这一步须要在webpack.config.js中进行配置:

// webpack.config.js
const DllReferencePlugin = require('webpack/lib/DllReferencePlugin')

module.exports = {
    entry: {},
    output: {},
    module: {},
    plugin: [
        new DllReferencePlugin({
        // manifest 就是以前打包出来的 *.manifest.json 文件
        manifest: path.join(__dirname, 'webpackDll', 'echarts-manifest.json'),
    }),
    ]
}

以上配置完成后,若是咱们处于开发环境,执行npm run dev打开浏览器会发现页面没法正常显示,且控制台有报错信息:

这里是由于咱们还须要在入口模板文件index.html中手动引入分离出来的插件文件。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title><%= htmlWebpackPlugin.options.title %></title>
  </head>
  <body>
    <div id="app"></div>
    <!-- 手动引入 --> 
    <script type="text/javascript" src="./webpackDLL/echarts-dll.2a6026f9.js"></script>
  </body>
</html>

以后在刷新页面就没有问题了。

这里须要特别注意,开发环境中手动引入对应插件的路径为./webpackDLL/*.2a6026f9.js,此时若是对项目进行打包部署,打包后index.html引用的依然是./webpackDLL/*.2a6026f9.js,很显然单是在本地环境中该资源的引用路径就是错误的;更甚之项目打包后的输出路径通常都会单独配置,好比dist目录,部署时也只会部署该目录下的文件。

因此仅仅是前面的配置,项目部署之后根本没法正常运行。

解决这个问题很显然有一个简单粗暴的方式:index.html中引入的路径依然不变,打包后的代码依然在dist目录下,只是打包完成后手动将对应的webpackDLL插件目录以及文件复制到dist目录下,这样直接将dist目录部署到服务器便可正常运行。

除了这种方式以外,咱们彻底能够借助webpack的一些插件来完成这个功能,这里就不演示了,你们能够本身尝试去完成。

下降打包后的文件体积

1.压缩文件

◕ image-webpack-loader

关于图片的压缩能够选择image-webpack-loader。正常状况下安装命令为:npm install image-webpack-loader --save-dev,只不过我在使用该命令安装时出现了不少错误,在网上收集到一些解决方案,最终发现将npm换成cnpm去安装image-webpack-loader才能安装成功:cnpm install image-webpack-loader --save-dev

关于image-webpack-loader的最基础的配置以下:

// webpack.config.js
module.exports = {
    entry: {},
    output: {},
    plugin: [],
    module: {
        rules:[
            {
            test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
            exclude: [resolve("src/icons")],
            use: [
              {
                loader: "url-loader",
                options: {
                  limit: 1024*10,
                  name: path.posix.join("assets", "images/[name].[hash:7].[ext]"),
                },
              },
              {
                loader: 'image-webpack-loader',// 压缩图片
                options: {
                  bypassOnDebug: true,  
                }
              }
            ]
          }
        ]
    }
}

更多有关image-webpack-loader的内容请查看:https://www.npmjs.com/package/image-webpack-loader

cache-loader4.1.0 要求webpack4.0.0
cache-loader 3.0.1 要求3.0.1

◕ webpack-parallel-uglify-plugin

该插件用于压缩JS代码(多线程压缩),用法前面已经介绍过,这里就不在介绍了。

经过压缩文件来减小文件的体积的同时会致使webpack打包时长增长,由于这至关于在作一件事的过程当中增长了一些步骤。

2. 抽离第三方库

CommonsChunkPluginwebpack官方提供的一个插件,经过配置这个插件,能够将公共的模块抽离出来。

webpack v4已经再也不支持该插件,使用SplitChunksPlugin代替。但因为本项目使用的是webpack v3,所以这里不对SplitChunksPlugin作介绍。

首先咱们须要在webpackentry选项对咱们须要分离的公共模块进行配置。

module.exports = {
    entry: {
        main: ["./src/main.js"], //原有的入口文件
        vender: ['echarts']     // 表示将echarts模块分离出来
    },
}

接着须要在plugin中配置这个公共模块的输出:

module.exports = {
    plugins:[
        new webpack.optimize.CommonsChunkPlugin({
            name: 'vender',  // 对应entry中配置的入口vender
            filename: '[name].js'  // 分离出来的模块以name选项做为文件名,也就是vender.js
        })
    ]
}

配置完成后对项目进行打包,就会看到打包结果中多出一个名为vendor.js的文件。

此时咱们能够去查看有使用过echarts的组件被打包后的js文件体积明显减小。

若是咱们还须要继续分离其余的一些公共模块,能够在entry中继续配置:

module.exports = {
    entry: {
        main: ["./src/main.js"], //原有的入口文件
        vender: ['echarts', 'vue', 'other-lib']
    },
}

若是前面配置的plugin的保持不变,则entry.vendor配置的公共模块统一会打包到vendor.js文件中;那若是配置的公共模块过多,就会致使抽离出来的vendor.js文件体积过大。

解决这个问题可使用前面咱们介绍过的动态连接库对第三方插件进行分离,后面实践部分会提到。

3.删除无用代码

一个产品在迭代的过程当中不可避免的会产生一些废弃代码,或者咱们在使用一个前端组件库时,只使用了组件库中的一小部分组件,而打包时会将整个组件库的内容进行打包。那无论是废弃代码或者未使用到的组件代码均可以称之为无用的代码,那很显然删除这些无用的代码也能够减小打包后的文件体积。

◕ purgecss-webpack-plugin

PurgeCSS是一个用来删除未使用的CSS代码的工具。首先对其进行安装:npm install purgecss-webpack-plugin -save-dev

该插件使用是须要和mini-css-extract-plugin插件结合使用,所以还须要安装mini-css-extract-plugin。不过特别须要注意mini-css-extract-plugin要求webpack v4

接着在webpack配置文件中进行配置:

// webpack.config.js
const path = require('path')
const glob = require('glob')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const PurgecssPlugin = require('purgecss-webpack-plugin')

const PATHS = {
  src: path.join(__dirname, 'src')
}

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: path.join(__dirname, 'dist')
  },
  optimization: {
    splitChunks: {
      cacheGroups: {
        styles: {
          name: 'styles',
          test: /\.css$/,
          chunks: 'all',
          enforce: true
        }
      }
    }
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          MiniCssExtractPlugin.loader,
          "css-loader"
        ]
      }
    ]
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: "[name].css",
    }),
    new PurgecssPlugin({
      paths: glob.sync(`${PATHS.src}/**/*`,  { nodir: true }),
    }),
  ]
}

更多有关purgecss-webpack-plugin的内容能够查看:https://www.purgecss.cn/

◕ tree-shaking

在webpack的官网中,对tree-shaking的解释以下:

官方文档有说明在webpack v4能够经过sideEffects来实现,同时给咱们演示了一些很基础的示例。

关于这个优化方案,无论是在一些相关概念的理解仍是项目的实践中均没有达到我想要的效果,因此在这里仅仅把这个优化点梳理在这里。关于该优化方案在项目中的具体配置和效果就不在演示了,以避免误导你们。

最后关于tree-shaking的一些知识,看到了一些解释的较为详细的文章,贴到这里供你们参考:

1. 【你的Tree-Shaking并没什么卵用】(https://segmentfault.com/a/1190000012794598)

2. 【Tree-Shaking性能优化实践 - 原理篇 】(https://juejin.cn/post/6844903544756109319)

打包分析工具

那除了前面咱们介绍的具体的优化方案以外,还有两个经常使用的打包分析工具能够帮助咱们分析构建过程和打包后的文件体积

1.speed-measure-webpack-plugin

speed-measure-webpack-plugin它是一个webpack插件,用于测量打包的速度,并输出打包过程当中每个loaderplugin的执行时间。

首先是对其进行安装:npm install speed-measure-webpack-plugin;接着在webpack.config.js中进行配置:

// webpack.config.js
// 引入
const SpeedMeasureWebpackPlugin = require('speed-measure-webpack-plugin');
// 建立实例
const smw = new SpeedMeasureWebpackPlugin();
// 调用实例的smw.wrap并将webpack的配置做为参数传入
module.exports = smw.wrap({
   entry: {},
   output: {}
   module: {},
   plugins:[],
})

完成以上步骤之后,咱们在执行npm run build,就能看到该插件输出的打包时长信息。

从这些信息里面咱们能很清楚看到每个pluginloader所花费的时长,这样咱们就能够针对耗费时间较长的部分进行优化。

2.webpack-bundle-analyzer

webpack-bundle-analyzer也是一个webpack插件,用于建立一个交互式的树形图可视化全部打包后的文件,包括文件的体积和文件里面包含的内容

它的使用也很是简单,首先是安装:npm install webpack-budle-analyzer;安装完成后,只须要在webpack的配置文件中写入以下内容:

// webpack.config.js
// 引入 
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; 

module.exports = {
  plugins: [
    new BundleAnalyzerPlugin()
  ]
}

以后咱们运行npm run build,打包结束之后webpack会输出以下日志。

接着会默认弹出浏览器窗口,打开http://127.0.0.1:8888/

图片来源于官网

若没有自动打开,能够手动输入地址。同时须要注意的是该插件默认启动在8888端口上,假如出现端口占用状况,能够对默认的端口进行配置,详情可参考:https://www.npmjs.com/package/webpack-bundle-analyzer

从页面中咱们能够清楚的看到每个文件的大小,同时还能够看到该文件中引入了那些模块、每个模块文件大小。根据这些内容,咱们就能够有针对性的处理一些大文件和这些大文件中一些体积较大的模块

总结

到此咱们已经列举了不少具体的webpack优化方案和每一种优化方案的简单配置。接下来咱们会将这些方案应用到实际的项目中,在实践开始以前咱们先对前面的内容简单作一个回顾和总结。

实践开始

此刻已是万事具有,只差实践了。上面的优化方案在实际的项目中效果如何,一块儿来看看吧。

缩短打包时长

首先咱们利用speed-measure-webpack-plugin对整个项目作一个打包时长分析。

打包耗时信息

这图虽然内容不全,可是重要的部分已经给你们展现出来了。经过这个耗时分析工具输出的日志信息,咱们能很清晰的看到整个打包耗时50秒,其中UglifyJsPlugin就执行了长达了33秒的时间,其余相对比较耗时的就是各类loader的执行。

关于缩短打包时长,后一项的优化都是在前面一项优化基础上进行的,因此总体打包时间会不断缩短。

1.使用webpack-parallel-uglify-plugin代替uglifyjs-webpack-plugin

根据前面的分析咱们急需优化的第一个点就是使用webpack-parallel-uglify-plugin代替uglifyjs-webpack-plugin插件,将js代码的压缩变成多线程。

js代码扩展成多线程压缩之后,在进行打包。

将JS代码压缩扩展为多线程

这个效果然的算是很是明显了,总体的打包时间由50秒 -> 36秒;JS代码的压缩也由33秒 -> 15秒,几乎节省了50%的时间。

2.开启缓存

接下来的一个优化点就是开启缓存,前面介绍了三种开启缓存的方式,只不过在本项目的实际应用中,只有hard-source-webpack-plugin这种方式效果比较明显,其他两种几乎没有效果。

配置了hard-source-webpack-plugin之后,第一次打包所耗费的时长基本不会发生变化,仍是上一步咱们优化后的30s

配置缓存后第一次打包

它的做用会在发生在下一次打包时。

配置缓存后第二次打包

配置hard-source-webpack-plugin后第一次打包时长没有发生变化是由于此时尚未缓存文件,第一次打包完成后才会生成缓存文件;以后第二次在进行打包,直接读取缓存文件,总体时间明显缩短;并且经过第二次打包的时长分析结果能够看到已经没有loader的耗时分析,也说明了本次打包是直接从缓存中读取的结果。

上面测试的第二次打包是在第一次的打包基础之上且而且没有改动代码。那实际开发时,咱们大多数都是对代码作了修改了而后再次打包,那这种状况下缓存对打包时长的影响又是如何呢?咱们来试一试便知。

在此我随意修改了两个.vue文件,分别给其增长了一行代码,而后在进行打包。

文件发生修改后再次打包

文件修改之后,对应的缓存文件就会失效,所以咱们看到对应loader从新执行,总体的打包时间也有所增长,不过整体来讲,该插件是能够有效缩短打包时长的。

3.开启多线程

前面咱们说过多线程是经过将webpackloader的执行过程从单线程扩展到多线程。由于前面咱们开启了缓存,loader的执行时间已经很是之短,因此在开启缓存的基础上在开启多线程基本是没有什么效果的,事实证实也是如此。

所以在这一步我将缓存关掉,使用happypack分别对babel-loadercss-loader开启了多线程,可是最终打包时长并无太大变化,仍是维持在30s

开启多线程这个优化方案在本项目中并无很明显的效果,可能源于项目自己loader处理时间就不长。即便开启了多线程,线程之间的通讯以及线程最后的汇总耗时和单线程处理耗时是同样的。

4.动态连接库

本次我用DLLPluginechartselement这两个组件进行了分离。

// webpack.dll.config.js
module.exports = {
  // 入口文件
  entry: {
    echarts: ['echarts'],
    element: ['element-ui']
  },
  // 其他代码省略
}

最后在进行打包,打包时长明显下降。

配置DLL,打包总体时长由17s->6s

最后关于DLL的配置在实践时,发现有两点特别须要注意:

第一个就是webpack.dll.config.js中的resolve配置项,其实在刚开始的时候,参照网上的一些配置对element-ui这个插件进行了分离,最后对整个项目进行打包部署后发现element-ui组件的table没法渲染。

table没法渲染

通过一番搜索,发现不少人在element-uigithub上提了不少相关的issue,说本身使用DLLPlugin分离了element-ui之后表格不渲染、tooltip控件失效。不过官方基本上都说不是element-ui自己的问题而且将issue至为closed

最后翻了翻这些issue,按照其中的一个办法添加了resolve配置后发现问题得以解决。

第二点须要注意其实在前面已经说过了,就是咱们须要在index.html入口模板中手动引入分离出来的第三方插件,同时生产环境下还须要将分离出来的插件代码复制到webpack打包输出目录下,项目部署后才能正常运行。

5.总结

到此,关于缩短打包时长这方面的优化基本完成了,咱们总共尝试了4种方案,最终将打包时长由最初的50s -> 6s,可见效果仍是很是明显的。

下降打包后的文件体积

在优化以前咱们依然是使用webpack-bundle-analyzer对打包后的文件体积进行分析。

这里我挑出来两个具备表明性的结果截图给你们,一个是入口文件main.js,里面引入的体积较大的模块element-ui的核心文件element-ui.common.jsvue核心文件vue.esm.js;另外一个是total.js,该模块是引入了体积较大的echarts文件。

1.压缩文件

前面咱们介绍了对jsimages进行压缩能够减小文件体积,在本项目中已经配置了webpack-parallel-uglify-pluginjs代码进行压缩,因此这里咱们仅仅尝试对image图片进行压缩。

配置image-webpack-loader之后,再次打包会很惊奇的发现并非全部的图片体积都会减小,有些图片的体积反正变大了。

对于该异常结果并无在深刻研究,因此暂时断定该项优化方案对本项目无效。

2.抽离第三方库

根据前面的分析,若是对应的文件体积减小,最直接的方式就是将vueechartselement-ui这些些体积较大的第三方库用CommonsChunkPlugin抽离出来。

分离出来之后,main.jstotal.js的文件体积明显降低:main.js1.5MB -> 755kBtotal.js819kB->29kB

可是分离出来的vendor.js体积达到了1.56MB

3.动态连接库

动态连接库在前面实际上归类到了缩短打包时长,但实际上它除了能有效的缩短打包时长,还能够将第三方库分离到不一样的文件,同时也解决了CommonsChunkPlugin出现的问题。

此次咱们使用DLLPluginvueechartselement这个三个插件进行分离。

// webpack.dll.config.js
module.exports = {
  // 入口文件
  entry: {
    echarts: ['echarts'],
    element: ['element-ui'],
    vue: ["vue"],
  },
  // 其他代码省略
}

分离出来的三个插件:

以后在进行打包,main.js的大小从1.5MB下降到800kB,其他引用到echarts插件的文件体积也由原来的几百kB下降到十几kB

总结

到此,本次关于webpack的打包优化实践就完成了,总体的打包时间是大大下降;对一些体积较大的文件进行了分离,也有效下降了文件的大小;可是也有一些优化方案在本项目中没有很明显的效果,甚至有些拔苗助长,至于缘由当下也没有仔细去研究。

本篇文章介绍的一些优化方案可能并不全,并且大都适用于webpack v3wekpack v4在不少时候已经默认开启一些优化方案,因此你们理性参考。后期有机会的话会尝试将项目的webpack版本进行升级,到时候在来总结分享。

同时,若是是真实的项目优化,全部的优化方案不能只关注打包时长是否下降或者文件体积是否减少,每个优化方案实践完成之后还须要在开发环境生成环境中对项目进行简单测试,若是项目运行正常才能说明此项优化方案是成功的。好比前面咱们实践的DLL优化方案,配置完成之后若是只关注打包时间文件体积可能会沾沾自喜,但实则将项目部署到服务器之后发现项目根本没法运行。

最后,若对本篇文章有疑问或者发现错误之处,还望指出,共同进步。

近期文章

JavaScript的执行上下文,真没你想的那么难
骨架屏(page-skeleton-webpack-plugin)初探

文末

若是这篇文章有帮助到你,❤️关注+点赞+收藏+评论+转发❤️鼓励一下做者

文章公众号首发,关注 不知名宝藏女孩 第一时间获取最新的文章

笔芯❤️~

相关文章
相关标签/搜索