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/