摘要:流程劃分縱觀整個(gè)打包過(guò)程,可以流程劃分為四塊。核心類關(guān)系圖功能實(shí)現(xiàn)模塊通過(guò)將源碼解析為樹并拆分,以及直至基礎(chǔ)模塊。通過(guò)的依賴和切割文件構(gòu)建出含有和包含關(guān)系的對(duì)象。通過(guò)模版完成主入口文件的寫入,模版完成切割文件的寫入。
前言
插件plugin,webpack重要的組成部分。它以事件流的方式讓用戶可以直接接觸到webpack的整個(gè)編譯過(guò)程。plugin在編譯的關(guān)鍵地方觸發(fā)對(duì)應(yīng)的事件,極大的增強(qiáng)了webpack的擴(kuò)展性。它的出現(xiàn)讓webpack從一個(gè)面向過(guò)程的打包工具,變成了一套完整的打包生態(tài)系統(tǒng)。
功能分析 Tapable既然說(shuō)到了事件流,那么就得介紹Tapable了,Tapable是webpack里面的一個(gè)小型庫(kù),它允許你自定義一個(gè)事件,并在觸發(fā)后訪問到觸發(fā)者的上下文。當(dāng)然他也支持異步觸發(fā),多個(gè)事件同步,異步觸發(fā)。本次實(shí)現(xiàn)用的是較早的v0.1.9版,具體文檔可查看tapable v0.19文檔
在webpack內(nèi)使用,如SingleEntryPlugin中
compiler.plugin("make",function(compilation,callback){ compilation.addEntry(this.context, new SingleEntryDependency({request: this.entry}), this.name, callback); })
在compiler內(nèi)部觸發(fā)。
this.applyPluginsParallel("make",compilation, err => { /* do something */ })
解析入口文件時(shí),通過(guò)EntryOptionPlugin解析entry類型并實(shí)例化SingleEntryPlugin, SingleEntryPlugin在調(diào)用compilation的addEntry函數(shù)開啟編譯。這種觀察者模式的設(shè)計(jì),解耦了compiler, compilation,并使它們提供的功能更加純粹,進(jìn)而增加擴(kuò)展性。
流程劃分縱觀整個(gè)打包過(guò)程,可以流程劃分為四塊。
初始化
構(gòu)建
封裝
文件寫入
模塊劃分接入plugin后,webpack對(duì)parse,resolve,build,writeSource等功能的大規(guī)模重構(gòu)。
目前拆分模塊為
Parser模塊,負(fù)責(zé)編譯module。
Resolver模塊,負(fù)責(zé)對(duì)文件路徑進(jìn)行解析。
ModuleFactory模塊,負(fù)責(zé)完成module的實(shí)例化。
Module模塊,負(fù)責(zé)解析出modules依賴,chunk依賴。構(gòu)建出打包后自身module的源碼。
Template模塊,負(fù)責(zé)提供bundle,chunk模塊文件寫入的模版。
Compilation模塊,負(fù)責(zé)文件編譯細(xì)節(jié),構(gòu)建并封裝出assets對(duì)象供Compiler模塊進(jìn)行文件寫入。
Compiler模塊,負(fù)責(zé)實(shí)例化compilation,bundle文件的寫入。監(jiān)聽modules的變化,并重新編譯。
核心類關(guān)系圖 功能實(shí)現(xiàn) Parser模塊通過(guò)exprima將源碼解析為AST樹,并拆分statements,以及expression直至Identifier基礎(chǔ)模塊。
解析到CallExpression時(shí)觸發(fā)call事件。
解析到MemberExpression,Identifier時(shí)觸發(fā)expression事件。
提供evaluateExpression函數(shù),訂閱Literal,ArrayExpression,CallExpression,ConditionalExpression等顆粒化的事件供evaluateExpression調(diào)用。
case "CallExpression": //do something this.applyPluginsBailResult("call " + calleeName, expression); //do something break; case "MemberExpression": //do something this.applyPluginsBailResult("expression " + memberName, expression); //do something break; case "Identifier": //do something this.applyPluginsBailResult("expression " + idenName, expression); //do something break;
this.plugin("evaluate Literal", (expr) => {}) this.plugin("evaluate ArrayExpression", (expr) => {}) this.plugin("evaluate CallExpression", (expr) => {}) ...
如需要解析require("a"),require.ensure(["b"],function(){})的時(shí)候,注冊(cè)plugin去訂閱"call require",以及"call require.ensure",再在回調(diào)函數(shù)調(diào)用evaluateExpression解析expression。
Resolver模塊封裝在enhanced-resolve庫(kù),提供異步解析文件路徑,以及可配置的filestream能力。在webpack用于緩存文件流以及以下三種類型模塊的路徑解析。
普通的module模塊
帶context的module模塊
loader模塊
用法如
ResolverFactory.createResolver(Object.assign({ fileSystem: compiler.inputFileSystem, resolveToContext: true }, options.resolve));
具體配置可去查看github文檔
ModuleFactory模塊子類有NormalModuleFactory,ContextModuleFactory。常用的NormalModuleFactory功能如下
實(shí)例化module之前,調(diào)用Resolver模塊解析出module和preloaders的絕對(duì)路徑。
通過(guò)正則匹配module文件名,匹配出rules內(nèi)的loaders,并和preloaders合并。
實(shí)例化module
這里主要是使用async庫(kù)的parallel函數(shù)并行的解析loaders和module的路徑,并整合運(yùn)行結(jié)果。
async.parallel([ (callback) => { this.requestResolverArray( context, loader, resolver, callback) }, (callback) => { resolver.normal.resolve({}, context, req, function (err, result) { callback(null, result) }); }, ], (err, result) => { let loaders = result[0]; const resource = result[1]; //do something })
async模塊是一整套異步編程的解決方案。async官方文檔
Module模塊運(yùn)行l(wèi)oaders數(shù)組內(nèi)的函數(shù),支持同步,異步loaders,得到編譯前源碼。
源碼交由Parser進(jìn)行解析,分析出modules依賴和blocks切割文件依賴
提供替換函數(shù),將源碼替換,如require("./a")替換為__webpack_require__(1)
一個(gè)編譯好的module對(duì)象包含modules依賴ModuleDependency和blocks依賴RequireEnsureDependenciesBlock,loaders,源碼_source,其數(shù)據(jù)結(jié)構(gòu)如下:
{ chunks: [], id: null, parser: Tapable { _plugins: { "evaluate Literal": [Array], "evaluate ArrayExpression": [Array], "evaluate CallExpression": [Array], "call require": [Array], "call require:commonjs:item": [Array], "call require.ensure": [Array] }, options: {}, scope: { declarations: [] }, state: { current: [Circular], module: [Circular] }, _currentPluginApply: undefined }, fileDependencies: [ "/Users/zhujian/Documents/workspace/webpack/simple-webpack/example/a.js" ], dependencies: [ ModuleDependency { request: "./module!d", range: [Array], class: [Function: ModuleDependency], type: "cms require" }, ModuleDependency { request: "./assets/test", range: [Array], class: [Function: ModuleDependency], type: "cms require" } ], blocks: [ RequireEnsureDependenciesBlock { blocks: [], dependencies: [Array], requires: [Array], chunkName: "", beforeRange: [Array], afterRange: [Array] } ], loaders: [], request: "/Users/zhujian/Documents/workspace/webpack/simple-webpack/example/a.js", fileName: "a.js", requires: [ [ 0, 7 ], [ 23, 30 ] ], context: "/Users/zhujian/Documents/workspace/webpack/simple-webpack/example", built: true, _source: RawSource { _result: { source: "require("./module!d"); require("./assets/test"); require.ensure(["./e","./b"], function () { console.log(1) console.log(1) console.log(1) console.log(1) require("./m"); require("./e"); }); " }, _source: "require("./module!d"); require("./assets/test"); require.ensure(["./e","./b"], function () { console.log(1) console.log(1) console.log(1) console.log(1) require("./m"); require("./e"); }); " } }Compilation模塊
通過(guò)entry和context,獲取到入口module對(duì)象,并創(chuàng)建入口chunk。
通過(guò)module的modules依賴和blocks切割文件構(gòu)建出含有chunk和modules包含關(guān)系的chunk對(duì)象。
給modules和chunks的排序并生成id,觸發(fā)一系列optimize相關(guān)的事件(如CommonsChunkPlugin就是使用optimize-chunks事件進(jìn)行開發(fā)),最終構(gòu)建出有文件名和源碼映射關(guān)系的assets對(duì)象
一個(gè)典型的含有切割文件的多入口entry的assets對(duì)象數(shù)據(jù)結(jié)構(gòu)如下:
assets: { "0.bundle.js": Chunk { name: "", parents: [Array], modules: [Array], id: 0, source: [Object] }, "main.bundle.js": Chunk { name: "main", parents: [], modules: [Array], id: 1, entry: true, chunks: [Array], blocks: true, source: [Object] }, "multiple.bundle.js": Chunk { name: "multiple", parents: [], modules: [Array], id: 2, entry: true, chunks: [Array], source: [Object] } }Compiler模塊
解析CLI, webpack配置獲取options對(duì)象,初始化resolver,parser對(duì)象。
實(shí)例化compilation對(duì)象,觸發(fā)make 并行事件調(diào)用compilation對(duì)象的addEntry開啟編譯。
獲取到assets對(duì)象,通過(guò)觸發(fā)before-emit事件開啟文件寫入。通過(guò)JsonMainTemplate模版完成主入口bundle文件的寫入,JsonpChunkTemplate模版完成chunk切割文件的寫入。 使用async.forEach管理異步多文件寫入的結(jié)果。
監(jiān)聽modules的變化,并重新編譯。
考慮到多入口entry的可能,make調(diào)用的是并行異步事件
this.applyPluginsParallel("make", compilation, err => { //do something compilation.seal(err=>{}) //do something }代碼實(shí)現(xiàn)
本人的簡(jiǎn)易版webpack實(shí)現(xiàn)simple-webpack
總結(jié)相信大家都有設(shè)計(jì)過(guò)業(yè)務(wù)/開源代碼,很多情況是越往后寫,越難維護(hù)。一次次的定制化的需求,將原有的設(shè)計(jì)改的支離破碎。這個(gè)時(shí)候可以試試借鑒webpak的思想,充分思考并抽象出穩(wěn)定的基礎(chǔ)模塊,劃分生命周期,將模塊之間的業(yè)務(wù)邏輯,特殊需求交由插件去解決。
完。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/96472.html
摘要:本篇的主要目標(biāo)是通過(guò)實(shí)際問題來(lái)介紹中容易被人忽略的細(xì)節(jié)以及源碼分析以最新發(fā)布的版本的源碼為例并且提供幾種解決方案。探究原因及源碼分析這里以最新發(fā)布的版本的源碼作為分析。解決方案加參數(shù)基于上面簡(jiǎn)要的分析,我們來(lái)嘗試下參數(shù)的作用。 注:本篇不是入門教程,入門請(qǐng)直接查看官方文檔。本篇的主要目標(biāo)是通過(guò)實(shí)際問題來(lái)介紹 webpack 中容易被人忽略的細(xì)節(jié), 以及源碼分析(以最新發(fā)布的 relea...
摘要:調(diào)用的目的是為了注冊(cè)你的邏輯指定一個(gè)綁定到自身的事件鉤子。這個(gè)對(duì)象在啟動(dòng)時(shí)被一次性建立,并配置好所有可操作的設(shè)置,包括,和。對(duì)象代表了一次資源版本構(gòu)建。一個(gè)對(duì)象表現(xiàn)了當(dāng)前的模塊資源編譯生成資源變化的文件以及被跟蹤依賴的狀態(tài)信息。 引言 在上一篇文章Tapable中介紹了其概念和一些原理用法,和這次討論分析webpack plugin的關(guān)聯(lián)很大。下面從實(shí)現(xiàn)一個(gè)插件入手。 demo插件 f...
摘要:筆者系貢獻(xiàn)者之一官方說(shuō)明簡(jiǎn)單來(lái)說(shuō)就是將文件變成,然后放入瀏覽器運(yùn)行。部分首先分析部分從做右到左,也就是被先后被和處理過(guò)了。源碼解析之二源碼解析之三寫作中源碼解析之四寫作中作者博客作者微博 筆者系 vue-loader 貢獻(xiàn)者(#16)之一 官方說(shuō)明 vue-loader is a loader for Webpack that can transform Vue components ...
摘要:源碼分析四模塊上一篇我們看到,通過(guò)對(duì)命令行傳入的參數(shù)和配置文件里的配置項(xiàng)做了轉(zhuǎn)換包裝,然后傳遞給的模塊去編譯。這一篇我們來(lái)看看做了些什么事。在上面的分析中,我們看到最核心的其實(shí)就是實(shí)例,接下來(lái)我們就看下它的類的內(nèi)部邏輯。 webpack 源碼分析(四)——complier模塊 上一篇我們看到,webpack-cli 通過(guò) `yargs 對(duì)命令行傳入的參數(shù)和配置文件里的配置項(xiàng)做了轉(zhuǎn)換包裝...
摘要:先上一張流程圖一般打包文件是通過(guò)調(diào)用這實(shí)際上等同于通過(guò)調(diào)用源碼如下將用戶本地的配置文件拼接上內(nèi)置的參數(shù)初始化對(duì)象編輯器對(duì)象,包含所有主環(huán)境相關(guān)內(nèi)容注冊(cè)插件和用戶配置的插件觸發(fā)和上注冊(cè)的事件注冊(cè)內(nèi)置插件源碼如下注冊(cè)觸發(fā)鉤子觸發(fā)鉤子觸發(fā)鉤子 先上一張流程圖showImg(https://segmentfault.com/img/bVbeW6Q?w=851&h=497);一般webpack打...
閱讀 4306·2021-09-26 10:11
閱讀 2700·2021-07-28 00:37
閱讀 3244·2019-08-29 15:29
閱讀 1207·2019-08-29 15:23
閱讀 3154·2019-08-26 18:37
閱讀 2492·2019-08-26 10:37
閱讀 623·2019-08-23 17:04
閱讀 2372·2019-08-23 13:44