esModule内联代码的导入导出实现
本文由 小茗同学 发表于 2024-01-23 浏览(296)
最后修改 2024-01-23 标签:esmodule inline import export

背景

浏览器层面的esModule在设计的时候不太完美,只能import外部JS,如果某个<script type="module">是通过内联代码直接写在HTML里面的反而没法导入:

<script type="module">export default 123;</script>
<script type="module">
// 无法导入上面的模块代码
</script>

只能改成下面这样:

// test.js
export default 123;

然后再导入单独的JS文件:

<script type="module">
	import test from './test.js';
	console.log(test);
</script>

hack

虽然这么做没啥实际意义,但我就偏不信这个邪,非得试试,实现之后可以比较方便写一些简单测试代码。既然浏览器只能导入外部JS文件,那我们就给它生成外部文件就好了:

<!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 .+['"])(.+)(?=['"][;\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>
	测试内联代码导入导出
	<!-- 无顺序要求,import可以在export的前面 -->
	<script type="text/module">
		import test from 'test';
		console.log('test:', test);
	</script>
	<script type="text/module" data-module="test">
		export default 111;
	</script>
	<script type="text/module">
		export default 222;
	</script>
	<script type="text/module">
		// 如果导入的模块未命名,可以按 text/module 在文档中出现的顺序取,这里是第2个(从0开始)
		import module2 from 'module2';
		console.log('module2:', module2);
	</script>
	<script type="text/module">
		// 找不到的不会替换,交由浏览器自行处理
		//import xxx from 'xxx';
	</script>
</body>
</html>