今天小編給大家分享一下Node的CJS與ESM有哪些不同點(diǎn)的相關(guān)知識(shí)點(diǎn),內(nèi)容詳細(xì),邏輯清晰,相信大部分人都還太了解這方面的知識(shí),所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來(lái)了解一下吧。
成都創(chuàng)新互聯(lián)公司-專業(yè)網(wǎng)站定制、快速模板網(wǎng)站建設(shè)、高性價(jià)比城東網(wǎng)站開(kāi)發(fā)、企業(yè)建站全套包干低至880元,成熟完善的模板庫(kù),直接使用。一站式城東網(wǎng)站制作公司更省心,省錢,快速模板網(wǎng)站建設(shè)找我們,業(yè)務(wù)覆蓋城東地區(qū)。費(fèi)用合理售后完善,10余年實(shí)體公司更值得信賴。
1.1 在 Node 中使用 ESM
Node 默認(rèn)只支持 CJS 語(yǔ)法,這意味著你書寫了一個(gè) ESM 語(yǔ)法的 js 文件,將無(wú)法被執(zhí)行。
如果想在 Node 中使用 ESM 語(yǔ)法,有兩種可行方式:
⑴ 在 package.json
中新增 "type": "module"
配置項(xiàng)。
⑵ 將希望使用 ESM 的文件改為 .mjs
后綴。
對(duì)于第一種方式,Node 會(huì)將和 package.json
文件同路徑下的模塊,全部當(dāng)作 ESM 來(lái)解析。
第二種方式不需要修改 package.json
,Node 會(huì)自動(dòng)地把全部 xxx.mjs
文件都作為 ESM 來(lái)解析。
同理,如果在
package.json
文件中設(shè)置"type": "commonjs"
,則表示該路徑下模塊以 CJS 形式來(lái)解析。 如果文件后綴名為.cjs
,Node 會(huì)自動(dòng)地將其作為 CJS 模塊來(lái)解析(即使在package.json
中配置為 ESM 模式)。
我們可以通過(guò)上述修改 package.json
的方式,來(lái)讓全部模塊都以 ESM 形式執(zhí)行,然后項(xiàng)目上的模塊都統(tǒng)一使用 ESM 語(yǔ)法來(lái)書寫。
如果存在較多陳舊的 CJS 模塊懶得修改,也沒(méi)關(guān)系,把它們?nèi)颗驳揭粋€(gè)文件夾,在該文件夾路徑下新增一個(gè)內(nèi)容為 {"type": "commonjs"}
的 package.json
即可。
Node 在解析某個(gè)被引用的模塊時(shí)(無(wú)論它是被 import
還是被 require
),會(huì)根據(jù)被引用模塊的后綴名,或?qū)?yīng)的 package.json
配置去解析該模塊。
1.2 ESM 引用 CJS 模塊的問(wèn)題
ESM 基本可以順利地 import
CJS 模塊,但對(duì)于具名的 exports(Named exports,即被整體賦值的 module.exports
),只能以 default export 的形式引入:
/** @file cjs/a.js **/ // named exports module.exports = { foo: () => { console.log("It's a foo function...") } } /** @file index_err.js **/ import { foo } from './cjs/a.js'; // SyntaxError: Named export 'foo' not found. The requested module './cjs/a.js' is a CommonJS module, which may not support all module.exports as named exports. foo(); /** @file index_err.js **/ import pkg from './cjs/a.js'; // 以 default export 的形式引入 pkg.foo(); // 正常執(zhí)行
1.3 CJS 引用 ESM 模塊的問(wèn)題
假設(shè)你在開(kāi)發(fā)一個(gè)供別人使用的開(kāi)源項(xiàng)目,且使用 ESM 的形式導(dǎo)出模塊,那么問(wèn)題來(lái)了 —— 目前 CJS 的 require
函數(shù)無(wú)法直接引入 ESM 包,會(huì)報(bào)錯(cuò):
let { foo } = require('./esm/b.js'); ^ Error [ERR_REQUIRE_ESM]: require() of ES Module BlogDemo3\220220\test2\esm\b.js from BlogDemo3\220220\test2\require.js not supported. Instead change the require of b.js in BlogDemo3\220220\test2\require.js to a dynamic import() which is available in all CommonJS modules. at Object.<anonymous> (BlogDemo3\220220\test2\require.js:4:15) { code: 'ERR_REQUIRE_ESM' }
按照上述錯(cuò)誤陳述,我們不能并使用 require
引入 ES 模塊(原因會(huì)在后續(xù)提及),應(yīng)當(dāng)改為使用 CJS 模塊內(nèi)置的動(dòng)態(tài) import
方法:
import('./esm/b.js').then(({ foo }) => { foo(); }); // or (async () => { const { foo } = await import('./esm/b.js'); })();
開(kāi)源項(xiàng)目當(dāng)然不能強(qiáng)制要求用戶改用這種形式來(lái)引入,所以又得借助 rollup 之類的工具將項(xiàng)目編譯為 CJS 模塊……
由上可見(jiàn)目前 Node.js 對(duì) ESM 語(yǔ)法的支持是有限制的,如果不借助工具處理,這些限制可能會(huì)很糟心。
對(duì)于想入門前端的新手來(lái)說(shuō),這些麻煩的規(guī)則和限制也會(huì)讓人困惑。
截至我落筆書寫本文時(shí), Node.js LTS 版本為 16.14.0
,距離開(kāi)始支持 ESM 的 13.2.0
版本已過(guò)去了兩年多的時(shí)間。
那么為何 Node.js 到現(xiàn)在還無(wú)法打通 CJS 和 ESM?
答案并非 Node.js 敵視 ESM 標(biāo)準(zhǔn)從而遲遲不做優(yōu)化,而是因?yàn)?—— CJS 和 ESM,二者真是太不一樣了。
2.1 不同的加載邏輯
在 CJS 模塊中,require()
是一個(gè)同步接口,它會(huì)直接從磁盤(或網(wǎng)絡(luò))讀取依賴模塊并立即執(zhí)行對(duì)應(yīng)的腳本。
ESM 標(biāo)準(zhǔn)的模塊加載器則完全不同,它讀取到腳本后不會(huì)直接執(zhí)行,而是會(huì)先進(jìn)入編譯階段進(jìn)行模塊解析,檢查模塊上調(diào)用了 import
和 export
的地方,并順騰摸瓜把依賴模塊一個(gè)個(gè)異步、并行地下載下來(lái)。
在此階段 ESM 加載器不會(huì)執(zhí)行任何依賴模塊代碼,只會(huì)進(jìn)行語(yǔ)法檢錯(cuò)、確定模塊的依賴關(guān)系、確定模塊輸入和輸出的變量。
最后 ESM 會(huì)進(jìn)入執(zhí)行階段,按順序執(zhí)行各模塊腳本。
所以我們常常會(huì)說(shuō),CommonJS 模塊是運(yùn)行時(shí)加載,ES6 模塊是編譯時(shí)輸出接口。
在上方 1.2 小節(jié),我們?cè)峒暗?ESM 中無(wú)法通過(guò)指定依賴模塊屬性的形式引入 CJS named exports:
/** @file cjs/a.js **/ // named exports module.exports = { foo: () => { console.log("It's a foo function...") } } /** @file index_err.js **/ import { foo } from './cjs/a.js'; // SyntaxError: Named export 'foo' not found. The requested module './cjs/a.js' is a CommonJS module, which may not support all module.exports as named exports. foo();
這是因?yàn)?ESM 獲取所指定的依賴模塊屬性(花括號(hào)內(nèi)部的屬性),是需要在編譯階段進(jìn)行靜態(tài)分析的,而 CJS 的腳本要在執(zhí)行階段才能計(jì)算出它們的 named exports 的值,會(huì)導(dǎo)致 ESM 在編譯階段無(wú)法進(jìn)行分析。
2.2 不同的模式
ESM 默認(rèn)使用了嚴(yán)格模式(use strict
),因此在 ES 模塊中的 this
不再指向全局對(duì)象(而是 undefined
),且變量在聲明前無(wú)法使用。
這也是為何在瀏覽器中,<script>
標(biāo)簽如要啟用原生引入 ES 模塊能力,必須加上 type="module"
告知瀏覽器應(yīng)當(dāng)把它和常規(guī) JS 區(qū)分開(kāi)來(lái)處理。
查看 ESM 嚴(yán)格模式的更多限制:
https://es6.ruanyifeng.com/#docs/module#%E4%B8%A5%E6%A0%BC%E6%A8%A1%E5%BC%8F
2.3 ESM 支持“頂級(jí) await”,但 CJS 不行。
ESM 支持頂級(jí) await
(top-level await),即 ES 模塊中,無(wú)須在 async
函數(shù)內(nèi)部就能直接使用 await
:
// index.mjs const { foo } = await import('./c.js'); foo();
到 Github 獲取示例代碼(test3):
https://github.com/VaJoy/BlogDemo3/tree/main/220220/test3
在 CSJ 模塊中是沒(méi)有這種能力的(即使使用了動(dòng)態(tài)的 import
接口),這也是為何 require
無(wú)法加載 ESM 的原因之一。
試想一下,一個(gè) CJS 模塊里的 require
加載器同步地加載了一個(gè) ES 模塊,該 ES 模塊里異步地 import
了一個(gè) CJS 模塊,該 CJS 模塊里又同步地去加載一個(gè) ES 模塊…… 這種復(fù)雜的嵌套邏輯處理起來(lái)會(huì)變得十分棘手。
查閱關(guān)于更多“如何實(shí)現(xiàn) require 加載 ESM”的討論:
https://github.com/nodejs/modules/issues/454
2.4 ESM 缺乏 __filename 和 __dirname
在 CJS 中,模塊的執(zhí)行需要用函數(shù)包起來(lái),并指定一些常用的值:
NativeModule.wrapper = [ '(function (exports, require, module, __filename, __dirname) { ', '\n});' ];
所以我們才可以在 CJS 模塊里直接用 __filename
、__dirname
。
而 ESM 的標(biāo)準(zhǔn)中不包含這方面的實(shí)現(xiàn),即無(wú)法在 Node 的 ESM 里使用 __filename
和 __dirname
。
從上方幾點(diǎn)可以看出,在 Node.js 中,如果要把默認(rèn)的 CJS 切換到 ESM,會(huì)存在巨大的兼容性問(wèn)題。
這也是 Node.js 目前,甚至未來(lái)很長(zhǎng)一段時(shí)間,都難以解決的一場(chǎng)模塊規(guī)范持久戰(zhàn)。
如果你希望不借助工具和規(guī)則,也能放寬心地使用 ESM,可以嘗試使用 Deno 替代 Node,它默認(rèn)采用了 ESM 作為模塊規(guī)范(當(dāng)然生態(tài)沒(méi)有 Node 這么完善)。
借助構(gòu)建工具可以實(shí)現(xiàn) CJS 模塊、ES 模塊的混用,甚至可以在同一個(gè)模塊同時(shí)混寫兩種規(guī)范的 API,讓開(kāi)發(fā)不再需要關(guān)心 Node.js 上面的限制。另外構(gòu)建工具還能利用 ESM 在編譯階段靜態(tài)解析的特性,實(shí)現(xiàn) Tree-shaking 效果,減少冗余代碼的輸出。
這里我們以 rollup 為例,先做全局安裝:
pnpm i -g rollup
接著再安裝 rollup-plugin-commonjs 插件,該插件可以讓 rollup 支持引入 CJS 模塊(rollup 本身是不支持引入 CJS 模塊的):
pnpm i --save-dev @rollup/plugin-commonjs
我們?cè)陧?xiàng)目根目錄新建 rollup 配置文件 rollup.config.js
:
import commonjs from 'rollup-plugin-commonjs'; export default { input: 'index.js', // 入口文件 output: { file: 'bundle.js', // 目標(biāo)文件 format: 'iife' }, plugins: [ commonjs({ transformMixedEsModules: true, sourceMap: false, }) ] };
plugin-commonjs
默認(rèn)會(huì)跳過(guò)所有含import/export
的模塊,如果要支持如import + require
的混合寫法,需要帶transformMixedEsModules
屬性。
接著執(zhí)行 rollup --config
指令,就能按照 rollup.config.js
進(jìn)行編譯和打包了。
示例
/** @file a.js **/ export let func = () => { console.log("It's an a-func..."); } export let deadCode = () => { console.log("[a.js deadCode] Never been called here"); } /** @file b.js **/ // named exports module.exports = { func() { console.log("It's a b-func...") }, deadCode() { console.log("[b.js deadCode] Never been called here"); } } /** @file c.js **/ module.exports.func = () => { console.log("It's a c-func...") }; module.exports.deadCode = () => { console.log("[c.js deadCode] Never been called here"); } /** @file index.js **/ let a = require('./a'); import { func as bFunc } from './b.js'; import { func as cFunc } from './c.js'; a.func(); bFunc(); cFunc();
打包后的 bundle.js
文件如下:
(function () { 'use strict'; function getAugmentedNamespace(n) { if (n.__esModule) return n; var a = Object.defineProperty({}, '__esModule', {value: true}); Object.keys(n).forEach(function (k) { var d = Object.getOwnPropertyDescriptor(n, k); Object.defineProperty(a, k, d.get ? d : { enumerable: true, get: function () { return n[k]; } }); }); return a; } let func$1 = () => { console.log("It's an a-func..."); }; let deadCode = () => { console.log("[a.js deadCode] Never been called here"); }; var a$1 = /*#__PURE__*/Object.freeze({ __proto__: null, func: func$1, deadCode: deadCode }); var require$$0 = /*@__PURE__*/getAugmentedNamespace(a$1); var b = { func() { console.log("It's a b-func..."); }, deadCode() { console.log("[b.js deadCode] Never been called here"); } }; var func = () => { console.log("It's a c-func..."); }; let a = require$$0; a.func(); b.func(); func(); })();
可以看到,rollup 通過(guò) Tree-shaking 移除掉了從未被調(diào)用過(guò)的 c 模塊的 deadCode
方法,但 a、b 兩模塊中的 deadCode
代碼段未被移除,這是因?yàn)槲覀冊(cè)谝?a.js
時(shí)使用了 require
,在 b.js
中使用了 CJS named exports,這些都導(dǎo)致了 rollup 無(wú)法利用 ESM 的特性去做靜態(tài)解析。
常規(guī)在開(kāi)發(fā)項(xiàng)目時(shí),還是建議盡量使用 ESM 的語(yǔ)法來(lái)書寫全部模塊,這樣可以最大化地利用構(gòu)建工具來(lái)減少最終構(gòu)建文件的體積。
以上就是“Node的CJS與ESM有哪些不同點(diǎn)”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,小編每天都會(huì)為大家更新不同的知識(shí),如果還想學(xué)習(xí)更多的知識(shí),請(qǐng)關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。
當(dāng)前題目:Node的CJS與ESM有哪些不同點(diǎn)
URL地址:http://aaarwkj.com/article28/gihdjp.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供建站公司、外貿(mào)網(wǎng)站建設(shè)、App設(shè)計(jì)、網(wǎng)站設(shè)計(jì)、品牌網(wǎng)站建設(shè)、品牌網(wǎng)站設(shè)計(jì)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)