发布时间:2023年10月13日
ESModule 作为 ECMA 推出的通用模块化标准,随着在 NodeJs 场景下的普及以及浏览器的原生支持,目前已覆盖前端开发所有的场景,但你真的了解它所有的使用方法吗?
这篇分享,让我们从几个实际发生的场景出发去探索:
在上图代码里,我们实现 在 Utils.ts 中输出一些通用的工具类,在其他文件中使用其中方法时,有如下两块种引入方式,哪种方式才是正确的?
import { isValidData, getRequiredRule } from './Utils';
import Utils from './Utils';
const { isValidData, getRequiredRule } = Utils;
我们在使用如 antd
、ProComponents
之类组件库时一般都使用第一种的写法,因此可能默认带入解构的思维惯性,但这种写法是错误的,应该按照第二种方式来做。
要解答这个问题,我们先看下 MDN 上关于使用 export
默认导出内容场景下的例子:
// 默认导出
export default expression;
export default function (…) { … } // also class, function*
export default function name1(…) { … } // also class, function*
export { name1 as default, … };
在实例中,有一个语法可能会引起你的注意: export { name as default, ... }
,这个语法其实和 export default name
是完全等价的,也就是说 default
其实只是 export
的一个保留字。
看到这里我们再回过头来看上图中的 Utils.ts
,其导出实际上可以写成这样
const utils = { isValidData, getRequiredRule, getMaxLengthRule };
export { utils as default };
我们再看下 MDN 上关于 import
引入 default
的例子中,可以看到两种写法:
// 写法一
import Utils from "./Utils";
// 写法二
import { default as Utils } from "./Utils";
将两者结合起来看,问题就很简单了,我们在 Utils.ts
中通过只暴露了 default
保留字指向的值,而并没有暴露 key 为 isValidData
等内容的值,所以无法直接通过解构的方式来拿到
在 JavaScript 中,一个文件被视为一个 模块,每个模块都可以有其自己的
export
,不论我们如何组织export
内容,这些内容最终都会被组织到一个大的export
中,通过export { xxx as default, yyy, zzz }
的方式暴露给其他模块使用。
// 将其他模块内容作为一个整体导入
import * as FullImport from 'library';
// 导入时解构并重命名
import { originExportName as newImportName } from 'library';
// 将模块作为副作用引入,不导入模块的任何值,仅运行文件代码,如 less 文件
import './effect.ts'
// 动态导入默认 export 时,需要显式的解构 default 再使用
const { default: defaultExport } = await import('./library.js');
在 export
模块,我们还是拿之前的 Utils.ts
来举例
在前面提到过 export default
等于 export { xx as default }
,那么我们如果想通过结构的方式拿到对应函数就很简单了 —— 不要通过 default
暴露,有两种写法:
export const isValidData = () => {}
export { isValidData }
import { isValidData } from './Utils'
的方式拿到对应的工具函数。至此可能还有一个问题,怎么在 Utils.ts
中把 format.js
的方法也暴露出去并能以结构的方式拿到?
很简单,全模块引入,再导出,那么就会出现下面这种写法:
但在实际编写代码时,编辑器会报错,通过查看文档和思考的话,我们会发现 export default
和 export {}
导出的内容完全是两个概念:
export default
中暴露出去的是一个变量,这个变量可以是常量、函数、类,因此可以带有计算逻辑,比如:export default { ...FormatUtils }
export
导出的是变量列表,这个列表中的变量在导出时应当是确定的,不能带有计算逻辑export
有几种写法了前面提到使用 export default
时可以进行计算,那么第一种写法就有,如下:
import FormatUtils from './format';
const isValidData = () => { ... };
export default {
isValidData,
...FormatUtils
}
import Utils from './Utils';
const {
isValidData,
formatXxx
} = Utils;
借助 default
变量可以进行计算的特点,我们实现了聚合导出的目的,但没有实现在 import
时解构引用的能力
前面在 import
的其他用法中提到聚合导入的 import *
语法,那么肯定有与之对应的 export *
语法,借助这个语法,我们可以将 format.ts
文件中提供的方法聚合至 Utils.ts
文件的导出变量之中,写法如下:
export * from './format';
const isValidData = () => { ... };
export { isValidData }
import {
isValidData,
formatXxx
} from './Utils';
这样我们在既实现聚合导出的目的基础上也实现了导入时的解构能力。
// 模块内变量导出时重命名变量
export { variable1 as name1, variable2 as name2 }
// 从某对象中解构导出并重命名
export const { name1, name2: rename } = Object
// 导出模块合集并聚合至当前模块的导出列表内,引入时以变量原始名称进行引入
export * from './format'
//导出模块合集并集合至一个变量聚合至当前模块的导出列表内,引入时以 as 后的名称进行引入
export * as FormatUtils from './format'
// 导出模块合集、重命名并聚合在当前模块导出列表内,引入时以 as 后的名称进行引入
export { default as FormatDefault, import1 as export1, import2 as export2 } from './format'
前端模块化一共有以下几种实现:
module.exports =
,在 NodeJS 中可以以 .cjs
作为文件后缀define
关键字定义模块,以 return
关键字返回导出变量,以 require
关键字引入模块,需要引入 require.js
作为运行时实现模块的运行// amd define
define(function() {
return {
hello: function() {
return "hello module";
},
};
});
// amd require
define(function() {
const helloModule = require('./module.js');
helloModule.hello();
});
define
关键字定义模块,通过对 define callback
中 exports
参数的调用实现暴露,以 require
实现调用,需要引入 sea.js
作为运行时实现模块运行// cmd define
define(function(require, exports, module) {
exports.hello = function() {
return "hello module";
};
});
// cmd require
define(function(require, exports, module) {
const helloModule = require("./module.js");
helloModule.hello()
});
CommonJS
与 AMD
两种模块的运行时,目前前端项目打包基本都是以此为基准(function(global, factory) {
typeof exports === "object" && typeof module !== "undefined"
? (module.exports = factory())
: typeof define === "function" && define.amd
? define(factory)
: ((global = global || self), (global.myBundle = factory()));
})(this, function() {
"use strict";
var main = () => {
return "hello world";
};
return main;
});