国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

webpack組織模塊的原理 - 基礎(chǔ)篇

leiyi / 2359人閱讀

摘要:每一個(gè)模塊的源代碼都會(huì)被組織在一個(gè)立即執(zhí)行的函數(shù)里。接下來(lái)看的生成代碼可以看到,的源代碼中關(guān)于引入的模塊的部分做了修改,因?yàn)闊o(wú)論是,或是風(fēng)格的,都無(wú)法被解釋器直接執(zhí)行,它需要依賴模塊管理系統(tǒng),把這些抽象的關(guān)鍵詞具體化。

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

編譯模塊

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

在這里用一個(gè)簡(jiǎn)單的案例來(lái)說(shuō)明Webpack打包模塊的原理。例如我們有一個(gè)模塊mA.js

var aa = 1;

function inc() {
  aa++;
}

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

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

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

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

console.log("mA.aa =" + mA.aa);
mA.inc();

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

從入口模塊app.js開始,分析所有模塊的依賴關(guān)系,把所有用到的模塊都讀取進(jìn)來(lái)。

每一個(gè)模塊的源代碼都會(huì)被組織在一個(gè)立即執(zhí)行的函數(shù)里。

改寫模塊代碼中和requireexport相關(guān)的語(yǔ)法,以及它們對(duì)應(yīng)的引用變量。

在最后生成的bundle文件里建立一套模塊管理系統(tǒng),能夠在runtime動(dòng)態(tài)加載用到的模塊。

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

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

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

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

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

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

function generated_mA(module, exports, webpack_require) {
  var aa = 1;
  
  function inc() {
    aa++;
  }

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

乍一看似乎和源代碼一模一樣。的確,mA沒有require或者import其它模塊,export用的也是傳統(tǒng)的CommonJS風(fēng)格,所以生成代碼沒有任何改動(dòng)。不過(guò)值得注意的是最后的module.exports = ...,這里的module就是外面?zhèn)鬟M(jìn)來(lái)的參數(shù)module,這實(shí)際上是在告訴我們,運(yùn)行這個(gè)函數(shù),模塊mA的源代碼就會(huì)被執(zhí)行,并且最后需要export的內(nèi)容就會(huì)被保存到外部,到這里就標(biāo)志著mA加載完成,而那個(gè)外部的東西實(shí)際上就后面要說(shuō)的模塊管理系統(tǒng)。

接下來(lái)看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["inc"]();
}

可以看到,app.js的源代碼中關(guān)于引入的模塊mA的部分做了修改,因?yàn)闊o(wú)論是require/exports,或是ES6風(fēng)格的import/export,都無(wú)法被JavaScript解釋器直接執(zhí)行,它需要依賴模塊管理系統(tǒng),把這些抽象的關(guān)鍵詞具體化。也就是說(shuō),webpack_require就是require的具體實(shí)現(xiàn),它能夠動(dòng)態(tài)地載入模塊mA,并且將結(jié)果返回給app

到這里你腦海里可能已經(jīng)初步逐漸構(gòu)建出了一個(gè)模塊管理系統(tǒng)的想法,一切的關(guān)鍵就是webpack_require,我們來(lái)看一下它的實(shí)現(xiàn):

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

function webpack_require(moduleId) {
  // 如果模塊已經(jīng)加載過(guò)了,直接從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就是我們之前定義過(guò)的所有模塊的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)行這個(gè)模塊的generated code,這個(gè)函數(shù)傳入的正是我們建立的module對(duì)象,以及它的exports域,這實(shí)際上就是CommonJS里exportsmodule的由來(lái)。當(dāng)運(yùn)行完這個(gè)函數(shù),模塊就被加載完成了,需要export的結(jié)果保存到了module對(duì)象中。

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

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

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

return webpack_require("./app.js");

即加載入口模塊app.js,當(dāng)運(yùn)行它時(shí),就會(huì)運(yùn)行generated_app;而它需要載入模塊mA,于是就會(huì)運(yùn)行webpack_require("./mA.js"),進(jìn)而運(yùn)行generated_mA。也就是說(shuō),所有的依賴的模塊就是這樣動(dòng)態(tài)地、遞歸地在runtime完成加載,并被放入InstalledModules緩存。

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ì)不同,我做這樣的改寫是為了解釋起來(lái)更清楚。

ES6的importexport

以上的例子里都是用傳統(tǒng)的CommonJS的寫法,現(xiàn)在更通用的ES6風(fēng)格是用importexport關(guān)鍵詞,它們看上去似乎只是語(yǔ)法糖,但實(shí)際上根據(jù)ES6的標(biāo)準(zhǔn),它們和CommonJS在關(guān)于模塊加載的使用和行為上會(huì)有一些微妙的不同。例如當(dāng)CommonJS輸出原始類型(非對(duì)象)變量時(shí),輸出的是這個(gè)變量的拷貝,這樣一旦模塊加載后,再去修改這個(gè)內(nèi)部變量的值,是不會(huì)影響到輸出的變量的;而ES6輸出的則是引用,這樣無(wú)論模塊內(nèi)部出現(xiàn)什么修改,都會(huì)反映在已經(jīng)加載的模塊上。關(guān)于ES6和CommonJS在模塊管理上的區(qū)別,如果你還不熟悉的話,建議先讀一下阮一峰大神的這篇文章。

對(duì)于Webpack或者其它模塊管理系統(tǒng)而言,要實(shí)現(xiàn)ES6特性的import/export,本質(zhì)上還是和require/exports類似的,也就是說(shuō)仍然使用module.export這一套機(jī)制,但是情況會(huì)變得比較復(fù)雜,因?yàn)榭赡艽嬖贑ommonJS和ES6模塊之間的相互引用。為了保持兼容,并且符合ES6的相應(yīng)標(biāo)準(zhǔn),Webpack在生成相應(yīng)語(yǔ)句的generated code時(shí),就要做很多特殊處理,關(guān)于這一塊內(nèi)容很多,深究起來(lái)可以多帶帶寫一篇文章,在這里我只是把我理解的部分寫出來(lái)。

export原始類型變量

對(duì)于CommonJS而言,export的是很直接的,因?yàn)樵创a里module.exports輸出什么,生成代碼里的輸出也原樣不變,例如我們之前定義的模塊mA.js

var aa = 1;
function inc() {
  aa++;
}

function get_aa() {
  return aa;
}

module.exports = {
  aa: aa,
  inc: inc,
  get_aa: get_aa;
}

生成代碼里,module.exports也會(huì)像源代碼里這樣寫,注意這里輸出的時(shí)候,aa作為一個(gè)原始類型,輸出到module.exports里的是一個(gè)拷貝,這樣一旦模塊mA加載后,再去調(diào)用inc(),修改的是模塊內(nèi)部的aa,而不會(huì)影響輸出后的aa:

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

console.log("mA.aa = " + mA.aa);  // 輸出1
mA.inc();
console.log("mA.aa = " + mA.aa);  // 仍然是1

// 這里會(huì)輸出2,因?yàn)間et_aa()拿到的是模塊內(nèi)部的aa原始引用。
console.log("mA.get_aa() = " + mA.get_aa());

然而ES6就完全不是這么一回事兒了,假如上面的模塊mA,我們用ES6輸出:

export {aa, inc, get_aa}

然后在別的模塊里加載mA

import {aa, inc} from "./mA.js"

console.log("aa = " + aa);  // 輸出1
inc();
console.log("aa = " + aa);  // 輸出2

這里不管mA輸出的是什么類型的數(shù)據(jù),輸出的都是它的引用,當(dāng)別的模塊載入mA時(shí),得到的也是mA模塊內(nèi)部變量的引用。要實(shí)現(xiàn)這個(gè)規(guī)則,mAgenerated code就不能簡(jiǎn)單地直接給module.exports設(shè)置aa這個(gè)原始變量類型了,而是像上面的get_aa那樣,給它定義getter。例如當(dāng)我們export aa,Webpack會(huì)生成類似于這樣的代碼:

var aa = 1;

defineGetter(module.exports, “aa”, function(){ return aa; });

defineGetter的定義如下:

function defineGetter(exports, name, getter) {
  if (!Object.prototype.hasOwnProperty.call(exports, name)) {
    Object.defineProperty(exports, name, {
      configurable: false,
      enumerable: true,
      get: getter,
    });
  }
}

這樣就實(shí)現(xiàn)了我們需要的引用功能,也就是說(shuō),在module.exports上,我們并不是定義aa這個(gè)原始類型,而是定義aa的getter,使之指向其原模塊內(nèi)部aa的引用。

不過(guò)對(duì)于export default,當(dāng)輸出原始類型時(shí),它又回到了拷貝,而不是getter引用的方式,即對(duì)于這樣的輸出:

export default aa;

Webpack會(huì)生成這樣的代碼:

module.exports["default"] = aa;

我還沒完全弄清楚這樣做是否符合ES6標(biāo)準(zhǔn),懂的童鞋可以留下評(píng)論。

當(dāng)然話說(shuō)回來(lái),模塊中直接輸出aa這樣的原始類型的變量還是挺少見的,但并非不可能。源代碼一旦有這樣的行為,ES6和CommonJS就會(huì)表現(xiàn)出完全不同的特性,所以Webpack也必須實(shí)現(xiàn)這種區(qū)別。

__esModule

Webpack對(duì)ES6模塊輸出的另一個(gè)特殊處理是__esModule,例如是我們定義ES6模塊mB.js:

let x = 3;

let printX = () => {
  console.log("x = " + x);
}

export {printX}
export default x

它使用了ES6的export,那么Webpack在mB的generated code會(huì)加上一句話:

function generated_mB(module, exports, webpack_require) {
  Object.defineProperty(module.exports, "__esModule", {value: true});
  // mB的具體代碼
  // ....
}

也就是說(shuō),它給mB的export標(biāo)注了一個(gè)__esModule,說(shuō)明它是ES6風(fēng)格的export。為什么要這樣做?因?yàn)楫?dāng)別人引用一個(gè)模塊時(shí),它并不知道這個(gè)模塊是以CommonJS還是ES6風(fēng)格輸出的,所以__esModule的意義就在于告訴別人,這是一個(gè)ES6模塊。關(guān)于它的具體作用我們繼續(xù)看下面的import部分。

import

這是一種比較簡(jiǎn)單的import方式:

import {aa} from "./mA.js"
// 基本等價(jià)于
var aa = require("./mA.js")["aa"]

但是當(dāng)別人這樣引用時(shí):

import m from "./m.js"

情況會(huì)稍微復(fù)雜一點(diǎn),它需要載入模塊mexport default部分,而模塊m可能并非是由ES6的export來(lái)寫的,也可能根本沒有export default。這樣在其它模塊中,當(dāng)一個(gè)依賴模塊以類似import m from "./m.js"這樣的方式加載時(shí),必須首先判斷得到的是不是一個(gè)ES6 export出來(lái)的模塊。如果是,則返回它的default,如果不是,則返回整個(gè)export對(duì)象。例如上面的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);

這就用到了之前export部分的__esModule了。我們定義get_export_default函數(shù):

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

這樣generated code運(yùn)行后在mAmB上會(huì)得到不同的結(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/export上,Webpack需要做很多特殊處理的地方。我的分析還并不完整,建議感興趣的童鞋自己去敲一下并且閱讀編譯后的代碼,仔細(xì)比較CommonJS和ES6的不同。不過(guò)就實(shí)現(xiàn)而言,它們并沒有本質(zhì)區(qū)別,而且Webpack最后生成的generated code也還是基于CommonJS的module/exports這一套機(jī)制來(lái)實(shí)現(xiàn)模塊的加載的。

模塊管理系統(tǒng)

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

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/93258.html

相關(guān)文章

  • Angular開山

    摘要:環(huán)境搭建今天給大家介紹種環(huán)境搭建的方法。官方的地址步驟安裝種子文件沒有的,可以自己下來(lái),然后打開,執(zhí)行。使用版本為版本。存放表單相關(guān)內(nèi)置組件與指令。存放網(wǎng)絡(luò)請(qǐng)求相關(guān)的服務(wù)等。等待加載完畢即可。從而實(shí)現(xiàn)了頁(yè)面的顯示 1:環(huán)境搭建 今天給大家介紹4種環(huán)境搭建的方法。 一:Angular-cli的安裝 官方指導(dǎo)文檔:www.angular.cn/guide/quickstart 請(qǐng)使用cn...

    Edison 評(píng)論0 收藏0
  • webpack組織模塊原理 - external模塊

    摘要:所以通常情況下當(dāng)你的庫(kù)需要依賴到例如,這樣的通用模塊時(shí),我們可以不將它打包進(jìn),而是在的配置中聲明這就是在告訴請(qǐng)不要將這個(gè)模塊注入編譯后的文件里,對(duì)于我源代碼里出現(xiàn)的任何這個(gè)模塊的語(yǔ)句,請(qǐng)將它保留。 這篇文章討論Webpack打包library時(shí)經(jīng)常需要用到的一個(gè)選項(xiàng)external,它用于避免將一些很通用的模塊打包進(jìn)你發(fā)布的library里,而是選擇把它們聲明成external的模塊,...

    Lavender 評(píng)論0 收藏0
  • Webpack系列-第一基礎(chǔ)雜記

    摘要:系列文章系列第一篇基礎(chǔ)雜記系列第二篇插件機(jī)制雜記系列第三篇流程雜記前言公司的前端項(xiàng)目基本都是用來(lái)做工程化的,而雖然只是一個(gè)工具,但內(nèi)部涉及到非常多的知識(shí),之前一直靠來(lái)解決問題,之知其然不知其所以然,希望這次能整理一下相關(guān)的知識(shí)點(diǎn)。 系列文章 Webpack系列-第一篇基礎(chǔ)雜記 Webpack系列-第二篇插件機(jī)制雜記 Webpack系列-第三篇流程雜記 前言 公司的前端項(xiàng)目基本都是用...

    Batkid 評(píng)論0 收藏0
  • 前端每周清單半年盤點(diǎn)之 JavaScript

    摘要:前端每周清單專注前端領(lǐng)域內(nèi)容,以對(duì)外文資料的搜集為主,幫助開發(fā)者了解一周前端熱點(diǎn)分為新聞熱點(diǎn)開發(fā)教程工程實(shí)踐深度閱讀開源項(xiàng)目巔峰人生等欄目。背后的故事本文是對(duì)于年之間世界發(fā)生的大事件的詳細(xì)介紹,闡述了從提出到角力到流產(chǎn)的前世今生。 前端每周清單專注前端領(lǐng)域內(nèi)容,以對(duì)外文資料的搜集為主,幫助開發(fā)者了解一周前端熱點(diǎn);分為新聞熱點(diǎn)、開發(fā)教程、工程實(shí)踐、深度閱讀、開源項(xiàng)目、巔峰人生等欄目。歡迎...

    Vixb 評(píng)論0 收藏0
  • webpack組織模塊原理 - 打包Library

    摘要:所以你編譯后的文件實(shí)際上應(yīng)當(dāng)只輸出,這就需要在配置里用來(lái)控制這樣上面的模塊加載函數(shù)會(huì)在返回值后面加一個(gè),這樣就只返回的部分。 之前一篇文章分析了Webpack打包JS模塊的基本原理,所介紹的案例是最常見的一種情況,即多個(gè)JS模塊和一個(gè)入口模塊,打包成一個(gè)bundle文件,可以直接被瀏覽器或者其它JavaScript引擎執(zhí)行,相當(dāng)于直接編譯生成一個(gè)完整的可執(zhí)行的文件。不過(guò)還有一種很常見的...

    legendmohe 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

leiyi

|高級(jí)講師

TA的文章

閱讀更多
最新活動(dòng)
閱讀需要支付1元查看
<