看看webpack都打出了些什麼

最近在看webpack的原理,以爲能夠分爲兩個方面來完成:javascript

  • 瞭解webpack打包出來的文件。
  • 瞭解webpack流程而且本身寫loader和plugin。

固然看源碼是能夠的,可是有點事倍功半而且沒有必要,我的以爲完成以上兩部分就能夠對webpack有不錯的瞭解了。本文主要關於webpack打包出來的文件的內容【但願可以提出不對或者能夠補充的地方,感受說的不是很清晰,歡迎指tu正cao】。java

配置以及待打包文件以下:node

// webpack.config.js
const path = require('path');
const webpack = require('webpack');

module.exports = {
    entry: {
        bundle1: path.resolve(__dirname, 'src/index1.js'),
        bundle2: path.resolve(__dirname, 'src/index2.js')
    },
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].js'
    },
    plugins: [
        new webpack.optimize.CommonsChunkPlugin({
            name: 'manifest'
        })
    ]
};
複製代碼
// index1.js
const test1 = require('./test1');
const test3 = require('./test3')
console.log(test1);
console.log(test3);

// test1.js
const str = 'test1 is loaded';
module.exports = str;

// test3.js
const str = 'test3 is loaded';
module.exports = str;

// index2.js
setTimeout(function() {
    require.ensure([], function() {
        const test2 = require('./test2');
        console.log(test2);
    });
}, 5000);

// test2.js

const str = 'test2 is async loaded';
module.exports = str;
複製代碼

module和chunk

首先了解module和chunk的概念:webpack

  • module 其實就是打包前,import 或者 require 的js 文件,如test1.js 與 index1.js。
  • chunk 是打包後的文件,即 bundle1.js、bundle2.js、0.js和manifest.js文件,這裏須要注意 一個 chunk 可能包含若干 module

三個核心的方法

打包結果文件(簡化版本):git

// manifest.js
(function(modules) { // webpackBootstrap
	// install a JSONP callback for chunk loading
	var parentJsonpFunction = window["webpackJsonp"];
	window["webpackJsonp"] = function webpackJsonpCallback(chunkIds, moreModules, executeModules) {
		// add "moreModules" to the modules object,
		// then flag all "chunkIds" as loaded and fire callback
		var moduleId, chunkId, i = 0, resolves = [], result;
		for(;i < chunkIds.length; i++) {
			chunkId = chunkIds[i];
			if(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(chunkIds, moreModules, executeModules);
		while(resolves.length) {
			resolves.shift()();
		}
		if(executeModules) {
			for(i=0; i < executeModules.length; i++) {
				result = __webpack_require__(__webpack_require__.s = executeModules[i]);
			}
		}
		return result;
	};
	// The module cache
	var installedModules = {};
	// objects to store loaded and loading chunks
	var installedChunks = {
		3: 0
	};
	// The require function
	function __webpack_require__(moduleId) {
		// Check if module is in cache
		if(installedModules[moduleId]) {
			return installedModules[moduleId].exports;
		}
		// Create a new module (and put it into the cache)
		var module = installedModules[moduleId] = {
			i: moduleId,
			l: false,
			exports: {}
		};
		// Execute the module function
		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
		// Flag the module as loaded
		module.l = true;
		// Return the exports of the module
		return module.exports;
	}
	// This file contains only the entry chunk.
	// The chunk loading function for additional chunks
	__webpack_require__.e = function requireEnsure(chunkId) {
		var installedChunkData = installedChunks[chunkId];
		if(installedChunkData === 0) {
			return new Promise(function(resolve) { resolve(); });
		}
		// a Promise means "currently loading".
		if(installedChunkData) {
			return installedChunkData[2];
		}
		// setup Promise in chunk cache
		var promise = new Promise(function(resolve, reject) {
			installedChunkData = installedChunks[chunkId] = [resolve, reject];
		});
		installedChunkData[2] = promise;
		// start chunk loading
		var head = document.getElementsByTagName('head')[0];
		var script = document.createElement('script');
		script.type = 'text/javascript';
		script.charset = 'utf-8';
		script.async = true;
		script.timeout = 120000;
		if (__webpack_require__.nc) {
			script.setAttribute("nonce", __webpack_require__.nc);
		}
		script.src = __webpack_require__.p + "" + chunkId + ".js";
		var timeout = setTimeout(onScriptComplete, 120000);
		script.onerror = script.onload = onScriptComplete;
		function onScriptComplete() {
			// avoid mem leaks in IE.
			script.onerror = script.onload = null;
			clearTimeout(timeout);
			var chunk = installedChunks[chunkId];
			if(chunk !== 0) {
				if(chunk) {
					chunk[1](new Error('Loading chunk ' + chunkId + ' failed.'));
				}
				installedChunks[chunkId] = undefined;
			}
		};
		head.appendChild(script);
		return promise;
	};
})
([]);
複製代碼

manifest.js 先運行注入了一些方法,下面三個是最核心的方法:github

  • webpackJsonp
  • webpack_require
  • webpack_require.e

這裏只大體的說下大體的做用以及重點的部分。web

webpackJsonp方法,接受三個參數chunkIds, moreModules, executeModules。這裏要分清楚chunk id和module id,chunk id指的是一個打包後文件的標示,而module id是每一個打包前的module的惟一標示也就是id。這裏須要分別用來表示各個chunk和module以及在以後的緩存過程當中使用到。json

chunkIds指的是這個chunk文件加載後須要被加載的chunk id的數組,因此默認會有自身chunk的id。若是有這個chunk會用到的module打包到的chunk須要被預加載的話,對應的chunk的id也會在chunkIds中。數組

moreModules指的是這個chunk加載後帶來的module的數組,其中的每一個module被以函數的形式包裹實現做用域上的隔離,其實和node的模塊加載的機制很像。promise

以bundle1.js爲例

webpackJsonp([1],[
/* 0 */
/***/ (function(module, exports, __webpack_require__) {

const test1 = __webpack_require__(1);
const test3 = __webpack_require__(2)

console.log(test1);
console.log(test3);

/***/ }),
/* 1 */
/***/ (function(module, exports) {

const str = 'test1 is loaded';

module.exports = str;

/***/ }),
/* 2 */
/***/ (function(module, exports) {

const str = 'test3 is loaded';

module.exports = str;

/***/ })
],[0]);
複製代碼

chunkIds是[1],moreModules是中間的數組參數,executeModules是[0](這裏要分清楚,[1]中的1是chunk id,而[0]中的0是module id 指的是須要被執行的module的id這裏指的就是打包以前的index.js文件)這裏運行了bundle1.js至關於執行了打包以前的index1.js文件。

moreModules數組中的元素舉個栗子:

/* 0 */
/***/ (function(module, exports, __webpack_require__) {

const test1 = __webpack_require__(1);
const test3 = __webpack_require__(2)

console.log(test1);
console.log(test3);

/***/ })
複製代碼

每一個module接收三個參數,第三個參數可選(取決於該module是否依賴其餘module,稍後說明)其中的module和exports是在開發時的模塊導出中常常遇到的。咱們在導出一個模塊的時候的操做:

module.exports = balabala;
複製代碼

Javascript的函數的參數傳遞是按值來傳遞的。在函數執行的stack中當變量的類型是對象(引用類型值)時儲存的是這個對象的地址,真正的對象存儲在這個地址指向的堆(heap)中。函數的參數在按值傳遞狀況下當參數類型是對象的時候傳遞的是對象的地址,這樣操做的時候指向的是heap中的同一個對象。

因此也就是變相的將module中export的內容掛載到了module.export對象上了。在打包後的代碼能夠看到,咱們只要執行moreModules數組中對應的元素的函數,就可以變相的將這個module想要export的內容掛載到輸入到函數的module的export對象上。

在介紹完module的接收的三個參數後。咱們能夠看到函數內部當須要引用某個module的時候,會調用__webpack_require__方法參入對應的module的id。

// The require function
function __webpack_require__(moduleId) {
	// Check if module is in cache
	if(installedModules[moduleId]) {
		return installedModules[moduleId].exports;
	}
	// Create a new module (and put it into the cache)
	var module = installedModules[moduleId] = {
		i: moduleId,
		l: false,
		exports: {}
	};
	// Execute the module function
	modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
	// Flag the module as loaded
	module.l = true;
	// Return the exports of the module
	return module.exports;
}
複製代碼

__webpack_require__方法比較簡單,其實就是傳入moduleId首先判斷下installedModules中是否有緩存(也就是以前加載過),有的話直接返回輸出的內容,沒有的話,就執行modules[moduleId].call(module.exports, module, module.exports, webpack_require);將module輸出的內容掛載到module.exports對象上,同時緩存到installedModules中,結果就是:

每一個module只會在最開始依賴到的時候加載一次,以後會從installedModules直接獲取不在加載。若是module依賴的module繼續依賴其餘module的話,上述的過程會遞歸的執行下去,可是加載過的依賴值會加載一次。

這裏能夠看到若是依賴的module被打包到獨立的chunk,而且這個chunk尚未被執行的話,這個時候modules[moduleId]就是undefined了。我以爲這個時候就和webpackJsonp的第一個參數有關了,也就是這個chunk依賴的chunk的id須要在chunkIds參數中。這裏牽扯到另外一個概念「循環依賴」的處理,這個打算以後再另外一篇文章中專門介紹。

簡單來講__webpack_require__的做用就是加載執行對應的module,而且緩存起來。

在瞭解了__webpack_require__後,回頭看下每一個chunk都有的IIFE的對應的webpackJsonp方法,webpackJsonp作的事情其實簡單來講:

  • 標記了每一個chunk是否加載過了。
  • 緩存了每一個加載的chunk帶來的module到modules對象中。
  • 將須要異步加載的chunk的回調(promise的resolve)統一收集而且執行。(這部分具體的過程感受理解的還不是很透徹)
  • 按順序加載executeModules中的module,加載的過程就已經執行了對應的module,返回最後的module的執行結果。

這裏還須要注意**webpack_require.e**方法,這個方法對應的是require.ensure方法,這個方法的做用是加載chunk,也就是對應的module會被打包成獨立的chunk。在執行require.ensure回調中的方法的時候以前會下載對應的chunk,從而實現chunk的按需加載。

從__webpack_require__.e的代碼中能夠看到,ta大體的思想是:判斷對應的chunk是否已經加載過了,若是已經加載過了,就return一個resolve了的promise,而後執行對應的回調函數的內容。若是chunk沒有加載過則用動態添加script標籤的方式加載對應的chunk(因此這裏的方法名叫作webpackJsonp也是有道理的,異步加載的方式和jsonp的方法有相似的地方)。而後標註chunk已加載,這裏還對若是chunk下載失敗的狀況下拋錯警告chunk加載失敗。

總結

至此算是大體的介紹了webpack打包後文件的內容,能夠看到webpack打包後的文件,本身實現了一套模塊加載的機制,這樣方便實現好比代碼分割等功能。

在瞭解了webpack的打包以後文件的結構,咱們並知道webpack的打包過程依靠不一樣 loader 和 plugin 組合來進行。loader負責將不一樣類型的文件進行轉換成js類型的數據,plugin在打包過程當中註冊對應的事件,webpack在對應的階段執行不一樣插件來實現對文件的處理。

在知道了這些以後,實現loader和plugin來真正的使用webpack。

內部分享的時候的 Slide

參考資料:

相關文章
相關標籤/搜索