type=module你了解,但 type=importmap你知道吗

更新日期: 2022-08-03阅读: 657标签: 模块

当ES模块第一次在ECMAScript 2015中被引入,作为在JavaScript中标准化模块系统的一种方式时,它是通过在import语句中指定相对或绝对路径来实现的。

import dayjs from "https://cdn.skypack.dev/dayjs@1.10.7"; // ES modules

console.log(dayjs("2019-01-25").format("YYYY-MM-DDTHH:mm:ssZ[Z]"));

这与模块在其他通用模块系统中的工作方式略有不同,例如CommonJS,以及在使用webpack这样的模块捆绑器时,使用的是更简单的语法

const dayjs = require('dayjs') // CommonJS

import dayjs from 'dayjs'; // webpack

在这些系统中,通过Node.js运行时或相关的构建工具,导入指定器被映射到一个特定(和版本)的文件。用户只需要在导入语句中应用裸露的模块指定符(通常是包名),围绕模块解析的问题就会被自动解决。

由于开发者已经熟悉了这种从npm导入包的方式,所以需要一个构建步骤来确保以这种方式编写的代码能够在浏览器中运行。这个问题由import maps解决了。从本质上讲,它允许将导入指定器映射到相对或绝对的URL上,这有助于控制模块的解析,而不需要应用构建步骤。

import maps 是怎么工作的

<script type="importmap">
{
  "imports": {
    "dayjs": "https://cdn.skypack.dev/dayjs@1.10.7",
  }
}
</script>
<script type="module">
  import dayjs from 'dayjs';

  console.log(dayjs('2019-01-25').format('YYYY-MM-DDTHH:mm:ssZ[Z]'));
</script>

import map 是通过html document中的 <script type="importmap">标签指定的。这个script 标签必须放在 document 中的中第一个 <script type="module">标签之前(最好是在<head>中),以便在进行模块解析之前对它进行解析。此外,目前每个 document 只允许有一个 import map,未来可能会取消这一限制。

在 script 标签内,一个JSON对象被用来指定document中 script 所需的所有必要的模块映射。一个典型的 import map 的结构如下所示。

<script type="importmap">
{
  "imports": {
    "react": "https://cdn.skypack.dev/react@17.0.1",
    "react-dom": "https://cdn.skypack.dev/react-dom",
    "square": "./modules/square.js",
    "lodash": "/node_modules/lodash-es/lodash.js"
  }
}
</script>

在上面的 imports 对象中,每个属性都对应着一个映射。映射的左边是 import 指定器的名称,而右边是指定器应该映射到的相对或绝对URL。

当在映射中指定相对URL时,确保它们总是以/、./或./开头。请注意,在 import map 中出现包并不意味着它一定会被浏览器加载。任何没有被页面上的 script 使用的模块都不会被浏览器加载,即使它存在于import map中。

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

你也可以在一个外部文件中指定你的映射,然后使用src属性链接到该文件(如上所示)。如果决定使用这种方法,请确保在发送文件时将其Content-Type标头设置为application/importmap+json。

注意,出于性能方面的考虑,推荐使用内联方式,本文的其余部分的事例,也会使用内联方式。

一旦指定了映射,就可以在import语句中使用import说明符,如下所示

<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 属性之类的位置。因此,如你的使用<script src="/app.js">之类的内容,浏览器将试图在该路径上下载一个字面上的app.js文件,而不管 import map 中的内容如何。

将指定者映射到整个包中

除了将一个指定器映射到一个模块,你也可以将一个指定器映射到一个包含多个模块的包。这是通过使用指定器键和以尾部斜线结尾的路径来实现的。

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

动态地构建 import map

映射也可以基于任意条件在 script 中动态构造,这种能力可以用来根据特征检测有条件地导入模块。下面的例子根据IntersectionObserver api是否被支持,在lazyload指定器下选择正确的文件进行导入。

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

如果你想使用这种方法,请确保在创建和插入 import map 脚本标签之前进行(如上所述),因为修改一个已经存在的导入地图对象不会有任何效果。

通过对哈希值的映射来提高脚本的可缓存性

实现静态文件长期缓存的常见技术是在文件名中使用文件内容的哈希值,这样文件就会一直在浏览器的缓存中,直到文件内容发生变化。当这种情况发生时,文件将得到一个新的名字,以便最新的更新立即反映在应用程序中。

在传统的 bundling scripts,的方式下,如果一个被多个模块依赖的依赖关系被更新,这种技术就会出现问题。这将导致所有依赖该依赖的文件被更新,迫使浏览器重新下载它们,即使只有一个字符的代码被改变。

import map 为这个问题提供了一个解决方案,它允许通过重映射技术单独更新每个依赖关系。假设你需要从一个名为post.bundle.8cb615d12a121f6693aa.js的文件中导入一个方法:

<script type="importmap">
  {
    "imports": {
      "post.js": "./static/dist/post.bundle.8cb615d12a121f6693aa.js",
    }
  }
</script>

而不是这样写:

import { something } from './static/dist/post.bundle.8cb615d12a121f6693aa.js'

可以这么写:

import { something } from 'post.js'

当更新文件的时候,只有 import map 需要更新。由于对其导出的引用没有更改,它们将保持在浏览器中的缓存,同时由于更新的哈希值,更新的脚本将再次被下载。

<script type="importmap">
  {
    "imports": {
      "post.js": "./static/dist/post.bundle.6e2bf7368547b6a85160.js",
    }
  }
</script>

使用同一模块的多个版本

在 import map 中很容易实现一个包对应多个版本,所需要做的就是在映射中使用不同的导入指定符,如下图所示:

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

通过使用作用域,也可以用同一个导入指定符来指代同一个包的不同版本。这允许我们在一个给定的作用域内改变导入指定符的含义。

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

有了这种映射,在/static/js路径下的任何模块,在导入语句中引用lodash/指定器时,将使用https://unpkg.com/lodash-es@3.10.1/,而其他模块将使用https://unpkg.com/lodash-es@4.17.21/。

使用带有 import map 的 NPM 包

正如在本文中所展示的,任何使用ES Modules的NPM包的生产版本都可以通过ESM、Unpkg和Skypack等CDN在 import map中使用。

即使NPM上的包不是为ES模块系统和本地浏览器导入行为设计的,像Skypack和ESM这样的服务也可以将它们转化为可在导入地图中使用的包。可以使用Skypack主页上的搜索栏来寻找浏览器优化的NPM包,这些包可以立即使用,而无需摆弄构建步骤。

检测 import map支持

只要支持HTMLScriptElement.supports()方法,就可以在浏览器中检测 import map的支持:

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

支持旧的浏览器


Import map 使得在浏览器中使用裸模块指定器成为可能,而无需依赖目前在JavaScript生态系统中普遍存在的复杂的构建系统,但目前网络浏览器中并不广泛支持它。

在整理本文时,Chrome和Edge浏览器的89版及以后的版本提供了全面支持,但Firefox、Safari和一些移动浏览器不支持这项技术。为了在这些浏览器中保留对 import map的使用,必须采用一个合适的 polyfill 。

一个可以使用的polyfill的例子是ES Module Shims polyfill,它为任何支持ES模块基线的浏览器(约94%的浏览器)添加了 import map 和其他新模块特性的支持。我们所需要做的就是在 import map 脚本之前在HTML文件中包含es-module-shim脚本

<script async src="https://unpkg.com/es-module-shims@1.3.0/dist/es-module-shims.js"></script>

在包括polyfill之后,可能会在你的控制台中得到一个JavaScript TypeError。这个错误可以被安全地忽略,因为它不会产生任何面向用户的后果。

总结

import map提供了一种更理智的方式来在浏览器中使用ES模块,而不局限于从相对或绝对的URL中导入。这使得我们可以很容易地移动代码,而不需要调整 import语句,并使个别模块的更新更加无缝,而不影响依赖这些模块的脚本的缓存能力。总的来说,import map为ES模块在服务器和浏览器中的使用方式带来了平等性。

作者:romaopedro199 译者:前端小智 来源:dev 原文:https://www.honeybadger.io/blog/import-maps/


链接: https://www.fly63.com/article/detial/11971

ES6模块功能:export和import的加载方式

ES6之前已经出现了js模块加载的方案,最主要的是CommonJS和AMD规范。commonjs主要应用于服务器,实现同步加载,如nodejs。AMD规范应用于浏览器,如requirejs,为异步加载。

Node的https模块_创建HTTPS服务器

Node的https模块:HTTPS服务器使用HTTPS协议,需要证书授权,SSL安全加密后传输,使用443端口

如何让 node 运行 es6 模块文件,及其原理

最新版的 node 支持最新版 ECMAScript 几乎所有特性,但有一个特性却一直到现在都还没有支持,那就是从 ES2015 开始定义的模块化机制。而现在我们很多项目都是用 es6 的模块化规范来写代码的,包括 node 项目

module、export、require、import的使用

module每个文件就是一个模块。文件内定义的变量、函数等等都是在自己的作用域内,都是自身所私有的,对其它文件不可见。在module中有一个属性exports,即:module.exports。它是该模块对外的输出值,是一个对象。

Node.js - 模块系统

模块是Node.js 应用程序的基本组成部分,文件和模块是一一对应的。换言之,一个 Node.js 文件就是一个模块,这个文件可能是JavaScript 代码、JSON 或者编译过的C/C++ 扩展。Node.js 提供了 exports 和 require 两个对象

ES模块基础用法及常见使用问题

ES6中引入了模块(Modules)的概念,相信大家都已经挺熟悉的了,在日常的工作中应该也都有使用。本文会简单介绍一下ES模块的优点、基本用法以及常见问题。

ES6 export 和 export default的区别

ES6中 export 和 export default 与 import使用的区别,使用 react native 代码详解,现在流行的前端框架,angular+ 主要使用 export 导出模块,react native 中使用 export default 导出模块,如今编辑器非常强大,安装插件会自动弹出模块名称,知道其导出怎么使用就可以了

export和export default的区别

export与export default均可用于导出常量、函数、文件、模块;你可以在其它文件或模块中通过import+(常量 | 函数 | 文件 | 模块)名的方式,将其导入,以便能够对其进行使用;

关于export和export default你不知道的事

网上有很多关于export和export default的文章,他们大部门都是只讲了用法,但是没有提到性能,打包等关键的东西。大家应该应该能理解import * from xxx会把文件中export default的内容都打包到文件中,而import {func} from xxx只会把文件中的func导入

最全的前端模块化方案

模块化主要是用来抽离公共代码,隔离作用域,避免变量冲突等。将一个复杂的系统分解为多个模块以方便编码。会讲述以下内容:CommonJS、AMD 及 核心原理实现、CMD 及 核心原理实现

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!