深入講解webpack模組的基本原理
這篇文章主要介紹了淺談webpack組織模組的原理,現在分享給大家,也給大家做個參考。
現在前端用Webpack打包JS和其它檔案已經是主流了,加上Node的流行,使得前端的工程方式和後端越來越像。所有的東西都模組化,最後統一編譯。 Webpack因為版本的不斷更新以及各種紛繁複雜的配置選項,在使用中出現一些迷之錯誤常常讓人無所適從。所以了解Webpack究竟是怎麼組織編譯模組的,產生的程式碼到底是怎麼執行的,還是很有好處的,否則它就永遠是個黑盒子。當然了我是前端小白,最近也是剛開始研究Webpack的原理,在這裡做一點記錄。
編譯模組
編譯兩個字聽起來就很黑科技,加上產生的程式碼往往是一大坨不知所雲的東西,所以常常會讓人卻步,但其實裡面的核心原則並沒有什麼難。所謂的Webpack的編譯,其實只是Webpack在分析了你的原始碼後,對其做出一定的修改,然後把所有原始碼統一組織在一個檔案裡而已。最後產生一個大的bundle JS文件,被瀏覽器或者其它Javascript引擎執行並傳回結果。
在這裡用一個簡單的案例來說明Webpack打包模組的原理。例如我們有一個模組mA.js
var aa = 1; function getDate() { return new Date(); } module.exports = { aa: aa, getDate: getDate }
我隨便定義了一個變數aa和一個函式getDate,然後export出來,這裡是用CommonJS的寫法。
然後再定義一個app.js,作為main文件,仍然是CommonJS風格:
var mA = require('./mA.js'); console.log('mA.aa =' + mA.aa); mA.getDate();
現在我們有了兩個模組,使用Webpack來打包,入口文件是app.js,依賴模組mA.js,Webpack要做幾件事:
從入口模組app.js開始,分析所有模組的依賴關係,把所有用到的模組都讀取進來。
每一個模組的原始碼都會被組織在一個立即執行的函數裡。
改寫模組程式碼中和require和export相關的語法,以及它們對應的引用變數。
在最後產生的bundle檔案建立一套模組管理系統,能夠在runtime動態載入用到的模組。
我們可以看上面這個例子,Webpack打包出來的結果。最後的bundle檔案總的來說是一個大的立即執行的函數,組織層次比較複雜,大量的命名也比較晦澀,所以我在這裡做了一定改寫和修飾,把它整理得盡量簡單易懂。
首先是把所有用到的模組都羅列出來,以它們的檔案名稱(一般是完整路徑)為ID,建立一張表:
var modules = { './mA.js': generated_mA, './app.js': generated_app }
關鍵是上面的generated_xxx是什麼?它是一個函數,它把每個模組的原始碼包裹在裡面,使其成為一個局部的作用域,從而不會暴露內部的變量,實際上就把每個模組都變成一個執行函數。它的定義一般是這樣:
function generated_module(module, exports, webpack_require) { // 模块的具体代码。 // ... }
在這裡模組的具體程式碼是指產生程式碼,Webpack稱之為generated code。例如mA,經過改寫得到這樣的結果:
function generated_mA(module, exports, webpack_require) { var aa = 1; function getDate() { return new Date(); } module.exports = { aa: aa, getDate: getDate } }
乍看之下似乎和原始碼一模一樣。的確,mA沒有require或import其它模組,export用的也是傳統的CommonJS風格,所以產生程式碼沒有任何改變。不過值得注意的是最後的module.exports = ...,這裡的module就是外面傳進來的參數module,這實際上是在告訴我們,運行這個函數,模組mA的源代碼就會被執行,並且最後需要export的內容就會被保存到外部,到這裡就標誌著mA載入完成,而那個外部的東西實際上就後面要說的模組管理系統。
接下來看app.js的生成程式碼:
function generated_app(module, exports, webpack_require) { var mA_imported_module = webpack_require('./mA.js'); console.log('mA.aa =' + mA_imported_module['aa']); mA_imported_module['getDate'](); }
可以看到,app.js的原始碼中關於引入的模組mA的部分做了修改,因為無論是require/ exports,或是ES6風格的import/export,都無法被JavaScript解釋器直接執行,它需要依賴模組管理系統,把這些抽象的關鍵字具體化。也就是說,webpack_require就是require的具體實現,它能夠動態地載入模組mA,並且將結果傳回給app。
到這裡你腦海裡可能已經初步逐漸建立了一個模組管理系統的想法,我們來看看webpack_require的實作:
// 加载完毕的所有模块。 var installedModules = {}; function webpack_require(moduleId) { // 如果模块已经加载过了,直接从Cache中读取。 if (installedModules[moduleId]) { return installedModules[moduleId].exports; } // 创建新模块并添加到installedModules。 var module = installedModules[moduleId] = { id: moduleId, exports: {} }; // 加载模块,即运行模块的生成代码, modules[moduleId].call( module.exports, module, module.exports, webpack_require); return module.exports; }
注意倒數第二句裡的modules就是我們之前定義過的所有模組的generated code:
var modules = { './mA.js': generated_mA, './app.js': generated_app }
webpack_require的邏輯寫得很清楚,首先檢查模組是否已經加載,如果是則直接從Cache中返回模組的exports結果。如果是全新的模組,那麼就建立對應的資料結構module,並且運行這個模組的generated code,這個函數傳入的正是我們建立的module對象,以及它的exports域,這實際上就是CommonJS裡exports和module的由來。當運行完這個函數,模組就被載入完成了,需要export的結果保存到了module物件中。
所以我们看到所谓的模块管理系统,原理其实非常简单,只要耐心将它们抽丝剥茧理清楚了,根本没有什么深奥的东西,就是由这三个部分组成:
// 所有模块的生成代码 var modules; // 所有已经加载的模块,作为缓存表 var installedModules; // 加载模块的函数 function webpack_require(moduleId);
当然以上一切代码,在整个编译后的bundle文件中,都被包在一个大的立即执行的匿名函数中,最后返回的就是这么一句话:
return webpack_require(‘./app.js');
即加载入口模块app.js,后面所有的依赖都会动态地、递归地在runtime加载。当然Webpack真正生成的代码略有不同,它在结构上大致是这样:
(function(modules) { var installedModules = {}; function webpack_require(moduleId) { // ... } return webpack_require('./app.js'); }) ({ './mA.js': generated_mA, './app.js': generated_app });
可以看到它是直接把modules作为立即执行函数的参数传进去的而不是另外定义的,当然这和上面的写法没什么本质不同,我做这样的改写是为了解释起来更清楚。
ES6的import和export
以上的例子里都是用传统的CommonJS的写法,现在更通用的ES6风格是用import和export关键词,在使用上也略有一些不同。不过对于Webpack或者其它模块管理系统而言,这些新特性应该只被视为语法糖,它们本质上还是和require/exports一样的,例如export:
export aa // 等价于: module.exports['aa'] = aa export default bb // 等价于: module.exports['default'] = bb
而对于import:
import {aa} from './mA.js' // 等价于 var aa = require('./mA.js')['aa']
比较特殊的是这样的:
import m from './m.js'
情况会稍微复杂一点,它需要载入模块m的default export,而模块m可能并非是由ES6的export来写的,也可能根本没有export default,所以Webpack在为模块生成generated code的时候,会判断它是不是ES6风格的export,例如我们定义模块mB.js:
let x = 3; let printX = () => { console.log('x = ' + x); } export {printX} export default x
它使用了ES6的export,那么Webpack在mB的generated code就会加上一句话:
function generated_mB(module, exports, webpack_require) { Object.defineProperty(module.exports, '__esModule', {value: true}); // mB的具体代码 // .... }
也就是说,它给mB的export标注了一个__esModule,说明它是ES6风格的export。这样在其它模块中,当一个依赖模块以类似import m from './m.js'这样的方式加载时,会首先判断得到的是不是一个ES6 export出来的模块。如果是,则返回它的default,如果不是,则返回整个export对象。例如上面的mA是传统CommonJS的,mB是ES6风格的:
// mA is CommonJS module import mA from './mA.js' console.log(mA); // mB is ES6 module import mB from './mB.js' console.log(mB);
我们定义get_export_default函数:
function get_export_default(module) { return module && module.__esModule? module['default'] : module; }
这样generated code运行后在mA和mB上会得到不同的结果:
var mA_imported_module = webpack_require('./mA.js'); // 打印完整的 mA_imported_module console.log(get_export_default(mA_imported_module)); var mB_imported_module = webpack_require('./mB.js'); // 打印 mB_imported_module['default'] console.log(get_export_default(mB_imported_module));
这就是在ES6的import上,Webpack需要做一些特殊处理的地方。不过总体而言,ES6的import/export在本质上和CommonJS没有区别,而且Webpack最后生成的generated code也还是基于CommonJS的module/exports这一套机制来实现模块的加载的。
模块管理系统
以上就是Webpack如何打包组织模块,实现runtime模块加载的解读,其实它的原理并不难,核心的思想就是建立模块的管理系统,而这样的做法也是具有普遍性的,如果你读过Node.js的Module部分的源代码,就会发现其实用的是类似的方法。这里有一篇文章可以参考。
上面是我整理给大家的,希望今后会对大家有帮助。
相关文章:
以上是深入講解webpack模組的基本原理的詳細內容。更多資訊請關注PHP中文網其他相關文章!

熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

AI Hentai Generator
免費產生 AI 無盡。

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

熱門話題

Vue是一款優秀的JavaScript框架,它可以幫助我們快速建立互動性強、高效性好的Web應用程式。 Vue3是Vue的最新版本,它引入了許多新的功能和功能。 Webpack是目前最受歡迎的JavaScript模組打包器和建置工具之一,它可以幫助我們管理專案中的各種資源。本文就為大家介紹如何使用Webpack打包和建構Vue3應用程式。 1.安裝Webpack

JavaAPI開發中使用Jetty7進行Web伺服器處理隨著互聯網的發展,Web伺服器已經成為了應用程式開發的核心部分,同時也是許多企業所關注的焦點。為了滿足日益增長的業務需求,許多開發人員選擇使用Jetty進行Web伺服器開發,其靈活性和可擴展性受到了廣泛的認可。本文將介紹如何在JavaAPI開發中使用Jetty7進行We

防擋臉彈幕,即大量彈幕飄過,但不會遮擋視訊畫面中的人物,看起來像是從人物背後飄過去的。機器學習已經火了好幾年了,但很多人都不知道瀏覽器中也能運行這些能力;本文介紹在視頻彈幕方面的實踐優化過程,文末列舉了一些本方案可適用的場景,期望能開啟一些腦洞。 mediapipeDemo(https://google.github.io/mediapipe/)展示主流防擋臉彈幕實現原理點播up上傳視訊伺服器後台計算提取視訊畫面中的人像區域,轉換成svg儲存用戶端播放視訊的同時,從伺服器下載svg與彈幕合成,人像

首先你會有個疑惑,frp是什麼呢?簡單的說frp就是內網穿透工具,配置客戶端以後,可以透過伺服器來存取內部網路。現在我的伺服器,已經用nginx做站了,80端口只有一個,那如果frp的服務端也想使用80端口,那該怎麼辦呢?經過查詢,這個是可以實現的,就是利用nginx的反向代理來實現。補充一下:frps就是伺服器端(server),frpc就是客戶端(client)。第一步:修改伺服器中nginx.conf設定檔在nginx.conf中http{}裡加入以下參數,server{listen80

表單驗證是Web應用程式開發中非常重要的環節,它能夠在提交表單資料之前對資料進行有效性檢查,避免應用程式出現安全漏洞和資料錯誤。使用Golang可以輕鬆實現網頁應用程式的表單驗證,本文將介紹如何使用Golang來實作網頁應用程式的表單驗證。一、表單驗證的基本要素在介紹如何實作表單驗證之前,我們需要知道表單驗證的基本要素是什麼。表單元素:表單元素是指

Cockpit是一個面向Linux伺服器的基於Web的圖形介面。它主要是為了使新用戶/專家用戶更容易管理Linux伺服器。在本文中,我們將討論Cockpit存取模式以及如何從CockpitWebUI切換Cockpit的管理存取。內容主題:駕駛艙進入模式查找當前駕駛艙訪問模式從CockpitWebUI啟用Cockpit的管理訪問從CockpitWebUI禁用Cockpit的管理訪問結論駕駛艙進入模式駕駛艙有兩種訪問模式:受限訪問:這是駕駛艙的默認訪問模式。在這種存取模式下,您無法從駕駛艙Web用戶

Web標準是一組由W3C和其他相關組織制定的規範和指南,它包括HTML、CSS、JavaScript、DOM、Web可訪問性和性能優化等方面的標準化,透過遵循這些標準,可以提高頁面的兼容性、可訪問性、可維護性和效能。 Web標準的目標是使Web內容能夠在不同的平台、瀏覽器和裝置上一致地展示和交互,提供更好的使用者體驗和開發效率。

PHP在Web開發中是屬於後端。 PHP是一種伺服器端腳本語言,主要用於處理伺服器端的邏輯,產生動態網頁內容。與前端技術相比,PHP更多地用於與資料庫互動、處理使用者請求以及生成頁面內容等後端操作。接下來透過具體的程式碼範例來說明PHP在後端開發中的應用。首先,我們來看一個簡單的PHP程式碼範例,用於連接資料庫並查詢資料:
