有了importmap还需要构建工具实现模块导入?

505 阅读4分钟

一、前言

当 ES modules 作为一种 JavaScript 标准的模块化规范首次在 ECMAScript 2015 中被提出的时候,其实是通过在 import 语句中强制指定相对或绝对路径来实现的,比如下面这种方式:

import {sum} from 'https://unpkg.com/lodash-es@4.17.21';
console.log(sum([1, 2, 3]))

这与其他常见的模块化系统的工作方式有所不同,比如 CommonJS,当我们使用 CommonJS 导入 lodash 时我们可以这样:

const lodash = require('lodash'); // CommonJS

在 require 中我们可以直接写入包的名称就行,这是因为在 Node 环境中他有自己的一套包查找规范,在 Node 运行时他会从项目中找到特定版本的依赖文件并加载进来,由于我们开发人员已经熟悉了这种从 npm 导入包的方式,所以在开发环境下要想在 ESM 规范下使用这种导包方式就必须先经过 bunder(webpack、rollup) 的编译构建步骤才能确保以这种方式编写的代码可以在浏览器上运行,所以像 webpack 这种构建工具他们需要自己实现了一套 ESM。

作为一个官方提出的规范,ES Module 已经得到了现代浏览器的内置支持。在现代浏览器中,如果在 HTML 中加入含有type="module"属性的 script 标签,那么浏览器会按照 ES Module 规范来进行依赖加载和模块解析:

<script type="module">  
  import {sum} from 'https://unpkg.com/lodash-es@4.17.21';
  console.log(sum([1, 2, 3])) 
</script>

这样虽然可以正常使用,但是很不贴合我们开发者的习性,现在我们今天的主角 import map 就登场了,它可以将我们导入的模块名称映射到他的相对或绝对路径上,从而让我们在不使用构建工具时也能使用简介的模块导入语法。

二、如何使用 Import maps

1、基本使用

我们可以通过 HTML 中的 <script type="importmap"> 标签来指定一个 Import maps

<script type="importmap">
{
  "imports": {
    "lodash": "https://unpkg.com/lodash-es@4.17.21",
  }
}
</script>
<script type="module">  
  import {sum} from 'lodash';
  console.log(sum([1, 2, 3])) 
</script>

为了在加载模块之前对 import maps 进行解析,这个 script 标签必须放在文档中第一个 <script type="module"> 之前,除此之外,每一个 HTML 中只能出现一个 import maps,但是这个限制将会在未来被取消,目前官方面临的最大挑战就是如何去处理多个映射,详细信息点击这里

同样的我们在 script 标签里也可以通过一个 JSON 对象来为需要导入的模块指定映射,一个典型的结构如下:

<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>

在上面这个对象里面,每个属性都对应一个映射,映射的左侧就是模块的名称,而右侧就是模块的相对或绝对路径。在指定相对路径的时候我们要确保这些路径始终是以 /、 ../ 或 ./ 开头。

另外,我们在 importmap 中声明的包并不一定意味着它一定会被浏览器加载,页面上没有使用的包是不会被加载的,即使我们在 impoermap 中声明了。

2、外部映射

除了直接在 script 中声明映射之外,我们还可以在外部文件中指定映射,然后使用 script 的 scr 属性链接到这个文件,如果你决定用这种方式,请确保发送文件时将其 Content-Type 设置为 application/importmap+json

<script type="importmap" src="importmap.json"></script>

但是要注意的是尽量不要使用这种方式,因为它的性能要比内联模式要差。

3、包的斜杠语法

在 JavaScript 生态系统中,一个包包含多个模块或者其他文件是很常见的,对于这种情况,加入我们像映射包的某个模块可以使用包的斜杠语法:

<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('xxx'));  
  console.log(toLower('xxx'));  
</script>

这种编写方式可以让你直接导入指定路径中的任何模块。

4、动态映射

我们也可以给予一些条件动态的去添加映射,比如下面的例子中我们通过判断是否存在 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>

5、使用不同版本的模块

import maps 中我们可以将多个版本的包影射进来:

<script type="importmap">  
  {  
    "imports": {  
      "lodash@3/""https://unpkg.com/lodash-es@3.10.1/",  
      "lodash@4/""https://unpkg.com/lodash-es@4.17.21/"  
    }  
  }  
</script>

除此之外,import maps 还提供了一个 scope API 让我们能够更细粒度的去管理不同版本的包,比如我们项目中用了 A 库,这个库依赖了 lodash@3 这个版本,但是我们想要用 lodash@4 里面的功能,此时就可以使用 scope 去做限定:

<script type="importmap">  
  {  
    "imports": {  
      "lodash""https://unpkg.com/lodash-es@4.17.21"  
    },
    "scope": {
        "/node_modules/A/": {
            "lodash": "https://unpkg.com/lodash-es@3.10.1"
        }
    }
  }  
</script>

三、兼容性

image.png 这项技术目前在 Chrome 和 Edge 浏览器 89 及更高版本提供了全面支持,但 Firefox、Safari 和一些移动浏览器还没有支持。我们可以通过下面的代码来判断浏览器的支持情况:

if (HTMLScriptElement.supports && HTMLScriptElement.supports('importmap')) {  
  // import maps is supported  
}

对于没有提供支持的浏览器我们可以使用 es-module-shims这个 polyfill,我们需要做的就是在使用 import maps 之前导入下面这行代码:

<script async src="https://ga.jspm.io/npm:es-module-shims@1.6.2/dist/es-module-shims.js"></script>

除此之外官网也推荐了一些其他的 polyfill 工具:

image.png

4、总结

import maps 提供了一种在浏览器中使用 ESM 的更明智的方法,这使得它不仅仅局限于从相对/绝对路径中去导入,它使您可以轻松地移动代码而无需调整导入语句,总而言之,import maps 使得 ESM在服务器和浏览器中的使用更加平等。

5、参考链接