后面咱们在深刻了解 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
包的名称,能够与imports
和exports
配合应用main
包的默认导出模块type
用于在加载.js
文件时确定模块类型exports
指定包导出了哪些模块imports
包导入了哪些模块,只供包外部应用
main
字段指定包的默认导出模块,在所有 NodeJS 版本中都实用。同时,exports
字段也能够定义包的入口点,而且除了 exports
定义的入口点以外,包内的其余模块将对外不可见,即 exports
同时还提供了肯定的封装个性。
当 main
和 exports
同时定义的时候,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-addons
与node
相似,用于 NodeJS 插件。import
当通过import
或者import()
形式加载模块时应用,与require
互斥。require
当通过require()
形式加载模块时应用,与import
互斥。default
兜底计划,指标文件能够为 CommonJS 文件也能够为 ES Module 文件,通常排在最初。
exports
字段中 key 的程序至关重要,排在后面的优先级更高。因而,排在后面的通常是条件要求最严格的,排在前面的通常是要求最宽泛的。除了下面官网反对的几个条件以外,社区还定义了
types
、deno
、browser
、development
、production
等条件。
子门路导出也反对设置条件,如下:
{
"main": "./main.js",
"exports": {
".": "./main.js",
"./feature": {
"node": "./feature-node.js",
"default": "./feature.js"
}
}
}
同时,条件导出还反对嵌套,如下,在 node
条件下,又辨别了 import
和 require
条件。
{
"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/po…。