Vite: 高階特性 Pure ESM

概述

  • ESM 已經逐步得到各大瀏覽器廠商以及 Node.js 的原生支持,正在成為主流前端模塊化方案。
    而 Vite 本身就是借助瀏覽器原生的 ESM 解析能力( type=“module” )實現了開發階段的 no-bundle ,即不用打包也可以構建 Web 應用。不過我們對于原生 ESM 的理解僅僅停留在 type=“module” 這個特性上面未免有些狹隘了,一方面瀏覽器和 Node.js 各自提供了不同的 ESM 使用特性,如 import maps 、package.json 的 imports 和exports 屬性等等,另一方面前端社區開始逐漸向 ESM 過渡,有的包甚至僅留下 ESM產物, Pure ESM 的概念隨之席卷前端圈,而與此同時,基于 ESM 的 CDN 基礎設施也如雨后春筍般不斷涌現,諸如 esm.sh 、 skypack 、 jspm 等等。
  • 因此你可以看到,ESM 已經不僅僅局限于一個模塊規范的概念,它代表了前端社區生態的走向以及各項前端基礎設施的未來,不管是瀏覽器、Node.js 還是 npm 上第三方包生態的發展,無一不在印證這一點。那么,作為一名 2022 年的前端,我覺得深入地了解ESM 的高級特性、社區生態都是有必要的,一方面彌補自己對于 ESM 認知上的不足,另一方面也能享受到社區生態帶給我們的紅利。在接下來的內容中,我將給你詳細介紹瀏覽器和 Node.js 中基于 ESM 實現的一些 高級特性 ,然后分析什么是 Pure ESM 模式,這種模式下存在哪些痛點,以及我們作為開發者,如何去擁抱 Pure ESM 的趨勢。

高階特性


1 )import map

  • 在瀏覽器中我們可以使用包含 type=“module” 屬性的 script 標簽來加載 ES 模塊,而模塊路徑主要包含三種:
    • 絕對路徑,如 https://cdn.skypack.dev/react
    • 相對路徑,如 ./module-a
    • bare import 即直接寫一個第三方包名,如 react 、 lodash
  • 對于前兩種模塊路徑瀏覽器是原生支持的,而對于 bare import ,在 Node.js 能直接執行,因為 Node.js 的路徑解析算法會從項目的 node_modules 找到第三方包的模塊路徑,但是放在瀏覽器中無法直接執行。而這種寫法在日常開發的過程又極為常見,除了將bare import 手動替換為一個絕對路徑,還有其它的解決方案嗎?
  • 答案是有的。現代瀏覽器內置的 import map 就是為了解決上述的問題,我們可以用一個簡單的例子來使用這個特性
    <!DOCTYPE html>
    <html lang="en">
    <head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
    </head>
    <body><div id="root"></div><script type="importmap">{"imports": {"react": "https://cdn.skypack.dev/react"}}</script><script type="module">import React from 'react';console.log(React)</script>
    </body>
    </html>
    
  • 在瀏覽器中執行這個 HTML,如果正常執行,那么你可以看到瀏覽器已經從網絡中獲取
    了 react 的內容,如下圖所示
  • 注意: importmap 可能存在瀏覽器兼容性問題,這里出現瀏覽器報錯也屬于正常情況,后文會介紹解決方案。

在這里插入圖片描述

  • 在支持 import map 的瀏覽器中,在遇到 type=“importmap” 的 script 標簽時,瀏覽器會記錄下第三方包的路徑映射表,在遇到 bare import 時會根據這張表拉取遠程的依賴代碼。如上述的例子中,我們使用 skypack 這個第三方的 ESM CDN 服務,通過 https://cdn.skypack.dev/react 這個地址我們可以拿到 React 的 ESM 格式產物。import map 特性雖然簡潔方便,但瀏覽器的兼容性卻是個大問題,在 CanIUse 上的兼容性數據如下:

請添加圖片描述

  • 它只能兼容市面上 68% 左右的瀏覽器份額,而反觀 type=“module” 的兼容性(兼容 95%
    以上的瀏覽器), import map 的兼容性實屬不太樂觀。但幸運的是,社區已經有了對應的
    Polyfill 解決方案——es-module-shims,完整地實現了包含 import map 在內的各大ESM 特性,還包括:
    • dynamic import 。即動態導入,部分老版本的 Firefox 和 Edge 不支持

    • import.meta 和 import.meta.url 。當前模塊的元信息,類似 Node.js 中的 __dirname 、 __filename

    • modulepreload 。以前我們會在 link 標簽中加上 rel=“preload” 來進行資源預加載,即在瀏覽器解析 HTML 之前就開始加載資源,現在對于 ESM 也有對應的modulepreload 來支持這個行為

    • JSON Modules 和 CSS Modules ,即通過如下方式來引入 json 或者 css :

      <script type="module">
      // 獲取 json 對象
      import json from 'https://site.com/data.json' assert { type: 'json' };
      // 獲取 CSS Modules 對象
      import sheet from 'https://site.com/sheet.css' assert { type: 'css' };
      </script>
      
  • 值得一提的是, es-module-shims 基于 wasm 實現,性能并不差,相比瀏覽器原生的行
    為沒有明顯的性能下降, 可以去這個地址查看具體的 benchmark 結果
  • 由此可見, import map 雖然并沒有得到廣泛瀏覽器的原生支持,但是我們仍然可以通過Polyfill 的方式在支持 type=“module” 的瀏覽器中使用 import map

2 ) Nodejs 包導入導出策略

  • 在 Node.js 中( >=12.20 版本 )有一般如下幾種方式可以使用原生 ES Module:

    • 文件以 .mjs 結尾;
    • package.json 中聲明 type: “module” 。
  • 那么,Nodejs 在處理 ES Module 導入導出的時候,如果是處理 npm 包級別的情況,其中的細節可能比你想象中更加復雜。

  • 首先來看看如何導出一個包,你有兩種方式可以選擇: main 和 exports 屬性。這兩個屬性均來自于 package.json ,并且根據 Node 官方的 resolve 算法,exports 的優先級 main 更高,也就是說如果你同時設置了這兩個屬性,那么 exports 會優先生效。

  • main 的使用比較簡單,設置包的入口文件路徑即可,如: "main": "./dist/index.js"

  • 需要重點梳理的是 exports 屬性,它包含了多種導出形式: 默認導出 、 子路徑導出 和 條件導出 ,這些導出形式如以下的代碼所示:

    // package.json
    {"name": "package-a","type": "module","exports": {// 默認導出,使用方式: import a from 'package-a'".": "./dist/index.js",// 子路徑導出,使用方式: import d from 'package-a/dist'"./dist": "./dist/index.js","./dist/*": "./dist/*", // 這里可以使用 `*` 導出目錄下所有的文件// 條件導出,區分 ESM 和 CommonJS 引入的情況"./main": {"import": "./main.js","require": "./main.cjs"},}
    }
    
  • 其中,條件導出可以包括如下常見的屬性:

    • node : 在 Node.js 環境下適用,可以定義為嵌套條件導出,如:
      {"exports": {{".": {"node": {"import": "./main.js","require": "./main.cjs"}     }}},
      }
      
    • import : 用于 import 方式導入的情況,如 import(“package-a”) ;
    • require : 用于 require 方式導入的情況,如 require(“package-a”) ;
    • default ,兜底方案,如果前面的條件都沒命中,則使用 default 導出的路徑
  • 當然,條件導出還包含 types 、 browser 、 develoment 、 production 等屬性,大家可以參考 Node.js 的詳情文檔,這里就不一一贅述了。

  • 在介紹完"導出"之后,我們再來看看 “導入” ,也就是 package.json 中的 imports 字段,一般是這樣聲明的:

    {"imports": {// key 一般以 # 開頭// 也可以直接賦值為一個字符串: "#dep": "lodash-es""#dep": {"node": "lodash-es","default": "./dep-polyfill.js"},},"dependencies": {"lodash-es": "^4.17.21"}
    }
    
  • 這樣你可以在自己的包中使用下面的 import 語句:

    // index.js
    import { cloneDeep } from "#dep";
    const obj = { a: 1 };
    // { a: 1 }
    console.log(cloneDeep(obj));
    
  • Node.js 在執行的時候會將 #dep 定位到 lodash-es 這個第三方包,當然,你也可以將其定位到某個內部文件。這樣相當于實現了 路徑別名 的功能,不過與構建工具中的 alias 功能不同的是,“imports” 中聲明的別名必須全量匹配,否則 Node.js 會直接拋錯。

Pure ESM


首先,什么是 Pure ESM ? Pure ESM 最初是在 Github 上的一個帖子中被提出來的,其中有兩層含義,一個是讓 npm 包都提供 ESM 格式的產物,另一個是僅留下 ESM 產物,拋棄 CommonJS 等其它格式產物


1 ) 對 Pure ESM 的態度

  • 當這個概念被提出來之后社區當中出現了很多不同的聲音,有人贊成,也有人不滿。但不
    管怎么樣,社區中的很多 npm 包已經出現了 ESM First 的趨勢,可以預見的是越來越多的包會提供 ESM 的版本,來擁抱社區 ESM 大一統的趨勢,同時也有一部分的 npm包做得更加激進,直接采取 Pure ESM 模式,如大名鼎鼎的 chalk 和 imagemin ,最新版本中只提供 ESM 產物,而不再提供 CommonJS 產物。對于 Pure ESM,我們到底應該支持還是反對呢?

  • 首先拋出結論:

    • 對于沒有上層封裝需求的大型框架,如 Nuxt、Umi,在保證能上 Pure ESM 的情況下,直接上不會有什么問題
    • 但如果是一個底層基礎庫,最好提供好 ESM 和 CommonJS 兩種格式的產物
  • 接下來,我們就來分析這個結論是怎么得出來的, 在 ESM 中,我們可以直接導入 CommonJS 模塊,如

    // react 僅有 CommonJS 產物
    import React from 'react';
    console.log(React)
    
  • Node.js 執行以上的原生 ESM 代碼并沒有問題,但反過來,如果你想在 CommonJS 中
    require 一個 ES 模塊,就行不通了:
    在這里插入圖片描述

  • 其根本原因在于 require 是同步加載的,而 ES 模塊本身具有異步加載的特性,因此兩者
    天然互斥,即我們無法 require 一個 ES 模塊

  • 那是不是在 CommonJS 中無法引入 ES 模塊了呢? 也不盡然,我們可以通過 dynamic import 來引入:
    在這里插入圖片描述

  • 不知道你注意到沒有,為了引入一個 ES 模塊,我們必須要將原來同步的執行環境改為 異步 的,這就帶來如下的幾個問題:

    • 如果執行環境不支持異步,CommonJS 將無法導入 ES 模塊;
    • jest 中不支持導入 ES 模塊,測試會比較困難;
    • 在 tsc 中,對于 await import() 語法會強制編譯成 require 的語法(詳情),只能靠 eval(‘await import()’) 繞過去。
  • 總而言之,CommonJS 中導入 ES 模塊比較困難。因此,如果一個基礎底層庫使用 Pure ESM ,那么潛臺詞相當于你依賴這個庫時(可能是直接依賴,也有可能是間接依賴),你自己的庫/應用的產物最好為 ESM 格式。也就是說, Pure ESM 是具有傳染性的,底層的庫出現了 Pure ESM 產物,那么上層的使用方也最好是 Pure ESM,否則會有上述的種種限制。

  • 但從另一個角度來看,對于大型框架(如 Nuxt)而言,基本沒有二次封裝的需求,框架本身如果能夠使用 Pure ESM ,那么也能帶動社區更多的包(比如框架插件)走向 Pure ESM,同時也沒有上游調用方的限制,反而對社區 ESM 規范的推動是一件好事情。

  • 當然,上述的結論也帶來了一個潛在的問題: 大型框架畢竟很有限,npm 上大部分的包還是屬于基礎庫的范疇,那對于大部分包,我們采用導出 ESM/CommonJS 兩種產物的方案,會不會對項目的語法產生限制呢?

  • 我們知道,在 ESM 中無法使用 CommonJS 中的 __dirname 、 __filename 、require.resolve 等全局變量和方法,同樣的,在 CommonJS 中我們也沒辦法使用ESM 專有的 import.meta 對象,那么如果要提供兩種產物格式,這些模塊規范相關的語法怎么處理呢?在傳統的編譯構建工具中,我們很難逃開這個問題,但新一代的基礎庫打包器 tsup 給了我們解決方案

2 ) 新一代的基礎庫打包器

  • tsup 是一個基于 Esbuild 的基礎庫打包器,主打無配置(no config)打包。借助它我們可以輕易地打出 ESM 和 CommonJS 雙格式的產物,并且可以任意使用與模塊格式強相關的一些全局變量或者 API,比如某個庫的源碼如下:
    export interface Options {data: string;
    }
    export function init(options: Options) {console.log(options);console.log(import.meta.url);
    }
    
  • 由于代碼中使用了 import.meta 對象,這是僅在 ESM 下存在的變量,而經過 tsup 打包后的 CommonJS 版本卻被轉換成了下面這樣:
    var getImportMetaUrl = () =>typeof document === "undefined" ?new URL("file:" + __filename).href :(document.currentScript && document.currentScript.src) ||new URL("main.js", document.baseURI).href;
    var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
    // src/index.ts
    function init(options) {console.log(options);console.log(importMetaUrl);
    }
    
  • 可以看到,ESM 中的 API 被轉換為 CommonJS 對應的格式,反之也是同理。最后,我們可以借助之前提到的條件導出,將 ESM、CommonJS 的產物分別進行導出,如下所示
    {"scripts": {"watch": "npm run build -- --watch src","build": "tsup ./src/index.ts --format cjs,esm --dts --clean"},"exports": {".": {"import": "./dist/index.mjs","require": "./dist/index.js",// 導出類型"types": "./dist/index.d.ts"}}
    }
    
  • tsup 在解決了雙格式產物問題的同時,本身利用 Esbuild 進行打包,性能非常強悍,也能生成類型文件,同時也彌補了 Esbuild 沒有類型系統的缺點,還是非常推薦大家使用的
  • 當然,回到 Pure ESM 本身,我覺得這是一個未來可以預見的趨勢,但對于基礎庫來說,現在并不適合切到 Pure ESM ,如今作為過渡時期,還是發 ESM/CommonJS 雙格式的包較為靠譜,而 tsup 這種工具能降低基礎庫構建上的成本。當所有的庫都有 ESM 產物的時候,我們再來落地 Pure ESM 就輕而易舉了

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/38300.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/38300.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/38300.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

綜合評價類模型——突變級數法

含義 首先&#xff1a;對評價目標進行多層次矛盾分解其次&#xff1a;利用突變理論和模糊數學相結合產生突變模糊隸屬函數再次&#xff1a;由歸一公式進行綜合量化運算最終&#xff1a;歸一為一個參數&#xff0c;即求出總的隸屬函數&#xff0c;從而對評價目標進行排序分析特點…

【linux/shell實戰案例】shell中變量的使用

目錄 一.linux變量聲明及定義 二.linux變量使用方法 三.linux變量使用花括號${name}和雙引號“$name”的區別 四.linux變量使用單引號$name和雙引號“$name”的區別 五.linux變量中使用命令 一.linux變量聲明及定義 #!/bin/bash namezhaodabao 等號兩邊不能有空格變量名…

ES6面試題——箭頭函數和普通函數有什么區別

1. this指向問題 <script> let obj {a: function () {console.log(this); // 打印出&#xff1a;{a: ?, b: ?}},b: () > {console.log(this); // 打印出Window {window: Window, self: Window,...}}, }; obj.a(); obj.b(); </script> 箭頭函數中的this是在箭…

成都市水資源公報(2000-2022年)

數據年限&#xff1a;2000-2022年&#xff0c;無2009年 數據格式&#xff1a;pdf、word、jpg 數據內容&#xff1a;降水量、地表水資源量、地下水資源量、水資源總量、蓄水狀況、平原區淺層地下水動態、水資源情況分析、供水量、用水量、污水處理、洪澇干旱等

類似李跳跳的軟件有什么,強烈推薦所有安卓手機安裝!!!

今天阿星分享一款讓安卓手機更順滑的神器——智慧島。你問我李跳跳&#xff1f;由于大家都知道的原因&#xff0c;那是個曾經讓廣告無處遁形的神兵利器&#xff0c;可惜現在它已經退休了。不過別擔心&#xff0c;智慧島接過了接力棒&#xff0c;繼續為我們的安卓體驗保駕護航。…

Raccon:更好防側信道攻擊的后量子簽名方案

1. 引言 安全社區已經開發出了一些出色的加密算法&#xff0c;這些算法非常安全&#xff0c;但最終&#xff0c;所有的數據都會被存儲在硅和金屬中&#xff0c;而入侵者越來越多地會在那里放置監視器來破解密鑰。 破解加密密鑰通常涉及暴力破解方法或利用實施過程中的缺陷。然…

2029年AI服務器出貨量將突破450萬臺,AI推理服務器即將爆發式增長

在2020年&#xff0c;新冠疫情與遠程辦公模式的興起推動了所有類型服務器的出貨量達到峰值&#xff0c;隨后幾年里&#xff0c;除了AI服務器之外的所有類別都回歸到了正常水平。 根據Omdia的研究數據&#xff0c;AI服務器的出貨量在2020年急劇上升&#xff0c;并且至今未顯示出…

瀏覽器中如何獲取用戶網絡狀態

網頁開發中存在需要獲取用戶是否在線的場景及用戶網絡狀態&#xff0c;瀏覽器提了navigator.onLine和navigator.connection可以實現這一需求。 獲取在線狀態 if (navigator.onLine) {console.log("online"); } else {console.log("offline"); }監聽網絡狀…

日志的介紹

知識鋪墊&#xff1a;在我們日常開發中&#xff0c;其實日志是和我們息息相關的。但可能平常都沒怎么注意到日志相關的知識點&#xff0c;也不怎么關注日志&#xff0c;然后&#xff0c;在生產環境中&#xff0c;日志是必不可少的存在&#xff0c;項目出現問題了都是通過日志來…

cesium 添加 Echarts 圖層(空氣質量點圖)

cesium 添加 Echarts 圖層(下面附有源碼) 1、實現思路 1、在scene上面新增一個canvas畫布 2、通坐標轉換,將經緯度坐標轉為屏幕坐標來實現 3、將ecarts 中每個series數組中元素都加 coordinateSystem: ‘cesiumEcharts’ 2、示例代碼 <!DOCTYPE html> <html lan…

Excel 數據篩選難題解決

人不走空 &#x1f308;個人主頁&#xff1a;人不走空 &#x1f496;系列專欄&#xff1a;算法專題 ?詩詞歌賦&#xff1a;斯是陋室&#xff0c;惟吾德馨 目錄 &#x1f308;個人主頁&#xff1a;人不走空 &#x1f496;系列專欄&#xff1a;算法專題 ?詩詞歌…

緩存穿透、雪崩與擊穿

緩存穿透、雪崩、擊穿 1、緩存穿透強調都沒有數據并發訪問布隆過濾器緩存NULL值 2、緩存雪崩強調批量Key過期并發訪問 3、緩存擊穿強調單個Key過期并發訪問互斥鎖邏輯過期 分布式并發控制 1、緩存穿透 緩存穿透是指數據庫和緩存都沒有的數據&#xff0c;這樣緩存永遠不會生效&…

圖形化用戶界面-java頭歌實訓

圖形化用戶界面 import java.awt.*; import javax.swing.*; public class GraphicsTester extends JFrame { public GraphicsTester() { super("Graphics Demo"); setSize(480, 300); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } public void paint…

服務器raid5壞盤-換盤-修復陣列過程

目錄 背景原因分析解決步驟名詞解釋進入raid管理界面換回舊4號&#xff0c;進行import再次更換4號盤 總結 背景 服務器除塵之后文件服務器部分文件不能訪問了,部分文件夾內容為空&#xff0c;起初以為是新配置的權限的問題&#xff0c;排查之后發現不僅僅是權限問題 jumpserv…

ISA95-標準2-數據字典部分的解析與設計指南

在 MES/MOM 系統中,ISA-95 第二部分的數據字典扮演著至關重要的角色,它確保了數據的一致性和準確性,為不同系統間的數據交換提供了標準化的術語和定義。以下是 MES/MOM 系統實現 ISA-95 第二部分數據字典的具體概念、功能模塊以及應用場景: 一、概念、功能模塊以及應用場景…

numpy - array(4)

arr1 np.array([[1, 2], [3, 4], [5, 6]]) &#xff08;1&#xff09;def insert(arr, obj, values, axisNone) 向array指定位置插入指定值 axis為默認值None時&#xff0c;如果array是多維數據,則先將array轉化成向量obj&#xff1a;插入的索引&#xff0c;接受int或者多…

VTK學習日志:基于VTK9.3.0+Visual Studio c++實現DICOM影像MPR多平面重建+V R體繪制4個視圖展示功能的實現(二)

前段時間對VTK9.3.0進行了編譯&#xff0c;開發了MPRVR實現的demo,顯示效果不是很理想&#xff0c;正好趁著周末有時間&#xff0c;再度對之前的程序進行優化和完善&#xff0c;先展示下效果&#xff1a; VTK實現MPRVR四視圖 再次講解下基于VTK的MPRVR實現的簡單項目創建過程&a…

linux守護進程生命周期管理-supervisord

簡介 supervisor是一個client/server系統,允許用戶控制多個類unix系統的進程,擺脫rc.d腳本的不方便性.supervisor具有簡單,集中化管理,搞笑,可擴展性,高兼容. 整套軟件包含:supervisord(守護進程),supervisorctl(命令行工具),web server(一個web交互界面),XML-RPC 交互 安裝 …

git回退commit的方式

在Git中&#xff0c;回退commit&#xff08;即撤銷之前的提交&#xff09;可以通過多種方式來實現。以下是一些常見的方法&#xff0c;以及它們的詳細步驟和注意事項&#xff1a; ### 1. 使用git revert命令 git revert命令用于撤銷某次commit&#xff0c;但它并不會刪除該comm…

FFmpeg 硬件編碼加速文檔介紹

介紹 硬件訪問:許多平臺提供了對專用硬件的訪問,這些硬件可以用于執行解碼、編碼或過濾等視頻相關操作。 性能與資源使用:使用硬件可以加快某些操作的速度或減少其他資源(特別是CPU)的使用,但可能會產生不同的結果或質量較低,或帶來在使用純軟件時不存在的額外限制。 硬…