Webpack生成文件分析

爲何要理解Webpack輸出文件

好比使用一個函數,咱們須要關注它的輸入和輸出,就能夠很好的使用它。若是想更好的理解這個函數,就須要看看它內部的實現。webpack

一樣,使用Webpack,咱們也須要關注它的輸入和輸出。web

初學Webpack,咱們通常都先了解輸入,(源碼和配置能夠理解成Webpack的輸入),可是不太瞭解輸出(生成的文件)。咱們只是看到頁面能正常展現,就能夠了。npm

可是想更好的使用Webpack,作打包優化,瞭解輸出文件的內容,是第一步。json

生成一個最簡單的Webpack輸出文件

先搞一些輸入文件

咱們能夠把webpack理解成一個本地操做文件的程序,讀取一些文件,根據咱們的配置,再生成一些文件。咱們的源代碼,就是webpack要讀取的文件。我把源代碼稱爲輸入文件。設計模式

./app/Greeter.js數組

module.exports = function () {
  var greet = document.createElement('div');
  greet.textContent = "Hi there and greetings!";
  return greet;
};
複製代碼

./app/main.js瀏覽器

const greeter = require('./Greeter.js');

document.querySelector("#root").appendChild(greeter());
複製代碼

咱們搞了兩個輸入文件,都放在app文件夾下,Greeter.js,main.js。main.js引用了Greeter.jsbash

再搞一個配置文件

./webpack.config.jsapp

module.exports = {
  entry: __dirname + "/app/main.js",//已屢次說起的惟一入口文件
  output: {
    path: __dirname + "/public",//打包後的文件存放的地方
    filename: "bundle.js"//打包後輸出文件的文件名
  },
  mode: 'development',
  devtool: false,
}
複製代碼

在package.json的scripts對象下,添加"start": "webpack"。在命令行工具中運行npm start。 就運行webpack的打包過程啦。將輸出文件放在public文件夾下。函數

分析生成文件bundle.js

經過上述步驟,咱們就生成了一個最簡單的bundle.js文件。也就是Webpack的輸出文件,本文的目的就是搞懂它。

/******/ (function(modules) { // webpackBootstrap
/******/ 	// The module cache
/******/ 	var installedModules = {};
/******/
/******/ 	// 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;
/******/ 	}
/******/
/******/
/******/ 	// expose the modules object (__webpack_modules__)
/******/ 	__webpack_require__.m = modules;
/******/
/******/ 	// expose the module cache
/******/ 	__webpack_require__.c = installedModules;
/******/
/******/ 	// define getter function for harmony exports
/******/ 	__webpack_require__.d = function(exports, name, getter) {
/******/ 		if(!__webpack_require__.o(exports, name)) {
/******/ 			Object.defineProperty(exports, name, { enumerable: true, get: getter });
/******/ 		}
/******/ 	};
/******/
/******/ 	// define __esModule on exports
/******/ 	__webpack_require__.r = function(exports) {
/******/ 		if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ 			Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ 		}
/******/ 		Object.defineProperty(exports, '__esModule', { value: true });
/******/ 	};
/******/
/******/ 	// create a fake namespace object
/******/ 	// mode & 1: value is a module id, require it
/******/ 	// mode & 2: merge all properties of value into the ns
/******/ 	// mode & 4: return value when already ns object
/******/ 	// mode & 8|1: behave like require
/******/ 	__webpack_require__.t = function(value, mode) {
/******/ 		if(mode & 1) value = __webpack_require__(value);
/******/ 		if(mode & 8) return value;
/******/ 		if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
/******/ 		var ns = Object.create(null);
/******/ 		__webpack_require__.r(ns);
/******/ 		Object.defineProperty(ns, 'default', { enumerable: true, value: value });
/******/ 		if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
/******/ 		return ns;
/******/ 	};
/******/
/******/ 	// getDefaultExport function for compatibility with non-harmony modules
/******/ 	__webpack_require__.n = function(module) {
/******/ 		var getter = module && module.__esModule ?
/******/ 			function getDefault() { return module['default']; } :
/******/ 			function getModuleExports() { return module; };
/******/ 		__webpack_require__.d(getter, 'a', getter);
/******/ 		return getter;
/******/ 	};
/******/
/******/ 	// Object.prototype.hasOwnProperty.call
/******/ 	__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ 	// __webpack_public_path__
/******/ 	__webpack_require__.p = "";
/******/
/******/
/******/ 	// Load entry module and return exports
/******/ 	return __webpack_require__(__webpack_require__.s = "./app/main.js");
/******/ })
/************************************************************************/
/******/ ({

/***/ "./app/Greeter.js":
/*!************************!*\
  !*** ./app/Greeter.js ***!
  \************************/
/*! no static exports found */
/***/ (function(module, exports) {

// Greeter.js
module.exports = function () {
  var greet = document.createElement('div');
  greet.textContent = "Hi there and greetings!";
  return greet;
};

/***/ }),

/***/ "./app/main.js":
/*!*********************!*\
  !*** ./app/main.js ***!
  \*********************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {

//main.js 
const greeter = __webpack_require__(/*! ./Greeter.js */ "./app/Greeter.js");
document.querySelector("#root").appendChild(greeter());

/***/ })

/******/ });
複製代碼

生成文件中的知識點

  1. 當即執行函數
  2. 文件內容分兩部分:包含運行時代碼和源代碼
  3. 模塊加載流程
  4. webpack是如何模擬源碼中require,module.export、export、import這些模塊引用的

當即執行函數

從總體上看bundle.js,就是一個當即執行的函數表達式(IIFE)。

(function (modules) {
    statements
})(modules對象集合);
複製代碼

這是一個被稱爲 自執行匿名函數 的設計模式,主要包含兩部分。第一部分是包圍在 圓括號運算符 () 裏的一個匿名函數,這個匿名函數擁有獨立的詞法做用域。這不只避免了外界訪問此 IIFE 中的變量,並且又不會污染全局做用域。

第二部分再一次使用 () 建立了一個當即執行函數表達式,JavaScript 引擎到此將直接執行函數。

-- 來自MDN的IIFE的解釋。

文件內容分兩部分:包含運行時代碼和源代碼

bundle.js中的內容分爲兩部分,運行時代碼和咱們的源代碼。自執行函數體都是代碼在瀏覽器端運行時所需的代碼。自執行函數的參數就是咱們的源代碼。

模塊加載流程

bundle文件中的自執行函數,還包含了參數,簡化一下是這樣的:

(function (modules) {
    statements
})({
    "./app/main.js": function(){ /*main.js源碼*/ }
    "./app/Greeter.js": function(){ /*Greeter.js源碼*/ }
})
複製代碼

參數就是包裹着咱們源代碼模塊的,已路徑爲key的對象,傳遞到匿名函數裏,就是modules變量。

main.js的加載執行

第一個模塊的加載,確定是在匿名函數中了。咱們看到匿名函數中聲明瞭一個__webpack_require__,它就是用來模擬瀏覽器端的require(模塊引用方法)。而後一些列代碼將__webpack_require__設置完畢後,最後一行執行了

return __webpack_require__(__webpack_require__.s = "./app/main.js");
複製代碼

嗯,require了"./app/main.js"。也就是加載並執行main.js中的代碼。

Greeter.js的加載執行

看一眼main.js編譯後代碼的樣子:

function(module, exports, __webpack_require__) {

//main.js 
const greeter = __webpack_require__(/*! ./Greeter.js */ "./app/Greeter.js");
document.querySelector("#root").appendChild(greeter());

/***/ }
複製代碼

上面說到它被執行,第一句又__webpack_require__了"./app/Greeter.js"。因此Greeter.js中的代碼,被加載並執行。

至此,瀏覽器端,模塊的加載執行流程,梳理完成。

webpack是如何模擬源碼中require,module.export、export、export default、import這些模塊引用的

目前能夠看出,require,module.export能夠被又__webpack_require__很好的模擬。咱們看看將Greeter.js中的module.export改爲export,會發生什麼:

// Greeter.js
export const greeter = function () {
  var greet = document.createElement('div');
  greet.textContent = "Hi there and greetings!";
  return greet;
};
複製代碼

生成的模塊,是下面這樣的:

function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "greeter", function() { return greeter; });
// Greeter.js
const greeter = function () {
  var greet = document.createElement('div');
  greet.textContent = "Hi there and greetings!";
  return greet;
};

/***/ }
複製代碼

此時,咱們在main.js打印一些require('./Greeter.js')。看看能獲得什麼:

const Greeter = require('./Greeter.js');
console.log(Greeter);
/*
Greeter對象
{
    greeter:f(), // f()是咱們寫的源代碼方法
    __esModule:true,
}
*/
複製代碼

webpack_require.d幫咱們將./Greeter.js又包了一層對象,裏面有咱們寫的greeter變量,指代咱們寫的函數。咱們能夠經過Greeter.greeter()調用Greeter.js中的greeter方法。

這樣也是複合預期的,export命令能夠處處多個對象,供外界使用。咱們能夠在Greeter.js下寫多個方法,那麼導出的對象,就是這樣的:

{
    greeter:f(),
    chinaGreeter:f(),
    AmericaGreeter:f(),
    __esModule:true,
}
複製代碼

那麼,咱們export default會編譯成什麼樣:

// Greeter.js
export default function () {
  var greet = document.createElement('div');
  greet.textContent = "Hi there and greetings!";
  return greet;
};
複製代碼

編譯後:

function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
// Greeter.js
/* harmony default export */ __webpack_exports__["default"] = (function () {
  var greet = document.createElement('div');
  greet.textContent = "Hi there and greetings!";
  return greet;
});

/***/ }

// Greeter.js導出的對象
{
    default: f(),
    __esModule:true,
}
複製代碼

跟export區別不大,只是導出變量的key變成了default。在main.js中,使用require('./Greeter.js').default,既可拿到咱們編寫的方法。

說完了導出模塊,再來看引入模塊的import。

//main.js 
import greeter from './Greeter.js';
document.querySelector("#root").appendChild(greeter());
複製代碼

編譯後

function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _Greeter_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./Greeter.js */ "./app/Greeter.js");
//main.js 

document.querySelector("#root").appendChild(Object(_Greeter_js__WEBPACK_IMPORTED_MODULE_0__["default"])());

/***/ })
複製代碼

import關鍵字,被編譯成了_Greeter_js__WEBPACK_IMPORTED_MODULE_0__["default"],完美的配合了export default。

再看一下import { greeter } from './Greeter.js'; 編譯後爲:

function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _Greeter_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./Greeter.js */ "./app/Greeter.js");
//main.js 

console.log('greeter', _Greeter_js__WEBPACK_IMPORTED_MODULE_0__["greeter"]);
document.querySelector("#root").appendChild(Object(_Greeter_js__WEBPACK_IMPORTED_MODULE_0__["greeter"])());

/***/ }
複製代碼

使用greeter的地方變成了_Greeter_js__WEBPACK_IMPORTED_MODULE_0__["greeter"],完美配合export的導出。

最後再看看import * as Greeter from './Greeter.js';的編譯後的形式

function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _Greeter_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./Greeter.js */ "./app/Greeter.js");
//main.js 

document.querySelector("#root").appendChild(_Greeter_js__WEBPACK_IMPORTED_MODULE_0__["greeter"]());

/***/ }
複製代碼

就是_Greeter_js__WEBPACK_IMPORTED_MODULE_0__表明Greeter對象,裏面有greeter等export的函數。

Runtime和Manifest這兩個概念的理解

我理解Runtime就是打包後文件中全部非源代碼的部分,都算是運行時代碼,幫助代碼在瀏覽器運行,用於鏈接運行時各模塊。

Manifest直譯過來是清單的意思,我理解Manifest並非指某句代碼,而是指運行時的數據:保留Webpack的模塊信息。就是上面生成代碼中,installedModules變量存儲的東西。

# 拆分到不一樣bundle的代碼如何在一塊兒運行的

咱們常用Webpack代碼拆分功能,那拆分以後的各個bundle是如何交互的呢?

咱們在這裏作一個實驗,拆分一下manifest。在配置文件中添加:

optimization: {
    runtimeChunk: {
      name: 'manifest'
    }
  }
複製代碼

在生成文件夾中,就產生了main.js,manifest.js兩個文件,咱們分別看看兩個文件中的代碼:

// main.js
(window["webpackJsonp"] = window["webpackJsonp"] || []).push([["index"],{

/***/ "./src/index.js":
/*!**********************!*\
  !*** ./src/index.js ***!
  \**********************/
/*! no static exports found */
/***/ (function(module, exports) {


// import greeter from './greeter';

// document.querySelector("#root").appendChild(greeter());
console.log('嗯哼');

/***/ })

},[["./src/index.js","manifest"]]]);
複製代碼

main.js中只有包含咱們源代碼模塊的部分了。而後將其push到一個window["webpackJsonp"]的全局變量裏。

再看看manifest.js:

/******/ (function(modules) { // webpackBootstrap
/******/ 	// install a JSONP callback for chunk loading
/******/ 	function webpackJsonpCallback(data) {
/******/ 	     ...
/******/ 		// add entry modules from loaded chunk to deferred list
/******/ 		deferredModules.push.apply(deferredModules, executeModules || []);
/******/
/******/ 		// run deferred modules when all chunks ready
/******/ 		return checkDeferredModules();
/******/ 	};
/******/ 	function checkDeferredModules() {
/******/ 	    ...
/******/
/******/ 		return result;
/******/ 	}
/******/
            ...
/******/ 	// The module cache
/******/ 	var installedModules = {};
/******/
/******/ 	var deferredModules = [];
/******/
/******/ 	// The require function
/******/ 	function __webpack_require__(moduleId) {
/******/
/******/ 		return module.exports;
/******/ 	}
/******/
/******/
/******/ 	var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || [];
/******/ 	var oldJsonpFunction = jsonpArray.push.bind(jsonpArray);
/******/ 	jsonpArray.push = webpackJsonpCallback;
/******/ 	jsonpArray = jsonpArray.slice();
/******/ 	for(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);
/******/ 	var parentJsonpFunction = oldJsonpFunction;
/******/
/******/
/******/ 	// run deferred modules from other chunks
/******/ 	checkDeferredModules();
/******/ })
/************************************************************************/
/******/ ([]);
複製代碼

這裏刪除了不少代碼,只留下關鍵的,咱們只需看 window["webpackJsonp"]這個變量,它是一個數組,而且重寫了push方法爲webpackJsonpCallback。這樣在main.js中調用push時,實則調用了webpackJsonpCallback方法。webpackJsonpCallback執行後續的加載邏輯。這樣,不一樣文件中的代碼,共同工做完成在瀏覽器端的運行。

結束語

以上就是wepback生成文件的基本分析了。後續咱們能夠爲咱們的wepack配置添加loader,plugin,而後看看bundle文件有哪些變化,進而更好理解loader和plugin的做用及工做原理。

另外提一句webpack的學習方法,我以爲webpack的學習,重在練習,光看文章不必定會理解的很好,wepback是一個工具,並且平常工做中,不多去改動它,因此要本身多多練習,多多實驗,才能比較好的掌握它。

相關文章
相關標籤/搜索