webpack 本身實現了一套模塊機制,不管是 CommonJS 模塊的 require 語法仍是 ES6 模塊的 import 語法,都可以被解析並轉換成指定環境的可運行代碼,以 web 爲例看看 webpack 如何來實現模塊機制。webpack
index.jsgit
import foo from './foo'
import bar from './bar'
console.log('run => index.js')
console.log(`log => foo.name: ${foo.name}`)
console.log(`log => bar.name: ${bar.name}`)
export default {
name: 'index'
}
複製代碼
foo.jsgithub
import bar from './bar'
console.log('run => foo.js')
console.log(`log => bar.name: ${bar.name}`)
export default {
name: 'foo'
}
複製代碼
bar.jsweb
console.log('run => bar.js')
export default {
name: 'bar'
}
複製代碼
webpack.config.js數組
const path = require('path')
module.exports = {
entry: './src/index',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js',
},
}
複製代碼
(function(modules) {
// webpackBootstrap
})([
/* 0 */
(function(module, __webpack_exports__, __webpack_require__) {
// bar.js
}),
/* 1 */
(function(module, __webpack_exports__, __webpack_require__) {
// index.js
}),
/* 2 */
(function(module, __webpack_exports__, __webpack_require__) {
// foo.js
})
]);
複製代碼
將打包以後的代碼簡化後很是清晰,就是一個當即調用函數表達式(IIFE:Immediately-Invoked Function Expression)。執行函數 webpackBootstrap 有一個形參 modules,對應的實參是一個數組,數組包含多個函數代碼塊,每一個函數代碼塊都表示一個模塊,擁有 module
__webpack_exports__
__webpack_require__
三個形參。經過註釋不難發現,每一個模塊對應的就是一個文件,而且擁有一個 id 值,根據 id 值大小順序添加進 modules 數組。瀏覽器
代碼的執行框架搭建好了,那啓動函數 webpackBootstrap 內究竟作了什麼讓模塊之間聯繫提來?模塊的三個形參究竟是什麼?緩存
先瞧瞧被簡化後的 webpackBootstrap 函數核心代碼:框架
(function(modules) { // webpackBootstrap
// The module cache
var installedModules = {};
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;
}
})([...])
...
// Load entry module and return exports
return __webpack_require__(__webpack_require__.s = 1);
複製代碼
沒有刪掉註釋,建議仔細瞧瞧,很容易就能明白模塊是如何正確加載並運行起來的。ide
首先定義了一個 installedModules
對象,它的做用是用來緩存已經加載過的模塊,而後聲明 __webpack_require__
函數,似曾相識對吧,沒錯,它就是模塊的第三個形參對應的實參。最後返回 __webpack_require__(1)
執行結果。函數
那再看看 __webpack_require__
函數,顧名思義,它就是用來加載模塊的函數,接收一個 moduleId
的形參,也就是模塊的 id 值。
首先判斷 moduleId
對應的模塊是否已被緩存,也就是在 installedModules
對象中能不能找到屬性 moduleId
對應的值,若是找到了,則直接返回模塊的輸出 exports
。從這裏能夠發現,不管被多少個模塊所依賴的模塊都只會被加載一次,結果相同,由於返回的是同一個對象的引用地址,因此若是某個模塊修改了對象內的屬性值,則會被同步反應到其它依賴此模塊的對象。
繼續,當模塊沒有被加載過的狀況下,定義一個模塊對象,同步加入 installedModules
對象緩存起來,模塊對象包含三個屬性,i
表示模塊 id 值,l
表示是否被加載,exports
表示模塊的輸出結果對象。
接着就是模塊執行的調用函數,經過 moduleId
從 modules 內找到模塊的函數代碼塊,使用 call 方法綁定函數內的 this 指向 module.exports
,傳入三個實參 module
module.exports
__webpack_require__
,與模塊函數的形參一一對應。
當模塊函數執行完成並返回結果以後,模塊標識爲已加載狀態,最後返回模塊的輸出對象。
目前瀏覽器尚未徹底支持 ES6 模塊語法,因此模塊內的 import 語法會如何處理?以 foo.js 爲例來瞧瞧模塊函數代碼塊內的代碼:
/* 2 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__bar__ = __webpack_require__(0);
console.log('run => foo.js')
console.log(`log => bar.name: ${__WEBPACK_IMPORTED_MODULE_0__bar__["a" /* default */].name}`)
/* harmony default export */ __webpack_exports__["a"] = ({
name: 'foo'
});
複製代碼
原來 import 語法被轉換了,模塊名稱變爲一個變量名稱,值是使用 __webpack_require__
函數根據依賴模塊 id 值獲取的輸出結果,而且模塊函數內的全部依賴模塊名稱都被轉換成對應的變量名稱,模塊的輸出結果被綁定在 __webpack_exports__
對象中,這裏 module.exports === __webpack_exports__
,等於就是模塊的輸出。
webpack 的模塊機制包含三大要點:
modules
保存全部模塊__webpack_require__
函數加載模塊installedModules
對象緩存模塊經過以上所有分析,webpack 實現模塊機制的原理我也思考出了一個大概的輪廓,下面是我我的理解的描述,若有錯誤,還望有大佬可以指點、指教,不勝感激。
使用 acorn 將代碼解析成 AST(抽象語法樹)
從入口(entry)模塊開始,以及後續各個依賴模塊。
分析 AST 根據關鍵詞 import``require
加載並肯定模塊之間的依賴關係、標識 id 值以及其它
生成輸出內容的 AST 並將模塊的 AST 根據 id 值順序插入 modules 的 AST
輸出內容是打包後輸出的文件內容,模塊 AST 會被包裹以後插入(本來的模塊代碼會被包裹進函數內)。
修改 AST 將各模塊引入依賴模塊的語法進行轉換並將模塊內全部的依賴標識進行對應替換
import 語法轉換和模塊標識替換,上文有描述。
輸出結果
以上只是大概輪廓,還有不少細節沒有涉及到,好比 helper 代碼、轉換規則、模塊類型等等。通過此番思考,對模塊的加載也算是有了較爲深入的理解,在此之上,也可以對 webpack 更多其它特性進行探索,好比 code-splitting、tree-shaking、scope hoisting 等等,路還很長,一步一個腳印。