用于前端模块化的 ES 模块
前言
ES modules 是原生 JavaScript 提供的模块功能,逐渐被更多的浏览器开始支持。
入门篇
先了解下基础用法。
html 文件中使用 ES modules 语法执行 js
在 script 标签中 设置 type = module,浏览器就会以 ES modules 的标准去执行 script 标签中的 JavaScript 代码。默认情况下,代码是以严格模式执行的。
<script type="module">
console.log('this is es module')
</script>
私有作用域
每一个设置 type=module 的标签内都会形成一个私有作用域。作用域之间的变量不能直接共享。
<script type="module">
var a = 1
console.log(a)
</script>
<script type="module">
console.log(a)
</script>
第 1 组 script 中的代码正常输出变量 a 的值,第 2 段会报以下错误:a is not defined。
外链 JS 文件加载
ES modules 会以 CORS 方式加载外部 js 文件。提供 js 文件的服务器需要设置 CORS 后,ES modules 的 script 标签才能正常加载文件。
<script type="module" src="https://unpkg.com/jquery@3.6.0/dist/jquery.js"></script>
<script type="module" src="https://abc.com/jquery@3.6.0/dist/jquery.js"></script>
第 1 组 script 标签加载成功,第 2 组加载失败。
延迟执行,作用与 defer 一致。
未设置 type = module 前,会先执行完 01_demo.js 的代码。p 标签的渲染会被阻塞。
设置 type = module 后,p 标签的渲染不会被阻塞。
<script src="./01_demo.js" type="module"></script>
<p>测试</p>
导出
导出方式
// module.js - 单独导出
export var name = "foo module";
export class A {}
export function bar() {}
// 统一导出
var name = "foo module";
class A {}
function bar() {}
export { name, A, bar }
// app.js - 导入
import { name } from "./module.js";
console.log(name);
导出重命名
const age = 25;
export { age as Age };
默认模块导出,导入时也需要重命名
const age = 25;
export { age as default };
export default age;
import { default as Age } from './module.js'
import Age from './module.js'
console.log(Age)
注意事项:
- 导出时,export 后面不是字面量对象。import 后面也不是对象解构。
- export default { name, age } 是等价于导出一个对象,export { a, b, c } 则是固定用法。
- 进行导入操作时,导入的是同一份引用,是引用关系,并非值,这一点与 common.js 不一样。在导入之后,不能对导入变量进行修改,它是一个只读的关系。
导入
- 导入语句中,需要指定完整路径,不能忽略文件后缀名。而 common js 是支持忽略文件后缀名。
- 路径必须以 / 开头。导入路径支持使用 cdn url 外链 js 文件的形式。
- 以下导入方式等价于执行导入文件中的 js 代码。
import {} from './module.js'
import './module.js'
- 将模块中所有内容一次性导入,通过对象.变量方式访问。
import * as from './module.js'
- 动态导入,import() 等价于函数调用。非函数调用形式只能至于顶层。
import('./module.js').then(module => {
console.log(module)
})
- 默认成员和具名成员导入。
import { name, age, default as title } from './module.js'
import title, { name, age } froom './module.js'
导出导入成员
一种代码组织方式,将散落模块组织成新模块。
// components/button.js
export const Button = "Button component";
// components/radio.js
export const Radio = "Radio component";
// components/index.js
import { Button } from "./button.js";
import { Radio } from "./radio.js";
export { Button, Radio };
// app.js
import { Button, Radio } from "./component/index.js";
console.log(Button);
console.log(Radio);
Polyfill 篇
ES modules 是 ES6 推出的新语法特性,在低版本浏览器上需要通过 Polyfill 方式去解决兼容性问题。
github.com/ModuleLoade…
script 添加 nomodule 设置,避免在正常浏览器出现代码执行两次的情况。nomodule 表示当前浏览器不支持 modules 的情况下加载使用。
<script nomodule src="https://unpkg.com/promise-polyfill@8.2.0/dist/polyfill.min.js"></script>
<script nomodule src="https://unpkg.com/browser-es-module-loader@0.4.1/dist/babel-browser-build.js"></script>
<script nomodule src="https://unpkg.com/browser-es-module-loader@0.4.1/dist/browser-es-module-loader.js"></script>
<script type="module">
import { foo } from './module.js'
console.log(foo)
</script>
Node 中应用篇
背景
Node 中对 ES modules 语法特性的支持还在实验阶段。node 版本大于 8.5,脚本执行命令添加 --experimental-modules 即可运行 ES modules 代码。
Node 对 ES modules 特性支持的源码,可点击链接了解:
github.com/nodejs/node…
应用
node --experimental-modules index.mjs。对于 .mjs 后缀名的文件,Node 会以 modules 特性去运行代码。
// 1. 自定义模块
import { foo, bar } from "./module.mjs";
console.log(foo);
console.log(bar);
// 2. 系统内置模块
import fs from "fs";
fs.writeFileSync("foo.txt", "Foo txt content!");
// 3. 第三方模块,不支持按单个成员方式导入,一般都是导出默认成员的方式。
import _ from "lodash";
console.log(_.camelCase("ES module"));
// 4. 系统内置模块支持单个导入
import { writeFileSync } from "fs";
writeFileSync("bar.txt", "Bar txt Content!");
与 CommonJS 交互
- ES modules 中可以导入 CommonJS 模块。
- CommonJS 中不能导入 ES modules 模块。
- CommonJS 始终只会导出一个默认成员。
- import 不是解构对象。
与 CommonJS 差异
新版本进一步支持
Node 项目的 package.json 中添加配置项 type: 'module',就可以使得项目中的 js 文件支持 es modules 方式进行模块的导入导出。配置完成后,项目中使用 CommonJS 导入方式的文件都需要将后缀名修改为 .mjs。
babel 兼容方案
babel 用于将新特性语法转换成低版本浏览器兼容的语法。
低版本 node 使用 babel 插件运行 es modules 代码
步骤如下:
- 安装 babel 插件
yarn add @babel/node @babel/core @babel/preset-env --dev
- 运行 index.js 文件
yarn babel-node index.js
- babel 转换机制
每一个 ES 新特性都有相应的 babel 插件进行转换。
- preset-env 是 js 新特性的集合。
yarn babel-node ./09-babel/index.js --presets=@babel/preset-env
- 第 4 步也可以通过 babelrc 设置。
{
"presets": ["@babel/preset-env"]
}
运行命令可以直接去除 presets: yarn babel-node ./09-babel/index.js
- 使用单个插件(非插件集合)
yarn add @babel/plugin-transform-modules-commonjs --dev
babelrc 设置
{
"plugins": [
"@babel/plugin-transform-modules-commonjs"
]
}
yarn babel-node ./09-babel/index.js
总结
es modules 逐渐被更多浏览器所支持,意味着在浏览器端将会有统一的模块化解决方案。在模块化思想指导下,将代码分成若干模块进行组织,程序将会更具扩展性和条理性。
上一篇: es6 中有什么?