不同配置下的webpack打包文件分析

bundle分析

無splitChunk時的main.bundle.js

如果不splitChunk,也不用dll提前構建,name打包出來的就只有一個文件,就叫main.bundle.js吧,這個文件幹啥呢?VScode command + K + 0摺疊所有代碼:
在這裏插入圖片描述
哦 , 原來是個自執行函數,看看參數:
在這裏插入圖片描述

一大坨object, 第一個是入口文件,其他貌似是依賴。接着看看函數拿這些參數要幹啥

前面都是方法的定義,走到最後一行:

return __webpack_require__(__webpack_require__.s = "./app.js");

webpack_require

執行__webpack_require__("./app.js),這個方法比較簡單:

/******/ 	function __webpack_require__(moduleId) {
/******/
/******/ 		// installedModules緩存中有moduleId模塊直接取出來用
/******/ 		if(installedModules[moduleId]) {
/******/ 			return installedModules[moduleId].exports;
/******/ 		}
/******/ 		// installedModules緩存中沒有的話就以moduleId爲key給自己加一個
/******/ 		var module = installedModules[moduleId] = {
/******/ 			i: moduleId,
/******/ 			l: false,
/******/ 			exports: {}
/******/ 		};
/******/
/******/ 		// modules就是那一大坨object,就是執行那一大坨object中key:"./app.js"對應的value,是個方法
/******/    // 模塊中一般都會module.exports要返回的結果,這不就寫到installedModules[moduleId].exports中了嘛
/******/ 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ 		// 把moduleId的加載狀態記爲已加載
/******/ 		module.l = true;
/******/
/******/ 		// Return installedModules[moduleId].exports
/******/ 		return module.exports;
/******/ 	}

哦,就是一個帶緩存及模塊加載狀態的webpack require嘛,,接着看看"./app.js"對應的回調方法要幹啥:

一大坨eval,裏面還是字符串,暈,爲啥?不清楚

定睛一看,是要require vue的運行時代碼和vue模板文件,這裏應該就會構建DOM樹吧
在這裏插入圖片描述

optimization

webpack配置

現在給webpack配置文件添加:

optimization: {
    splitChunks: {
      chunks: 'all',  // 依賴都分離出來
      name: 'vendor', // 分離出來的bundle名稱,命名即output中的name
      minChunks: 1,
      // minChunks: 2, // 被共享的最小次數,如loadash只被引用了一次是不是將其抽離的
    },
   // runtimeChunk: true
  },

就是分離依賴的意思

build結果及main.bundle.js

build agian,看看dist文件輸出,除過main.bundle.js還有一個vendor.bundle.js,

先看看main.bundle.js,還是一個自執行函數,參數還是那一大坨object,不過方法體中貌似多了很多東西,撿最重要的說

var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || [];
/******/ 	var oldJsonpFunction = jsonpArray.push.bind(jsonpArray);

					// 就是下面這句最重要,在jsonpArray.push時執行webpackJsonpCallback,而在jsonpArray就是window["webpackJsonp"],
          // 所以當先加載main.bundle.js後加載vendor.bundle.js時也會執行到webpackJsonpCallback
/******/ 	jsonpArray.push = webpackJsonpCallback; 
					
          // 再將jsonpArray還原爲自己本來的樣子,去掉push方法
/******/ 	jsonpArray = jsonpArray.slice();
/******/ 	for(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);
/******/ 	var parentJsonpFunction = oldJsonpFunction;

/******/ 	// add entry module to deferred list,splitChunk出來的bundle也是一個entry
/******/ 	deferredModules.push(["./app.js","vendor"]);  

這段代碼非常關鍵,也說明爲啥main.bundle.js和vendor.bundle.js引入順序不同也能運行成功的原因。

window[“webpackJsonp”]是啥?容我打開vendor.bundle.js,手動格式化後給大家看看:
在這裏插入圖片描述
正是給window[“webpackJsonp”] push一個數組(arr),arr0又是一個數組,arr1結果又是那一大坨object,原來是分離出的依賴
在這裏插入圖片描述
看到這裏就能看懂main.bundle.js截取的代碼註釋了

webpackJsonpCallback window[“webpackJsonp”] push時標記vendor已加載

而webpackJsonpCallback幹啥呢

// window["webpackJsonp"] push的東西是一個數組,往上第二個找圖,就是參數data
function webpackJsonpCallback(data) {
/******/ 		var chunkIds = data[0]; //  ["vendor"]
/******/ 		var moreModules = data[1]; // 一大坨object
/******/ 		var executeModules = data[2];
/******/
/******/ 		var moduleId, chunkId, i = 0, resolves = [];
/******/ 		for(;i < chunkIds.length; i++) {
/******/ 			chunkId = chunkIds[i]; // chunkId爲'vendor'
/******/ 			// some code here and installedChunks初始值爲{ "main": 0 }
/******/ 			installedChunks[chunkId] = 0;
/******/ 		}

						// modules中加入vendor.bundle.js中的那一大坨object,當然main.bundle.js自執行方法的入參對應的那一大坨object也在其中,至此,所有的依賴都加載進來了
/******/ 		for(moduleId in moreModules) {
/******/ 			if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
/******/ 				modules[moduleId] = moreModules[moduleId];
/******/ 			}
/******/ 		}
/******/    // some code here
/******/
/******/ 		// run deferred modules when all chunks ready,英文解釋已經很清晰了
/******/ 		return checkDeferredModules();
/******/ 	};

checkDeferredModules bundle加載完後執行app.js回調

現在看看這個checkDeferredModules,它纔是最終執行代碼

// deferredModules.push(["./app.js","vendor"]);

/******/ 	function checkDeferredModules() {
/******/ 		var result;
/******/ 		for(var i = 0; i < deferredModules.length; i++) {
/******/ 			var deferredModule = deferredModules[i]; // deferredModule爲["./app.js","vendor"]
/******/ 			var fulfilled = true;
/******/ 			for(var j = 1; j < deferredModule.length; j++) { // 從1開始
/******/ 				var depId = deferredModule[j]; // depId爲vendor
/******/ 				if(installedChunks[depId] !== 0) fulfilled = false;
/******/ 			}
							// 如果vendor加載後執行__webpack_require__("./app.js");即執行main.bundle.js自執行函數入參中"./app.js"對應的value回調
/******/ 			if(fulfilled) {
/******/ 				deferredModules.splice(i--, 1);
/******/ 				result = __webpack_require__(__webpack_require__.s = deferredModule[0]);
/******/ 			}
/******/ 		}
/******/
/******/ 		return result;
/******/ 	}

最開始的時候已經解釋了"./app.js"回調是幹啥的

運行時代碼抽離

splitChunk後發現main.bundle.js自執行函數不純潔,如果把他的入參也取出來,那main.bundle.js不就是真正的runtime了

optimize幫你解決,配置爲:optimize.runtimeChunk: true

build again,發現這時有3個文件,分別是:
在這裏插入圖片描述

這時runtime~main.bundle.js中就是運行時的代碼了,而main.bundle.js和vendor.bundle.js都是給window[“webpackJsonp”] push了

DllPlugin (dynamic link libray)動態鏈接庫

終於說到DllPlugin了,它的目的就是提前構建,然後vendor中需要moduleId時只需從提前構建的那一大坨object裏取就行,既然是提前構建,那必須不能和webpack.config.js一起運行啊,那就新建一個webpack.dll.js吧

const webpack = require('webpack');
const path = require('path');

module.exports = {
  mode: "development",
  entry: {
    commonLib: ['vue']
  },
  output: {
    path: path.resolve(__dirname, './dllJs'),
    filename: 'dll.[name].js',
    library: '[name]_library'
  },
  plugins: [
    new webpack.DllPlugin({
      path: './[name].manifest.json',
      name: '[name]_library',
      context: __dirname
    })
  ]
}

和webpack普通的配置一樣,entry中包含的依賴即需要提前構建的,這裏以vue舉例,webpack.DllPlugin裏的name要和output中的library對應,後面會說到

webpack --config webpack.dll.js

執行後發現dllJs文件下有一個dll.commonLib.js文件,根目錄下有一個commonLib.manifest.json文件,看看commonLib.manifest.json文件,包含了vue的運行時代碼路徑。

"./node_modules/vue/dist/vue.runtime.esm.js": {
      "id": "./node_modules/vue/dist/vue.runtime.esm.js",
      "buildMeta": {
        "exportsType": "namespace",
        "providedExports": ["default"]
      }
    },

而dll.commonLib.js呢?
在這裏插入圖片描述
將一個很熟悉的自執行函數賦值給了commonLib_library,這個名字就是 webpack.DllPlugin中的name。

打開看看,自執行函數的入參最重要的是包含了vue的運行時代碼,很符合預期,這就是我要提前構建的代碼呀
在這裏插入圖片描述
此方法中關鍵的一步

return __webpack_require__(__webpack_require__.s = 0);

0就是那一大坨object中
在這裏插入圖片描述
看樣子是將此文件中的__webpack_require__寫到入參module的exports中

全局來看這個文件就是:

var commonLib_librarycommonLib_library = __webpack_require__

和webpack.config.js結合

那提前構建好了後怎麼用呢,這就需要在webpack.config.js中添加配置了

optimization: {
    splitChunks: {
      chunks: 'all',
      name: 'vendor',
      minChunks: 1
    },
    runtimeChunk: true
  },
plugins: [
    new VueLoaderPlugin(),
    new webpack.DllReferencePlugin({
      context: __dirname,   // 我猜是node_modules的路徑。。。,可省略
      manifest: require('./commonLib.manifest.json') // 引入dll構建好的json
    })
  ]

build webpack.config.js again

生成的文件是
在這裏插入圖片描述
先看看 main.bundle.js,仍然是window[“webpackJsonp”] push ,找找vue.runtime.esm.js

/***/ "./node_modules/vue/dist/vue.runtime.esm.js":
/*!*************************************************************************************************!*\
  !*** delegated ./node_modules/vue/dist/vue.runtime.esm.js from dll-reference commonLib_library ***!
  \*************************************************************************************************/
/*! exports provided: default */
/***/ (function(module, exports, __webpack_require__) {

eval("module.exports = (__webpack_require__(/*! dll-reference commonLib_library */ \"dll-reference commonLib_library\"))(\"./node_modules/vue/dist/vue.runtime.esm.js\");\n\n//# sourceURL=webpack:///delegated_./node_modules/vue/dist/vue.runtime.esm.js_from_dll-reference_commonLib_library?");

/***/ }),

噁心的eval,果然evil

上面最關鍵的是執行

module.exports = (__webpack_require__("dll-reference commonLib_library"))("./node_modules/vue/dist/vue.runtime.esm.js")

webpack_require(“dll-reference commonLib_library”)又是什麼鬼,command + f後輸入 “dll-reference commonLib_library”:
在這裏插入圖片描述
原來是把commonLib_library寫到module.exports上,commonLib_library就是dll.commonLib.js執行結果,及dll.commonLib.js裏邊的__webpack_require__

所以使是用dll.commonLib.js裏邊的__webpack_require__("./node_modules/vue/dist/vue.runtime.esm.js"),哦哦,原理是就是這麼動態鏈接的!!!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章