🎸

Viteで複数のHTMLファイルをビルドする

2025/01/30に公開

はじめに

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ファイルを作成します。

tsconfig.app.json
{
  "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"]
}
tsconfig.node.json
{
  "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"]
}
tsconfig.json
{
  "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へと繋ぎこむための処理を書きます。

main.tsx
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タグを追加します。

profile.html
<!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を作成します。

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から呼び出します。

Profile.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に記載します。

https://rollupjs.org/configuration-options/#input

vite.config.ts
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