构建优化篇~~DllPlugin到底为我们做了什么

2,137 阅读5分钟

序言

其实很多人在面试的时候都会被问到关于webpack优化的问题,不管是构建还是打包加载,可能我们都离不开DllPlugin,也许很多人对DllPlugin已经很熟悉了,但是有很多人对DllPlugin一点都不了解。不管之前有没有过了解,从现在开始我们来分析下DllPlugin插件到底为我们做了什么???

说明

这里其实我们是对dll打包结果以及webpack打包结果进行分析,不对webpack如何打包成dll进行分析,各位郎君切记啊!!!

DllPlugin 是干什么的呢???

例如我们使用vue/ vue-router/ vuex来写项目,虽然我们写代码只有5k,但是打包结果却200k。这是怎么回事呢??? 其实就是将vue等文件都打包到主文件中了。 如果我们想提高加载速度就需要将共同的文件进行抽离,那么插件DllPlugin的机会就来了。

DllPlugin 的使用

1. 运行项目结构

图片.png

同志们!!! 这是我们测试demo的目录结构,后面也会陆陆续续把代码粘贴出来,如果感兴趣的小伙伴也可以自己运行下

1. webpack.dll.config.js 配置实现

const path = require('path')
const { DllPlugin } = require('webpack')

const pathResolve = (url) => path.resolve(__dirname, url)

module.exports = {
  mode: 'development',
  entry: {
    utils: ['isarray', 'is-promise']
  },
  output: {
    path: pathResolve('../dist'),
    filename: 'utils.dll.js',
    library: '_dll_utils'
  },
  plugins: [
    new DllPlugin({
      name: '_dll_utils',
      path: path.join(__dirname, '../dist', 'utils.manifest.json')
    })
  ]
}
  • 上述的代码就是DllPlugin简单的配置,接下来我们会着重讲解下注意点:
    • output.library 以及new DllPlugin().name 必须保持一致。因为生成的文件靠这个来进行关联,至于这个是干什么的??? 我们会在后续打包分析中讲解到
    • 按照上述的配置 我们会生成两个文件utils.dll.js 以及utils.manifest.json
    • 插件更多的信息,请参照DllPlugin详细讲解

2. webpack.config.js 配置实现

const path = require('path')
const DllReferencePlugin = require('webpack/lib/DllReferencePlugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')

const pathResolve = (url) => path.resolve(__dirname, url)

module.exports = {
  mode: 'development',
  devtool: false,
  entry: pathResolve('../src/index.js'),
  output: {
    path: pathResolve('../dist'),
    filename: 'bundle.js'
  },
  plugins: [
    new DllReferencePlugin({
      manifest: require('../dist/utils.manifest.json')
    }),
    new HtmlWebpackPlugin({
      template: pathResolve('../public/index.html')
    })
  ]
}
  • 上述的代码是编译的配置文件,这里还算比较简单。我们的主要关注点在插件DllReferencePlugin中。其余的很稀疏平常

3. src/index.js 代码实现

let isarray = require('isarray')
console.log('isarray([1, 2, 3])=', isarray([1, 2, 3]))

4. package.json 配置

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

编译结果分析

1. 编译结果目录

图片.png

2. utils.dll.js 代码分析

// 暴露的全局变量  打包内容将以全局的方式挂载到这里
var _dll_utils;

// 最外层的IIFE
(() => {
  var __webpack_modules__ = {
    "./node_modules/isarray/index.js": (module) => {
      eval(
        "var toString = {}.toString;\n\nmodule.exports = Array.isArray || function (arr) {\n  return toString.call(arr) == '[object Array]';\n};\n\n\n//# sourceURL=webpack://_dll_utils/./node_modules/isarray/index.js?"
      );
    },

    "?2e89": (module, __unused_webpack_exports, __webpack_require__) => {
      eval(
        "module.exports = __webpack_require__;\n\n//# sourceURL=webpack://_dll_utils/dll_utils?"
      );
    },

    "./node_modules/is-promise/index.mjs": (
      __unused_webpack___webpack_module__,
      __webpack_exports__,
      __webpack_require__
    ) => {
      "use strict";
      eval(
        "__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */   "default": () => (/* binding */ isPromise)\n/* harmony export */ });\nfunction isPromise(obj) {\n  return !!obj && (typeof obj === 'object' || typeof obj === 'function') && typeof obj.then === 'function';\n}\n\n\n//# sourceURL=webpack://_dll_utils/./node_modules/is-promise/index.mjs?"
      );
    },
  };
  var __webpack_module_cache__ = {};

  // 加载模块方法
  function __webpack_require__(moduleId) {
    // 获取以及判断缓存
    var cachedModule = __webpack_module_cache__[moduleId];
    if (cachedModule !== undefined) {
      return cachedModule.exports;
    }
    var module = (__webpack_module_cache__[moduleId] = {
      exports: {},
    });

    // 开始加载模块
    __webpack_modules__[moduleId](module, module.exports, __webpack_require__);

    return module.exports;
  }
  var __webpack_exports__ = __webpack_require__("?2e89");
  _dll_utils = __webpack_exports__;
})();
  • 上述的代码都有对应的注释,请大家可以认真阅读,但是不阅读也没有关系,我们会挨个解析:
    • 首先映入眼帘的是代码var _dll_utils,这个变量就是我们在webpack.dll.config.js中进行配置的。在代码编译结束后其实是放到全局上的,作为一个全局变量来实现的
    • 代码var __webpack_exports__ = __webpack_require__("?2e89"); _dll_utils = __webpack_exports__;是执行文件的入口。webpack会给入口一个随机的值,这里就是(?2e89). 开始加载模块
    • 图片.png
      • 上述的描述没有用粘贴代码的方式,而是用截图的方式,主要是怕看不全,不能将前后串链起来。
      • 看到代码module.exports = __webpack_require__; 其实就是将函数(__webpack_require__)赋值给了_dll_utils全局变量了。 到现在可以这么说了,变量_dll_utils 就是全局加载函数了

3. bandle.js 代码分析

// 最外层IIFE
(() => {
  // 模块方法
  var __webpack_modules__ = {
    // 模块B
    "./node_modules/isarray/index.js": (
      module,
      __unused_webpack_exports,
      __webpack_require__
    ) => {

      // 加载模块<dll-reference _dll_utils>
      module.exports = __webpack_require__("dll-reference _dll_utils")(
        "./node_modules/isarray/index.js"
      );
    },

    "dll-reference _dll_utils": (module) => {
      "use strict";
      module.exports = _dll_utils;
    },
  };
  var __webpack_module_cache__ = {};

  // 加载模块
  function __webpack_require__(moduleId) {
    // 判断模块中是否存在
    var cachedModule = __webpack_module_cache__[moduleId];
    if (cachedModule !== undefined) {
      return cachedModule.exports;
    }


    var module = (__webpack_module_cache__[moduleId] = {
      exports: {},
    });

    // 加载执行模块
    __webpack_modules__[moduleId](module, module.exports, __webpack_require__);

    return module.exports;
  }

  var __webpack_exports__ = {};

  // 执行函数入口
  (() => {

    // 加载模块<isarray/index.js>
    let isarray = __webpack_require__("./node_modules/isarray/index.js");
    console.log("isarray([1, 2, 3])=", isarray([1, 2, 3]));
  })();
})();
  • 上述代码是webpack编译结果,只不过经过了适当的删减,这里还是从入口开始分析:
    • let isarray = __webpack_require__("./node_modules/isarray/index.js"); 左侧代码就是整个文件的执行入口, 开始加载模块
    • 图片.png
      • 当我们加载模块./node_modules/isarray/index.js的时候,就是执行上述截图中第二个裱框的地方
      • 还记得我们上文的关于_dll_utils的分析吗??? 此时_dll_utils其实就是个模块加载函数,而加载的模块上图的./node_modules/isarray/index.js这个模块。

优化:

  • 经过这么多的分析,相信大家对这个也有一定的了解。这里我们可以说下关于DllPlugin的优化(打包结束后的*.dll.js):
    • 我们可以将文件(*.dll.js)放置到服务器上,但是我们可以使用强缓存的方式来处理。因为一旦我们打包成功了dll文件,意味着文件的变化几率特别小,所以我们可以使用强缓存。但是总归要加载一次呢,第一次怎么办呢??
    • 我们可以把文件以cdn的方式进行引入,使用cdn进行加速加载,其实还有很多策略。比如:preload。预加载等, 打包压缩。让包尽可能小 而且 加载快
    • 例如我们实际的生产过程中将vue/ vue-router/ vuex/ element-plus/ loadsh 都进行打包。就能在请求时进行加速,速度可想而知啊!!!!!

结尾

好了,废话就这么多了。希望能帮助到大家吧。如果有什么不对的地方,也希望大家多多指正啊。你看说了这么多废话了,自我介绍给忘记了,你看我这脑子。。。 个人logo