详解基于DllPlugin和DllReferencePlugin的webpack构建优化

作者:南栀时间:2026-10-22 15:43:57浏览:446

本文详细解析如何利用Webpack的DllPlugin和DllReferencePlugin插件优化Vue项目的构建速度。通过预编译核心依赖库(vendor_library),分离业务代码与第三方库,显著减少重复编译时间。同时结合BundleAnalyzerPlugin分析包体积,并探讨按需加载策略对构建耗时与最终文件大小的影响,提供从配置到实战的完整优化方案。

项目背景与构建痛点

本项目基于vue-cli webpack2模板构建,集成了vue、vue-router、axios、muse-ui以及iview等依赖。目前单次构建耗时约40秒,这一速度令人难以接受。虽然在常规Web开发中构建时间并非核心关注点,但在开发Hybrid App时,频繁的构建需求使得优化速度变得至关重要。

项目结构划分与预编译策略

通常,我们可以将项目结构划分为三个主要部分。

分类 说明 变动频率
vendor_library 核心库 低
vendor 一般项目依赖 中等
code 业务逻辑 高

首先是vendor_library,包含vue、vue-router、axios等变动频率极低的文件。这些核心库非常适合利用DllPlugin和DllReferencePlugin进行预编译处理。

其次是vendor和code部分,这部分代码在开发阶段每次构建都需要重新编译。但在完成特定开发任务后,需要评估是否将某些vendor依赖移入vendor_library以进一步优化。

包体积分析与按需加载优化

接下来介绍BundleAnalyzerPlugin插件的使用。

BundleAnalyzerPlugin是一个用于分析Webpack生成包体组成,并以可视化方式反馈给开发者的工具。

项目背景与构建痛点

Vue项目默认已集成该插件。执行npm run build --report命令后,即可查看当前项目的依赖情况及包体组成,从而据此做出相应的调整。

以下是主要的优化方向。

实施按需加载策略。

muse-ui和iview均提供了按需加载的支持,只需按照官方文档进行相应配置即可。

分类 耗时 muse-ui iview
before 13256ms 234KB 337KB
after 43211ms 79KB 75KB

先来看优化后的结果。虽然被称为“优化后”,但构建时间反而延长了。这符合预期,因为之前直接引用的是dist目录下的文件,现在改为从src目录获取源码,增加了编译过程。

然而,文件体积的减少令人欣慰,具体效果取决于项目中模块的使用程度。在本项目中,仅使用了iview的四个控件,从结果看,按需加载在减小体积方面更为划算。虽然这并非时间上的优化,但由于DllPlugin解决了构建耗时问题,使得按需加载的优势得以充分发挥。

DllPlugin原理与优势解析

DllPlugin和DllReferencePlugin用于预编译资源模块。

Dll的概念借鉴自Windows系统的动态链接库。一个dll包本质上是纯依赖库,本身不可运行,仅供应用程序引用。

在打包dll时,Webpack会将所有包含的库建立索引,并写入一个manifest文件。引用dll的代码(即dll user)在打包时,只需读取该manifest文件即可。

这样做有以下几个好处:

Dll打包后独立存在,只要其包含的库没有增减或升级,其hash值就不会变化。因此,线上的dll代码无需随应用版本频繁更新。

当App部分代码修改后,只需编译App代码。只要dll中包含的库未增减或升级,就不需要重新打包dll,从而大幅提高每次编译的速度。

如果拥有多个项目且使用了相同的依赖库,它们可以共用同一个dll。

简而言之,我们可以将项目中公共且基本不会变动的模块(如前文所述的vendor_library)进行预编译。在后续项目构建过程中,直接引用预编译好的dll,无需再次编译。

这也是为什么按需加载能发挥更好效果的原因。在正常构建流程中,使用按需加载会导致每次构建时间比全量加载更长。而使用DllPlugin后,核心库只需编译一次,后续直接引用,从而抵消了按需加载带来的额外编译开销。

先来看一下改进后的结果。

分类 耗时
before 57192ms
after 7890ms

构建速度是否快到飞起?

DllPlugin实战配置步骤

DllPlugin和DllReferencePlugin的具体使用方法。

新建webpack.dll.conf.js文件。


//webpack.dll.conf.js
process.env.NODE_ENV = 'production'
const path = require('path');
var utils = require('./utils')
var vueLoaderConfig = require('./vue-loader.conf')
var ExtractTextPlugin = require('extract-text-webpack-plugin')
var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
const webpack = require('webpack');
//vue项目默认有一个static目录.我就把导出目录放在了static/dll/目录下
const srcPath = path.join(__dirname, '../static/dll/');
//需要编译的模块
const vendors = ['vue/dist/vue.esm.js',
  'vue-router',
  'vue-localstorage',
  'material-design-icons/iconfont/material-icons.css',
  'iview/dist/styles/iview.css',
  'iview/src/components/tag/tag.vue'
  'muse-ui/src/appBar'
];
webpackConfig = {
  entry: {
    vendor: vendors
  },
  resolve: {
    extensions: ['.js', '.vue', '.json']
  },
  module: {
    loaders: [
      {
        test: /\.vue$/,
        loader: 'vue-loader',
        options: vueLoaderConfig
      },
      {
        test: /iview.src.*?js$/,
        loader: 'babel-loader'
      },
      {
        test: /muse-ui.src.*?js$/,
        loader: 'babel-loader'
      },
      {
        test: /\.css$/,
        use: ExtractTextPlugin.extract({
          use: "css-loader"
        })
      },
      {
        test: /\.(woff2?|eot|ttf|otf|svg)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          publicPath: './',
          limit: 1000,
          name: 'fonts/[name].[ext]'
        }
      }]
  },
  output: {
    path: srcPath, // 输出的路径
    filename: '[name].dll.js', // 输出的文件,将会根据entry命名为vendor.dll.js
    library: '[name]_library' // 暴露出的全局变量名
  },
  plugins: [
    new ExtractTextPlugin({
      filename: '[name].dll.css'
    }),
    new OptimizeCSSPlugin({
      cssProcessorOptions: {
        safe: true
      }
    }),
    new webpack.optimize.UglifyJsPlugin({ // uglifjs压缩
      compress: {
        warnings: false
      }
    }),
    new webpack.DllPlugin({
      path: path.join(srcPath, '[name]-mainfest.json'), // 描述依赖对应关系的json文件
      name: '[name]_library',
      context: __dirname // 执行的上下文环境,对之后DllReferencePlugin有用
    })
  ]
}
if (process.env.npm_config_report) {
  var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
  webpackConfig.plugins.push(new BundleAnalyzerPlugin())
}
module.exports = webpackConfig;

这本质上是一个正常的构建配置文件,根据项目需求修改即可。唯一区别在于plugins中多了一个DllPlugin,它会生成一个描述依赖对应关系的json文件。

static目录结构如下。


|____dll
| |____fonts
| |____vendor-mainfest.json
| |____vendor.dll.css
| |____vendor.dll.js

根据构建结果修改模板index.html。


//head 标签
<link type="text/css" rel="stylesheet" href="./static/dll/vendor.dll.css" rel="external nofollow" >
//body 标签
<script src="./static/dll/vendor.dll.js"></script>

修改webpack.base.conf.js。


plugins: [
    ...,
    new webpack.DllReferencePlugin({
      context: __dirname,
      manifest: require('../static/dll/vendor-mainfest.json') // 指向生成的json文件
    })
  ]

执行构建命令。


webpack --config ./build/webpack.dll.conf.js

你也可以将该语句放入npm run脚本中。'build:dll:report'是为了优化dll依赖关系而添加的额外命令。前文已提及,我们可以借助BundleAnalyzerPlugin插件查看当前包的依赖情况和包体组成。


 //package.json
"scripts":{
  "build:dll": "webpack --config ./build/webpack.dll.conf.js",
  "build:dll:report": "npm run build:dll --report"
}

npm run build …看看这次的构建时间吧

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。

作者最新文章