🚅

Viteをexpressにぶちこむ

2022/02/23に公開

ExpressサーバーとViteを組み合わせたら楽だなとか思ったのでやってみました。

Viteのアプリを作成

今回はModuleAという名前でViteアプリを作成します。

npm init vite
または
yarn create vite

現在のディレクトリ構成

.
├── root
├── package.json
├── yarn.lock
└── modules
    └── ModuleA
        ├── src
        ├── .gitignore
        ├── index.html
        ├── package.json
        ├── tsconfig.json
        ├── tsconfig.node.json
        └── vite.config.ts

Expressアプリを作成

まずはモジュールのインストール。

mkdir modules/ModuleB
cd modules/ModuleB

yarn init
yarn add express vite
yarn add -D typescript @types/node @types/express

次にコードを書いていきます。

src/index.ts
import express from 'express'

const app = express()

app.listen(3000, () => {
  console.log('listening on port 3000')
})

書いてあるそのまんまです。
expressをインポートして3000番でサーバーを起動しています。


次にviteのサーバー用のコードを

src/viteServer.ts
import express from 'express'
import fs from 'fs-extra'
import path from 'path'
import { createServer } from 'vite'

export const createViteDevServer = async (cwd = path.resolve('../', 'bacon-front-vite')) => {
    if (!fs.existsSync(cwd)) throw new Error(`No such directory: ${cwd}`)
    const app = express.Router()

    const vite = await createServer({
        root: cwd,
        logLevel: 'info',
        server: {
            middlewareMode: true,
            watch: {
                usePolling: true,
                interval: 100,
            },
        },
    })

    app.use(vite.middlewares)

    app.use('*', async (req, res) => {
        try {
            const url = req.originalUrl

            const html = fs.readFileSync(path.resolve(cwd, 'index.html'), 'utf-8')

            res.status(200)
                .set({ 'Content-Type': 'text/html' })
                .end(await vite.transformIndexHtml(url, html))
        } catch (e) {
            if (e instanceof Error) {
                vite && vite.ssrFixStacktrace(e)
                console.log(e.stack)
                res.status(500).end(e.stack)
            }
        }
    })

    return { app, vite }
}

上から順番に

cwdにはviteプロジェクトのディレクトリを指定します。
次にルーターとviteのサーバーを作成して、
viteのミドルウェアをルーターにバインドします。

src/viteServer.ts
export const createViteDevServer = async (cwd) => {
    if (!fs.existsSync(cwd)) throw new Error(`No such directory: ${cwd}`)
    const app = express.Router()

    const vite = await createServer({
        root: cwd,
        logLevel: 'info',
        server: {
            middlewareMode: true,
            watch: {
                usePolling: true,
                interval: 100,
            },
        },
    })
    app.use(vite.middlewares)
    ...
}

まずviteプロジェクトのディレクトリからindex.htmlを読み込みます。
つぎにvite.transformIndexHtmlにURLとHTMLを渡してビルドし、htmlを返します。

src/viteServer.ts
export const createViteDevServer = async (cwd) => {
    ...

    app.use('*', async (req, res) => {
        try {
            const url = req.originalUrl

            const html = fs.readFileSync(path.resolve(cwd, 'index.html'), 'utf-8')

            res.status(200)
                .set({ 'Content-Type': 'text/html' })
                .end(await vite.transformIndexHtml(url, html))
        } catch (e) {
            if (e instanceof Error) {
                res.status(500).end(e.stack)
            }
        }
    })

    return { app, vite }
}

最後にはじめのindex.tsを少し書き換えます。

src/index.ts
import express from 'express'
import { createViteDevServer } from './viteServer'

const app = express()

app.listen(3000, () => {
  console.log('listening on port 3000')
})

+ createViteDevServer('../../ModuleA').then(({app}) => {
+     app.use(app)
+ })

現在のディレクトリ構成

.
├── root
├── package.json
├── yarn.lock
└── modules
    ├── ModuleA
    │   ├── src
    │   ├── .gitignore
    │   ├── index.html
    │   ├── package.json
    │   ├── tsconfig.json
    │   ├── tsconfig.node.json
    │   └── vite.config.ts
    └── ModuleB
        ├── src
        │   ├── index.ts
        │   └── viteServer.ts
        ├── package.json
        └── tsconfig.json

起動する

ModuleB内でsrc/index.tsを起動します。
今回はts-nodeで起動します。

yarn ts-node src/index.ts

しばらくすると以下のようなログが表示されます。

Pre-bundling dependencies:
  react
  react-dom
  baseui
  chart.js
  styletron-engine-atomic
  (...and 26 more)
(this will be run only when your dependencies or config have changed)

この状態で指定し他ポート(今回は3030)にアクセスすると...

無事表示されました。

ファイルを書き換えると自動で更新されます。

まとめ

vite強い!!

Discussion