nodejs html引用js_NodeJS与模块系统
本文作者:劉觀宇,360 奇舞團高級前端工程師、技術經理,W3C CSS 工作組成員。
孤山寺北賈亭西,水面初平云腳低。幾處早鶯爭暖樹,誰家新燕啄春泥。亂花漸欲迷人眼,淺草才能沒馬蹄。最愛湖東行不足,綠楊陰里白沙堤。 ———— 唐.白居易《錢塘湖春行》
自從 Node8.5 以后, Node 開始支持引入 ES 模塊1。在新開的項目中,筆者嘗試使用了這種方式。由于目前 NodeJS 對于 ES 模塊尚屬試驗性支持,因此需要在啟動時候加入參數:--experimental-modules,完整的命令如:node --experimental-modules index.mjs。這樣程序就愉快地運行起來了。不過當筆者嘗試加入一些新的依賴之后問題出現了:
這個問題在于,很多 NodeJS 依賴包,由于使用 CommonJS 的方式編寫,對于 ES Module 的支持尚不完善。因此包在使用過程中,存在一定的兼容問題。
這篇文章我們就來談談,在 NodeJS 環境下,CommonJS 和 ES Module 區別和聯系,以及互操作的方法。
在 NodeJS 誕生時候,ES Module 模塊系統還沒有誕生,因此 NodeJS 最初采用的是 CommonJS 模塊系統。我們之前所使用的 NodeJS 包,大部分都是 CommonJS 模塊。CommonJS 模塊和 ES Module 之間有很大的區別。這也就是為什么ES Module使用.mjs區別對待的原因。 下面這張圖闡述了 NodeJS 對于兩種模塊系統的解析流程。
那么它們在行為上有什么區別呢?
主要有兩點:
一、引入模塊時機的區別:CommonJS 模塊是運行時加載,換句話說是在 NodeJS 腳本執行時才加載進來,而 ES Module 則是在靜態分析時候就確定了引用關系。這有點像給目標模塊建立了一個符號鏈接,或者說建立了一個指針。從這個意義上說,ES Module 的 import 的加載效率應該略高于 CommonJS。而 CommonJS 這種特性也給動態加載模塊提供了可能。
二、CommonJS 模塊加載實際上是把 module.exports 對象進行了一次拷貝。因此當原模塊的存儲在棧空間的值,也就是基本類型值在第一次加載過程中已經被緩存掉,當它發生了改變,不會影響引入這個模塊的代碼的值。讀者可以嘗試在加載后打印下require.cache看一看緩存的結構。下面看這個例子:
// cjs.js
var?inner?=?3;
let?innerIncrease?=?()?=>?{
?inner++;
}
module.exports?=?{
?inner,
?innerIncrease
};
// main.js
var?mod?=?require('./cjs');
console.log(mod.inner);
mod.innerIncrease();
console.log(mod.inner);
我們如果執行 node main.js 會發現兩次執行的結果都為 3。那是因為 inner 的值在第一次引入時候被緩存掉了。
如果上述代碼使用 ES module。因為都是引用值,所以外部可以感知內部變化。
// mjs.mjs
export?let?inner?=?3;
export?let?innerIncrease?=?()?=>?{
?inner++;
}
// main.mjs
var?mod?=?require('./mjs.mjs');
console.log(mod.inner);
mod.innerIncrease();
console.log(mod.inner);
這里因為"import"的都是引用,會內外同步,因此依次打印出 3 和 4。
如果你啟用了--experimental-modules,則意味這幾件事:
對于同樣的文件名,當模塊沒有寫明擴展名時,加入--experimental-modules的總是先去加載.mjs 擴展名的同名模塊,如果沒有才會去加載.js。
只要使用 import/export 命令的,必須使得擴展名為 .mjs。
.mjs 文件中不能使用 require 函數,否則會報ReferenceError: require is not defined錯誤。
import 命令是異步加載,意思是,后面的模塊不會等待前面的加載完再去加載。如果有先后依賴關系的模塊,不能在 import 中同級寫,這個和 CommonJS 模塊 不一樣。
頂層文件,this指向undefined。arguments、require、module、exports、__filename和__dirname均為undefined
既然差異很難彌合,那么用擴展名來區分兩套體系從目前來看是很必要的。但是有時候,互相操作又是不可避免的。
那么如何互操作呢?
如果 ES Module 需要加載 CommonJS 模塊,NodeJS 將把 module.exports 當作一個對象賦給 default 對象,即相當于:export default {...module.exports 對象}
假設我們有 cjs 模塊 cjs.js,如下:
// cjs.js
module.exports?=?{
?"Hello":?"你好",
?"World":?"世界"
}
我們可以在需要的 mjs 文件中這樣使用:
// main.mjs
import?cjsExport?from?'./cjs.js'
// 以下兩種也可以
/***
* import {default as cjsExport} from './cjs.js'
* import * as cjsExport from './cjs.js'
***/
const?{Hello,?World}?=?cjsExport
但是,這種方式是不允許的:import { someExports } from 'module'。
那么 CommonJS 能否加載 ES Module 呢?答案是肯定的,不過稍微有點麻煩。同時,使用這個特性需要在 NodeJS 9.7.0 版本以上,同時加入--experimental-modules,這里2是當時的 changelog
// es.mjs
export?default?{
?"a"?:?1,
?"b"?:?2
}
// main.mjs
(async?_?=>?{
?const?es?=?await?import('./es.mjs');
?console.log(es);
})()
其它幾種 export 語法也可以用這種方式導入,想要知曉所有 export 語法的,可以參考高峰老師的之前在周刊發表的這篇文章3。
回到我們開頭提出的問題,在 mjs 模塊里,可以使用上面提到的相應的加載 CommonJS 模塊的寫法,就可以正常運行了。
隨著 ES Module 越來越完善,我們可以期待,今后 ES module 會越來越多。我們看到在現在這個多種模塊機制并存的時代,很多重要的庫在新版本的源碼中都做了適配,基本的方案是:利用擴展名引入的優先級,提供多種擴展名的文件。如.js 適應 CommonJS,.mjs 文件適配 ES module,這種方案也使得庫的使用者,比較平滑地切換各種模塊系統。當然,各種預編譯器也可以在不得已的時候提供必要的幫助。
參考資料
http://es6.ruanyifeng.com/#docs/module-loader
https://nodejs.org/api/esm.html
https://www.zcfy.cc/article/es-modules-and-node-js-hard-choices-477.html
文內鏈接
https://github.com/nodejs/node/blob/master/doc/changelogs/CHANGELOG_V8.md#8.5.0
https://github.com/nodejs/node/blob/master/doc/changelogs/CHANGELOG_V9.md#9.7.0
https://mp.weixin.qq.com/s/bIU_FvesizFJ3D_6KWRPHA
關于奇舞周刊
《奇舞周刊》是360公司專業前端團隊「奇舞團」運營的前端技術社區。關注公眾號后,直接發送鏈接到后臺即可給我們投稿。
總結
以上是生活随笔為你收集整理的nodejs html引用js_NodeJS与模块系统的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: airtest自动化测试_自动化测试必备
- 下一篇: idea console中文乱码_Pyt