はじめに
引き続き42Tokyoの課題に取り組んでいる間に初めてのことで沼ったのでまとめておきます。
問題
結論から言うと、今回沼った原因はinnerHTMLでDOMに追加されたscriptは実行されないという、ブラウザのセキュリティ上の仕様を理解していなかったことです。
実現したかったこと
実現したかったことは、htmlのdiv内に別のhtmlを埋め込み、そのhtmlの要素をスクリプトで変更することでした。
言葉で説明するとややこしいので、今回はサンプルとして以下のindex.htmlのdiv=appにapp.htmlを埋め込んで、そのhtml内の要素をスクリプトで変更することを実現目標とします。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>Hello World</h1>
<div id="app">
</div>
</body>
</html>
サンプルコード
まずは問題を再現するためのサンプルコードを用意していきます。
サーバーはfastifyで立てます。
import fastify from "fastify";
import fastifyStatic from "@fastify/static";
import path from 'path';
import process from 'process';
const server = fastify({logger: true});
server.listen({port: 8080});
server.register(fastifyStatic, {
root: path.join(process.cwd(), ''),
prefix: '/'
});
server.setNotFoundHandler((__request, reply) => {
reply.sendFile('index.html');
});
間違ったアプローチ
まずは実際に私が間違えてとったアプローチを再現します。
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>Hello World</h1>
<div id="app">
</div>
<script src="index.js" defer></script>
</body>
</html>
index.js
document.addEventListener('DOMContentLoaded', async () => {
const h1 = document.getElementsByTagName('h1')[0];
if (h1)
h1.textContent = 'Hello Index';
const response = await fetch('app.html');
const htmlContent = await response.text();
const app = document.getElementById('app');
if (app) {
app.innerHTML = htmlContent;
}
})
app.html
<h2>Hoge</h2>
<script src="app.js" defer></script>
app.js
document.addEventListener('DOMContentLoaded', ()=> {
const h2 = document.getElementsByTagName('h2')[0];
if (h2) {
h2.textContent = 'Hello App';
}
});
app.jsで変更したはずの"Hello App"が表示されず"Hoge"のままになっています。
この問題はどうやらinnerHTMLで埋め込んだhtml内に含まれているscriptはセキュリティ上実行されないというもののようです。エラーコードとして表示されないのでこのことに気づくまで1時間ほど溶かしました。
解決したコード
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>Hello World</h1>
<div id="app">
</div>
<script type="module" src="index.js" defer></script>
</body>
</html>
index.js
import {renderApp} from './app.js';
document.addEventListener('DOMContentLoaded', async () => {
const h1 = document.getElementsByTagName('h1')[0];
if (h1)
h1.textContent = 'Hello Index';
renderApp();
})
app.js
export async function renderApp() {
const response = await fetch('app.html');
const htmlContent = await response.text();
const app = document.getElementById('app');
if (app) {
app.innerHTML = htmlContent;
}
const h2 = document.getElementsByTagName('h2')[0];
if (h2) {
h2.textContent = 'Hello App';
}
}
app.index
<h2>Hoge</h2>
これで無事に表示されました。
終わりに
少しでも同じ問題にぶつかった方の参考になれば幸いです。

