esModule的import export一个很特殊的地方
本文由 小茗同学 发表于 2024-01-24 浏览(266)
最后修改 2024-01-24 标签:

回顾

先来个简单回顾:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
	<meta charset="UTF-8">
	<title>测试内联代码导入导出</title>
	<script>
		// 预编译带 text/babel 的script标签,处理 import
		function preCompileInlineModuleScript() {
			const srcMap = {}; // 存储所有内联模块的blob src地址
			[...document.querySelectorAll('script[type="text/module"]')]
				.map((script, idx) => {
					// 如果未通过 data-module 指定模块名,生成类似 module0 的默认名称
					const moduleName = script.dataset?.module || `module${idx}`;
					const url = URL.createObjectURL(new Blob([script.textContent], { type: 'text/javascript' }));
					srcMap[moduleName] = url;
					return script;
				})
				.forEach(script => {
					const { src, textContent, dataset } = script;
					if (!textContent) {
						return;
					}
					// 自动检索类似 `import Demo from 'demo'` 这样的代码
					const newContent = textContent.replace(/(?<=(import|export) .+['"])(.+)(?=['"][;\n])/g, (m, _, src) => srcMap[src] || src);
					const ele = document.createElement('script');
					ele.textContent = newContent;
					ele.type = 'module';
					script.parentNode.insertBefore(ele, script); // 依次插在原script前面
					script.remove();
				});
		}
		document.addEventListener("DOMContentLoaded", preCompileInlineModuleScript);
	</script>
</head>
<body>
	测试内联代码导入导出
	<script type="text/module" data-module="demo1">
	// export default const a = 1; // 语法错误,为什么不能这么写?因为default可以看成是`const default`的语法糖

	// 正确写法1
	// const a = 1;
	// export default a;

	// 正确写法2
	// export default 1;

	// 导出函数
	// export default function () {}

	// 导出类,注意一个模块只能有一个默认导出
	// export default class {}

	export default {
		a: 1,
		b: 2,
	}
	</script>

	<script type="text/module">
	import demo, * as all from 'demo1';
	// import { a } from 'demo1'; // 报错

	console.log(demo); // 输出 {a: 1, b: 2}
	console.log(all); // 输出 {default: {a: 1, b: 2}}
	</script>


	<script type="text/module" data-module="demo2">
	export default 'hello';
	export const a = 1;
	// 语法错误,export 导出的大括号不是一个对象,只是一个特殊的语法
	// export {
	//	 b: 2,
	//	 c: 3,
	// };

	// 正确写法
	const b = 2;
	const c = 3;
	export { b, c };
	</script>

	<script type="text/module">
	import demo, * as all from 'demo2';
	import { a, b, c } from 'demo2';

	console.log(demo); // 输出 'hello'
	console.log(all); // 输出 {default: 'hello', a: 1, b: 2, c: 2}
	console.log(a, b, c); // 输出 1 2 3
	</script>
</body>
</html>

特殊地方

先有一个a.js:

// a.mjs
export default 'hello';
export const a = 1;
const b = 2;
const c = 3;
export { b, c };

然后有一个b.js,这个js什么都不做,只是做一层转发透传:

// b.mjs
export * from './a.mjs';
// c.mjs
import * as a from './a.mjs';
import * as b from './b.mjs';
console.log('a', a); //  a正常:{ a: 1, b: 2, c: 3, default: 'hello' }
console.log('b', b);//  b不正常,少了default { a: 1, b: 2, c: 3 }

这一点很奇怪,和我们预期不一致。实际上在导入的时候*是包含default的,可是导出的时候default却被漏掉了。export * from 'a'等价于export {a, b, c} from 'a',不等价于export {a, b, c, default} from 'a'

// b.mjs
export * from './a.mjs';
export {a, b, c} from './a.mjs'; // 二者完全等价

如果想要在b中实现完全透传,正确写法:

// b.mjs
export * from './a.mjs';
export { default } from './a.mjs'; // default必须显示导出

// c.mjs
import * as b from './b.mjs';
console.log(b); // { a: 1, b: 2, c: 3, default: 'hello' }

怎么理解这件事情呢?default是一个特殊的关键字,是不能作为变量名的,但是可以作为对象的key值,我们在利用import * as xx导入的时候由于xx是一个对象,所以可以正常导入,但是在导出的时候由于default太特殊所以被过滤掉了。

const default = 1; // 不合法,default不能做变量名
export { default };

import { default } from 'a'; // 同样不合法

// 但是将import和export结合的时候却可以
export { default } from 'a';

// 或者这样写
import a from 'a';
export default a;