摘要:流程劃分縱觀整個打包過程,可以流程劃分為四塊。核心類關系圖功能實現模塊通過將源碼解析為樹并拆分,以及直至基礎模塊。通過的依賴和切割文件構建出含有和包含關系的對象。通過模版完成主入口文件的寫入,模版完成切割文件的寫入。
前言
插件plugin,webpack重要的組成部分。它以事件流的方式讓用戶可以直接接觸到webpack的整個編譯過程。plugin在編譯的關鍵地方觸發對應的事件,極大的增強了webpack的擴展性。它的出現讓webpack從一個面向過程的打包工具,變成了一套完整的打包生態系統。
功能分析 Tapable既然說到了事件流,那么就得介紹Tapable了,Tapable是webpack里面的一個小型庫,它允許你自定義一個事件,并在觸發后訪問到觸發者的上下文。當然他也支持異步觸發,多個事件同步,異步觸發。本次實現用的是較早的v0.1.9版,具體文檔可查看tapable v0.19文檔
在webpack內使用,如SingleEntryPlugin中
compiler.plugin("make",function(compilation,callback){ compilation.addEntry(this.context, new SingleEntryDependency({request: this.entry}), this.name, callback); })
在compiler內部觸發。
this.applyPluginsParallel("make",compilation, err => { /* do something */ })
解析入口文件時,通過EntryOptionPlugin解析entry類型并實例化SingleEntryPlugin, SingleEntryPlugin在調用compilation的addEntry函數開啟編譯。這種觀察者模式的設計,解耦了compiler, compilation,并使它們提供的功能更加純粹,進而增加擴展性。
流程劃分縱觀整個打包過程,可以流程劃分為四塊。
初始化
構建
封裝
文件寫入
模塊劃分接入plugin后,webpack對parse,resolve,build,writeSource等功能的大規模重構。
目前拆分模塊為
Parser模塊,負責編譯module。
Resolver模塊,負責對文件路徑進行解析。
ModuleFactory模塊,負責完成module的實例化。
Module模塊,負責解析出modules依賴,chunk依賴。構建出打包后自身module的源碼。
Template模塊,負責提供bundle,chunk模塊文件寫入的模版。
Compilation模塊,負責文件編譯細節,構建并封裝出assets對象供Compiler模塊進行文件寫入。
Compiler模塊,負責實例化compilation,bundle文件的寫入。監聽modules的變化,并重新編譯。
核心類關系圖 功能實現 Parser模塊通過exprima將源碼解析為AST樹,并拆分statements,以及expression直至Identifier基礎模塊。
解析到CallExpression時觸發call事件。
解析到MemberExpression,Identifier時觸發expression事件。
提供evaluateExpression函數,訂閱Literal,ArrayExpression,CallExpression,ConditionalExpression等顆粒化的事件供evaluateExpression調用。
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(){})的時候,注冊plugin去訂閱"call require",以及"call require.ensure",再在回調函數調用evaluateExpression解析expression。
Resolver模塊封裝在enhanced-resolve庫,提供異步解析文件路徑,以及可配置的filestream能力。在webpack用于緩存文件流以及以下三種類型模塊的路徑解析。
普通的module模塊
帶context的module模塊
loader模塊
用法如
ResolverFactory.createResolver(Object.assign({ fileSystem: compiler.inputFileSystem, resolveToContext: true }, options.resolve));
具體配置可去查看github文檔
ModuleFactory模塊子類有NormalModuleFactory,ContextModuleFactory。常用的NormalModuleFactory功能如下
實例化module之前,調用Resolver模塊解析出module和preloaders的絕對路徑。
通過正則匹配module文件名,匹配出rules內的loaders,并和preloaders合并。
實例化module
這里主要是使用async庫的parallel函數并行的解析loaders和module的路徑,并整合運行結果。
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模塊運行loaders數組內的函數,支持同步,異步loaders,得到編譯前源碼。
源碼交由Parser進行解析,分析出modules依賴和blocks切割文件依賴
提供替換函數,將源碼替換,如require("./a")替換為__webpack_require__(1)
一個編譯好的module對象包含modules依賴ModuleDependency和blocks依賴RequireEnsureDependenciesBlock,loaders,源碼_source,其數據結構如下:
{ 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模塊
通過entry和context,獲取到入口module對象,并創建入口chunk。
通過module的modules依賴和blocks切割文件構建出含有chunk和modules包含關系的chunk對象。
給modules和chunks的排序并生成id,觸發一系列optimize相關的事件(如CommonsChunkPlugin就是使用optimize-chunks事件進行開發),最終構建出有文件名和源碼映射關系的assets對象
一個典型的含有切割文件的多入口entry的assets對象數據結構如下:
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對象,初始化resolver,parser對象。
實例化compilation對象,觸發make 并行事件調用compilation對象的addEntry開啟編譯。
獲取到assets對象,通過觸發before-emit事件開啟文件寫入。通過JsonMainTemplate模版完成主入口bundle文件的寫入,JsonpChunkTemplate模版完成chunk切割文件的寫入。 使用async.forEach管理異步多文件寫入的結果。
監聽modules的變化,并重新編譯。
考慮到多入口entry的可能,make調用的是并行異步事件
this.applyPluginsParallel("make", compilation, err => { //do something compilation.seal(err=>{}) //do something }代碼實現
本人的簡易版webpack實現simple-webpack
總結相信大家都有設計過業務/開源代碼,很多情況是越往后寫,越難維護。一次次的定制化的需求,將原有的設計改的支離破碎。這個時候可以試試借鑒webpak的思想,充分思考并抽象出穩定的基礎模塊,劃分生命周期,將模塊之間的業務邏輯,特殊需求交由插件去解決。
完。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/96472.html
摘要:本篇的主要目標是通過實際問題來介紹中容易被人忽略的細節以及源碼分析以最新發布的版本的源碼為例并且提供幾種解決方案。探究原因及源碼分析這里以最新發布的版本的源碼作為分析。解決方案加參數基于上面簡要的分析,我們來嘗試下參數的作用。 注:本篇不是入門教程,入門請直接查看官方文檔。本篇的主要目標是通過實際問題來介紹 webpack 中容易被人忽略的細節, 以及源碼分析(以最新發布的 relea...
摘要:調用的目的是為了注冊你的邏輯指定一個綁定到自身的事件鉤子。這個對象在啟動時被一次性建立,并配置好所有可操作的設置,包括,和。對象代表了一次資源版本構建。一個對象表現了當前的模塊資源編譯生成資源變化的文件以及被跟蹤依賴的狀態信息。 引言 在上一篇文章Tapable中介紹了其概念和一些原理用法,和這次討論分析webpack plugin的關聯很大。下面從實現一個插件入手。 demo插件 f...
摘要:筆者系貢獻者之一官方說明簡單來說就是將文件變成,然后放入瀏覽器運行。部分首先分析部分從做右到左,也就是被先后被和處理過了。源碼解析之二源碼解析之三寫作中源碼解析之四寫作中作者博客作者微博 筆者系 vue-loader 貢獻者(#16)之一 官方說明 vue-loader is a loader for Webpack that can transform Vue components ...
摘要:源碼分析四模塊上一篇我們看到,通過對命令行傳入的參數和配置文件里的配置項做了轉換包裝,然后傳遞給的模塊去編譯。這一篇我們來看看做了些什么事。在上面的分析中,我們看到最核心的其實就是實例,接下來我們就看下它的類的內部邏輯。 webpack 源碼分析(四)——complier模塊 上一篇我們看到,webpack-cli 通過 `yargs 對命令行傳入的參數和配置文件里的配置項做了轉換包裝...
摘要:先上一張流程圖一般打包文件是通過調用這實際上等同于通過調用源碼如下將用戶本地的配置文件拼接上內置的參數初始化對象編輯器對象,包含所有主環境相關內容注冊插件和用戶配置的插件觸發和上注冊的事件注冊內置插件源碼如下注冊觸發鉤子觸發鉤子觸發鉤子 先上一張流程圖showImg(https://segmentfault.com/img/bVbeW6Q?w=851&h=497);一般webpack打...
閱讀 4270·2021-09-26 10:11
閱讀 2666·2021-07-28 00:37
閱讀 3223·2019-08-29 15:29
閱讀 1178·2019-08-29 15:23
閱讀 3124·2019-08-26 18:37
閱讀 2468·2019-08-26 10:37
閱讀 597·2019-08-23 17:04
閱讀 2347·2019-08-23 13:44