首頁 web前端 js教程 深入講解webpack模組的基本原理

深入講解webpack模組的基本原理

May 31, 2018 pm 01:58 PM
web 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要做幾件事:

  1. 從入口模組app.js開始,分析所有模組的依賴關係,把所有用到的模組都讀取進來。

  2. 每一個模組的原始碼都會被組織在一個立即執行的函數裡。

  3. 改寫模組程式碼中和require和export相關的語法,以及它們對應的引用變數。

  4. 在最後產生的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部分的源代码,就会发现其实用的是类似的方法。这里有一篇文章可以参考。

上面是我整理给大家的,希望今后会对大家有帮助。

相关文章:

vue iview组件表格 render函数的使用方法详解

微信小程序实现换肤功能

nodejs实现解析xml字符串为对象的方法示例

以上是深入講解webpack模組的基本原理的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

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

AI Clothes Remover

AI Clothes Remover

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

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
4 週前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
4 週前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
4 週前 By 尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解鎖Myrise中的所有內容
1 個月前 By 尊渡假赌尊渡假赌尊渡假赌

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

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

VUE3入門教學:使用Webpack進行打包和構建 VUE3入門教學:使用Webpack進行打包和構建 Jun 15, 2023 pm 06:17 PM

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

Java API 開發中使用 Jetty7 進行 Web 伺服器處理 Java API 開發中使用 Jetty7 進行 Web 伺服器處理 Jun 18, 2023 am 10:42 AM

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

Web 端即時防擋臉彈幕(基於機器學習) Web 端即時防擋臉彈幕(基於機器學習) Jun 10, 2023 pm 01:03 PM

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

怎麼設定nginx保證frps伺服器與web共用80埠 怎麼設定nginx保證frps伺服器與web共用80埠 Jun 03, 2023 am 08:19 AM

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

如何使用Golang實作網頁應用程式的表單驗證 如何使用Golang實作網頁應用程式的表單驗證 Jun 24, 2023 am 09:08 AM

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

如何從駕駛艙Web使用者介面啟用管理訪問 如何從駕駛艙Web使用者介面啟用管理訪問 Mar 20, 2024 pm 06:56 PM

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

web標準是什麼東西 web標準是什麼東西 Oct 18, 2023 pm 05:24 PM

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

PHP在Web開發中是屬於前端還是後端? PHP在Web開發中是屬於前端還是後端? Mar 24, 2024 pm 02:18 PM

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

See all articles