◆ importModule 関数を自作
◆ script タグごとに URL を作って別ファイルとしてインポート

1 つの HTML ファイルにすべてを含めるときでも JavaScript の処理を分けたいことがあります
関数にまとめて返り値として外部にエクスポートするような作りでもいいのですが 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>

スッキリして良さそうな気がします

改良版