摘要:要求模塊編寫必須在真正的代碼之外套上一層規(guī)定的代碼包裝,樣子看起來是這樣的模塊代碼通過傳遞一個(gè)簽名為的回調(diào)函數(shù)給函數(shù),就可以把需要注入的變量和函數(shù)注入到模塊代碼內(nèi)。
之前寫的文章急速Js全棧教程得到了不錯(cuò)的閱讀量,霸屏掘金頭條3天,點(diǎn)贊過千,閱讀近萬,甚至還有人在評(píng)論區(qū)打廣告,可見也是一個(gè)小小的生態(tài)了;)??磥砗蚃S全棧有關(guān)的內(nèi)容,還是有人頗有興趣的。
這次文章的內(nèi)容,是JavaScript模塊。JavaScript Module 真是很討厭,但是不得不了解的話題。奇葩在于:
它一個(gè)非常老的語言,并且使用非常廣泛
可是它很多年來也不支持模塊。這得廠家當(dāng)前是多大的心呢
再一個(gè)可是,它可以直接用現(xiàn)有的語言機(jī)制,實(shí)現(xiàn)自己的模塊,這個(gè)就厲害了,因?yàn)樗尫帕松鐓^(qū)的力量。事實(shí)證明,社區(qū)果然不可小看,這個(gè)年代,螞蟻雄兵勝過大象的
再再一個(gè)但是,它的模塊還可以有很多型的,這說的是分裂
這么多型的模塊,還搞了各自獨(dú)立的標(biāo)準(zhǔn)出來,這說的是整合
最近的ES2017,終于在前端也有了媲美后端的模塊,但是大家并不準(zhǔn)備把它用起來,很多人表示需要繼續(xù)Webpack玩轉(zhuǎn)ES6模塊。
把ES6模塊真用的起來,可以不在乎Webpack等打包工具帶來的加載優(yōu)化,各種小文件不必打包這點(diǎn)來說,我看還得加上HTTP/2的配合就好很多了。這也是文章將要介紹的一個(gè)主旨吧。ES6模塊的引入,確實(shí)有可能對(duì)當(dāng)前主流的打包模式有些影響,參考文章6內(nèi)有所論述
文章自然也不少,但是寫作此文的理由還是存在:
我還沒有看到一個(gè)完整的全覽,并且結(jié)合HTTP/2的更加沒有看到。
而且,在我看來,即使有了ES6模塊,也得了解和學(xué)習(xí)之前拼出來的各種模塊,因?yàn)樯鐓^(qū)內(nèi)的代碼還大量的使用這樣的模塊,其中的一些設(shè)計(jì)模式,比如IIFE,也是值得一看的。
看到JS社區(qū)的熱情和推動(dòng)力,相信JS發(fā)展的未來是美好的
目錄最古老的模塊加載標(biāo)簽
此方法的若干問題
全局變量。全局命名污染和命名沖突
依賴管理。都需要HTML管理,而不是分層管理依賴,多文件加載次序非常關(guān)鍵
效率。太多HTTP請(qǐng)求,和并行加載效率低下
有問題引發(fā)的解決方法
命令空間,匿名閉包、依賴引入
當(dāng)前主流的模塊技術(shù)
Nodejs的做法,Commonjs方案
Nodejs借鑒
Require.js實(shí)踐,AMD和CMD,依賴就近原則
從手寫模塊,到自動(dòng)編譯,Browerify,Webpack,Rollup
剛剛落地的模塊技術(shù)
ES6模塊,官方發(fā)力,對(duì)現(xiàn)有技術(shù)的影響
彌補(bǔ)ES6問題,HTTP/2
最佳實(shí)踐
從腳本加載開始一切從Javascript的加載開始,自有Javascript依賴,第一個(gè)加載模塊的方案就是使用HTML標(biāo)簽,也就是標(biāo)簽。也就是說,Javascript本身根本就沒有模塊和加載的定義,它是利用HTML來完成本該自己做的工作。
這是初學(xué)者遇到的第一個(gè)令人困惑的問題。這樣的語言,根本就是一個(gè)玩具!也許有些尷尬,但是現(xiàn)實(shí)就是如此。并且如此的加載方案,在稍微大點(diǎn)的工程中,會(huì)遇到幾個(gè)違反常識(shí)的問題:
全局命名污染。就是說每一個(gè)被加載的模塊都會(huì)引入新的全局變量,他們會(huì)污染全局空間,而且必須小心命名,避免名字的沖突
依賴關(guān)系單一。此種加載方式,必須按照依賴關(guān)系排序,依賴性最大的模塊一定要放到最后加載,當(dāng)依賴關(guān)系很復(fù)雜的時(shí)候,代碼的編寫和維護(hù)都會(huì)變得困難。
加載和執(zhí)行效率難以細(xì)顆粒度的調(diào)優(yōu)。一個(gè)個(gè)的按依賴次序加載和執(zhí)行。雖然加載往往是可以并行的,但是執(zhí)行時(shí)串行的。加載的時(shí)候,瀏覽器會(huì)停止網(wǎng)頁渲染,加載文件越多,網(wǎng)頁失去響應(yīng)的時(shí)間就會(huì)越長(zhǎng)
還是從一個(gè)案例開始。這個(gè)案例,不會(huì)做任何有意義的工作,也不會(huì)做什么功能演示,而只是驗(yàn)證古典的Javascript加載的能力和限定,驗(yàn)證這些問題的存在,進(jìn)而找到解決問題的方法。
假設(shè)我們現(xiàn)在有一個(gè)主程序,它在index.html內(nèi),一個(gè)模塊dep1,一個(gè)模塊dep2,依賴關(guān)系是index.html依賴dep1,dep1依賴dep2。代碼都不復(fù)雜,就是直接列表如下:
文件index.html
// index.html
文件dep1.js
var v1 = "dep1" function dep1(){ return v1+"-"+dep2() }
文件dep2.js
var v2 = "dep2" function dep2(){ return v2 }
當(dāng)使用瀏覽器加載index.html文件時(shí),如我所愿,它會(huì)隨后加載dep2,dep1,并調(diào)用函數(shù)dep1,后者調(diào)用dep2,然后在控制臺(tái)輸出:
dep1+dep2
功能是有效的,依賴關(guān)系是是對(duì)的,輸出也是如期望的。但是它也帶來了額外的問題:
全局變量污染。在本案例中,可以在console內(nèi)驗(yàn)證,發(fā)現(xiàn)變量v1,v2,函數(shù)dep1,dep2都是全局變量。但是由于script的加載機(jī)制,以及當(dāng)前采用的Javascript函數(shù)和變量的定義不是局部化的,導(dǎo)致了這樣的問題。
依賴關(guān)系并不嚴(yán)密。事實(shí)上,dep2內(nèi)的引入變量和函數(shù),只有dep1看得到即可,無需導(dǎo)入到全局變量?jī)?nèi)。
加載和執(zhí)行效率難以細(xì)顆粒度的調(diào)優(yōu)。本例內(nèi),dep1依賴dep2,它們被并行轉(zhuǎn)入,但是執(zhí)行必須是串行的,首先執(zhí)行dep2,然后執(zhí)行dep1,在此案例中,這樣做是合適的,但是有不少代碼模塊之間并不存在依賴關(guān)系,它們完全可能并發(fā)裝入并發(fā)執(zhí)行,但是使用script裝入是不能如此的,它會(huì)按照標(biāo)簽的次序一個(gè)個(gè)的執(zhí)行。如果有比較好的指定依賴關(guān)系的方法就好了。
討論到此,我感覺我在重復(fù)先輩們的話,實(shí)際上1960年代,第一屆軟件工程會(huì)議,就提出了模塊化編程的概念,并且在之后多年一直努力的批評(píng)全局變量和Goto語句了。有時(shí)候,你會(huì)發(fā)現(xiàn),這樣看起來非常不濟(jì)的語言,卻可以在現(xiàn)實(shí)的項(xiàng)目中如魚得水,發(fā)展的非常的好。而軟件工程思想指導(dǎo)下的一些名流語言卻早早夭折。這是另外一個(gè)有趣的話題了,或許以后有機(jī)會(huì)談到。
后端的借鑒后端Nodejs干凈利索的解決了此問題。做法就是對(duì)每一個(gè)裝入的模塊都注入一個(gè)require函數(shù)和一個(gè)exports對(duì)象。其中require函數(shù)可以被模塊用來引入其他模塊,而exports對(duì)象則被用來引出當(dāng)前模塊的功能接口。還是以前文提到的作為案例,做法就是:
文件index.js
// index.js var d = require("./dep1") console.log(d.dep1())
文件dep1.js
var d = require("./dep2") var v1 = "dep1" function dep1(){ return v1+"-"+d.dep2() } exports.dep1 = dep1
文件dep2.js
var v2 = "dep2" function dep2(){ return v2 } exports.dep2 = dep2
執(zhí)行命令:
$ node index.js dep1-dep2
這里有一點(diǎn)變化,就是在nodejs內(nèi)使用index.js代替了index.html??梢钥吹剑?/p>
Nodejs提供了很好的局部化定義變量和函數(shù)的能力,如果使用exports聲明引出,其他模塊看不到本模塊的定義。比如v2變量沒有聲明引出,當(dāng)然實(shí)際上在本案例內(nèi)本來也不必引出,那么在dep1內(nèi)并不會(huì)看到v2變量。類似的v1也不會(huì)出現(xiàn)在index.js內(nèi)。
Nodejs提供了更加細(xì)粒度的依賴關(guān)系。index.js依賴dep1,但是并不依賴于dep2,那么index.js就只要引入dep1,而不必同時(shí)引入dep2。這樣的依賴關(guān)系,更加符合實(shí)際工程代碼的需求,而不是一股腦的、不分層次的引入全部需要用到的代碼。
在傳統(tǒng)的服務(wù)器開發(fā)的諸多語言中,模塊都是最基礎(chǔ)也是最必備的,像是JavaScript連個(gè)內(nèi)置模塊支持都沒有的是不常見的(或者說根本沒有?)。使用諸如的require和exports,就在后端干凈利索的解決了困惱前端的模塊問題,這不免讓前端覺得應(yīng)該效仿之。當(dāng)然,Nodejs加載模塊是同步的,這個(gè)是不能在前端效仿的。因?yàn)楹蠖藦拇疟P加載代碼,速度根本不是問題,而前端加載的都是從網(wǎng)絡(luò)上進(jìn)行的, 如果同步的話,加上Javas本身的單線程限定,整個(gè)UI就會(huì)因?yàn)榧虞d代碼而被卡死的。對(duì)比下兩者的速度差異,你就明白了:
硬盤 I/O ----------------- HDD: 100 MB/s SSD: 600 MB/s SATA-III: 6000 Mb/s ----------------- 網(wǎng)速 I/O ADSL: 4 Mb/s 4G: 100 Mb/s Fiber: 100 Mb/s借鑒后的樣子,先看看Modules/Async規(guī)范
思路倒也簡(jiǎn)單,只要自己編寫一個(gè)庫(kù),有它來異步加載其他模塊,并在加載時(shí)注入需要的require和exports即可。這方面的庫(kù)有幾個(gè),比如requirejs,sea.js等。因?yàn)槲覀冎皇菫榱酥v清楚概念和思路,因此會(huì)那概念上最清晰,和Nodejs最為一致的庫(kù)來說明問題,并不會(huì)因?yàn)槟莻€(gè)更加主流而去選擇它。從這個(gè)標(biāo)準(zhǔn)看,sea.js是說明概念問題的最佳模塊裝入庫(kù)。
sea.js 是一個(gè)模塊加載器,模塊加載器需要實(shí)現(xiàn)兩個(gè)基本功能:
實(shí)現(xiàn)模塊定義規(guī)范
加載運(yùn)行符合規(guī)范的模塊
核心落腳點(diǎn),就在規(guī)范二字上。sea.js要求模塊編寫必須在真正的代碼之外套上一層規(guī)定的代碼包裝,樣子看起來是這樣的:
define(function(require, exports, module) { // 模塊代碼 });
通過傳遞一個(gè)簽名為function(require, exports, module)的回調(diào)函數(shù)給define函數(shù),就可以把需要注入的變量和函數(shù)注入到模塊代碼內(nèi)。之前的實(shí)例代碼,在這里寫成:
文件index.js
// index.js define(function(require, exports, module) { var d = require("./dep1") console.log(d.dep1()) });
文件dep1.js
define(function(require, exports, module) { var d = require("./dep2") var v1 = "dep1" function dep1(){ return v1+"-"+d.dep2() } exports.dep1 = dep1 });
文件dep2.js
define(function(require, exports, module) { var v2 = "dep2" function dep2(){ return v2 } exports.dep2 = dep2 });
除了加上一層有點(diǎn)看起來莫名其妙的外套代碼,其他的模塊代碼,你該怎么寫就怎么寫。倘若不是那么潔癖,這樣的代碼確實(shí)解決了之前使用script標(biāo)簽加載代碼帶來的全局變量污染等問題,并且還是可以異步加載的,那些看起來不錯(cuò)的依賴關(guān)系,也如Nodejs一樣。以上代碼,可以直接把nodejs對(duì)應(yīng)的代碼拷貝過來,加上外套即可運(yùn)行。
我們不妨加入seajs文件,來看看實(shí)際的使用效果:
//index.html
這里為了偷懶,我使用了seajs的CDN文件。如果有遇到什么問題,你不妨自己下載一個(gè)seajs文件,然后改成你的URL即可。
加載此HTML文件,可以在控制臺(tái)看到輸出:
dep1+dep2
說明seajs執(zhí)行效果不錯(cuò)!
seajs通過use方法來加載入口模塊,可選接收一個(gè)回調(diào)函數(shù),當(dāng)模塊加載完成會(huì)調(diào)用此回調(diào)函數(shù),并傳入對(duì)應(yīng)的模塊作為參數(shù)
來獲取到模塊后,等待模塊(包括模塊依賴的模塊)加載完成會(huì)調(diào)用回調(diào)函數(shù)。
分析模塊的依賴,按依賴關(guān)系遞歸執(zhí)行document.createElement(‘script’),這些標(biāo)簽的創(chuàng)建會(huì)導(dǎo)致瀏覽器加載對(duì)應(yīng)的腳本
對(duì)模塊的價(jià)值,都是異步加載,瀏覽器不會(huì)失去響應(yīng),它指定的回調(diào)函數(shù),只有前面的模塊都加載成功后,才會(huì)運(yùn)行,解決了依賴性的問題。
可以在控制臺(tái)輸入:
seajs.data.fetchedList
查看文件加載清單。
因?yàn)椴皇钦Z言自帶,而是社區(qū)通過現(xiàn)有的語言特性,硬造出來的一個(gè)模塊系統(tǒng),因?yàn)榭雌饋泶a不免累贅。但是在沒有原生模塊的情況下,這樣做確實(shí)是管用的。要知道真正的原生模塊,在ES6標(biāo)準(zhǔn)之后才出現(xiàn),這都是2015年的事兒了。在一些有名的應(yīng)用如Gmail、Google Map的推動(dòng)下,Web從簡(jiǎn)單的展示到App的變化,迫切需要這樣類似的模塊技術(shù),大家等不了那么久,先弄一個(gè)能用的是很重要的。
為什么要套這層外殼呢?就是為了解決全局變量污染問題。在JavaScript語言內(nèi),唯一提供本地作用域的就是函數(shù)和閉包,通過閉包function(require, exports, module),模塊加載器給模塊注入了必要的函數(shù)和變量。看起來在模塊之內(nèi)的任何地方都可以使用require和exports,但是他沒都不是全局變量,而是閉包內(nèi)變量。這些變量都是局部化的,絕對(duì)不會(huì)污染全局空間。
使用require函數(shù),可以就近指定對(duì)其他模塊的依賴,函數(shù)本身是由sea.js這樣的模塊加載器提供,它會(huì)內(nèi)部構(gòu)造依賴關(guān)系圖譜,并根據(jù)依賴關(guān)系,設(shè)置加入script標(biāo)簽的次序。
更加詳細(xì)的理解這層外殼,可以閱讀seajs源代碼,代碼量并不大,值得一讀?;蛘呖纯创藛柎?/p>
當(dāng)然Seajs也引入了自己的規(guī)范,叫做CMD規(guī)范。它的前身是Modules/Wrappings規(guī)范。SeaJS更多地來自 Modules/2.0 的觀點(diǎn),同時(shí)借鑒了 RequireJS 的不少東西,比如將Modules/Wrappings規(guī)范里的 module.declare改為define等。
說是規(guī)范,卻不像是一般的規(guī)范那么冗長(zhǎng),可能打印出來也就一兩頁的紙張而已,這也是JavaScript社區(qū)的一個(gè)特點(diǎn)吧。Modules/Wrappings
seajs的作者在一篇文章中提到了業(yè)界在開發(fā)前端模塊加載器時(shí)的場(chǎng)景:
大概 09 年 - 10 年期間,CommonJS 社區(qū)大牛云集。CommonJS 原來叫 ServerJS,推出 Modules/1.0 規(guī)范后,在 Node.js 等環(huán)境下取得了很不錯(cuò)的實(shí)踐。09年下半年這幫充滿干勁的小伙子們想把 ServerJS 的成功經(jīng)驗(yàn)進(jìn)一步推廣到瀏覽器端,于是將社區(qū)改名叫 CommonJS,同時(shí)激烈爭(zhēng)論 Modules 的下一版規(guī)范。分歧和沖突由此誕生,逐步形成了三大流派:
Modules/1.x 流派。這個(gè)觀點(diǎn)覺得 1.x 規(guī)范已經(jīng)夠用,只要移植到瀏覽器端就好。要做的是新增 Modules/Transport 規(guī)范,即在瀏覽器上運(yùn)行前,先通過轉(zhuǎn)換工具將模塊轉(zhuǎn)換為符合 Transport 規(guī)范的代碼。主流代表是服務(wù)端的開發(fā)人員。現(xiàn)在值得關(guān)注的有兩個(gè)實(shí)現(xiàn):越來越火的 component 和走在前沿的 es6 module transpiler。
Modules/Async 流派。這個(gè)觀點(diǎn)覺得瀏覽器有自身的特征,不應(yīng)該直接用 Modules/1.x 規(guī)范。這個(gè)觀點(diǎn)下的典型代表是 AMD 規(guī)范及其實(shí)現(xiàn) RequireJS。
Modules/2.0 流派。這個(gè)觀點(diǎn)覺得瀏覽器有自身的特征,不應(yīng)該直接用 Modules/1.x 規(guī)范,但應(yīng)該盡可能與 Modules/1.x 規(guī)范保持一致。這個(gè)觀點(diǎn)下的典型代表是 BravoJS 和 FlyScript 的作者。BravoJS 作者對(duì) CommonJS 的社區(qū)的貢獻(xiàn)很大,這份 Modules/2.0-draft 規(guī)范花了很多心思。FlyScript 的作者提出了 Modules/Wrappings 規(guī)范,這規(guī)范是 CMD 規(guī)范的前身??上У氖?BravoJS 太學(xué)院派,F(xiàn)lyScript 后來做了自我閹割,將整個(gè)網(wǎng)站(flyscript.org)下線了。這個(gè)故事有點(diǎn)悲壯,下文細(xì)說。
也談?wù)剅equire.js這個(gè)模塊加載器是更加主流的。之所以不是首先提到它,是因?yàn)楦拍钌蟻碚fseajs更加簡(jiǎn)明。和seajs相比,requirejs是更加主流的框架。它的差異主要是一些零零散散的不同,比如模塊代碼的外套是不太一樣的:
require(["moduleA", "moduleB", "moduleC"], function (moduleA, moduleB, moduleC){
// some code here
});
導(dǎo)出模塊變量和函數(shù)的方式,也是不同的。requirejs的引出方式是直接返回:
return {foo:foo}
一樣的案例,使用requirejs的話,代碼是這樣的:
index.html文件
index.js文件:
require(["./dep1"], function (d){ console.log(d.dep1()) });
dep1.js文件:
define(["./dep2"], function (d){ var v1 = "dep1" function dep1(){ return v1+"-"+d.dep2() } return {dep1:dep1} });
dep2.js文件:
define(function() { var v2 = "dep2" function dep2(){ return v2 } return {dep2:dep2} });
瀏覽器打開文件index.html,可以看到控制臺(tái)輸出一樣的結(jié)果。
稍加對(duì)比require.js和sea.js。使用Require.js,默認(rèn)推薦的模塊格式是:
define(["a","b"], function(a, b) { // do sth })
使用seajs的時(shí)候,類似的功能,代碼這樣寫:
define(function(require, exports, module) { var a = require("a") var b = require("b") // do sth ... })
Seajs的做法是更加現(xiàn)代的。我需要用的時(shí)候,我才去引用它,而不是實(shí)現(xiàn)什么都引用好,然后用的時(shí)候直接用就好。
Modules/1.x規(guī)范以require.js為代表的Modules/Async流派,尊重了瀏覽器的特殊性,代價(jià)是不管寫什么模塊,都得自己給自己穿上一層外套,對(duì)于有代碼潔癖的人來說,這樣的情況是看不下去的。最好是開發(fā)人員編寫干干凈凈的模塊代碼,框架開發(fā)者做一個(gè)工具,這個(gè)工具自動(dòng)的把這些代碼轉(zhuǎn)義成客戶端認(rèn)可的異步代碼。即在瀏覽器上運(yùn)行前,先通過轉(zhuǎn)換工具將模塊轉(zhuǎn)換為符合規(guī)范的代碼。這就是Modules/1.x 流派的做法。需要注意的是1.x和2.0還有Async流派不能簡(jiǎn)單的認(rèn)為版本號(hào)大的就更好。倒是理解成各自不同的解決方案為好。
以我們自己的案例來說,就是可以直接把nodejs代碼那里,使用一個(gè)工具做一個(gè)轉(zhuǎn)換,即可得到符合前端需要的代碼,這些代碼是異步加載的、是可以保證模塊變量局部化的、是可以由良好的依賴關(guān)系定義的。工具browerfy就是做這個(gè)的。我們來試試具體是怎么玩的。
首先安裝此工具:
npm install --global browserify
到你的nodejs代碼內(nèi),然后轉(zhuǎn)換此代碼,生成一個(gè)新的js文件,一般命名為bundle.js:
browserify index.js -o bundle.js
然后創(chuàng)建index.html并引入bundle.js:
使用瀏覽器打開此HTML文件,可以在控制臺(tái)看到熟悉的輸出,這說明轉(zhuǎn)換是有效的:
dep1+dep2
本身nodejs的代碼,是不能在瀏覽器執(zhí)行的,瀏覽器內(nèi)也沒有什么require函數(shù),但是轉(zhuǎn)換后就可以執(zhí)行了。那么,轉(zhuǎn)換的過程,到底玩了什么魔術(shù)?
像是browserify這樣的工具,就是找到全面被引入的代碼,解析它的依賴關(guān)系,并且自動(dòng)的加入我們?cè)趓equirejs里面需要的外套代碼。盡管bundle.js文件并不是為了閱讀優(yōu)化的,但是可以取出其中的代碼片段來證實(shí)我們的觀點(diǎn):
{"./dep2":2}],2:[function(require,module,exports){ var v2 = "dep2" function dep2(){ return v2 } exports.dep2 = dep2 },{}],3:[function(require,module,exports){ var d = require("./dep1") console.log(d.dep1()) },{"./dep1":1}]},{},[3]);
我們可以看到本來的nodejs代碼,以及它們對(duì)應(yīng)的外套。還是比較簡(jiǎn)單,就不進(jìn)一步解釋了。browserify不但完成了加外套代碼的工作,還同時(shí)把若干小文件打成一個(gè)大的文件,對(duì)于當(dāng)前使用的HTTP主流版本1.1來說,這樣做會(huì)讓加載效率更高。但是對(duì)于HTTP/2.0來說,它已經(jīng)支持了多個(gè)文件在一個(gè)連接內(nèi)交錯(cuò)傳遞,因此再做打包的意義就不大了。只是...HTTP/2.0的普及還需要時(shí)日。
browerify完成的工作簡(jiǎn)明而單一。另外一個(gè)主流的同類工具叫做webpack,不但可以轉(zhuǎn)換js代碼,還可以打包c(diǎn)ss文件、圖片文件,并且可以做一些工程化的管理,代價(jià)就是webpack學(xué)起來也困難的多。實(shí)際上像是Vuejs這樣的UI開發(fā)框架,內(nèi)部就是使用了webpack做工程化管理和代碼轉(zhuǎn)譯的。但是在模塊化方面,兩者是差不多的。就不另外介紹了。
ES6 Module時(shí)間到了May 9, 2018,我看到了阮一峰發(fā)布了這樣的微博:
今天 Firefox 60發(fā)布,默認(rèn)打開了ES6 模塊支持,至此所有瀏覽器都默認(rèn)支持ES6模塊。前端開發(fā)模式可能因此大變?,F(xiàn)在的方案是所有模塊發(fā)到npm,本地寫好入口文件,再用webpack打包成一個(gè)腳本。但是如果瀏覽器原生支持,為什么還要打包呢?至少簡(jiǎn)單的應(yīng)用可以直接加載入口文件,瀏覽器自己去抓取依賴。 ????
這里所有瀏覽器指的是Edge、Firefox、Chrome、Safari。當(dāng)然,再一次沒有IE。如果想要支持IE或者比較老的版本的話,還是需要使用打包器來完成代碼的轉(zhuǎn)譯。另外很多人表示會(huì)繼續(xù)使用Webpack,原因很簡(jiǎn)單,Webpack不僅僅是完成模塊打包工作,還有壓縮、混淆等,并且很多框架還需要依賴它。所以遷移并非一朝一夕之功。而無需考慮老版本瀏覽器的兼容的代碼,是完全可以大量的使用它了。了不起在把Webpack加起來轉(zhuǎn)換ES Module到加外套的代碼就是了。
ES6 Module不是requirejs那樣加外套的樣子,也不是Nodejs使用require函數(shù)的樣子,而是另外一套有官方提出的模塊模式。它使用的是import、export關(guān)鍵字。官方的就是不一樣,社區(qū)是加不了關(guān)鍵字的。同樣的案例,使用ES6 Module就是這樣的了。
index.html文件:
dep1.js文件
import {dep2} from "./dep2.js" var v1 = "dep1" export function dep1(){ return v1+"-"+dep2() }
dep2.js文件:
var v2 = "dep2" export function dep2(){ return v2 }
ES6 Module要求必須有后臺(tái)的HTTP服務(wù)器,而不能直接在文件系統(tǒng)上完成Demo的測(cè)試。所幸使用Nodejs搭建一個(gè)服務(wù)器也非常簡(jiǎn)單直接:
npm i http-server -g http-server
在瀏覽器內(nèi)訪問此HTML文件的URL,可以看到控制臺(tái)輸出:dep1+dep2。這個(gè)輸出,已經(jīng)是你的老朋友了。
Nodejs在10.9才支持實(shí)驗(yàn)版本的ES6 Module,是落后了點(diǎn),但是對(duì)于Nodejs來說,新的模塊技術(shù)本來也就并不迫切。
最佳實(shí)踐建議綜合以上的內(nèi)容,我認(rèn)為,在不必考慮古老的瀏覽器兼容的情況下,最好的實(shí)踐是這樣的:
直接使用ES6 Module編寫模塊代碼
使用Rollup清除沒有調(diào)用的代碼,降低代碼的大小
使用Ugly工具壓縮和混淆代碼
使用HTTP/2做網(wǎng)絡(luò)傳遞協(xié)議
這樣的實(shí)踐,會(huì)隨著HTTP/2的逐步普及和ES6被更多的開發(fā)者采用,而成為更好的選擇。
使用ES6 Module的壞處是無法像require那樣動(dòng)態(tài)的加載。但是好處是可以精確指明對(duì)于一個(gè)庫(kù),我們使用的是那些,這就給工具提供了優(yōu)化的可能,就是說如果我引入了一個(gè)庫(kù),但是這個(gè)庫(kù)內(nèi)有些我不會(huì)用的,那么這些不會(huì)被用到的代碼也不會(huì)加載到前端了。這個(gè)功能對(duì)于后端來說意義不大,但是對(duì)于前端來說,就是非常令人喜歡的功能了。實(shí)際上,這樣的工具已經(jīng)有了,比較知名的就是rollup,它屬于了一種被稱為tree-shaking的技術(shù)優(yōu)化使用代碼。
而以往做模塊打包,很多的原因是HTTP/1.1傳遞大量小文件的時(shí)候開銷比較大,而打包成單一的問題,就可以更好的利用HTTP/1.1的傳輸特性。但是HTTP/2.0的一個(gè)大的特色就是可以在單一的連接內(nèi),并發(fā)和交錯(cuò)的傳遞多個(gè)流,因?yàn)樵谝粋€(gè)連接內(nèi)交錯(cuò)的傳遞多個(gè)文件,就可以不再有HTTP/1.1的連接開銷了。因此,在HTTP/2.0被采納的網(wǎng)絡(luò)里面,打包單一文件的價(jià)值幾乎沒有了。直接使用小文件默認(rèn)情況下就可以得到比較好的優(yōu)化傳輸。
按照現(xiàn)在的技術(shù)發(fā)展的勢(shì)頭,要不了幾年,打包器將不再那么必要,使用原生代碼編寫模塊將會(huì)成為主流的。
參考參考文章不少,其中模塊歷史和選型如下:
前端模塊化開發(fā)那點(diǎn)歷史
梳理的還是比較清晰
有點(diǎn)黑客精神的小伙伴,玩的很廣譜
介紹Bower
npm for Beginners: A Guide for Front-end Developers
Es6module 出來了,是否應(yīng)該重新考慮打包的方案?
未來這篇文章預(yù)計(jì)想要編寫的YUI方法,YUI Combo方法,想了想還是算了,因?yàn)檫@樣的恐龍代碼,已經(jīng)在日常的代碼實(shí)踐中逐步消失,作為一個(gè)曾經(jīng)比較重要,現(xiàn)在則退居二線的代碼庫(kù),對(duì)它最好的贊許就是讓它退休,也不必給讀者增加額外的閱讀負(fù)擔(dān)了。畢竟require.js、browerify、webpack都工作的不錯(cuò),在此基礎(chǔ)上發(fā)展的Vuejs、React.js也的得到了更多的認(rèn)可。
本文講到的模塊規(guī)范和實(shí)踐工具,為編寫一個(gè)廣為社區(qū)認(rèn)可的模塊起到了最基礎(chǔ)的規(guī)范作用。但是,JavaScript社區(qū)最為令人稱道的就是代碼庫(kù)倉(cāng)庫(kù)。包括NPM倉(cāng)庫(kù),Bower倉(cāng)庫(kù)。在這些倉(cāng)庫(kù)內(nèi),有模塊依賴管理工具,還有工程化工具。這些內(nèi)容,它們當(dāng)然是重要的,不在本文的范圍內(nèi)。
作為前端開發(fā)者,有人采用Bower管理組件依賴,也有人使用Npm做類似的工作。有很多時(shí)候,這樣的實(shí)踐是令人困惑的。還有這里npm and the front end,NPM官方也對(duì)npm在前端的使用,提出了自己的看法。
這些未盡的內(nèi)容,或許在未來的文章中表達(dá)之。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/97327.html
摘要:定制元素可以在原生元素外創(chuàng)建定制元素。此定制元素內(nèi)部有一個(gè)加號(hào)按鈕,一個(gè)減號(hào)按鈕,一個(gè)顯示當(dāng)前值。此主題會(huì)在下一部分內(nèi)介紹。定制元素的屬性元素的屬性被稱為,對(duì)象內(nèi)的屬性被稱為。做響應(yīng)的同步處理。 Web Components 全攬 Web Components技術(shù)可以把一組相關(guān)的HTML、JS代碼和CSS風(fēng)格打包成為一個(gè)自包含的組件,只要使用大家熟悉的標(biāo)簽即可引入此組件。Web Com...
摘要:僅在年上半年,就有數(shù)據(jù)顯示全球范圍內(nèi)相關(guān)產(chǎn)業(yè)融資規(guī)模達(dá)到億美元,最熱的地方并不在美國(guó)歐洲日本,而是在中國(guó)中國(guó)一國(guó)的融資規(guī)模就達(dá)到了億美元,占到了全球份額的。人才極度短缺必然導(dǎo)致其薪資水漲船高。 showImg(http://upload-images.jianshu.io/upload_images/13825820-55da3500ae119588.jpg?imageMogr2/au...
摘要:最近看了知乎上的一個(gè)話題在工作中,為什么程序員常常瞧不起程序員個(gè)人從業(yè)多年,用過的后端語言,如果你非要讓我說哪種語言好,我會(huì)說凡是宏哥說的都是對(duì)的,凡是宏哥提倡的都要堅(jiān)持。只有真正的理解了宏哥思想才可以洞穿一切,走出空谷。 最近看了知乎上的一個(gè)話題「在工作中,為什么 Java 程序員常常瞧不起 PHP 程序員?」 個(gè)人從業(yè)多年,用過的后端語言 ASP、ASP.NET、Java、PHP、...
摘要:最近看了知乎上的一個(gè)話題在工作中,為什么程序員常常瞧不起程序員個(gè)人從業(yè)多年,用過的后端語言,如果你非要讓我說哪種語言好,我會(huì)說凡是宏哥說的都是對(duì)的,凡是宏哥提倡的都要堅(jiān)持。只有真正的理解了宏哥思想才可以洞穿一切,走出空谷。 最近看了知乎上的一個(gè)話題「在工作中,為什么 Java 程序員常常瞧不起 PHP 程序員?」 個(gè)人從業(yè)多年,用過的后端語言 ASP、ASP.NET、Java、PHP、...
摘要:二模塊化誤區(qū)加快加載和執(zhí)行的速度,一直是前端優(yōu)化的一個(gè)熱點(diǎn)。結(jié)果文件減少,也達(dá)到了預(yù)期的效果。避免不必要的延遲。最后再根據(jù)文件的功能類型,來決定是放在頁面的頭部還是尾部。 注:本文是純技術(shù)探討文,無圖無笑點(diǎn),希望您喜歡 一.前言 軟件行業(yè)極其缺乏前端人才這是圈內(nèi)的共識(shí)了,某種程度上講,同等水平前端的工資都要比后端高上不少,而圈內(nèi)的另一項(xiàng)共識(shí)則是——網(wǎng)頁是公司的臉面! 幾年前,谷歌的一項(xiàng)...
閱讀 2847·2021-11-22 15:22
閱讀 19014·2021-09-22 15:00
閱讀 1433·2021-09-07 09:58
閱讀 1236·2019-08-30 13:01
閱讀 2408·2019-08-29 16:27
閱讀 2344·2019-08-26 13:25
閱讀 1618·2019-08-26 12:13
閱讀 934·2019-08-26 11:53