webpack工作流程
先祭出之前畫的webpack工作流程圖
源碼分析
webpack源碼很多谆构,看著看著就被各種類虾攻、設(shè)計(jì)模式繞暈了铡买。所以這篇文章會(huì)以webpack的打包流程為主線,分析每個(gè)環(huán)節(jié)的代碼所在的位置霎箍、大致做了哪些事情奇钞。
1.執(zhí)行webpack命令
一般來說我們都會(huì)在項(xiàng)目的package.json中配置一條打包命令,如:
"build": "node_modules/.bin/webpack --config webpack.prod.js",
當(dāng)我們執(zhí)行npm run build后漂坏,先會(huì)執(zhí)行bin/webpack.js這個(gè)文件景埃,內(nèi)部會(huì)執(zhí)行l(wèi)ib/webpack.js這個(gè)文件媒至。
2.初始化參數(shù)
在webpack.js中,會(huì)讀取初始參數(shù)并校驗(yàn)
const webpack = (options, callback) => {
const webpackOptionsValidationErrors = validateSchema(
webpackOptionsSchema,
options
);
if (webpackOptionsValidationErrors.length) {
throw new WebpackOptionsValidationError(webpackOptionsValidationErrors);
}
// ...
};
webpack函數(shù)的options參數(shù)就是我們命令中配置的webpack.prod.js這個(gè)文件的內(nèi)容谷徙,webpack拿到數(shù)據(jù)后會(huì)利用webpackOptionsSchema進(jìn)行json數(shù)據(jù)內(nèi)容的校驗(yàn)拒啰。
3.確定編譯入口文件
編譯前webpack會(huì)先注冊(cè)內(nèi)部、外部插件完慧;
其中內(nèi)部插件在WebpackOptionsApply.js中注冊(cè)图呢,然后會(huì)觸發(fā)相應(yīng)的hook調(diào)用EntryOptionPlugin插件;
通過entry插件程序開始從入口文件編譯骗随。
4.編譯文件
編譯時(shí)會(huì)用到compilation(編譯對(duì)象)蛤织,調(diào)用它的_addModuleChain從入口進(jìn)行編譯,它會(huì)利用moduleFactory將文件構(gòu)建成module如NormalModule鸿染;
接著調(diào)用module的build方法編譯當(dāng)前模塊指蚜;
最后找到模塊的依賴模塊并循環(huán)構(gòu)建這些依賴形成遞歸;
說明:
module的build方法會(huì)先使用匹配到的loaders進(jìn)行代碼轉(zhuǎn)換涨椒,然后交給Parser.js模塊摊鸡;
Parser利用acron解析module的抽象語法樹,然后walk出模塊的所有dependencies并將其設(shè)置在module中蚕冬。
5.輸出資源
執(zhí)行完編譯文件階段后免猾,我們得到了一棵根結(jié)點(diǎn)為入口模塊的依賴樹,并且樹上的結(jié)點(diǎn)都是經(jīng)過編譯的囤热;
最后調(diào)用compilation.seal將這棵樹上的代碼打包至一個(gè)bundle.js文件中猎提。
bundle文件大致結(jié)構(gòu)
(function (modules) {
var installedModules = {};
function __webpack_require__(moduleId) {}
// Load entry module and return exports
return __webpack_require__(0);
})(
[
function (module, __webpack_exports__, __webpack_require__) {
console.log('module1');
},
function (module, __webpack_exports__, __webpack_require__) {
console.log('module2');
}
]
)
bundle文件中定義了一個(gè)自執(zhí)行函數(shù),然后調(diào)用這個(gè)函數(shù)傳遞了一個(gè)數(shù)組作為參數(shù)旁蔼,數(shù)組中的每一項(xiàng)都是打包后的模塊代碼锨苏;
自執(zhí)行函數(shù)中有一個(gè)installedModules對(duì)象緩存已加載的模塊,還有一個(gè)__webpack_require__
是加載模塊的方法棺聊,最后加載了模塊0也就是我們的入口模塊作為業(yè)務(wù)程序的開始伞租。
手寫一個(gè)打包工具
git地址:https://github.com/shejiJiang/mini-webpack
最后我參考webpack的打包流程,手寫了一個(gè)簡單的打包工具限佩。拋棄了webpack的一堆概念葵诈,把打包流程代碼全寫在了lib/webpack.js文件中;
在webpack.js定義了一個(gè)打包方法祟同,會(huì)調(diào)用buildModule方法從入口開始編譯模塊作喘,然后調(diào)用bundle方法進(jìn)行打包,最后調(diào)用output輸出耐亏;
其中buildModule是一個(gè)遞歸執(zhí)行的過程徊都,它會(huì)先利用@babel/parser解析ast沪斟,再利用@babel/core編譯代碼广辰,然后利用@babel/traverse找到模塊的依賴暇矫,最后對(duì)每個(gè)依賴模塊遞歸執(zhí)行buildModule進(jìn)行編譯。
參考項(xiàng)目:https://github.com/ronami/minipack