使用 Webpack 的 DllPlugin 提升项目构建速度

9,147 阅读7分钟

本文介绍了 Webpack 中 DllPlugin 插件的使用,以及配合使用 AddAssetHtmlPlugin 将构建好的 JS 文件插入到 html 页面中。

本文 Demo 地址

本文项目代码位置:源码地址

欢迎 Star!


DLLPlugin 和 DllReferencePlugin 简介

DLLPlugin 就是将包含大量复用模块且不会频繁更新的库进行编译,只需要编译一次,编译完成后存在指定的文件(这里可以称为动态链接库)中。在之后的构建过程中不会再对这些模块进行编译,而是直接使用 DllReferencePlugin 来引用动态链接库的代码。因此可以大大提高构建速度。一般会对常用的第三方模块使用这种方式,例如 react、react-dom、lodash 等等。只要这些模块不升级更新,这些动态链接库就不需要重新编译。


在 Webpack 中进行使用

需要插件

Webpack 已经内置了对动态链接库的支持,需要通过两个内置插件的配合使用。它们分别是:

  • DllPlugin 插件:用于打包出一个个单独的动态链接库文件
  • DllReferencePlugin 插件:用于在主配置文件中去引入 DllPlugin 插件打包好的动态链接库文件

创建项目

找一个空文件夹,打开命令行,执行命令

# 创建项目目录
$ mkdir webpack-dll-demo

# 初始化 package.json 文件
$ npm init -y 

# 创建 src 文件夹
$ mkdir src

# 创建 public 文件夹
$ mkdir public

# 安装需要用到的插件
$ npm install webpack webpack-cli html-webpacl-plugin clean-webpacl-plugin friendly-errors-webpack-plugin -D

# 安装 lodash 插件,用于演示 DllPlugin 用法
$ npm install lodash

在 public 目录下创建 index.html 文件

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Webpak DllPlugin 的使用</title>
</head>
<body>
    <div id="root"></div>
</body>
</html>

在 src 目录下创建 index.js 文件

index.js

import { join } from 'lodash';

function createSpan(){
    const element = document.createElement('span');
    element.innerHTML = join(['Hello', 'DllPlugin'], ' , ');
    return element;
}

document.querySelector('#root').appendChild(createSpan());

当前项目目录结构

webpack-prod-demo
|- /public
  |- index.html
|- /src
  |- index.js
|- package.json

使用 DllPlugin 和 DllReferencePlugin(分为三步)

一、先编写一个配置文件专门用来编译生成动态链接库(使用 DllPlugin)

webpack_dll.config.js

const path = require('path');
const webpack = require('webpack');
const CleanWebpaclPlugin = require('clean-webpack-plugin');
const FirendlyErrorePlugin = require('friendly-errors-webpack-plugin');

module.exports = {
    mode: 'production',
    entry: {
        // 将 lodash 模块作为入口编译成动态链接库
        lodash: ['lodash']
    },
    output: {
        // 指定生成文件所在目录
        // 由于每次打包生产环境时会清空 dist 文件夹,因此这里我将它们存放在了 public 文件夹下
        path: path.resolve(__dirname, 'public/vendor'),
        // 指定文件名
        filename: '[name].dll.js',
        // 存放动态链接库的全局变量名称,例如对应 lodash 来说就是 lodash_dll_lib
        // 这个名称需要与 DllPlugin 插件中的 name 属性值对应起来
        // 之所以在前面 _dll_lib 是为了防止全局变量冲突
        library: '[name]_dll_lib'
    },
    plugins: [
        new CleanWebpaclPlugin(['vendor'], {
            root: path.resolve(__dirname, 'public')
        }),
        new FirendlyErrorePlugin(),
        
        // 接入 DllPlugin
        new webpack.DllPlugin({
            // 描述动态链接库的 manifest.json 文件输出时的文件名称
            // 由于每次打包生产环境时会清空 dist 文件夹,因此这里我将它们存放在了 public 文件夹下
            path: path.join(__dirname, 'public', 'vendor', '[name].manifest.json'),
            // 动态链接库的全局变量名称,需要和 output.library 中保持一致
            // 该字段的值也就是输出的 manifest.json 文件 中 name 字段的值
            // 例如 lodash.manifest.json 中就有 "name": "lodash_dll_lib"
            name: '[name]_dll_lib'
        })
    ]
}

二、编写配置文件用来打包项目(使用 DllReferencePlugin)

webpack.config.js

const path = require('path');
const webpack = require('webpack');
const HTMLWebpackPlugin = require('html-webpack-plugin');
const CleanWebpaclPlugin = require('clean-webpack-plugin');
const FirendlyErrorePlugin = require('friendly-errors-webpack-plugin');

module.exports = {
    mode: 'production',
    devtool: 'source-map',
    entry: './src/index.js',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'build-[hash:5].js'
    },
    plugins: [
        new HTMLWebpackPlugin({
            title: 'Webpak DllPlugin 的使用',
            template: './public/index.html'
        }),
        new CleanWebpaclPlugin(['dist']),
        new FirendlyErrorePlugin(),
        new webpack.DefinePlugin({
            'process.env.NODE_ENV': JSON.stringify('production')
        }),
        // 告诉 Webpack 使用了哪些动态链接库
        new webpack.DllReferencePlugin({
            // 描述 lodash 动态链接库的文件内容
            manifest: require('./public/vendor/lodash.manifest.json')
        })
    ]
}

三、在 index.html 文件中引入动态链接库

由于动态链接库我们一般只编译一次,之后就不用编译,复用模块都被打包到了动态链接库中,因此入口的 index.js 文件中已经不包含这些模块了,所以要在 index.html 中单独引入。

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Webpak DllPlugin 的使用</title>
</head>
<body>
    <div id="root"></div>
    <script src="../public/vendor/lodash.dll.js"></script>
</body>
</html>

注意:由于在打包项目的时候会清理掉 dist 文件,所以我将生成的动态链接库放到了 public 目录下,所以这里是引入 public 下的动态链接库。

我们在 package.json 中添加两条指令:

  • build:打包项目
  • build:dll:编译生成动态链接库

package.json

...
"scripts": {
    "build": "webpack --config webpack.config.js",
    "build:dll": "webpack --config webpack_dll.config.js"
}
...

运行

根据上面所说的三个步骤,Dll 的用法已经结束了。现在我们运行一下看看结果。

打开命令行,执行命令

# 生成动态链接库,只需要运行一次这个指令,以后打包项目不需要再执行这个指令
$ npm run build:dll

# 打包项目
$ npm run build

在浏览器中打开 dist 文件夹下的 index.html 文件,可以看到浏览器上出现:Hello , DllPlugin。说明项目配置成功。

DllPlugin 和 DllReferencePlugin 分别做了什么

运行 npm run build:dll 指令之后,可以看到项目中 public 目录下多出了一个 vendor 的文件夹,可以看到其中包含两个文件:

  • lodash.dll.js 里面包含 lodash 的基础运行环境,也就是 lodash 模块
  • lodash.manifest.json 也是由 DllPlugin 生成出,用于描述动态链接库文件中包含哪些模块

lodash.dll.js

var lodash_dll_lib=...  // 此处代码过多,进行省略

lodash.manifest.json

{"name":"lodash_dll_lib","content":{"./node_modules/lodash/lodash.js":{"id":1,"buildMeta":{"providedExports":true}},"./node_modules/webpack/buildin/global.js":{"id":2,"buildMeta":{"providedExports":true}},"./node_modules/webpack/buildin/module.js":{"id":3,"buildMeta":{"providedExports":true}}}}

对比之后可以明白

  • 一个动态链接库文件中包含了大量模块的代码,这些模块存放在一个数组里,用数组的索引号作为 ID。 并且还通过 lodash_dll_lib 变量把自己暴露在了全局中,也就是可以通过 window.lodash_dll_lib 可以访问到它里面包含的模块

  • manifest.json 文件清楚地描述了与其对应的 dll.js 文件中包含了哪些模块,以及每个模块的路径和 ID

至此,Dll 的使用以及配置完成了。但是这里还有值得思考的地方:目前看来,项目可以正常运行,但是现在动态链接库是存放到 public 目录下的,如果我们需要将项目打包上线的话,如何能够让动态链接库自动也存放到 dist 目录下呢?如何在我们不手动添加脚本的情况下,自动将动态链接库引入到 index.html 文件中呢?如果有兴趣的话,可以继续往下来看一看配合 add-asset-html-webpack-plugin 的使用。


add-asset-html-webpack-plugin 的使用

上面也已经说了,虽然 Dll 的使用和配置没有问题了,但是还不是很满意,打包的时候不能将动态链接库自动的存放到 dist 文件夹,也不能自动在 html 文件中引入动态链接库脚本。所以这时候 add-asset-html-webpack-plugin 就派上用场了。

安装插件

$ npm install add-asset-html-webpack-plugin -D

使用

在 webpack.config.js 文件中进行使用

webpack.config.js

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

module.exports = {
    ...,
    plugins: [
        ...,
        // 该插件将把给定的 JS 或 CSS 文件添加到 webpack 配置的文件中,并将其放入资源列表 html webpack插件注入到生成的 html 中。
        new AddAssetHtmlPlugin([
            {
                // 要添加到编译中的文件的绝对路径,以及生成的HTML文件。支持globby字符串
                filepath: require.resolve(path.resolve(__dirname, 'public/vendor/lodash.dll.js')),
                // 文件输出目录
                outputPath: 'vendor',
                // 脚本或链接标记的公共路径
                publicPath: 'vendor'
            }
        ])
    ]
}

此时可以删除 index.html 文件中手动引入的脚本了

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Webpak DllPlugin 的使用</title>
</head>
<body>
    <div id="root"></div>
    <!-- 删除下面这行引入脚本 -->
-    <script src="../public/vendor/lodash.dll.js"></script>
</body>
</html>

运行项目

打开命令行,执行命令:

# 打包项目
$ npm run build
  • 现在查看项目中 dist 文件夹,可以看到 public 目录下 vendor 文件夹中的 js 文件已经全部自动拷贝到 dist 目录中的 vendor 文件夹下了

  • 打开 dist 文件夹中的 index.html 文件,可以看到已经自动将生成的脚本文件引入了

  • 在浏览器中打开 index.html,可以看到 'Hello , DllPlugin' 也能够正常显示

add-asset-html-webpack-plugin 更多配置请参考 github 地址:AddAssetHtmlPlugin 配置


总结

  • Dll 动态链接库的使用可以提高项目构建速度,因为对于大量复用的模块可以提前进行编译,且只需要编译一次,之后的开发中,使用这些模块的地方都不会再重新进行编译

  • DllPlugin 和 DllReferencePlugin 需要配合使用

    • DllPlugin 用于打包出一个个单独的动态链接库文件并生成对应的主清单文件用于描述动态链接库中包含哪些模块
    • DllReferencePlugin 用于在主清单文件中去引入 DllPlugin 插件打包好的动态链接库文件
  • 可以使用 AddAssetHtmlPlugin 将生成的动态链接库文件拷贝到出口文件夹下,然后 HTMLWebpackPlugin 就会自动的将脚本文件注入到生成的 html 文件中去

  • **注意:**如想测试一下构建速度是否有提升,可以将 webpack.config.js 中的 DllReferencePlugin 和 AddAssetHtmlPlugin 使用注释起来,运行 npm run build,观察打包时间;再将注释打开,运行 npm run build,观察打包时间,进行对比,即可发现区别

如是第一次打包,请先运行 npm run build:dll 生成动态链接库。

本文 Demo 地址:源码地址。 欢迎 Star!