JavaScript模塊化原理淺析

01

寫在前面

模塊化簡單來說就是是指把一個復雜的系統(tǒng)分解到多個模塊以方便編碼。JS模塊化的大致流程為:CommonJS(服務(wù)端) -> AMD(瀏覽器端)-> UMD(兼容了CommonJS和AMD) -> ES Module(ES6標準)。本文將從它們的用法進行介紹,簡單實現(xiàn)其原理。并簡易實現(xiàn)一個模塊化打包工具webpack。

本文將從以下幾部分進行總結(jié):

CommonJS的用法及原理

AMD的用法及原理

ES2015標準化

自動化構(gòu)建

簡易實現(xiàn)webpack

02

CommonJS

CommonJS 是一種使用廣泛的JavaScript模塊化規(guī)范,核心思想是通過require方法來同步地加載依賴的其他模塊,通過 module.exports 導出需要暴露的接口。

CommonJS的用法

采用CommonJS來進行導入導出的代碼用法如下:

//?用require方法導入

constsomeFun=require('./moduleA');

someFun();

//?用module.exports導出

module.exports?=?someFunc;

CommonJS原理

a.js:

console.log('aaa');

exports.name?='這是a模塊的內(nèi)容';

b.js:

letfs?=require('fs');

letpath?=require('path');

letb?=?req('./a.js');

//?req即使CommonJS中的require方法

functionreq(mod){

letfilename?=?path.join(__dirname,?mod);

letcontent?=?fs.readFileSync(filename,'utf8');

/**

*?最后一個參數(shù)是函數(shù)的內(nèi)容體,相當于以下函數(shù)

*function?fn(exports,?module,?require,?__dirname,?__filename)?{

*??module.exports?=?'這是另外一個文件導出的內(nèi)容'

*??return?module.exports

*}

*/

letfn?=newFunction('exports','require','module','__filename','__dirname',?content?+'\n?return?module.exports;');

letmodule=?{

exports:?{}

};

returnfn(module.exports,?req,module,?__filename,?__dirname);

}

03

AMD

AMD 也是一種 JavaScript 模塊化規(guī)范,與 CommonJS 最大的不同在于它采用異步的方式去加載依賴的模塊。 AMD 規(guī)范主要是為了解決針對瀏覽器環(huán)境的模塊化問題,最具代表性的實現(xiàn)是 requirejs。

順便給大家推薦一個裙,它的前面是 537,中間是631,最后就是 707。想要學習前端的小伙伴可以加入我們一起學習,互相幫助。群里每天晚上都有大神免費直播上課,如果不是想學習的小伙伴就不要加啦。

AMD 的優(yōu)點:

可在不轉(zhuǎn)換代碼的情況下直接在瀏覽器中運行

可加載多個依賴

代碼可運行在瀏覽器環(huán)境和 Node.js 環(huán)境下

AMD 的缺點:

JavaScript 運行環(huán)境沒有原生支持 AMD,需要先導入實現(xiàn)了 AMD 的庫后才能正常使用。

//?使用define方法定義一個模塊

define('a',?[],function(){

return'a';

});

define('b',?['a'],function(a){

returna?+'b';

});

//?使用require來導入和使用

require(['b'],function(b){

console.log(b);

});

AMD的原理

letfactories?=?{};

/**

*?實現(xiàn)AMD的define方法

*?@param?moduleName?模塊的名字

*?@param?dependencies?依賴

*?@param?factory?工廠函數(shù)

*/

functiondefine(modName,?dependencies,?factory){

factory.dependencies?=?dependencies;

factories[modName]?=?factory;

}

/**

*?實現(xiàn)AMD的require方法

*?@param?mods?引入的模塊

*?@param?callback?回調(diào)函數(shù)

*/

functionrequire(modNames,?callback){

letloadedModNames?=?modNames.map(function(modName){

letfactory?=?factories[modName];

letdependencies?=?factory.dependencies;

letexports;

require(dependencies,function(...dependencyMods){

exports?=?factory.apply(null,?dependencyMods);

});

returnexports;

})

callback.apply(null,?loadedModNames);

}

04

ES2015模塊化

ES2015 模塊化是ECMA提出的JavaScript模塊化規(guī)范,它在語言的層面上實現(xiàn)了模塊化。瀏覽器廠商和Node.js 都宣布要原生支持該規(guī)范。它將逐漸取代CommonJS和AMD規(guī)范,成為瀏覽器和服務(wù)器通用的模塊解決方案。 采用 ES2015 模塊化導入及導出時的代碼如下:

//?使用import導入

import{?name?}from'./person.js';

//?使用export導出

exportconstname?='musion';

ES2015模塊雖然是終極模塊化方案,但它的缺點在于目前無法直接運行在大部分 JavaScript 運行環(huán)境下,必須通過構(gòu)建工具轉(zhuǎn)換成標準的 ES5 后才能正常運行。

06

自動化構(gòu)建

自動化構(gòu)建就是做這件事情,把源代碼轉(zhuǎn)換成發(fā)布到線上的可執(zhí)行 JavaScrip、CSS、HTML 代碼,包括如下內(nèi)容:

順便給大家推薦一個裙,它的前面是 537,中間是631,最后就是 707。想要學習前端的小伙伴可以加入我們一起學習,互相幫助。群里每天晚上都有大神免費直播上課,如果不是想學習的小伙伴就不要加啦。

代碼轉(zhuǎn)換:ECMASCRIPT6 編譯成 ECMASCRIPT5、LESS 編譯成 CSS 等。

文件優(yōu)化:壓縮 JavaScript、CSS、HTML 代碼,壓縮合并圖片等。

代碼分割:提取多個頁面的公共代碼、提取首屏不需要執(zhí)行部分的代碼讓其異步加載。

模塊合并:在采用模塊化的項目里會有很多個模塊和文件,需要構(gòu)建功能把模塊分類合并成一個文件。

自動刷新:監(jiān)聽本地源代碼的變化,自動重新構(gòu)建、刷新瀏覽器。

代碼校驗:在代碼被提交到倉庫前需要校驗代碼是否符合規(guī)范,以及單元測試是否通過。

自動發(fā)布:更新完代碼后,自動構(gòu)建出線上發(fā)布代碼并傳輸給發(fā)布系統(tǒng)。

07

webpack

webpack 是一個打包模塊化 JavaScript 的工具,在 webpack 里一切文件皆模塊,通過 Loader 轉(zhuǎn)換文件,通過 Plugin 注入鉤子,最后輸出由多個模塊組合成的文件。Webpack 專注于構(gòu)建模塊化項目。

一切文件: JavaScript、CSS、SCSS、圖片、模板,在 Webpack 眼中都是一個個模塊,這樣的好處是能清晰的描述出各個模塊之間的依賴關(guān)系,以方便 Webpack 對模塊進行組合和打包。 經(jīng)過 Webpack 的處理,最終會輸出瀏覽器能使用的靜態(tài)資源。

webpack簡單揭秘

用webpack來進行打包構(gòu)建的時候,查看打包??之后的文件,刪掉多余的代碼,剩下的核心代碼如下:

(function(modules){

functionrequire(moduleId){

varmodule=?{

exports:?{}

};

//?moduleId?->?模塊的名字

modules[moduleId].call(module.exports,module,module.exports,require);

returnmodule.exports;

}

returnrequire("./index.js");

})?({

"./index.js":

(function(module,?exports,?require){

eval("console.log('hello');\n\n");

})

});

簡易實現(xiàn)一個webpack

#!?/usr/bin/env?node

//?這個文件用來描述如何打包

constpathLib?=require('path');

constfs?=require('fs');

letejs?=require('ejs');

letcwd?=?process.cwd();

let{?entry,output:?{?filename,?path?}?}?=require(pathLib.join(cwd,'./webpack.config.js'));

letscript?=?fs.readFileSync(entry,'utf8');

letbundle?=`

(function?(modules)?{

function?require(moduleId)?{

var?module?=?{

exports:?{}

};

modules[moduleId].call(module.exports,?module,?module.exports,?require);

return?module.exports;

}

return?require("<%-entry%>");

})

({

"<%-entry%>":

(function?(module,?exports,?require)?{

eval("<%-script%>");

})

});

`

letbundlejs?=?ejs.render(bundle,?{

entry,

script

});

try{

fs.writeFileSync(pathLib.join(path,?filename),?bundlejs);

}catch(e)?{

console.error('編譯失敗?',?e);

}

console.log('compile?sucessfully!');

依賴其它模塊的情況

#!?/usr/bin/env?node

//?這個文件用來描述如何打包

constpathLib?=require('path');

constfs?=require('fs');

letejs?=require('ejs');

letcwd?=?process.cwd();

let{?entry,output:?{?filename,?path?}?}?=require(pathLib.join(cwd,'./webpack.config.js'));

letscript?=?fs.readFileSync(entry,'utf8');

letmodules?=?[];

script.replace(/require\(['"](.+?)['"]\)/g,function(){

letname?=arguments[1];

letscript?=?fs.readFileSync(name,'utf8');

modules.push({

name,

script

});

});

letbundle?=`

(function?(modules)?{

function?require(moduleId)?{

var?module?=?{

exports:?{}

};

modules[moduleId].call(module.exports,?module,?module.exports,?require);

return?module.exports;

}

return?require("<%-entry%>");

})

({

"<%-entry%>":

(function?(module,?exports,?require)?{

eval(\`<%-script%>\`);

})

<%if(modules.length>0){%>,<%}%>

<%for(let?i=0;i

let?module?=?modules[i];%>

"<%-module.name%>":

(function?(module,?exports,?require)?{

eval(\`<%-module.script%>\`);

})

<%?}%>

});

`

letbundlejs?=?ejs.render(bundle,?{

entry,

script,

modules

});

try{

fs.writeFileSync(pathLib.join(path,?filename),?bundlejs);

}catch(e)?{

console.error('編譯失敗?',?e);

}

console.log('compile?sucessfully!');

08

補充閱讀

JavaScript 模塊化七日談:

https://huangxuan.me/2015/07/09/js-module-7day/

JavaScript模塊化編程簡史(2009-2016):

https://yuguo.us/weblog/javascript-module-development-history/

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容