我一直在试验新的 native ECMAScript module support最近已添加到浏览器中。很高兴终于能够从 JavaScript 直接干净地导入脚本。
/example.html 🔍
<script type="module">
import {example} from '/example.js';
example();
</script>
/example.js
export function example() {
document.body.appendChild(document.createTextNode("hello"));
};
但是,这只允许我导入由单独的外部 JavaScript 文件定义的模块。我通常更喜欢内联一些用于初始渲染的脚本,这样它们的请求就不会阻塞页面的其余部分。对于传统的非正式结构库,它可能看起来像这样:
/inline-traditional.html 🔍
<body>
<script>
var example = {};
example.example = function() {
document.body.appendChild(document.createTextNode("hello"));
};
</script>
<script>
example.example();
</script>
然而,天真地内联模块文件显然是行不通的,因为它会删除用于向其他模块标识模块的文件名。 HTTP/2 服务器推送可能是处理这种情况的规范方式,但它仍然不是在所有环境中的一个选项。
是否可以使用 ECMAScript 模块执行等效转换?
<script type="module">
有什么办法吗?在同一文档中导入另一个模块导出的模块?
我想这可以通过允许脚本指定文件路径来实现,并且表现得好像它已经从该路径下载或推送。
/inline-name.html 🔍
<script type="module" name="/example.js">
export function example() {
document.body.appendChild(document.createTextNode("hello"));
};
</script>
<script type="module">
import {example} from '/example.js';
example();
</script>
或者可能采用完全不同的引用方案,例如用于本地 SVG 引用:
/inline-id.html 🔍
<script type="module" id="example">
export function example() {
document.body.appendChild(document.createTextNode("hello"));
};
</script>
<script type="module">
import {example} from '#example';
example();
</script>
但这些假设都没有实际起作用,而且我还没有看到一个替代方案。
最佳答案
一起破解我们自己的import from '#id'
内联脚本之间的导出/导入不受 native 支持,但将我的文档的实现组合在一起是一个有趣的练习。代码打高尔夫球到一个小块,我这样使用它:
<script type="module" data-info="https://stackoverflow.com/a/43834063">let l,e,t
='script',p=/(from\s+|import\s+)['"](#[\w\-]+)['"]/g,x='textContent',d=document,
s,o;for(o of d.querySelectorAll(t+'[type=inline-module]'))l=d.createElement(t),o
.id?l.id=o.id:0,l.type='module',l[x]=o[x].replace(p,(u,a,z)=>(e=d.querySelector(
t+z+'[type=module][src]'))?a+`/* ${z} */'${e.src}'`:u),l.src=URL.createObjectURL
(new Blob([l[x]],{type:'application/java'+t})),o.replaceWith(l)//inline</script>
<script type="inline-module" id="utils">
let n = 1;
export const log = message => {
const output = document.createElement('pre');
output.textContent = `[${n++}] ${message}`;
document.body.appendChild(output);
};
</script>
<script type="inline-module" id="dogs">
import {log} from '#utils';
log("Exporting dog names.");
export const names = ["Kayla", "Bentley", "Gilligan"];
</script>
<script type="inline-module">
import {log} from '#utils';
import {names as dogNames} from '#dogs';
log(`Imported dog names: ${dogNames.join(", ")}.`);
</script>
而不是 <script type="module">
, 我们需要使用像 <script type="inline-module">
这样的自定义类型来定义我们的脚本元素.这可以防止浏览器尝试自己执行它们的内容,而将它们留给我们处理。该脚本(下面的完整版)找到所有 inline-module
文档中的脚本元素,并将它们转换为具有我们想要的行为的常规脚本模块元素。
内联脚本不能直接相互导入,因此我们需要为脚本提供可导入的 URL。我们生成一个 blob:
他们每个人的 URL,包含他们的代码,并设置 src
属性从该 URL 运行而不是内联运行。 blob:
URL 就像来自服务器的普通 URL,因此它们可以从其他模块导入。每次我们看到后续的 inline-module
试图从 '#example'
导入, 其中example
是 inline-module
的 ID我们已经转换,我们修改该导入以从 blob:
导入网址代替。这保持了模块应该具有的一次性执行和引用重复数据删除。
<script type="module" id="dogs" src="blob:https://example.com/9dc17f20-04ab-44cd-906e">
import {log} from /* #utils */ 'blob:https://example.com/88fd6f1e-fdf4-4920-9a3b';
log("Exporting dog names.");
export const names = ["Kayla", "Bentley", "Gilligan"];
</script>
模块脚本元素的执行总是推迟到文档被解析之后,因此我们无需担心尝试支持传统脚本元素在文档仍在解析时修改文档的方式。
export {};
for (const original of document.querySelectorAll('script[type=inline-module]')) {
const replacement = document.createElement('script');
// Preserve the ID so the element can be selected for import.
if (original.id) {
replacement.id = original.id;
}
replacement.type = 'module';
const transformedSource = original.textContent.replace(
// Find anything that looks like an import from '#some-id'.
/(from\s+|import\s+)['"](#[\w\-]+)['"]/g,
(unmodified, action, selector) => {
// If we can find a suitable script with that id...
const refEl = document.querySelector('script[type=module][src]' + selector);
return refEl ?
// ..then update the import to use that script's src URL instead.
`${action}/* ${selector} */ '${refEl.src}'` :
unmodified;
});
// Include the updated code in the src attribute as a blob URL that can be re-imported.
replacement.src = URL.createObjectURL(
new Blob([transformedSource], {type: 'application/javascript'}));
// Insert the updated code inline, for debugging (it will be ignored).
replacement.textContent = transformedSource;
original.replaceWith(replacement);
}
警告:这个简单的实现不处理初始文档被解析后添加的脚本元素,或者允许脚本元素从文档中出现在它们之后的其他脚本元素导入。如果你同时拥有 module
和 inline-module
文档中的脚本元素,它们的相对执行顺序可能不正确。源代码转换是使用粗略的正则表达式执行的,该正则表达式不会处理一些边缘情况,例如 ID 中的句点。
关于javascript - 在 HTML 中内联 ECMAScript 模块,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43817297/