<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          面試官:Webpack 究竟打包出來的是什么?

          共 13745字,需瀏覽 28分鐘

           ·

          2021-11-04 22:17

          前言

          Webpack 作為普遍使用的打包工具,在開發(fā)和發(fā)布的過程中產(chǎn)出的代碼結(jié)構(gòu),你是否關(guān)心過?本文為你揭開它的神秘面紗。

          1、開發(fā)

          一般情況,開發(fā)的過程都會(huì)使用 devServer 并開啟 hot 熱更新。假如我們有一個(gè)頁面入口文件 index.js 和依賴模塊 dateUtils.js,代碼如下:

          src\pages\index\index.js

          import dateUtils from '@/utils/dateUtils'
          dateUtils.print()


          src\utils\dateUtils.js

          export default {
          print() {
          console.log('DateUtils.js==>>print', new Date())
          }
          }

          Ok,我們來看打包后的代碼:

          (function(modules) { // webpackBootstrap
          function hotCreateRequire(moduleId) {
          var me = installedModules[moduleId];
          if (!me) return __webpack_require__;
          var fn = function(request) {
          //省略
          }
          return fn
          }
          function hotCreateModule(moduleId) {
          }

          // 模塊緩存,被執(zhí)行過的模塊都會(huì)放到這里面
          var installedModules = {};

          // require 函數(shù)
          function __webpack_require__(moduleId) {

          // 檢查模塊是否在緩存中,有就取出來返回模塊的 exports 屬性
          if(installedModules[moduleId]) {
          return installedModules[moduleId].exports;
          }
          // 創(chuàng)建模塊并放入緩存
          var module = installedModules[moduleId] = {
          i: moduleId,
          l: false,
          exports: {},
          hot: hotCreateModule(moduleId),
          parents: (hotCurrentParentsTemp = hotCurrentParents, hotCurrentParents = [], hotCurrentParentsTemp),
          children: []
          };

          // 執(zhí)行模塊
          modules[moduleId].call(module.exports, module, module.exports, hotCreateRequire(moduleId));

          // 修改標(biāo)識
          module.l = true;

          // 返回模塊的 exports 屬性
          return module.exports;
          }

          // 省略代碼

          // 加載入口模塊
          return hotCreateRequire(0)(__webpack_require__.s = 0);
          }
          ({
          // 省略掉其他模塊代碼
          "./src/pages/index/index.js":
          (function(module, __webpack_exports__, __webpack_require__) {
          "use strict";
          __webpack_require__.r(__webpack_exports__);
          var _utils_dateUtils__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @/utils/dateUtils */ "./src/utils/dateUtils.js");

          _utils_dateUtils__WEBPACK_IMPORTED_MODULE_0__["default"].print();
          }),

          "./src/utils/dateUtils.js":
          (function(module, __webpack_exports__, __webpack_require__) {
          "use strict";
          __webpack_require__.r(__webpack_exports__);
          __webpack_exports__["default"] = ({
          print: function print() {
          console.log('DateUtils.js==>>print', new Date());
          }
          });
          }),

          0:
          (function(module, exports, __webpack_require__) {

          __webpack_require__(/*! G:\WebDev\webpack-study\node_modules\webpack-dev-server\client\index.js?http://0.0.0.0:85 */"./node_modules/webpack-dev-server/client/index.js?http://0.0.0.0:85");
          __webpack_require__(/*! G:\WebDev\webpack-study\node_modules\webpack\hot\dev-server.js */"./node_modules/webpack/hot/dev-server.js");
          module.exports = __webpack_require__(/*! G:\WebDev\webpack-study\src\pages\index\index.js */"./src/pages/index/index.js");
          })
          });

          打包后的代碼在瀏覽器中格式化之后非常多,對于我們來說,我們的關(guān)注放在代碼塊和打包后的代碼執(zhí)行流程上,精簡一下:

          (function(modules) { // webpackBootstrap
          函數(shù)體代碼
          }
          // 省略調(diào)其他模塊代碼
          ({
          "./src/pages/index/index.js":
          (function(module, __webpack_exports__, __webpack_require__) {
          }),

          "./src/utils/dateUtils.js":
          (function(module, __webpack_exports__, __webpack_require__) {
          }),

          0:
          (function(module, exports, __webpack_require__) {
          })
          });

          我們看到打包后的代碼其實(shí)就是一個(gè) IIFE。這個(gè)函數(shù)接受一個(gè)對象類型的參數(shù),其中這個(gè)參數(shù)的 key 就是模塊路徑,value 則是對模塊代碼包裹后的一個(gè)函數(shù),該函數(shù)有幾個(gè)固定參數(shù)(這個(gè)其實(shí)可以解釋 Node 中模塊文件中 require 和 module 是如何來的?其實(shí)就是 Node 在模塊之外包裝了一層,把 require 和 module 給傳了進(jìn)來)。暫且先不管參數(shù)具體是什么,我們接著看函數(shù)體里是什么:

          (function(modules) { // webpackBootstrap
          function hotCreateRequire(moduleId) {
          var me = installedModules[moduleId];
          if (!me) return __webpack_require__;
          var fn = function(request) {
          //省略
          }
          return fn
          }
          function hotCreateModule(moduleId) {
          }

          // 模塊緩存,被執(zhí)行過的模塊都會(huì)放到這里面
          var installedModules = {};

          // require 函數(shù)
          function __webpack_require__(moduleId) {

          // 檢查模塊是否在緩存中,有就取出來返回模塊的 exports 屬性
          if(installedModules[moduleId]) {
          return installedModules[moduleId].exports;
          }
          // 創(chuàng)建模塊并放入緩存
          var module = installedModules[moduleId] = {
          i: moduleId,
          l: false,
          exports: {},
          hot: hotCreateModule(moduleId),
          parents: (hotCurrentParentsTemp = hotCurrentParents, hotCurrentParents = [], hotCurrentParentsTemp),
          children: []
          };

          // 執(zhí)行模塊
          modules[moduleId].call(module.exports, module, module.exports, hotCreateRequire(moduleId));

          // 修改標(biāo)識
          module.l = true;

          // 返回模塊的 exports 屬性
          return module.exports;
          }

          // 省略代碼

          // 加載入口模塊
          return hotCreateRequire(0)(__webpack_require__.s = 0);
          }
          ({
          // 省略調(diào)其他模塊代碼
          });

          我們看函數(shù)體里定義了很多對象和方法,含有 hot 的部分基本上和熱更新模塊相關(guān)。這些是被添加進(jìn)來的代碼,最終上線是沒有的。我們直接看函數(shù)體的最后一行,它執(zhí)行了 hotCreateRequire(0)(__webpack_require__.s = 0)。這一行兩個(gè)括號,很明顯 hotCreateRequire 是返回了一個(gè)函數(shù)出來。我們接下來看看看 hotCreateRequire :

          function hotCreateRequire(moduleId) {
          var me = installedModules[moduleId];
          if (!me) return __webpack_require__;
          var fn = function(request) {
          if (me.hot.active) {
          if (installedModules[request]) {
          if (installedModules[request].parents.indexOf(moduleId) === -1) {
          installedModules[request].parents.push(moduleId);
          }
          } else {
          hotCurrentParents = [moduleId];
          hotCurrentChildModule = request;
          }
          if (me.children.indexOf(request) === -1) {
          me.children.push(request);
          }
          } else {
          console.warn(
          "[HMR] unexpected require(" +
          request +
          ") from disposed module " +
          moduleId
          );
          hotCurrentParents = [];
          }
          return __webpack_require__(request);
          };
          }
          //
          return fn
          }

          啟動(dòng)的時(shí)候,傳入了 moduleId 是 0,在 installedModules 中找不到模塊,直接返回了?webpack_require__。然后繼續(xù)執(zhí)行這個(gè)返回的函數(shù),并傳入?yún)?shù) __webpack_require.s = 0,那么其實(shí)是執(zhí)行了下面這個(gè)函數(shù)代碼:

          (function(module, exports, __webpack_require__) {

          __webpack_require__(/*! G:\WebDev\webpack-study\node_modules\webpack-dev-server\client\index.js?http://0.0.0.0:85 */"./node_modules/webpack-dev-server/client/index.js?http://0.0.0.0:85");
          __webpack_require__(/*! G:\WebDev\webpack-study\node_modules\webpack\hot\dev-server.js */"./node_modules/webpack/hot/dev-server.js");
          module.exports = __webpack_require__(/*! G:\WebDev\webpack-study\src\pages\index\index.js */"./src/pages/index/index.js");
          })

          這個(gè)函數(shù),通過 __webpack_require__ 執(zhí)行了三個(gè)模塊,前兩個(gè)是在 devServer 添加的入口文件,為了實(shí)現(xiàn)開發(fā)和熱更新的一些功能。最后一行 moduel.exports 屬性是我們的 index.js 模塊執(zhí)行的返回,這樣就執(zhí)行到了我們的程序入口。

          在 index.js 中我們看到通過 import 導(dǎo)入的 dateutils 也是通過 __webpack_require__ 進(jìn)行模塊引用的,并對里面的方法進(jìn)行了調(diào)用。那么,以此類推,所有的模塊引用都是這么實(shí)現(xiàn)的。

          2、生產(chǎn)模式

          我們把這個(gè)代碼使用生產(chǎn)模式進(jìn)行打包,可以得到如下代碼:

          !function(e) {
          // 模塊緩存
          var t = {};
          // 模塊require函數(shù)
          function n(r) {
          if (t[r])
          return t[r].exports;
          var o = t[r] = {
          i: r,
          l: !1,
          exports: {}
          };
          return e[r].call(o.exports, o, o.exports, n),
          o.l = !0,
          o.exports
          }
          // 執(zhí)行入口
          n(n.s = 0)
          }([function(e, t, n) {
          e.exports = n(1)
          }
          , function(e, t, n) {
          "use strict";
          n.r(t),
          n(2).a.print()
          }
          , function(e, t, n) {
          "use strict";
          t.a = {
          print: function() {
          console.log("DateUtils.js==>>print", new Date)
          }
          }
          }
          ]);
          //# sourceMappingURL=entry_index~._m_nosources-source-map.min.js.map

          因?yàn)?,我們的代碼足夠簡單,沒有其他的依賴模塊被打進(jìn)來。和開發(fā)模式類似,整體上它也是一個(gè) IIFE,只不過做了一些混淆和壓縮的處理。另外,參數(shù)變成了數(shù)組類型。同時(shí),去掉了開發(fā)模式下的一些輔助代碼。我們很容易和上面的東西做一些對應(yīng)(見注釋)。

          3、再進(jìn)一步

          OK,開發(fā)模式和生產(chǎn)模式的代碼結(jié)構(gòu)幾乎一樣。只是參數(shù)類型略有差別,開發(fā)模式下,key 是作為模塊的標(biāo)識來使用的。熱更新開啟后,修改模塊可以很輕易的修改該模塊的代碼。

          另外,我們的項(xiàng)目代碼里無論是使用 require/module.exports 這種 ComomonJS 的模塊化方案,還是采用 import/export 的 ESM 模塊化方案。通過 webpack 打包最終其實(shí)是 ComomonJS 的模塊化方案,也就是說,它可以像 CommonJs 那樣進(jìn)行動(dòng)態(tài)模塊引用。

          還有就是如果使用了 ESM 的 export (僅有 export.default 這種方式除外,它和 CommonJS 沒什么差別,都是只導(dǎo)出了一個(gè)對象出來),在打包后的代碼有一些區(qū)別。比如:

          esmtest.js

          export let flag = false
          setTimeout(()=>{
          flag = true
          }, 1000)



          index.js

          const j = require('./js/esmtest')
          console.log('0', j, j.flag) // f.falg 為 false

          setTimeout(()=>{
          const j = require('./js/esmtest')
          console.log('2000', j, j.flag) // f.falg 為 true
          }, 2000)


          打包后代碼

          "./src/pages/index/js/esmtest.js":
          (function(module, __webpack_exports__, __webpack_require__) {
          "use strict";
          __webpack_require__.r(__webpack_exports__);
          /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "flag", function() { return flag; });
          var flag = false;

          var obj = {};
          setTimeout(function () {
          flag = true;
          obj.objFlag = true;
          }, 1000);
          })

          這里出來了 .d 和 .r 方法,這個(gè)在之前的函數(shù)體里有定義:

            // 為 exports 定義 getter 函數(shù)
          __webpack_require__.d = function(exports, name, getter) {
          if(!__webpack_require__.o(exports, name)) {
          Object.defineProperty(exports, name, { enumerable: true, get: getter });
          }
          };

          // 在 exports 上定義 __esModule 屬性
          __webpack_require__.r = function(exports) {
          if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
          Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
          }
          Object.defineProperty(exports, '__esModule', { value: true });
          };

          Ok,通過定義 getter 函數(shù)的方式,我們在 esmtest.js 中對于 flag 的改動(dòng)代碼起了作用,隨后在 index.js 模塊?2s 后的定時(shí)器中的打印也說明了這一點(diǎn)。

          最后一個(gè)是如果采用代碼分割或動(dòng)態(tài)引入的情況下,會(huì)怎么樣?我們直接上打包前的代碼:

          index.js

          import('./js/dynamicImport').then(dm=>{
          dm.default.hello(123)
          })

          dynamicImport.js

          export default {
          hello(msg){
          console.log('dynamicImport', msg)
          }
          }

          我們再來看在開發(fā)模式下打包的代碼在瀏覽器里多了一個(gè) chunk 文件請求,里面打包后的代碼如下:

          (window["webpackJsonp"] = window["webpackJsonp"] || []).push([[0],{
          "./src/pages/index/js/dynamicImport.js":
          (function(module, __webpack_exports__, __webpack_require__) {

          "use strict";
          __webpack_require__.r(__webpack_exports__);
          /* harmony default export */ __webpack_exports__["default"] = ({
          hello: function hello(msg) {
          console.log('dynamicImport', msg);
          }
          });
          })
          }]);

          主體代碼沒什么差別,就是我們的 dynamicImport.js 模塊的代碼,然后外面調(diào)用了?window["webpackJsonp"].push?這個(gè)方法。那么這個(gè)方法是哪里來的,我們看一下 index.js 打包后的代碼部分:

          (function(modules) { // webpackBootstrap
          function webpackJsonpCallback(data) {
          var chunkIds = data[0];
          var moreModules = data[1];

          // add "moreModules" to the modules object,
          // then flag all "chunkIds" as loaded and fire callback
          var moduleId, chunkId, i = 0, resolves = [];
          for(;i < chunkIds.length; i++) {
          chunkId = chunkIds[i];
          if(Object.prototype.hasOwnProperty.call(installedChunks, chunkId) && installedChunks[chunkId]) {
          resolves.push(installedChunks[chunkId][0]);
          }
          installedChunks[chunkId] = 0;
          }
          for(moduleId in moreModules) {
          if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
          modules[moduleId] = moreModules[moduleId];
          }
          }
          if(parentJsonpFunction) parentJsonpFunction(data);

          while(resolves.length) {
          resolves.shift()();
          }
          };


          __webpack_require__.e = function requireEnsure(chunkId) {
          var promises = [];
          var installedChunkData = installedChunks[chunkId];
          if(installedChunkData !== 0) { // 0 means "already installed".
          if(installedChunkData) {
          promises.push(installedChunkData[2]);
          } else {

          // 開始請求 chunk 文件
          var script = document.createElement('script');
          var onScriptComplete;

          onScriptComplete = function (event) {
          // avoid mem leaks in IE.
          script.onerror = script.onload = null;
          clearTimeout(timeout);
          var chunk = installedChunks[chunkId];
          if(chunk !== 0) {
          if(chunk) {
          var errorType = event && (event.type === 'load' ? 'missing' : event.type);
          var realSrc = event && event.target && event.target.src;
          error.message = 'Loading chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realSrc + ')';
          error.name = 'ChunkLoadError';
          error.type = errorType;
          error.request = realSrc;
          chunk[1](error);
          }
          installedChunks[chunkId] = undefined;
          }
          };

          document.head.appendChild(script);
          }
          }
          return Promise.all(promises);
          };

          var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || [];
          jsonpArray.push = webpackJsonpCallback;

          return hotCreateRequire(0)(__webpack_require__.s = 0);
          }
          ({
          "./src/pages/index/index.js":
          (function(module, __webpack_exports__, __webpack_require__) {
          "use strict";
          __webpack_require__.r(__webpack_exports__);

          __webpack_require__.e(/*! import() */ 0).then(__webpack_require__.bind(null, /*! ./js/dynamicImport */ "./src/pages/index/js/dynamicImport.js")).then(function (dm) {
          console.log('dm', dm);
          dm.default.hello(123);
          });
          }),
          // 省略代碼
          });

          我們簡單看一下上面的這部分代碼。首先,這部分代碼先執(zhí)行,在 window 上掛載了一個(gè) window["webpackJsonp"],并為它定義了一個(gè) push 方法就是 webpackJsonpCallback,用于從異步請求的文件中加載模塊。此外,定義一個(gè)?__webpack_require__.e?方法去異步請求 chunk 文件,并返回一個(gè) Promise 對象。

          至此,關(guān)于 Webpack 打包后的內(nèi)容部分的介紹全部結(jié)束,你學(xué)廢了嗎?

          瀏覽 88
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  蜜桃视频在线观看91 | 99视频在线播放观看精品 | 天天摸日日摸人人看 | 人妻女大香蕉大香蕉 | 日本尤物在线免费观看 |