前言
經(jīng)典面試題目就是《Common.js和ES module的區(qū)別》,這一題很多人都會熟練地背出答案。
Commonjs
- CommonJs可以動態(tài)加載語句,代碼發(fā)生在運(yùn)行時(shí)
- CommonJs導(dǎo)出值是拷貝, 不好排查引起變量污染
ES module(后續(xù)簡稱esm)
- ESM是靜態(tài)的,代碼發(fā)生在編譯時(shí)
- ESM導(dǎo)出是引用值之前都存在映射關(guān)系,并且值都是可讀的,不能修改
參考聊聊什么是CommonJs和Es Module及它們的區(qū)別
esm是JavaScript模塊化的未來。因?yàn)樗鉀Q了變量污染,代碼維護(hù),代碼依賴的問題。它讓你的代碼更加科學(xué)。這也是deno默認(rèn)采用esm的原因。
回歸正題,我們有什么方法在Nodejs或者瀏覽器直接運(yùn)行esm代碼,這是個(gè)有趣而又實(shí)際的問題。
如何在Nodejs環(huán)境允許
1.利用編譯工具運(yùn)行esm
最常見的方式是利用webpack等打包工具搭配babel使用。隨著webpack和vue的大熱,這些工具似乎成為了標(biāo)配,但是webpack的缺點(diǎn)也很明顯,它能讓commonjs和esm的混寫,導(dǎo)致代碼存在一些寫法不規(guī)范的情況,我相信這種情況是普遍出現(xiàn)在業(yè)務(wù)代碼里面,也存在于antd3這樣的知名第三方組件庫中。
而rollup則是基于ES6的語法規(guī)范進(jìn)行編譯,它的輕便小巧,非常適合npm庫的打包。新興的打包工具例如esbuild和swc,也可以實(shí)現(xiàn)編譯打包,即使速度越來越快,但是還是需要編譯的過程。這些倉庫很重要的一個(gè)特點(diǎn)就是使用esm語法。
以上這些工具都可以應(yīng)用于esm語法編譯,但是有很多項(xiàng)目不一定需要打包編譯這樣耗時(shí)的流程的,例如一些cli工具、簡易微服務(wù)等,如何保證高效正確的運(yùn)行esm代碼呢?
2. 利用第三方庫運(yùn)行esm
在Nodejs版本較低的情況,我們可以利用一些工具,工具的使用形式有幾種,一種是Module Loader,另一種是Command Line(簡稱為cli)。
Module Loader,這里介紹standard-things/esm,它可以preload第三方提供的esm包,從此,可以做到babelless, bundleless。你不需要使用大型編譯工具也可以直接運(yùn)行esm代碼,使用方式如下。
node -r esm index.js
同樣,egoist/esbuild-register這個(gè)庫在esbuild的支持下,同樣可以做到Module Loader的效果,利用esbuild的高性能特性,代碼運(yùn)行效率更高。
node -r esbuild-register index.js
Command Line,基于封裝后的cli,不過是換一種形式進(jìn)行模塊的提前處理。babel-node直接利用它的babel語法優(yōu)勢來運(yùn)行esm代碼。由于babel本身還是js的實(shí)現(xiàn),它的官方文檔也表明了不建議在生產(chǎn)環(huán)境使用,會導(dǎo)致內(nèi)存高占用的問題,這也是這一類工具的通病。
babel-node index.js
同樣,esno可以直接在命令行運(yùn)行esm代碼。原理基于esbuild。在這里更推薦使用這種方式,鑒于esbuild是由go語言實(shí)現(xiàn),能夠較大程度解決內(nèi)存高占用的問題,保證了一定的執(zhí)行性能。
esno index.ts
esmo index.ts
這一類第三方倉庫適合在低版本nodejs且非生產(chǎn)環(huán)境使用,它們的存在是為了便利性,而并非實(shí)用性和穩(wěn)定性。怎么樣才能高效地運(yùn)行esm代碼?
3.Native Nodejs運(yùn)行esm
Node verison 13.2.0 起開始正式支持 ES Modules 特性
所以利用Native Nodejs環(huán)境運(yùn)行esm代碼是非常必要的,高版本的Nodejs提供了直接運(yùn)行esm的功能,這里建議使用lts14版本。有兩種方式運(yùn)行esm代碼:
第一種,package.json中填寫type: "modules",表明模塊的類型。此后,直接運(yùn)行node index.js即可。
// pakage.json
{
...
"type": "modules"
}
第二種,則是將文件名改成.mjs,標(biāo)明該文件是esm代碼。這兩種方式最大的區(qū)別則是模塊作用域。前者是包的作用域,它的聲明是以package為維度。后者則是以文件為維度,不受限于包的作用域。
如何在瀏覽器運(yùn)行esm

瀏覽器的情況有別于Nodejs環(huán)境,在大部分的新版本瀏覽器都支持esm的運(yùn)行。esm 級別的代碼編譯和打包,可以有效地減少包的體積和資源傳輸速度。這也是為什么像 vite 這樣的框架會采用現(xiàn)代瀏覽器的打包模式(外加legacy兼容模式)的原因。具體的原理是在 html 當(dāng)中的 script 標(biāo)簽加入type="module" 則表明它引入的是esm代碼,當(dāng)舊瀏覽器沒法支持esm的情況下,它會讀取nomodule script中的地址,讀取兼容版本的 js 代碼。這樣一來,可以有效地減少大部分瀏覽器加載的 js 體積,又保證了老瀏覽器的兼容性問題。
<script type="module" src="dist/index.js"></script>
<script nomodule src="dist/index.legacy.js"></script>
總結(jié)
如今Nodejs和瀏覽器環(huán)境都能對esm語法有了很好的 Native 支持。作為前端工程師的我們,應(yīng)該要保持著技術(shù)的前瞻性,在寫一個(gè)倉庫的時(shí)候,我們要想到要用typescript,esm還是common.js呢?為什么我們不選擇比較新的 js 運(yùn)行環(huán)境,迎接Javascript的第三個(gè)時(shí)代,參考《ESM Import與Bundleless》。