Viteで複数のHTMLファイルをビルドする
はじめに
ReactなどのSingle Page Applicationでは、ルートのindex.htmlからscriptタグでReactに接続し、Reactコンポーネントをレンダリングしています。
この記事では、HTMLファイルがindex.htmlだけでなく、複数ある場合のビルド方法をまとめていきたいと思います。
想定として、HTML/CSSで作成したWebサイトに、後から一部分をコンポーネント化や、Reactでの処理を追加したい時などに利用できると思います。
作業ディレクトリの初期化
任意のディレクトリで複数HTMLファイルを作成したのち、pnpm init
します。
次にVite/React/TypeScriptに必要なライブラリをインストールします。
$ pnpm add react react-dom
$ pnpm add -D vite typescript @vitejs/plugin-react @types/react @types/react-dom
tsconfigファイルを作成します。
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
"jsx": "react-jsx",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["src"]
}
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"target": "ES2022",
"lib": ["ES2023"],
"module": "ESNext",
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["vite.config.ts"]
}
{
"files": [],
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" }
]
}
この状態で、作業ディレクトリに存在するファイルは以下のようになるはずです。
.
├── index.html
├── profile.html
├── node_modules
├── package.json
├── pnpm-lock.yaml
├── tsconfig.app.json
├── tsconfig.json
└── tsconfig.node.json
Reactを部分的に導入する
index.htmlをHomeのLPとして、profile.htmlにReactを導入していきます。
src/main.tsx
を作り、HTMLのタグからidを取得してReactへと繋ぎこむための処理を書きます。
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
function App() {
return (
<h1>Hello world!</h1>
)
}
createRoot(document.getElementById('profile')!).render(
<StrictMode>
<App />
</StrictMode>,
)
profile.htmlにid="profile"
を持つ空のdivタグとsrc/main.tsx
を呼び出すためのscriptタグを追加します。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Profile</title>
</head>
<body>
<div id="profile"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
次にvite.config.ts
を作成します。
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
})
ここまでできればdevサーバー上では/profile.html
のパスでHello World!
という文字列(Reactによってレンダーされている)を確認できるでしょう。
main.tsxのAppを削除し、新たにProfile.tsxを作ってmain.tsxから呼び出します。
export function Profile() {
return (
<div>
<h1>Profile</h1>
<ul>
<li> - Webエンジニアの26歳</li>
<li> - 茨城県出身</li>
<li> - 品行方正</li>
</ul>
<a href="index.html">戻る</a>
</div>
)
}
ただし現状だとdev環境で確認できているものを正しくビルドできません。
ビルド
今回行うビルドの設定はvite.config.ts
でdefineConfig.build.rollupOptionsに記載します。
export default defineConfig({
plugins: [react()],
build: {
rollupOptions: {
input: {
home: resolve(__dirname, "/index.html"),
profile: resolve(__dirname, "/profile.html")
}
}
}
})
エントリーポイントに名前をマッピングしたオブジェクトを指定すると、それらは別々の出力チャンクにバンドルされます。
これによって、複数HTMLファイルをビルドし、distディレクトリに作成されます。さらに、ビルドされたJavaScriptファイルがdist/profile.htmlから読み込まれてReactコンポーネントがレンダーされます。
Discussion