當 ES Module
最開始作爲一種新的 JavaScript
模塊化方案在 ES6
中被引入的候,其實是通過在 import
語句中強制指定相對路徑或絕對路徑來實現的。
import dayjs from "https://cdn.skypack.dev/[email protected]"; // ES modules console.log(dayjs("2022-08-12").format("YYYY-MM-DD"));
這和其他常見的模塊化系統(例如 CommonJS
)的工作方式略有不同,並且在使用像 webpack
這樣的模塊打包工具的時候會使用更簡單的語法:
const dayjs = require('dayjs') // CommonJS import dayjs from 'dayjs'; // webpack
在這些系統裏,模塊導入語句通過 Node.js
運行時或相關構建工具映射到特定(版本)的文件。用戶只需要在 import
語句中直接編寫模塊說明符(通常是包名),模塊就可以自動處理。
由於開發人員已經熟悉了這種從 npm
導入包的方式,因此必須要先經過一個的構建步驟才能確保以這種方式編寫的代碼可以在瀏覽器中運行。
Import maps
就可以解決這個問題,它可以將模塊說明符(包名)自動映射到它的相對或絕對路徑。從而讓我們不使用構建工具也能使用簡潔的模塊導入語法。
如何使用 Import maps
我們可以通過 HTML
中的 <script type="importmap">
標籤來指定一個 Import maps
。
<script type="importmap"> { "imports": { "dayjs": "https://cdn.skypack.dev/[email protected]", } } </script>
爲了成功的在模塊解析之前對其進行解析。這個 script
標籤必須放在文檔中第一個 <script type="module">
標籤之前(最好放在 <head>
中),另外,目前每個 HTML
只允許編寫一個 Import maps
。
<script type="module"> import dayjs from 'dayjs'; console.log(dayjs("2022-08-12").format("YYYY-MM-DD"));</script>
在 script
標籤內,我們可以通過一個 JSON
對象來爲文檔中的腳本所需導入的模塊指定所有必要的映射。一個典型的 importmap
結構如下所示:
<script type="importmap"> { "imports": { "react": "https://cdn.jsdelivr.net/npm/react/umd/react.production.min.js", "react-dom": "https://cdn.jsdelivr.net/npm/react-dom/umd/react-dom.production.min.js" "square": "./modules/square.js", "lodash": "/node_modules/lodash-es/lodash.js" } } </script>
在上面的 import
對象中,每個屬性對應一個映射。映射的左側是導入說明符的名稱(一般是包名),而右側是說明符需要映射到的相對或絕對路徑。在映射中指定相對路徑時,必須要確保它們始終以 /、../或 ./
開頭。
另外,importmap
中聲明的包並不一定意味着它一定會被瀏覽器加載。頁面上的腳本沒有使用到的任何模塊都不會被瀏覽器加載,即便你在 importmap
中聲明瞭它。
編寫好 importmap
之後,你就可以在後面的腳本中直接使用 ES Module
語法了。
<script type="module"> import { cloneDeep } from 'lodash'; const objects = [{ a: 1 }, { b: 2 }]; const deep = cloneDeep(objects); console.log(deep[0] === objects[0]);</script>
外部映射
你還可以在外部文件中指定你的映射,然後使用 script
的 src
屬性鏈接到這個文件(Content-Type Header
必須要設置爲 application/importmap+json
才能正常加載)。
<script type="importmap" src="importmap.json"></script>
不過儘量不要使用這種方式,因爲它的性能比直接內聯編寫要差。
映射整個包
除了將一個說明符映射到模塊之外,你還可以將一個說明符映射到包含多個模塊的包:
<script type="importmap"> { "imports": { "lodash/": "/node_modules/lodash-es/" } } </script>
這種編寫方式可以讓你直接導入指定路徑中的任何模塊,相應的,瀏覽器也會把所有組件模塊下載下來。
<script type="module"> import toUpper from 'lodash/toUpper.js'; import toLower from 'lodash/toLower.js'; console.log(toUpper('ConardLi')); console.log(toLower('ConardLi'));</script>
動態映射
你也可以基於一些條件在 script 中添加一個動態映射,比如,在下面的示例中我們通過判斷是否存在 IntersectionObserver API
來導入不同文件:
<script> const importMap = { imports: { lazyload: 'IntersectionObserver' in window ? './lazyload.js' : './lazyload-fallback.js', }, }; const im = document.createElement('script'); im.type = 'importmap'; im.textContent = JSON.stringify(importMap); document.currentScript.after(im);</script>
使用同一模塊的不同版本
使用 importmap
我們可以將不同的版本的模塊映射到不同的包名中:
<script type="importmap"> { "imports": { "lodash@3/": "https://unpkg.com/[email protected]/", "lodash@4/": "https://unpkg.com/[email protected]/" } } </script>
另外你還可以通過 scopes
來實現同一個包不同模塊的更細粒度的版本控制:
<script type="importmap"> { "imports": { "lodash/": "https://unpkg.com/[email protected]/" }, "scopes": { "/static/js": { "lodash/": "https://unpkg.com/[email protected]/" } } }</script>
/static/js
下的模塊會使用 3.10.1
版本,而其他模塊會使用 4.17.21
版本。
兼容性
這項技術目前在 Chrome
和 Edge
瀏覽器 89
及更高版本提供了全面支持,但 Firefox、Safari
和一些移動瀏覽器還沒有支持。我們可以通過下面的代碼來判斷瀏覽器的支持情況:
if (HTMLScriptElement.supports && HTMLScriptElement.supports('importmap')) { // import maps is supported }
對於沒有提供支持的瀏覽器,我們可以使用下面這個 polyfill
:https://github.com/guybedford/es-module-shims
另外官方也推薦了一些其他 importmap
相關的 polyfill
和工具:
https://github.com/WICG/import-maps#community-polyfills-and-tooling