好比使用一個函數,咱們須要關注它的輸入和輸出,就能夠很好的使用它。若是想更好的理解這個函數,就須要看看它內部的實現。webpack
一樣,使用Webpack,咱們也須要關注它的輸入和輸出。web
初學Webpack,咱們通常都先了解輸入,(源碼和配置能夠理解成Webpack的輸入),可是不太瞭解輸出(生成的文件)。咱們只是看到頁面能正常展現,就能夠了。npm
可是想更好的使用Webpack,作打包優化,瞭解輸出文件的內容,是第一步。json
咱們能夠把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文件。也就是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());
/***/ })
/******/ });
複製代碼
從總體上看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變量。
第一個模塊的加載,確定是在匿名函數中了。咱們看到匿名函數中聲明瞭一個__webpack_require__,它就是用來模擬瀏覽器端的require(模塊引用方法)。而後一些列代碼將__webpack_require__設置完畢後,最後一行執行了
return __webpack_require__(__webpack_require__.s = "./app/main.js");
複製代碼
嗯,require了"./app/main.js"。也就是加載並執行main.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中的代碼,被加載並執行。
至此,瀏覽器端,模塊的加載執行流程,梳理完成。
目前能夠看出,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直譯過來是清單的意思,我理解Manifest並非指某句代碼,而是指運行時的數據:保留Webpack的模塊信息。就是上面生成代碼中,installedModules變量存儲的東西。
咱們常用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是一個工具,並且平常工做中,不多去改動它,因此要本身多多練習,多多實驗,才能比較好的掌握它。