亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

淺談webpack組織模塊的原理

 更新時間:2018年03月10日 14:28:19   作者:hyuan  
這篇文章主要介紹了淺談webpack組織模塊的原理,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧

現(xiàn)在前端用Webpack打包JS和其它文件已經(jīng)是主流了,加上Node的流行,使得前端的工程方式和后端越來越像。所有的東西都模塊化,最后統(tǒng)一編譯。Webpack因為版本的不斷更新以及各種各樣紛繁復(fù)雜的配置選項,在使用中出現(xiàn)一些迷之錯誤常常讓人無所適從。所以了解一下Webpack究竟是怎么組織編譯模塊的,生成的代碼到底是怎么執(zhí)行的,還是很有好處的,否則它就永遠(yuǎn)是個黑箱。當(dāng)然了我是前端小白,最近也是剛開始研究Webpack的原理,在這里做一點記錄。

編譯模塊

編譯兩個字聽起來就很黑科技,加上生成的代碼往往是一大坨不知所云的東西,所以常常會讓人卻步,但其實里面的核心原理并沒有什么難。所謂的Webpack的編譯,其實只是Webpack在分析了你的源代碼后,對其作出一定的修改,然后把所有源代碼統(tǒng)一組織在一個文件里而已。最后生成一個大的bundle JS文件,被瀏覽器或者其它Javascript引擎執(zhí)行并返回結(jié)果。

在這里用一個簡單的案例來說明Webpack打包模塊的原理。例如我們有一個模塊mA.js

var aa = 1;

function getDate() {
 return new Date();
}

module.exports = {
 aa: aa,
 getDate: getDate
}

我隨便定義了一個變量aa和一個函數(shù)getDate,然后export出來,這里是用CommonJS的寫法。

然后再定義一個app.js,作為main文件,仍然是CommonJS風(fēng)格:

var mA = require('./mA.js');

console.log('mA.aa =' + mA.aa);
mA.getDate();

現(xiàn)在我們有了兩個模塊,使用Webpack來打包,入口文件是app.js,依賴于模塊mA.js,Webpack要做幾件事情:

  1. 從入口模塊app.js開始,分析所有模塊的依賴關(guān)系,把所有用到的模塊都讀取進(jìn)來。
  2. 每一個模塊的源代碼都會被組織在一個立即執(zhí)行的函數(shù)里。
  3. 改寫模塊代碼中和require和export相關(guān)的語法,以及它們對應(yīng)的引用變量。
  4. 在最后生成的bundle文件里建立一套模塊管理系統(tǒng),能夠在runtime動態(tài)加載用到的模塊。

我們可以看一下上面這個例子,Webpack打包出來的結(jié)果。最后的bundle文件總的來說是一個大的立即執(zhí)行的函數(shù),組織層次比較復(fù)雜,大量的命名也比較晦澀,所以我在這里做了一定改寫和修飾,把它整理得盡量簡單易懂。

首先是把所有用到的模塊都羅列出來,以它們的文件名(一般是完整路徑)為ID,建立一張表:

var modules = {
 './mA.js': generated_mA,
 './app.js': generated_app
}

關(guān)鍵是上面的generated_xxx是什么?它是一個函數(shù),它把每個模塊的源代碼包裹在里面,使之成為一個局部的作用域,從而不會暴露內(nèi)部的變量,實際上就把每個模塊都變成一個執(zhí)行函數(shù)。它的定義一般是這樣:

function generated_module(module, exports, webpack_require) {
  // 模塊的具體代碼。
  // ...
}

在這里模塊的具體代碼是指生成代碼,Webpack稱之為generated code。例如mA,經(jīng)過改寫得到這樣的結(jié)果:

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用的也是傳統(tǒng)的CommonJS風(fēng)格,所以生成代碼沒有任何改動。不過值得注意的是最后的module.exports = ...,這里的module就是外面?zhèn)鬟M(jìn)來的參數(shù)module,這實際上是在告訴我們,運(yùn)行這個函數(shù),模塊mA的源代碼就會被執(zhí)行,并且最后需要export的內(nèi)容就會被保存到外部,到這里就標(biāo)志著mA加載完成,而那個外部的東西實際上就后面要說的模塊管理系統(tǒng)。

接下來看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的源代碼中關(guān)于引入的模塊mA的部分做了修改,因為無論是require/exports,或是ES6風(fēng)格的import/export,都無法被JavaScript解釋器直接執(zhí)行,它需要依賴模塊管理系統(tǒng),把這些抽象的關(guān)鍵詞具體化。也就是說,webpack_require就是require的具體實現(xiàn),它能夠動態(tài)地載入模塊mA,并且將結(jié)果返回給app。

到這里你腦海里可能已經(jīng)初步逐漸構(gòu)建出了一個模塊管理系統(tǒng)的想法,我們來看一下webpack_require的實現(xiàn):

// 加載完畢的所有模塊。
var installedModules = {};

function webpack_require(moduleId) {
 // 如果模塊已經(jīng)加載過了,直接從Cache中讀取。
 if (installedModules[moduleId]) {
  return installedModules[moduleId].exports;
 }

 // 創(chuàng)建新模塊并添加到installedModules。
 var module = installedModules[moduleId] = {
  id: moduleId,
  exports: {}
 };
 
 // 加載模塊,即運(yùn)行模塊的生成代碼,
 modules[moduleId].call(
  module.exports, module, module.exports, webpack_require);
 
 return module.exports;
}

注意倒數(shù)第二句里的modules就是我們之前定義過的所有模塊的generated code:

var modules = {
 './mA.js': generated_mA,
 './app.js': generated_app
}

webpack_require的邏輯寫得很清楚,首先檢查模塊是否已經(jīng)加載,如果是則直接從Cache中返回模塊的exports結(jié)果。如果是全新的模塊,那么就建立相應(yīng)的數(shù)據(jù)結(jié)構(gòu)module,并且運(yùn)行這個模塊的generated code,這個函數(shù)傳入的正是我們建立的module對象,以及它的exports域,這實際上就是CommonJS里exports和module的由來。當(dāng)運(yùn)行完這個函數(shù),模塊就被加載完成了,需要export的結(jié)果保存到了module對象中。

所以我們看到所謂的模塊管理系統(tǒng),原理其實非常簡單,只要耐心將它們抽絲剝繭理清楚了,根本沒有什么深奧的東西,就是由這三個部分組成:

// 所有模塊的生成代碼
var modules;
// 所有已經(jīng)加載的模塊,作為緩存表
var installedModules;
// 加載模塊的函數(shù)
function webpack_require(moduleId);

當(dāng)然以上一切代碼,在整個編譯后的bundle文件中,都被包在一個大的立即執(zhí)行的匿名函數(shù)中,最后返回的就是這么一句話:

return webpack_require(‘./app.js');

即加載入口模塊app.js,后面所有的依賴都會動態(tài)地、遞歸地在runtime加載。當(dāng)然Webpack真正生成的代碼略有不同,它在結(jié)構(gòu)上大致是這樣:

(function(modules) {
 var installedModules = {};
 
 function webpack_require(moduleId) {
   // ...
 }

 return webpack_require('./app.js');
}) ({
 './mA.js': generated_mA,
 './app.js': generated_app
});

可以看到它是直接把modules作為立即執(zhí)行函數(shù)的參數(shù)傳進(jìn)去的而不是另外定義的,當(dāng)然這和上面的寫法沒什么本質(zhì)不同,我做這樣的改寫是為了解釋起來更清楚。

ES6的import和export

以上的例子里都是用傳統(tǒng)的CommonJS的寫法,現(xiàn)在更通用的ES6風(fēng)格是用import和export關(guān)鍵詞,在使用上也略有一些不同。不過對于Webpack或者其它模塊管理系統(tǒng)而言,這些新特性應(yīng)該只被視為語法糖,它們本質(zhì)上還是和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'

情況會稍微復(fù)雜一點,它需要載入模塊m的default export,而模塊m可能并非是由ES6的export來寫的,也可能根本沒有export default,所以Webpack在為模塊生成generated code的時候,會判斷它是不是ES6風(fēng)格的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標(biāo)注了一個__esModule,說明它是ES6風(fēng)格的export。這樣在其它模塊中,當(dāng)一個依賴模塊以類似import m from './m.js'這樣的方式加載時,會首先判斷得到的是不是一個ES6 export出來的模塊。如果是,則返回它的default,如果不是,則返回整個export對象。例如上面的mA是傳統(tǒng)CommonJS的,mB是ES6風(fēng)格的:

// 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函數(shù):

function get_export_default(module) {
 return module && module.__esModule? module['default'] : module;
}

這樣generated code運(yùn)行后在mA和mB上會得到不同的結(jié)果:

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在本質(zhì)上和CommonJS沒有區(qū)別,而且Webpack最后生成的generated code也還是基于CommonJS的module/exports這一套機(jī)制來實現(xiàn)模塊的加載的。

模塊管理系統(tǒng)

以上就是Webpack如何打包組織模塊,實現(xiàn)runtime模塊加載的解讀,其實它的原理并不難,核心的思想就是建立模塊的管理系統(tǒng),而這樣的做法也是具有普遍性的,如果你讀過Node.js的Module部分的源代碼,就會發(fā)現(xiàn)其實用的是類似的方法。這里有一篇文章可以參考。

以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • layuiAdmin循環(huán)遍歷展示商品圖片列表的方法

    layuiAdmin循環(huán)遍歷展示商品圖片列表的方法

    今天小編就為大家分享一篇layuiAdmin循環(huán)遍歷展示商品圖片列表的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2019-09-09
  • 分享10個原生JavaScript技巧

    分享10個原生JavaScript技巧

    本文給大家分享的是個人總結(jié)的10條非常常用的原生javascript的小技巧,都是平時項目中用到的,這里總結(jié)記錄下來,有需要的小伙伴可以參考下。
    2015-04-04
  • uniapp 對于scroll-view滑動和頁面滑動的聯(lián)動處理方法

    uniapp 對于scroll-view滑動和頁面滑動的聯(lián)動處理方法

    這篇文章主要介紹了uniapp 對于scroll-view滑動和頁面滑動的聯(lián)動處理方法,本文通過示例代碼給大家介紹的非常詳細(xì),感興趣的朋友一起看看吧
    2024-08-08
  • JS運(yùn)動框架之分享側(cè)邊欄動畫實例

    JS運(yùn)動框架之分享側(cè)邊欄動畫實例

    這篇文章主要介紹了JS運(yùn)動框架之分享側(cè)邊欄動畫,實例分析了javascript操作div及css的技巧,需要的朋友可以參考下
    2015-03-03
  • Bootstrap選項卡動態(tài)切換效果

    Bootstrap選項卡動態(tài)切換效果

    這篇文章主要為大家詳細(xì)介紹了Bootstrap選項卡動態(tài)切換效果,點擊登錄和注冊可以實現(xiàn)任意切換,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2016-11-11
  • 移動設(shè)備手勢事件庫Touch.js使用詳解

    移動設(shè)備手勢事件庫Touch.js使用詳解

    這篇文章主要介紹了移動設(shè)備手勢事件庫Touch.js的使用方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-08-08
  • 使用layui的layer組件做彈出層的例子

    使用layui的layer組件做彈出層的例子

    今天小編就為大家分享一篇使用layui的layer組件做彈出層的例子,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2019-09-09
  • js如何引入wasm文件

    js如何引入wasm文件

    這篇文章主要介紹了js如何引入wasm文件問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-04-04
  • 一文搞懂JavaScript中的this綁定規(guī)則

    一文搞懂JavaScript中的this綁定規(guī)則

    這篇文章主要為大家詳細(xì)介紹了原生JS之this綁定規(guī)則,這樣大家再也不用擔(dān)心this指向問題了。文中的示例代碼講解詳細(xì),需要的可以參考一下
    2022-09-09
  • JS優(yōu)化冗余代碼的技巧分享

    JS優(yōu)化冗余代碼的技巧分享

    這篇文章主要為大家整理了18個JavaScript優(yōu)化冗余代碼的技巧,文中的示例代碼簡潔易懂,具有一定的借鑒價值,感興趣的小伙伴可以了解一下
    2023-08-08

最新評論