一篇内容学习完JS的Import Maps
Import Maps是一种针对web页面去控制imports行为的新方式,潜在地可以使你摈弃掉你的构建系统。
当Es modules在Ecmascript 2015中第一次被引进并作为标准化js模块系统的一种方式,它是通过在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的模块bunlder时使用了更简单的语法:
const dayjs = require('dayjs') // CommonJS
import dayjs from 'dayjs'; // webpack
在这些系统中,import说明符通过Node.js运行时或相关的构建工具映射到特定(和版本化)文件。用户只需在import语句中应用最简单的说明符(通常是包名),就可以自动解决模块解析问题。
由于开发人员已经熟悉从npm导入包的这种方式,因此需要一个构建步骤来确保以这种方式编写的代码可以在浏览器中运行。Import Maps解决了这个问题。本质上,它允许将import说明符映射到相对或绝对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 Maps是通过HTML文档中的
在script标记中,JSON对象用于为文档中脚本所需的模块指定所有必要的映射。典型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中存在的包并不一定意味着它将由浏览器加载。页面上脚本未使用的任何模块都不会被浏览器加载,即使它存在于import map中。
<script type="importmap" src="importmap.json"></script>
您还可以在外部文件中指定映射,然后使用src属性链接到该文件(如上所示)。如果您决定使用这种方法,请确保发送的文件的Content-Type头设置为application/importmap+json。请注意,出于性能原因,建议使用内联方法,即本文剩余部分将如何展示示例的方式。
指定mapping后,可以在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>
应该注意,import map中的映射不会影响URL,例如<script>标记的src属性。因此,如果您使用类似
将说明符映射到整个包
除了将说明符映射到模块之外,还可以将一个说明符映射到包含多个模块的包。这是通过使用以斜杠结尾的说明符键和路径来完成的。
<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 Maps
映射也可以基于任意条件在脚本中动态构建,并且该功能可以用于基于特征检测有条件地导入模块。下面的示例根据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 maps脚本标记之前执行此操作(如上所述),因为修改现有的导入映射对象不会产生任何效果。
通过Hash映射来提高脚本的缓存
实现静态文件长期缓存的一种常见技术是在文件的名称中使用文件内容的哈希值,以便在文件内容发生更改之前,文件仍保留在浏览器缓存中。发生这种情况时,文件将获得一个新名称,以便最近更新立即反映在应用程序中。
对于bundler脚本的传统方式,如果更新几个模块所依赖的依赖项,则此技术可能会失败。这将导致依赖于该依赖项的所有文件被更新,这将迫使浏览器重新下载它们,即使只更改了一个字符的代码。
Import Maps允许通过重新映射技术分别更新每个依赖项,从而解决了这个问题。假设您需要从名为post.bundle8cb615d12a121f6693aa.js的文件中导入一个方法,您可以有一个如下所示的import map:
<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。由于对其导出的引用没有更改,因此由于hash变化而更新的脚本再次下载时,它们将保持缓存在浏览器中。
<script type="importmap">
{
"imports": {
"post.js": "./static/dist/post.bundle.6e2bf7368547b6a85160.js",
}
}
</script>
使用同一个模块的不同版本
在Import Maps中使用同一个模块的不同版本是很容易的一件事,你只需要在mapping中申明不同的说明符即可,像这样:
<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说明符的含义。
<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>
通过这个mapping,任何在/static/js路径下面使用了lodash/说明符的引用都会关联到3.10.1的版本,其他的则关联到4.17.21的版本。
NPM包与Import Maps一起使用
正如我在本文中所演示的,使用ES模块的任何NPM包的生产版本都可以通过ESM、Unpkg和Skypack等CDN在Import Maps中使用。即使NPM上的软件包不是为ES模块系统和本地浏览器导入行为而设计的,Skypack和ESM等服务也可以将其转换为可在import map中使用的软件包。您可以使用Skypack主页上的搜索栏来查找浏览器优化的NPM包,这些包可以立即使用,而无需修改构建步骤。
代码检测Import Maps支持
只要判断HTMLScriptElement.supports()方法,就可以在浏览器中检测Import Mpas支持。以下代码段可用于此目的:
if (HTMLScriptElement.supports && HTMLScriptElement.supports('importmap')) {
// import maps is supported
}
老的浏览器支持情况
Import Maps可以在浏览器中使用裸模块说明符,而不依赖于JavaScript生态系统中当前流行的复杂构建系统,但目前web浏览器并不广泛支持它。在撰写本文时,Chrome和Edge浏览器的89版及更高版本提供了全面支持,但Firefox、Safari和一些移动浏览器不支持此技术。要在此类浏览器中保留导入贴图的使用,必须使用适当的polyfill。
可以使用的polyfill的一个例子是ES Module Shims polyfill,它为任何具有ES模块基线支持的浏览器(约94%的浏览器)添加了对导入映射和其他新模块功能的支持。您只需在导入映射脚本之前将es模块填充脚本包含在HTML文件中即可:
<script async src=“https://unpkg.com/es-module-shims@1.3.0/dist/es-module-shims.js“></script>
在这样的浏览器中,在包含polyfill之后,您仍然可能在控制台中获得JavaScript TypeError。可以安全地忽略此错误,因为它不会对用户造成任何后果。
Uncaught TypeError: Error resolving module specifier “lodash/toUpper.js”. Relative module specifiers must start with “./”, “../” or “/”.
与Import Maps相关的其他polyfill和工具可以在其GitHub中找到。
结论
Import Maps提供了一种在浏览器中使用ES模块的更明智的方式,而不限于从相对或绝对URL导入。这使得在不需要调整import语句的情况下轻松地移动代码,并使单个模块的更新更加无缝,不会影响依赖于这些模块的脚本的可缓存性。总的来说,Import Maps为服务器和浏览器中使用ES模块的方式带来了一致性。
转载自:https://juejin.cn/post/7207002389802025017