HTML ファイル内の script タグ間で ESM import したい
- カテゴリ:
- JavaScript
- コメント数:
- Comments: 0
◆ importModule 関数を自作
◆ script タグごとに URL を作って別ファイルとしてインポート
◆ script タグごとに URL を作って別ファイルとしてインポート
1 つの HTML ファイルにすべてを含めるときでも JavaScript の処理を分けたいことがあります
関数にまとめて返り値として外部にエクスポートするような作りでもいいのですが ESM があるのですから import/export を使いたいです
しかし 現状では異なる script タグをインポートすることはできません
以前できるようになりそうな提案を見かけた気がしますが 入りそうという話も聞かないですし 入ったとしても使えるようになるのはまだまだ先そうです
やりたいのはこういう感じのことです
上の script で export して それを下の script で import して使います
そのままでは動かないので 各 script を別ファイルとして扱うことにします
普通の script タグだと実行されてしまうので実行されないように適当な type をつけます
こういう感じになりました
data-name で名前をつけて この名前でインポートします
インポートでは importModule という関数を使うようにしてます
中で動的に URL を作成して別ファイルとして扱うようにしてます
インポートを循環参照にする場合トップレベルで await するとお互いに待ち続けてインポートが終わらなくなります
await するなら関数の中など トップレベル以外でする必要があります
メインの部分だけ text/module じゃないのが気になるので全部 text/module にします
完全に text/module のみだと実行が行えないのでエントリポイント部分は importModule をグローバルに定義とメインの呼び出しだけにしてみました
importModule の定義は外部ファイルに移動してます
スッキリして良さそうな気がします
改良版
関数にまとめて返り値として外部にエクスポートするような作りでもいいのですが ESM があるのですから import/export を使いたいです
しかし 現状では異なる script タグをインポートすることはできません
以前できるようになりそうな提案を見かけた気がしますが 入りそうという話も聞かないですし 入ったとしても使えるようになるのはまだまだ先そうです
やりたいのはこういう感じのことです
<script type="module" id="foo">
export const foo = () => {
console.log("FOO")
}
</script>
<script type="module" id="bar">
import foo from document.getElementById("foo")
foo()
</script>
上の script で export して それを下の script で import して使います
そのままでは動かないので 各 script を別ファイルとして扱うことにします
普通の script タグだと実行されてしまうので実行されないように適当な type をつけます
こういう感じになりました
<!DOCTYPE html>
<script>
const module_cache = {}
window.importModule = async (name) => {
if (module_cache[name]) return module_cache[name]
const elem = document.querySelector(`script[data-name="${name}"`)
if (!elem) throw new Error("no module")
const code = elem.innerHTML
const url = URL.createObjectURL(new Blob([code], { type: "text/javascript" }))
module_cache[name] = import(url).finally(() => URL.revokeObjectURL(url))
module_cache[name] = await module_cache[name]
return module_cache[name]
}
</script>
<script type="text/module" data-name="add">
export const add = (a, b) => a + b
</script>
<script type="text/module" data-name="up">
const { add } = await importModule("add")
const up = x => add(x, 1)
export default up
</script>
<script type="module">
importModule("up").then(module => {
console.log(module.default(1))
// 2
})
</script>
data-name で名前をつけて この名前でインポートします
インポートでは importModule という関数を使うようにしてます
中で動的に URL を作成して別ファイルとして扱うようにしてます
インポートを循環参照にする場合トップレベルで await するとお互いに待ち続けてインポートが終わらなくなります
await するなら関数の中など トップレベル以外でする必要があります
<!DOCTYPE html>
<script>
const module_cache = {}
window.importModule = async (name) => {
if (module_cache[name]) return module_cache[name]
const elem = document.querySelector(`script[data-name="${name}"`)
if (!elem) throw new Error("no module")
const code = elem.innerHTML
const url = URL.createObjectURL(new Blob([code], { type: "text/javascript" }))
module_cache[name] = import(url).finally(() => URL.revokeObjectURL(url))
module_cache[name] = await module_cache[name]
return module_cache[name]
}
</script>
<script type="text/module" data-name="c1">
let c2
importModule("c2").then(mod => c2 = mod)
export const name = "C1"
export default () => `${name} - ${c2?.name}`
</script>
<script type="text/module" data-name="c2">
let c1
importModule("c1").then(mod => c1 = mod)
export const name = "C2"
export default () => `${name} - ${c1?.name}`
</script>
<script type="module">
importModule("c1").then(module => {
console.log(module.default())
// C1 - undefined
setTimeout(() => {
console.log(module.default())
// C1 - C2
}, 50)
})
</script>
メインの部分だけ text/module じゃないのが気になるので全部 text/module にします
完全に text/module のみだと実行が行えないのでエントリポイント部分は importModule をグローバルに定義とメインの呼び出しだけにしてみました
importModule の定義は外部ファイルに移動してます
<!DOCTYPE html>
<script type="text/module" data-name="add">
export const add = (a, b) => a + b
</script>
<script type="text/module" data-name="up">
const { add } = await importModule("add")
const up = x => add(x, 1)
export default up
</script>
<script type="text/module" data-name="main">
const module = await importModule("up")
console.log(module.default(1))
// 2
</script>
<script type="module">
import "https://gistcdn.githack.com/nexpr/a698476ca62ebfdb9867c3aac617dd49/raw/c06b093e062f98b441a45fd1429defed8dbe8d24/import-module.js"
importModule("main")
</script>
スッキリして良さそうな気がします
改良版