欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

教你徹底搞懂ESM與CJS互相轉(zhuǎn)換

 更新時間:2023年03月03日 11:29:40   作者:candyTong  
這篇文章主要為大家介紹了ESM與CJS互相轉(zhuǎn)換的理解與實現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

正文

ESM 和 CJS 是我們常用的模塊格式,兩種模塊系統(tǒng)具有不同的語法和加載機制。在項目中,我們可能會遇到 ESM 和 CJS 轉(zhuǎn)換的場景:

  • ESM 引入只支持 CJS 的庫
  • 開發(fā) npm 庫的時候,寫 ESM 然后編譯成 CJS。
  • ……

最近在項目中也剛好遇到的轉(zhuǎn)換上的一些問題,于是就研究了一下

本文將介紹 ESM 和 CJS 之間轉(zhuǎn)換,幫助大家加深對它們的了解,并從中了解它們之間轉(zhuǎn)換的細節(jié)與局限性

ESM 轉(zhuǎn) CJS

ESM 轉(zhuǎn) CJS 的使用場景非常常見,例如:

  • npm 庫,需要同時提供 ESM 和 CJS,供開發(fā)者自行選擇使用。一般是用 ESM 開發(fā),然后同時輸出 ESM 和 CJS
  • 使用 ESM 進行開發(fā),但最后由于兼容性、性能等原因,編譯成 CJS 在線上運行。例如:利用 Vite、webpack 等構(gòu)建工具進行開發(fā) 開發(fā)

各大工具,如 TSC、Babel、Vite、webpack、Rollup 等,都自帶了 ESM 轉(zhuǎn) CJS 的能力。

export 的轉(zhuǎn)換

  • 情況一,只有默認導(dǎo)出:
export default 666

Rollup 會轉(zhuǎn)換成

modules.exports = 666

很好理解,modules.exports 導(dǎo)出的整個東西就是默認導(dǎo)出嘛

用 CJS 引用該模塊的方式:

const lib = require('lib')
console.log(lib)
// 666
  • 情況二,只有命名導(dǎo)出:
export const a = 123
export const b = 234

轉(zhuǎn)換成

module.exports.a = 123
module.exports.b = 234

命名導(dǎo)出用 module.exports.xxx 一個個導(dǎo)出就行

用 CJS 引用該模塊的方式:

const {a, b} = require('lib')
console.log(a, b)
// 123 234
  • 情況三:默認導(dǎo)出和命名導(dǎo)出同時存在
export default 666
export const a = 123
export const b = 234

這時候會發(fā)現(xiàn),前面兩種情況的轉(zhuǎn)換思路不能用了,你不能這樣轉(zhuǎn)換

modules.exports = 666
module.exports.a = 123
module.exports.b = 234

畢竟 modules.exports 不是對象,因此設(shè)置不了屬性。

那莫得辦法了,只能這樣表示了:

  • module.exports.default 為默認導(dǎo)出
  • module.exports.xxx 其他為命名導(dǎo)出

為了跟前兩種情況做區(qū)分,因此還要新增一個標記__esModule

于是就會編譯成這樣的代碼:

+ Object.defineProperty(exports, '__esModule', { value: true })
+ module.exports.default = 666
- module.exports = 666
module.exports.a = 123
module.exports.b = 234

用 CJS 引用該模塊的方式:

const lib = require('lib')
console.log(lib.default, lib.a, lib.b)
// 666 123 234

在這種情況下,必須要用 .default 訪問默認導(dǎo)出

但這樣子看起來非常的別扭,但是沒有辦法,混用默認導(dǎo)出和命名導(dǎo)出是有代價的。

為什么我們項目中,從來就遇到過該問題?

一般情況下,我們使用 ESM 寫項目,然后編譯成 CJS

假如,我們寫的代碼引用了上述的代碼(默認導(dǎo)出和命名導(dǎo)出混用):

// foo.js
import lib from 'lib'
import {a, b} from 'lib'
console.log(lib, a, b)

這段代碼,會被轉(zhuǎn)換成:

'use strict';
var lib = require('lib');
function _interopDefault (e) { 
  return e && e.__esModule ? e : { default: e }; 
}
var lib__default = /*#__PURE__*/_interopDefault(lib);
console.log(lib__default.default, lib.a, lib.b);

_interopDefault 函數(shù)會自動根據(jù) __esModule,將導(dǎo)出對象標準化,使 .default 一定為默認導(dǎo)出

  • 如果有 __esModule,那就不用處理
  • 沒有 __esModule,就將其放到 default 屬性中,作為默認導(dǎo)出

工具在轉(zhuǎn)譯 lib.js 的同時,也會轉(zhuǎn)譯引入它的 foo.js,會加上標準化 require 對象的邏輯。

我們的項目,在編譯的時候,全部 ESM 模塊都轉(zhuǎn)為 CJS(不是只轉(zhuǎn)換一個,不轉(zhuǎn)另外一個) ,在這個過程中它自動屏蔽了模塊默認導(dǎo)出的差異,由于編譯工具已經(jīng)幫我們處理好,因此我們沒有任何感知。

如果我們直接寫 CJS,去引入 ESM 轉(zhuǎn)換后的 CJS,就需要自行處理該問題

要想盡量避免這種情況,建議全部都使用命名導(dǎo)出,由于沒有默認導(dǎo)出,就不需要擔心默認導(dǎo)出是 module.exports 還是 module.exports.default,都用以下方式進行引入即可:

const {a, b} = require('lib')

這樣開發(fā)者在任何情況下都沒有心智負擔。

import 的轉(zhuǎn)換

其實上一小節(jié)已經(jīng)講了

import lib from 'lib'
import {a, b} from 'lib'
console.log(lib, a, b)

會被轉(zhuǎn)換成

'use strict';
var lib = require('lib');
function _interopDefault (e) { 
  return e && e.__esModule ? e : { default: e }; 
}
var lib__default = /*#__PURE__*/_interopDefault(lib);
console.log(lib__default.default, lib.a, lib.b);

加上 _interopDefault,屏蔽了不同情況下默認導(dǎo)出的差異,因此如果所有代碼都是從 ESM 轉(zhuǎn) CJS,就不用擔心默認導(dǎo)出的差異問題。

小結(jié)

其實 ESM 轉(zhuǎn) CJS,不同的工具的輸出會稍微有些不同。以上是 Rollup 的的轉(zhuǎn)換方式,個人認為這種更為簡潔,而 TSC 的轉(zhuǎn)換則更復(fù)雜。

不過這些工具的思路都是相同的,都遵守 __esModule 的約定,標記 __esModule 的模塊默認導(dǎo)出是 .default

ESM 轉(zhuǎn) CJS 有哪些局限性?

存在以下情況可能無法進行轉(zhuǎn)換:

  • 存在循環(huán)依賴
  • import.meta,這個特性只能在 ESM 中使用

CJS 轉(zhuǎn) ESM

CJS 轉(zhuǎn) ESM 的場景不多,一般不會用 CJS 寫 npm 庫然后輸出 ESM;用 CJS 寫的庫,當時不會輸出 ESM。新寫的 npm 庫,一般來說也是用 ESM 寫。

因此一般只有寫 ESM 項目,引入了一個只有 CJS 的庫時,且編譯出 ESM 時,才會用到 CJS 轉(zhuǎn) ESM。

為什么我們用 webpack 寫 ESM,然后引入 CJS 的時候,基本上沒遇到什么問題?

要運行 ESM 引入 CJS 的代碼,有兩種方式:

  • 把 ESM 轉(zhuǎn) CJS,然后運行 CJS
  • 把 CJS 轉(zhuǎn)成 ESM,然后運行 ESM

因為 webpack 是前者,ESM 轉(zhuǎn) CJS 能夠很好地進行轉(zhuǎn)換。

CJS 轉(zhuǎn) ESM,沒有一種統(tǒng)一的轉(zhuǎn)換標準(相對來說,ESM 轉(zhuǎn) CJS 有 __esModule 約定),不同的工具和庫,可能轉(zhuǎn)換出來的結(jié)果是不一樣的,可能會導(dǎo)致代碼不兼容。

export 的轉(zhuǎn)換

場景一:

module.exports = {
    a: 3,
    b: 4
}

Rollup 會轉(zhuǎn)換成

var lib = {
    a: 3,
    b: 4
};
export { lib as default };

module.exports 會被當做默認導(dǎo)出

而 esbuild 會這樣轉(zhuǎn)換

var __getOwnPropNames = Object.getOwnPropertyNames;
var __commonJS = (cb, mod) => function __require() {
  return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
};
var require_lib = __commonJS({
  "src/cjs/lib.js"(exports, module) {
    module.exports = {
      a: 3,
      b: 4
    };
  }
});
export default require_lib();

esbuild 會給代碼包一層輔助函數(shù),然后將代碼搬過去就好了。好處是,這樣編譯工具就不需要考慮代碼的真正意義,直接簡單包一層即可

這種情況下,雖然 Rollup 和 esbuild 轉(zhuǎn)換的代碼不太相同,但代碼的運行結(jié)果是相同的

場景二:

exports.c =123

Rollup 會轉(zhuǎn)換成:

var lib = {};
var c = lib.c =123;
export { c, lib as default };

Rollup 會轉(zhuǎn)換成默認導(dǎo)出和命名導(dǎo)出。

esbuild 則轉(zhuǎn)換成:

var __getOwnPropNames = Object.getOwnPropertyNames;
var __commonJS = (cb, mod) => function __require() {
  return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
};
var require_lib = __commonJS({
  "src/cjs/lib.js"(exports) {
    exports.d = 666;
  }
});
export default require_lib();

仍然是包一層輔助函數(shù),但 esbuild 全部都當做默認導(dǎo)出

在這種情況下,Rollup 和 esbuild 轉(zhuǎn)換的代碼,其運行結(jié)果是不同的

場景三:

exports.d = 123
module.exports = {
    a: 3,
    b: 4
}
exports.c =123

exports.d = 123 其實是無效的

Rollup 會編譯成這樣:

var libExports = {};
var lib$1 = {
  get exports(){ return libExports; },
  set exports(v){ libExports = v; },
};
(function (module, exports) {
	exports.d =123;
	module.exports = {
	    a: 3,
	    b: 4
	};
	exports.c =123;
} (lib$1, libExports));
var lib = libExports;
export { lib as default };

此時 Rollup 也會加上一層輔助函數(shù)

而 esbuild 仍然是加一層輔助函數(shù)

var __getOwnPropNames = Object.getOwnPropertyNames;
var __commonJS = (cb, mod) => function __require() {
  return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
};
var require_lib = __commonJS({
  "src/cjs/lib.js"(exports, module) {
    exports.d = 666;
    module.exports = {
      a: 3,
      b: 4
    };
    exports.c = 666;
  }
});
export default require_lib();

輔助函數(shù)的好處之前也說了,不需要關(guān)注代碼邏輯,可以看到,即使 exports.d = 666; 是一行無效語句,照樣執(zhí)行也是沒有問題的,不需要先分析出代碼的語義。

總體對比下來,esbuild 的處理還是相對簡單的

require 的轉(zhuǎn)換

const lib = require('./lib')
const {c} = require('./lib')
console.info(lib,c)

Rollup 轉(zhuǎn)換成:

import require$$0 from './lib';
const lib = require$$0;
const {c} = require$$0;
console.info(lib,c);

require 的轉(zhuǎn)換比較簡單,不管你解不解構(gòu),反正我就只有默認引入

而 esbuild。。。還不支持,干脆就報錯了

小結(jié)

為什么工具的轉(zhuǎn)換結(jié)果是不同的?

CJS 轉(zhuǎn)換成 ESM 是有歧義的

module.export.a = 123
module.export.b = 345

等價于

module.export = {
    a: 123,
    b: 345,
}

那么它是默認導(dǎo)出,還是命名導(dǎo)出呢?都行

本質(zhì)上,是因為 CJS 只有一個導(dǎo)出方式,不確定它對應(yīng)的是 ESM 的命名導(dǎo)出還是默認導(dǎo)出。

用一個形象點的例子就是,女朋友回了一句哦,但是你不知道女朋友是想說肯定的意思,還是表示無語的意思、還是其他別的意思。。。

對于 require

const {c} = require('./lib')

你說這個是默認導(dǎo)入呢?還是命名導(dǎo)入?好像也都行。。。

正是由于這個歧義,且沒有一個標準去規(guī)范這個轉(zhuǎn)換行為,因此不同工具的轉(zhuǎn)換結(jié)果是不同的

CJS 轉(zhuǎn)換成 ESM 有哪些局限性?

  • 不同工具的轉(zhuǎn)換結(jié)果不同
  • CJS 模塊可以使用 require.resolve 方法查找模塊的路徑,而 ESM 模塊不可以
  • CJS 模塊可以導(dǎo)入和導(dǎo)出非 JavaScript 文件,例如 JSON
  • CJS 在運行時導(dǎo)入導(dǎo)出,支持運行時改變導(dǎo)入導(dǎo)出的內(nèi)容,以下代碼是合法的:
module.exports.a = 123
if( Date.now() % 2){
    module.exports.b = 234
}

由于沒有統(tǒng)一的標準,CJS 轉(zhuǎn) ESM 的工具,相對來說少了很多,目前僅有少量工具能夠進行轉(zhuǎn)換,esbuildbabel-plugin-transform-commonjs、@rollup/commonjs。

有時候 Vite 使用一些 CJS 包不兼容,也是因為有些 CJS 轉(zhuǎn)不了 ESM。但幸運的是,目前大部分常見的 npm 包,都已經(jīng)支持 ESM,或者能夠比較好的被轉(zhuǎn)換成 ESM,因此也不需要太擔心 Vite 的問題。

以上就是教你徹底搞懂ESM與CJS互相轉(zhuǎn)換的詳細內(nèi)容,更多關(guān)于ESM與CJS互相轉(zhuǎn)換的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • NodeJs?Express路由使用流程解析

    NodeJs?Express路由使用流程解析

    路由路徑和請求方法一起定義了請求的端點,它可以是字符串、字符串模式或者正則表達式。后端在獲取路由后,可通過一系列類似中間件的函數(shù)去執(zhí)行事務(wù)
    2023-01-01
  • Node.JS中事件輪詢(Event Loop)的解析

    Node.JS中事件輪詢(Event Loop)的解析

    對NodeJs的事情輪詢機造一孔之見。查閱了些許材料后,總算掀開了其神奇的里紗。下面這篇文章主要介紹了Node.JS中事件輪詢(Event Loop)的相關(guān)資料,需要的朋友可以參考借鑒,下面來一起看看吧。
    2017-02-02
  • Mac下通過brew安裝指定版本的nodejs教程

    Mac下通過brew安裝指定版本的nodejs教程

    今天小編就為大家分享一篇Mac下通過brew安裝指定版本的nodejs教程,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-05-05
  • nodeJs實現(xiàn)基于連接池連接mysql的方法示例

    nodeJs實現(xiàn)基于連接池連接mysql的方法示例

    這篇文章主要介紹了nodeJs實現(xiàn)基于連接池連接mysql的方法,結(jié)合具體實例形式分析了nodejs連接池操作mysql數(shù)據(jù)庫連接的實現(xiàn)與使用技巧,需要的朋友可以參考下
    2018-02-02
  • Node.js+Express+Mysql 實現(xiàn)增刪改查

    Node.js+Express+Mysql 實現(xiàn)增刪改查

    這篇文章主要介紹了Node.js+Express+Mysql 實現(xiàn)增刪改查,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2019-04-04
  • 利用nodejs讀取圖片并將二進制數(shù)據(jù)轉(zhuǎn)換成base64格式

    利用nodejs讀取圖片并將二進制數(shù)據(jù)轉(zhuǎn)換成base64格式

    這篇文章主要介紹了利用nodejs讀取圖片并將二進制數(shù)據(jù)轉(zhuǎn)換成base64格式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-08-08
  • node.js使用stream模塊實現(xiàn)自定義流示例

    node.js使用stream模塊實現(xiàn)自定義流示例

    這篇文章主要介紹了node.js使用stream模塊實現(xiàn)自定義流,結(jié)合實例形式詳細分析了node.js基于stream模塊實現(xiàn)自定義的可讀流、可寫流、可讀寫流等相關(guān)操作技巧,需要的朋友可以參考下
    2020-02-02
  • nodejs制作小爬蟲功能示例

    nodejs制作小爬蟲功能示例

    這篇文章主要介紹了nodejs制作小爬蟲功能,結(jié)合實例形式分析了node.js安裝request、cheerio模塊及請求發(fā)送、數(shù)據(jù)庫操作等相關(guān)實現(xiàn)技巧,需要的朋友可以參考下
    2020-02-02
  • 利用Node.js和MySQL實現(xiàn)創(chuàng)建API服務(wù)器

    利用Node.js和MySQL實現(xiàn)創(chuàng)建API服務(wù)器

    這篇文章主要為大家詳細介紹了如何使用Node.js和MySQL創(chuàng)建API服務(wù)器的步驟,這也是從前端邁向全棧的一個開始,文中的示例代碼講解詳細,感興趣的小伙伴可以了解下
    2024-01-01
  • Node.js全局可用變量、函數(shù)和對象示例詳解

    Node.js全局可用變量、函數(shù)和對象示例詳解

    JavaScript中有一個特殊的對象,稱為全局對象(Global Object),它及其所有屬性都可以在程序的任何地方訪問,即全局變量,下面這篇文章主要給大家介紹了關(guān)于Node.js全局可用變量、函數(shù)和對象的相關(guān)資料,需要的朋友可以參考下
    2023-03-03

最新評論