前面我们在深入理解 ES Module 中详细介绍过 ES Module 的工作原理。目前,ES Module 已经在逐步得到各大浏览器厂商以及 NodeJS 的原生支持。像 vite 等新一代的构建工具已经逐步使用 ES Module 并有计划的运用到生产环境中。因此,了解如何在浏览器以及 NodeJS 中使用 ES Module 是必要的。

在浏览器中使用

支持 ES Module 的浏览器通过 script 标签上的 type 字段来识别 ES Module,即 type=module 就是 ES Module。

<script type="module">
import { foo } from 'bar';
export default foo;
</script>
<script type="module" src="/path/to/script"></script>

浏览器在遇到 type=module 的 script 标签时,会将其作为 ES Module 来解析,如果有依赖模块时,会递归的加载依赖模块。模块加载原理与 Webpack 是类似的。

现在问题来了,浏览器如何加载模块呢?

有三种主要方式:

  • 绝对路径,比如 http://domain.com/path/to/module
  • 相对路径,比如:./path/to/module
  • 包名(裸说明符,bare specifier),比如: lodash-es

绝对路径和相对路径都很好理解,与普通的 script 用法是一样的。直接使用包名浏览器如何处理呢?

我们在使用 Webpack 等打包器的时候,项目依赖的模块是安装在 node_modules 目录下的。在打包器执行构建的时候,会从 node_modules 中查询依赖的包,找到对应的模块,最终将模块代码合并到最终的构建输出文件中。

在浏览器中,其实是一样的,只不过我们要告诉浏览器去哪里找这些包。目前有一个规范(草案阶段)给出了解决方案,那就是 import-map。我们简单说明一下。

<script type="importmap">
{
"imports": {
"moment": "/node_modules/moment/src/moment.js",
"lodash": "/node_modules/lodash-es/lodash.js",
}
}
</script>

通过 type=importmap 的 script 标签,来告诉浏览器可以在哪里找到这些模块。

caniuse 上看,目前主流浏览器对 import-map 的支持不一,因此,我们还不能在浏览器中直接使用。

现在常规的做法还是经一道打包器的处理,将依赖的模块都打到最终的构建输出中(代码依然是 ES Module)。

在 NodeJS 中使用

NodeJS 有三种方式来识别 ES Module,分别是:

  • .mjs 后缀结尾的文件。
  • .js 后缀结尾的文件,但是所在包 package.json 中设置了 type 字段并且值为 module
  • 命令行中指定了 --input-type=module 参数

除了命令行以外,NodeJS 在处理 ES Module 的时候,都与 package.json 中的字段有关,这里详细说明下。

package.json 中与模块处理的字段主要有如下几个。

  • name 包的名称,可以与 importsexports 配合使用
  • main 包的默认导出模块
  • type 用于在加载 .js 文件时确定模块类型
  • exports 指定包导出了哪些模块
  • imports 包导入了哪些模块,只供包内部使用

main 字段指定包的默认导出模块,在所有 NodeJS 版本中都适用。同时,exports 字段也可以定义包的入口点,而且除了 exports 定义的入口点以外,包内的其他模块将对外不可见,即 exports 同时还提供了一定的封装特性。

mainexports 同时定义的时候,exports 的优先级比 main 更高,即 NodeJS 会忽略 main 中的定义。

exports

exports 字段定义了包导出的模块,有这么几种定义方式,我们分别说明。

. 导出

{
"exports": {
".": "./lib/index.js"
}
}

. 导出定义了包的默认导出模块,即 import xxx from 'package' 的导出模块。

如果 . 不与其他导出一同使用的话(就像上面的样例一样),可以简写为:

{
"exports": "./lib/index.js"
}

子路径导出

{
"exports": {
"./lib": "./lib/index.js"
}
}

上面的例子定义了 import xxx from 'package/lib' 导出的模块。当然,如果我们想将 ./lib 目录下的所有的模块不受限制的导出的话,可以这么设置:

{
"exports": {
"./lib/*": "./lib/*.js",
}
}

路径中的 * 只做字符串替换,即 import xxx from 'package/lib/a/b/c.js' 将会最终被定位到 ./node_modules/package/lib/a/b/c.js

exports 中的 ./lib 等都是相对于包的根目录而言,且子路径导出都需要以 ./ 开头。

如果我们想禁止 ./lib 目录下的某些模块被外部使用,同时又想通过 * 的方式导出模块,我们可以显式的将某一个目录导出设置为 null,如下。

{
"exports": {
"./lib/*": "./lib/*.js",
"./lib/private-internal/*": null
},
}

条件导出

{
"main": "./main-require.cjs",
"exports": {
"import": "./main-module.js",
"require": "./main-require.cjs"
},
"type": "module"
}

条件导出支持的条件如下:

  • node NodeJS 环境下适用,既可以是 ES Module 文件,也可以是 CommonJS 文件,通常不需要显式指定。
  • node-addonsnode 类似,用于 NodeJS 插件。
  • import 当通过 import 或者 import() 方式加载模块时使用,与 require 互斥。
  • require 当通过 require() 方式加载模块时使用,与 import 互斥。
  • default 兜底方案,目标文件可以为 CommonJS 文件也可以为 ES Module 文件,通常排在最后。

exports 字段中 key 的顺序至关重要,排在前面的优先级更高。因此,排在前面的通常是条件要求最严格的,排在后面的通常是要求最宽泛的。

除了上面官方支持的几个条件以外,社区还定义了 typesdenobrowserdevelopmentproduction 等条件。

子路径导出也支持设置条件,如下:

{
"main": "./main.js",
"exports": {
".": "./main.js",
"./feature": {
"node": "./feature-node.js",
"default": "./feature.js"
}
}
}

同时,条件导出还支持嵌套,如下,在 node 条件下,又区分了 importrequire 条件。

{
"main": "./main.js",
"exports": {
"node": {
"import": "./feature-node.mjs",
"require": "./feature-node.cjs"
},
"default": "./feature.mjs"
}
}

我们可以通过如下方式指定条件:

node --conditions=development main.js

上面介绍了几种模块导出方式,这里需要强调的一点是,exports 显示定义了包导出的模块,未在 exports 导出的模块,外界不可访问。exports 给了包的开发者定义对外 API 的能力。

imports

我们可以通过 imports 定义导入包内模块的快捷方式。imports 字段中所有的 key 都需要以 # 开头。

{
"imports": {
"#dep": {
"node": "dep-node-native",
"default": "./dep-polyfill.js"
}
},
"dependencies": {
"dep-node-native": "^1.0.0"
}
}

exports 不同的是,imports 允许导入包外模块。上面的样例中,在 node 条件下,import '#dep' 会导入 dep-node-native,在其他环境中,会导入 ./dep-polyfill.js

imports 也支持子路径导入,与 exports 类似,如下:

{
"imports": {
"#internal/*": "./src/internal/*.js"
}
}

效果如下:

import internalZ from '#internal/z';
// Loads ./node_modules/es-module-package/src/internal/z.js

小结

本文介绍了如何在浏览器和 NodeJS 中使用 ES Module 的方法。

如果你只是单纯的做页面开发,借助于成熟的构建工具,可能不太需要注意这些细节。但是掌握了基本原理,可以更好的帮助我们排查问题。

如果你是包开发者,那么如果想要使用 ES Module 并且想让包的使用者也能享受到 ES Module 的优点的话,就需要对模块的导入导出非常熟悉了。

常见面试知识点、技术方案分析、教程,都可以扫码关注公众号“众里千寻”获取,或者来这里 https://everfind.github.io/posts/

使用 ES Module 的正确姿势的更多相关文章

  1. 判断是否为gif/png图片的正确姿势

    判断是否为gif/png图片的正确姿势 1.在能取到图片后缀的前提下 1 2 3 4 5 6 7 8 9 //假设这是一个网络获取的URL NSString *path = @"http:/ ...

  2. 在Linux(ubuntu server)上面安装NodeJS的正确姿势

    上一篇文章,我介绍了 在Windows中安装NodeJS的正确姿势,这一篇,我们继续来看一下在Linux上面安装和配置NodeJS. 为了保持一致,这里也列举三个方法 第一个方法:通过官网下载安装 h ...

  3. 程序员取悦女朋友的正确姿势---Tips(iOS美容篇)

    前言 女孩子都喜欢用美图工具进行图片美容,近来无事时,特意为某人写了个自定义图片滤镜生成器,安装到手机即可完成自定义滤镜渲染照片.app独一无二,虽简亦繁. JH定律:魔镜:最漂亮的女人是你老婆魔镜: ...

  4. ios监听ScrollView/TableView滚动的正确姿势

    主要介绍 监测tableView垂直滚动的舒畅姿势 监测scrollView/collectionView横向滚动的正确姿势 1.监测tableView垂直滚动的舒畅姿势 通常我们用KVO或者在scr ...

  5. 玩转 Ceph 的正确姿势

    玩转 Ceph 的正确姿势 本文先介绍 Ceph, 然后会聊到一些正确使用 Ceph 的姿势:在集群规模小的时候,Ceph 怎么玩都没问题:但集群大了(到PB级别),这些准则可是保证集群健康运行的不二 ...

  6. 解锁redis锁的正确姿势

    解锁redis锁的正确姿势 redis是php的好朋友,在php写业务过程中,有时候会使用到锁的概念,同时只能有一个人可以操作某个行为.这个时候我们就要用到锁.锁的方式有好几种,php不能在内存中用锁 ...

  7. jquery选中radio或checkbox的正确姿势

    jquery选中radio或checkbox的正确姿势 Intro 前几天突然遇到一个问题,没有任何征兆的..,jquery 选中radio button单选框时,一直没有办法选中,后来查了许多资料, ...

  8. 程序员节应该写博客之.NET下使用HTTP请求的正确姿势

    程序员节应该写博客之.NET下使用HTTP请求的正确姿势 一.前言 去年9月份的时候我看到过外国朋友关于.NET Framework下HttpClient缺陷的分析后对HttpClient有了一定的了 ...

  9. 使用 win10 的正确姿势

    17年9月初,写了第一篇<使用 win10 的正确姿势>,而现在半年多过去,觉得文章得更新一些了,索性直接来个第二版吧. -----2018.3.24 写 一. 重新定义桌面 我的桌面: ...

随机推荐

  1. ACwing1212. 地宫取宝

    题目: X 国王有一个地宫宝库,是 n×m 个格子的矩阵,每个格子放一件宝贝,每个宝贝贴着价值标签. 地宫的入口在左上角,出口在右下角. 小明被带到地宫的入口,国王要求他只能向右或向下行走. 走过某个 ...

  2. c++模板移除引用

    背景 一个函数内部需将函数内的一个变量转为其参数的类型, 函数的参数是引用 本文要演示的环境需要c++11支持(使用了新的关键字 using) 例如 get_value 内部将dobuble类型数据转 ...

  3. 【LeetCode】1400. 构造 K 个回文字符串 Construct K Palindrome Strings

    作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 统计奇数字符出现次数 日期 题目地址:https:// ...

  4. 【九度OJ】题目1076:N的阶乘 解题报告

    [九度OJ]题目1076:N的阶乘 解题报告 标签(空格分隔): 九度OJ 原题地址:http://ac.jobdu.com/problem.php?pid=1076 题目描述: 输入一个正整数N,输 ...

  5. 【LeetCode】762. Prime Number of Set Bits in Binary Representation 解题报告(Python)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 遍历数字+质数判断 日期 题目地址:https:// ...

  6. 解决opencv:AttributeError: 'NoneType' object has no attribute 'copy'

    情况一: 路径中有中文,更改即可 情况二:可以运行代码,在运行结束时显示 AttributeError: 'NoneType' object has no attribute 'copy' 因为如果是 ...

  7. CS5211|CS5211参数|eDP转LVDS转换器芯片

    CS5211概述 CS5211是一个eDP到LVDS转换器,配置灵活,适用于低成本显示系统.CS5211与eDP 1.2兼容,支持1车道和2车道模式,每车道速度为1.62Gbps和2.7Gbps.CS ...

  8. 基于Spring MVC + Spring + MyBatis的【银行卡系统】

    资源下载:https://download.csdn.net/download/weixin_44893902/45604256 练习点设计: 删除.新增 一.语言和环境 实现语言:JAVA语言. 环 ...

  9. Java初学者作业——使用记事本编写Java程序

    返回本章节 返回作业目录 需求说明: 使用记事本编写 Java 程序,输出"大家好!我的梦想是做一名 Java 高级工程师!". 为 Java 程序添加单行和多行注释以及文档注释. ...

  10. 移动端的样式重置(CSS RESET)

    /********** * reset *********/ * {box-sizing: border-box; -webkit-tap-highlight-color: rgba(0,0,0,0) ...