シングルページアプリケーションのライブラリでマルチページしたい！

# A.viteならサクッとできた！

# Q.なぜそんなことを...

A.使ったことないライブラリの試作やちょっとしたwebアプリを作るときに、都度環境構築とか面倒くさいなぁ～、lintなどなどは共有しつつ、別ページとして構築できないかなぁという思いがありました。また、試作等個人の小規模な利用を意図しているので、あまり壮大なモノレポの仕掛けを用意したくないという思いもありました。いつも使ってるvite + αで上記のような要望を実現したいというのが始まりです。

# 今回作ったもの
github上で公開しています。以下の「やってみよう」と、一部内容が相違しています。具体的には下記の説明ではlintなどを省いてます。

https://github.com/Showichiro/multi-page-sample

# vite公式の説明を見てみましょう

https://ja.vitejs.dev/guide/build.html#%E3%83%9E%E3%83%AB%E3%83%81%E3%83%98%E3%82%9A%E3%83%BC%E3%82%B7%E3%82%99%E3%82%A2%E3%83%95%E3%82%9A%E3%83%AA

以下、上記のページから引用です。
>以下のようなソースコード構造があるとします:
>```
>├── package.json
>├── vite.config.js
>├── index.html
>├── main.js
>└── nested
>   ├── index.html
>   └── nested.js
>```
> 開発時には、/nested/ に移動またはリンクするだけで、通常の静的ファイルサーバと同じように期待通りに動作します。
> ビルド時には、エントリーポイントとして複数の .html ファイルを指定するだけです:
> ```js
>// vite.config.js
>import { resolve } from 'path'
>import { defineConfig } from 'vite'
>
>export default defineConfig({
>  build: {
>    rollupOptions: {
>      input: {
>        main: resolve(__dirname, 'index.html'),
>        nested: resolve(__dirname, 'nested/index.html'),
>      },
>    },
>  },
>})
引用終わり。

つまり、開発時はaタグなどで遷移すれば良さそう、ビルドはconfigに手を入れたら良さそう、という感じですね。

# やってみよう

## とりあえず箱を作る

```bash
# npmなりyarnなりお好きなパッケージマネージャで
pnpm create vite
```
色々な質問をされるのでCUIと対話します。
著者はviteの扱うライブラリの中ではReactが一番書けるので`react-ts`で進めています。

### 中身を見てみる
```
.
├── index.html
├── node_modules
├── package.json
├── pnpm-lock.yaml
├── public
│   └── vite.svg
├── src
│   ├── App.css
│   ├── App.tsx
│   ├── assets
│   │   └── react.svg
│   ├── index.css
│   ├── main.tsx
│   └── vite-env.d.ts
├── tsconfig.json
├── tsconfig.node.json
└── vite.config.ts
```
テンプレートからReactのコードが出力されました。いつも見るやつですね。

## 早速２つのページに分けてみよう

### src配下をいじっていきます
ディレクトリはこんな感じにしました。
```
src/
├── assets
│   └── react.svg
├── sample1
│   ├── index.css
│   ├── index.html
│   └── main.tsx
├── sample2
│   ├── index.css
│   ├── index.html
│   └── main.tsx
└── vite-env.d.ts
```

それぞれのディレクトリの中身は以下のようにしてみました。
index.htmlはmain.tsxを呼ぶだけ、main.tsxでは相互に対してaタグでリンクを張っています。cssは雑に背景色をつけています。

#### sample1
```react:sample1/main.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';

ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
  <React.StrictMode>
    <div>
      <div>sample1</div>
      <div>
        <a href="../sample2/index.html">to sample2</a>
      </div>
    </div>
  </React.StrictMode>,
);
```

```html:sample1/index.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite + React + TS</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="./main.tsx"></script>
  </body>
</html>
```

```css:sample1/index.css
html {
    background-color: cornsilk;
}
```

#### sample2

```react:sample2/main.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';

ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
  <React.StrictMode>
    <div>
      <div>sample2</div>
      <div>
        <a href="../sample1/index.html">to sample1</a>
      </div>
    </div>
  </React.StrictMode>,
);
```

```html:sample2/index.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite + React + TS</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="./main.tsx"></script>
  </body>
</html>
```

```css:sample2/index.css
html {
    background-color: aqua;
}
```

### configはこんな風に
公式ページに書いてあったように、エントリーポイントを指定しています。

```typescript:vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { resolve } from 'path';

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react()],
  root: 'src',
  build: {
    rollupOptions: {
      input: {
        sample1: resolve(__dirname, 'src/sample1/index.html'),
        sample2: resolve(__dirname, 'src/sample2/index.html'),
      },
    },
  },
});
```

## 起動してみよう

```bash
pnpm dev
```

`http://localhost:5173/sample1/index.html`を見ると
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/619165/ebb33672-e73f-47c9-0d34-c1112799e57e.png)

`http://localhost:5173/sample2/index.html`を見ると
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/619165/f883d77b-341c-6c29-fe85-ee75a2dd2fb3.png)

リンクを押下すると相互に遷移できることも確認できます。

## ビルドしてみよう

```bash
pnpm build
```

```console
../dist/sample1/index.html            0.54 kB
../dist/sample2/index.html            0.54 kB
../dist/assets/index-780b1a3e.css     0.03 kB │ gzip:  0.05 kB
../dist/assets/index-040d043c.css     0.03 kB │ gzip:  0.05 kB
../dist/assets/sample2-cddba5cd.js    0.36 kB │ gzip:  0.22 kB
../dist/assets/sample1-4decda64.js    0.36 kB │ gzip:  0.22 kB
../dist/assets/client-2ce6d88c.js   142.82 kB │ gzip: 45.84 kB
```
html/js/cssいずれもページごとに生成されていることがわかります。マルチページになっていますね。
超デカいライブラリがいても、それを使わない(importしない)ページのjsのサイズなどは抑えられます。また、cssも1ページじゃないので競合しないですね。

# 何に使えるんでしょうか？

SPAやりつつ巨大な仕掛けは用意せずマルチページにしたいという需要を叶えてくれそう。それが仕事などで具体的にどのような要件と合致するかはイメージついてないですが。