從零實現一個迷你 Webpack

大家好,我是若川。我持續組織了近一年的源碼共讀活動,感興趣的可以?點此掃碼加我微信?lxchuan12?參與,每周大家一起學習200行左右的源碼,共同進步。同時極力推薦訂閱我寫的《學習源碼整體架構系列》?包含20余篇源碼文章。歷史面試系列。另外:目前建有江西|湖南|湖北籍前端群,可加我微信進群。

本文為來自 字節跳動-國際化電商-S項目 的文章,已授權 ELab 發布。

webpack 是當前使用較多的一個打包工具,將眾多代碼組織到一起使得在瀏覽器下可以正常運行,下面以打包為目的,實現一個簡易版 webpack,支持單入口文件的打包,不涉及插件、分包等。

前置知識

舉個🌰,先來看看下面這個 demo,例子很簡單,一個 index.js,里面引用了一個文件 a.js,a.js 內部引入了 b.js,通過 webpack 最簡單的配置,將 index.js 文件作為入口進行打包。

來看看打包后的內容是怎樣的

//?index.js
require('./a.js');
console.log('entry?load');//?a.js
require("./b.js");
const?a?=?1;
console.log("a?load");
module.exports?=?a;//?b.js
console.log("b?load");
const?b?=?1;
module.exports?=?b;
1d1729f34fd6aecf20213c0b119ae3e7.png

可以看到打包產物是一個立即執行函數,函數初始先定義了多個 module,每個 module 是實際代碼中被 require 的文件內容,同時由于瀏覽器不支持 require 方法,webpack 內部自行實現了一個 __webpack__require__,并將代碼中的 require 全部替換為該函數(從打包結果可看出)。

webpack__require 定義之后,便開始執行入口文件,同時可以看出,webpack 的打包過程便是通過入口文件,將直接依賴和間接依賴以 module 的形式組織到一起,并通過自行實現的 require 實現模塊的同步加載。

了解了打包產物后,便可以開始實現簡易版的 webpack ,最終打包產物與 webpack 保持一致。

初始化參數

根據 Node 接口 | webpack 中文文檔[1] 可以知道,webpack node api 對外暴露出了 webpack 方法,通過調用 webpack 方法傳入配置,返回 compiler 對象,compiler 對象包含 run 方法可執行編譯,即

const?webpack?=?require('webpack');?//?引用?webpackconst?compiler?=?webpack(options);?//?傳入配置生成?compiler?對象compiler.run((err,?stats)?=>?{??//?執行編譯,?傳入回調});

因此,首先需要實現一個 webpack 方法,同時該方法支持傳入 webpack 配置,返回 compiler 實例,webpack 官方支持了以 cli 的形式運行 webpack 命令和指定參數、配置文件,這一部分暫時簡單實現,我們暴露出一個方法,方法接收用戶的配置。

//?mini-webpack/core/index.jsfunction?webpack()?{//?創建compiler對象const?compiler?=?new?Compiler(options);
}module.exports?=?webpack;

如上,實現了一個 webpack 方法,可傳入一個 options 參數,包括用戶指定的打包入口 entry、output 等。

webpack({entry:?'./index.js',output:?{path:?path.resolve(__dirname,?"dist"),filename:?"[name].js",},module:?{rules:?[]}
})

編譯

上面已經實現了 webpack 配置的傳入,compiler 的創建,接下來還需要實現 Compiler 類,該類內部暴露一個 run 方法,用于執行編譯。

首先需要明確編譯過程需要做的事情。

  1. 讀取入口文件,將入口文件交給匹配的 loader 處理,返回處理后的代碼

  1. 開始編譯 loader 處理完的代碼

  1. 若代碼中依賴了其他文件,則對 require 函數替換為 webpack 自行實現的 __webpack__require__, 保存該文件的處理結果,同時讓其他文件回到第 1 步進行處理,不斷循環。

  1. 編譯結束后,每個文件都有其對應的處理結果,將這些文件的編譯結果從初始的入口文件開始組織到一起。

入口文件 loader 處理

讀取入口文件,將入口文件交給 匹配的 loader 處理

//?mini-webpack?compiler.jsconst?fs?=?require('fs');
class?Compiler?{constructor(options)?{this.options?=?options?||?{};//?保存編譯過程編譯的?modulethis.modules?=?new?Set();}run(callback)?{const?entryChunk?=?this.build(path.join(process.cwd(),?this.options.entry));}build(modulePath)?{let?originCode?=?fs.readFileSync(modulePath);originCode?=?this.dealWidthLoader(modulePath,?originCode.toString());return?this.dealDependencies(originCode,?modulePath);}//?將源碼交給匹配的?loader?處理dealWidthLoader(modulePath,?originCode)?{[...this.options.module.rules].reverse().forEach(item?=>?{if?(item.test(modulePath))?{const?loaders?=?[...item.use].reverse();loaders.forEach(loader?=>?originCode?=?loader(originCode))}})return?originCode}
}module.exports?=?Compiler;

入口文件處理

這里需要開始處理入口文件的依賴,將其 require 轉換成 自定義的 __webpack_require__,同時將其依賴收集起來,后續需要不斷遞歸處理其直接依賴和間接依賴,這里用到了 babel 進行處理。

//?調用?webpack?處理依賴的代碼dealDependencies(code,?modulePath)?{const?fullPath?=?path.relative(process.cwd(),?modulePath);//?創建模塊對象const?module?=?{id:?fullPath,dependencies:?[]?//?該模塊所依賴模塊絕對路徑地址};//?處理?require?語句,同時記錄依賴了哪些文件const?ast?=?parser.parse(code,?{sourceType:?"module",ast:?true,});//?深度優先?遍歷語法Treetraverse(ast,?{CallExpression:?(nodePath)?=>?{const?node?=?nodePath.node;if?(node.callee.name?===?"require")?{//?獲得依賴的路徑const?requirePath?=?node.arguments[0].value;const?moduleDirName?=?path.dirname(modulePath);const?fullPath?=?path.relative(path.join(moduleDirName,?requirePath),?requirePath);????????????????????//?替換?require?語句為?webpack?自定義的?require?方法node.callee?=?t.identifier("__webpack_require__");//?將依賴的路徑修改成以當前路行為基準node.arguments?=?[t.stringLiteral(fullPath)];const?exitModule?=?[...this.modules].find(item?=>?item.id?===?fullPath)//?該文件可能已經被處理過,這里判斷一下if?(!exitModule)?{//?記錄下當前處理的文件所依賴的文件(后續需逐一處理)module.dependencies.push(fullPath);}}},});//?根據新的?ast?生成代碼const?{?code:?compilerCode?}?=?generator(ast);//?保存處理后的代碼module._source?=?compilerCode;//?返回當前模塊對象return?module;}

依賴處理

到這里為止便處理完了入口文件,但是在處理文件過程,還收集了入口文件依賴的其他文件未處理,因此,在 dealDependencies 尾部,加入以下代碼

//?調用?webpack?處理依賴的代碼dealDependencies(code,?modulePath)?{.........//?為當前模塊掛載新的生成的代碼module._source?=?compilerCode;//?遞歸處理其依賴module.dependencies.forEach((dependency)?=>?{const?depModule?=?this.build(dependency);//?同時保存下編譯過的依賴this.modules.add(depModule);});.........//?返回當前模塊對象return?module;}

Chunk

在上面的步驟中,已經處理了入口文件、依賴文件,但目前它們還是分散開來,在 webpack 中,是支持多個入口,每個入口是一個 chunk,這個 chunk 將包含入口文件及其依賴的 module

//?mini-webpack?compiler.jsconst?fs?=?require('fs');
class?Compiler?{constructor(options)?{this.options?=?options?||?{};//?保存編譯過程編譯的?modulethis.modules?=?new?Set();}run(callback)?{const?entryModule?=?this.build(path.join(process.cwd(),?this.options.entry));const?entryChunk?=?this.buildChunk("entry",?entryModule);}build(modulePath)?{}//?將源碼交給匹配的?loader?處理dealWidthLoader(modulePath,?originCode)?{}//?調用?webpack?處理依賴的代碼dealDependencies(code,?modulePath)?{????}buildChunk(entryName,?entryModule)?{return?{name:?entryName,//?入口文件編譯結果entryModule:?entryModule,//?所有直接依賴和間接依賴編譯結果modules:?this.modules,};}
}module.exports?=?Compiler;

文件生成

至此我們已經將入口文件和其所依賴的所有文件編譯完成,現在需要將編譯后的代碼生成對應的文件。

根據最上面利用官方 webpack 打包出來的產物,保留其基本結構,將構造的 chunk 內部的 entryModule 的 source 以及 modules 的 souce 替換進去,并根據初始配置的 output 生成對應文件。

//?mini-webpack?compiler.jsconst?fs?=?require('fs');
class?Compiler?{constructor(options)?{this.options?=?options?||?{};//?保存編譯過程編譯的?module,下面會講解到this.modules?=?new?Set();}run(callback)?{const?entryModule?=?this.build(path.join(process.cwd(),?this.options.entry));const?entryChunk?=?this.buildChunk("entry",?entryModule);this.generateFile(entryChunk);}build(modulePath)?{}//?將源碼交給匹配的?loader?處理dealWidthLoader(modulePath,?originCode)?{}//?調用?webpack?處理依賴的代碼dealDependencies(code,?modulePath)?{????}buildChunk(entryName,?entryModule)?{}generateFile(entryChunk)?{//?獲取打包后的代碼const?code?=?this.getCode(entryChunk);if?(!fs.existsSync(this.options.output.path))?{fs.mkdirSync(this.options.output.path);}//?寫入文件fs.writeFileSync(path.join(this.options.output.path,this.options.output.filename.replace("[name]",?entryChunk.name)),code);}getCode(entryChunk)?{return?`(()?=>?{//?webpackBootstrapvar?__webpack_modules__?=?{${entryChunk.modules.map(module?=>?`"${module.id}":?(module,?__unused_webpack_exports,?__webpack_require__)?=>?{${module._source}}`).join(',')}};var?__webpack_module_cache__?=?{};function?__webpack_require__(moduleId)?{//?Check?if?module?is?in?cachevar?cachedModule?=?__webpack_module_cache__[moduleId];if?(cachedModule?!==?undefined)?{return?cachedModule.exports;}//?Create?a?new?module?(and?put?it?into?the?cache)var?module?=?(__webpack_module_cache__[moduleId]?=?{exports:?{},});//?Execute?the?module?function__webpack_modules__[moduleId](module,module.exports,__webpack_require__);//?Return?the?exports?of?the?modulereturn?module.exports;}var?__webpack_exports__?=?{};//?This?entry?need?to?be?wrapped?in?an?IIFE?because?it?need?to?be?isolated?against?other?modules?in?the?chunk.(()?=>?{${entryChunk.entryModule._source};})();})()`;}
}module.exports?=?Compiler;

試試在瀏覽器下跑一下生成的代碼

65f3711d4508c46519fa686f0a1b62ac.png

符合預期,至此便完成了一個極簡的 webpack,針對單入口文件進行打包。當然真正的 webpack 遠非如此簡單,這里僅僅只是實現其一個打包思路。

?? 謝謝支持

以上便是本次分享的全部內容,希望對你有所幫助^_^

喜歡的話別忘了 分享、點贊、收藏 三連哦~。

歡迎關注公眾號 ELab團隊 收貨大廠一手好文章~

  • 字節跳動校/社招內推碼: WWCM1TA

  • 投遞鏈接: https://job.toutiao.com/s/rj1fwQW

可憑內推碼投遞 字節跳動-國際化電商-S項目 團隊 相關崗位哦~

參考資料

[1]

Node 接口 | webpack 中文文檔: https://webpack.docschina.org/api/node/#webpack

- END -

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/274440.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/274440.shtml
英文地址,請注明出處:http://en.pswp.cn/news/274440.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

ios 刷新遮罩遮罩_在Adobe XD中進行遮罩的3種方法

ios 刷新遮罩遮罩Are you new to Adobe XD? Or maybe you’re just stuck on how to create a simple mask? Here are 3 quick tips for how to mask your photos and designs in Adobe XD.您是Adobe XD的新手嗎? 或者,也許您只是停留在如何創建簡單的…

C#除法運算

C#中進行算術運算容易牽扯到類型的自動轉換,這種自動轉換稱之為隱式轉換,當然還可以人為的強制轉換 隱式轉換要求:不丟失精度,而且轉換前后都為數值 強制轉換:容易丟失可能會丟失精度 1 namespace 除法運算2 {3 cl…

Vite 4.0 正式發布!

源碼共讀我新出了:第40期 | vite 是如何解析用戶配置的 .env 的鏈接:https://www.yuque.com/ruochuan12/notice/p40也可以點擊文末閱讀原文查看,歡迎學習記筆記~12 月 9 日,Vite 4.0 正式發布。下面就來看看 Vite 4.0 有哪些更新吧…

代碼復審

我們CodingCook復審的是WWW的代碼,他們的項目是時間管理助手(TimeLine)。只是跟根據自己的經驗來看,不一定準 先說一下整體的感覺。WWW的代碼用了應該是比較符合面向對象的思想,借口,封裝隨處可見&#xff…

圖像標注技巧_保護互聯網上圖像的一個簡單技巧

圖像標注技巧補習 (TUTORIAL) Have you ever worried about sharing your images on the Internet? Anytime you upload something to the web you risk the chance of your work being used (without permission) by another.您是否曾經擔心過要在Internet上共享圖像&#xf…

【VueConf 2022】尤雨溪:Vue的進化歷程

大家好,我是若川。我持續組織了近一年的源碼共讀活動,感興趣的可以 點此掃碼加我微信 lxchuan12 參與,每周大家一起學習200行左右的源碼,共同進步。同時極力推薦訂閱我寫的《學習源碼整體架構系列》 包含20余篇源碼文章。歷史面試…

WCF netTcpBinding寄宿到IIS7

config配置文件不多說 <?xml version"1.0" encoding"utf-8" ?> <configuration><system.serviceModel><behaviors><serviceBehaviors><behavior name"myBehavior"><serviceMetadata/></behavior…

ar軟件測試工具_如何為用戶測試制作快速的AR原型

ar軟件測試工具We had a project recently with an element of AR-based interaction, which it turned out was impossible to create as a prototype in either Invision or Framer (our current stack). This had a massive impact on our ability to test with users in a …

未來ui設計的發展趨勢_2025年的未來UI趨勢?

未來ui設計的發展趨勢Humans are restless.人類是不安的。 Once we find something that works, we get used to it and we crave the next big thing. The next innovation. When will the future finally arrive? And when it does, how long will it take us to get used …

內存泄露檢測 vld

VLD是一款開源檢測內存泄露軟件的簡稱&#xff1a;Visual Leak Detector 網站&#xff1a;http://vld.codeplex.com/ 使用&#xff1a; 1. 安裝vld 或者 下載相關 .h&#xff0c;lib&#xff0c;dll 文件 2. 方法很簡單&#xff0c;只要在包含入口函數的.cpp文件中包含vld.h就可…

Monorepo 在網易的工程改造實踐

大家好&#xff0c;我是若川。我持續組織了近一年的源碼共讀活動&#xff0c;感興趣的可以 點此掃碼加我微信 lxchuan12 參與&#xff0c;每周大家一起學習200行左右的源碼&#xff0c;共同進步。同時極力推薦訂閱我寫的《學習源碼整體架構系列》 包含20余篇源碼文章。歷史面試…

這一年,Vue.js 生態開源之旅帶給我很大收獲~

大家好&#xff0c;我是若川。我持續組織了近一年的源碼共讀活動&#xff0c;感興趣的可以 點此掃碼加我微信 lxchuan12 參與&#xff0c;每周大家一起學習200行左右的源碼&#xff0c;共同進步。同時極力推薦訂閱我寫的《學習源碼整體架構系列》 包含20余篇源碼文章。歷史面試…

CSSyphus:煩躁不安的煩惱設計指南。

I’m trapped at home with my website. Or maybe it’s trapped at home with me. While some are using the weird lump of time provided by lockdown to indulge in baking, dancing, painting, singing, I’m using it to play around with code.我 被自己的網站困在家里。…

重構與臭豆腐4

重構要繼續&#xff0c;臭豆腐要做。   這個重構中各種提取類&#xff0c;方法&#xff0c;字段&#xff0c;可以方便的理解&#xff0c;如果使用了設置模式就更加邏輯清晰了。切東西也要講究刀法的。 重構可以方便的使用設計模式。設計模式為重構提供了目標。 比如多個if 可…

你構建的代碼為什么這么大?如何優化~

大家好&#xff0c;我是若川。我持續組織了近一年的源碼共讀活動&#xff0c;感興趣的可以 點此掃碼加我微信 lxchuan12 參與&#xff0c;每周大家一起學習200行左右的源碼&#xff0c;共同進步。同時極力推薦訂閱我寫的《學習源碼整體架構系列》 包含20余篇源碼文章。歷史面試…

用戶體驗需求層次_需求和用戶體驗

用戶體驗需求層次Shortly after the start of 2020 I led the development of a new website, and it went live in August. A week before the deployment, I paused development and took a step back in order to write about the project. Taking that pause, that step ba…

VMwareWorkstation設置U盤啟動(或U盤使用)

最近在工作中&#xff0c;經常要和LINUX部署打交道&#xff0c;一般在生產環境部署之前需要在自己的機器上進行測試。比如使用U盤安裝操作系統等。 在機器上安裝了VMware Workstation9.0&#xff0c;運行多個測試虛擬機。理由所當然的要使用此做一些操作系統部署&#xff0c;…

類從未使用_如果您從未依賴在線銷售,如何優化您的網站

類從未使用初學者指南 (A beginner’s guide) If you own a small business with a store front, you might have never had to rely on online sales. Maybe you’re a small clothing store or a coffee shop. You just made that website so people could find you online, …

狼書三卷終大成,狼叔親傳Node神功【留言送書】

大家好&#xff0c;我是若川。之前送過N次書&#xff0c;可以點此查看回饋粉絲&#xff0c;現在又和博文視點合作再次爭取了幾本書&#xff0c;具體送書規則看文末。眾所周知&#xff0c;我在參加掘金人氣作者打榜活動&#xff08;可點擊跳轉&#xff09;&#xff0c;需要大家投…

entity framework5 sqlserver2005 事務(TransactionScope)報未啟用MSDTC錯誤解決辦法

詳情請看&#xff1a;http://stackoverflow.com/questions/12809958/ef-how-do-i-call-savechanges-twice-inside-a-transaction using (var transaction new TransactionScope()) {// Do somethingdb.SaveChanges();// Do something elsedb.SaveChanges();tramsaction.Comple…