Closed6

Vite

Kazuki MatsudaKazuki Matsuda

Vite を使う理由

  • ES モジュールがブラウザで使用できるようになるまで、JS にネイティブのモジュールシステムはなかった
  • だから、webpack、Rollup、Parcel などのバンドラが生まれた
  • ただ、これらのツールは JS の使用量が多くなるとパフォーマンスの問題が発生する
    • 開発サーバーを立ち上げるときに、アプリケーション全体を隅々までクロールしてビルドする必要がある
  • Vite はこの問題にどう対応するか
    • アプリケーションのモジュールを依存関係とソースコードの 2 つのカテゴリーに分割
    • esbuild を使用して依存関係の事前バンドル
    • ソースコードはネイティブ ESM を行使してソースコードを提供
    • ブラウザも実質的にバンドラーの一部に
    • Vite はブラウザのリクエストに応じて、ソースコードを変換して提供するだけ
    • キャッシュなども使用してより高速に
  • Vite は開発サーバーではバンドルしないが、プロダクションでは Rollup でバンドルする
    • ネットワークのラウンドトリップ増加が問題
    • esbuild でバンドルしない理由は、Rollup のエコシステムを利用したいから
    • 開発ビルドに esbuild、本番ビルドに Rollup を使用するためビルド結果に差異が出やすいなどの問題
    • 将来的には Rolldown に置き換えられる方向
    • Rolldown は Rollup の Rust ポートを構築する取り組み
Kazuki MatsudaKazuki Matsuda

特徴

  • npm 依存関係の解決と事前バンドル
    • node_modules/.vite に事前バンドル済みの依存関係をキャッシュする
    • 再バンドルしたい場合は、vite --force or node_modules/.vite を削除
import { someMethod } from 'my-dep'import { someMethod } from '/node_modules/.vite/deps/my-dep.js?v=f3sf2ebd'
  • HMR
    • create-vite を使用した場合は設定不要で HMR が有効になる
  • TypeScript
  • JSX
    • .jsx と .tsx も標準サポート
    • esbuild によりトランスパイルされる
Kazuki MatsudaKazuki Matsuda

静的アセット

import imgUrl from './img.png'
document.getElementById('hero-img').src = imgUrl
  • 開発中 imgUrl -> /img.png
  • 本番ビルド imgUrl -> /assets/img.2d8efhg.png
Kazuki MatsudaKazuki Matsuda

SSR

import fs from 'fs'
import path from 'path'
import { fileURLToPath } from 'url'
import express from 'express'
import { createServer as createViteServer } from 'vite'

const __dirname = path.dirname(fileURLToPath(import.meta.url))

async function createServer() {
  const app = express()

  // ミドルウェアモードで Vite サーバーを作成し、app type を 'custom' に指定します。
  // これにより、Vite 自体の HTML 配信ロジックが無効になり、親サーバーが
  // 制御できるようになります。
  const vite = await createViteServer({
    server: { middlewareMode: true },
    appType: 'custom'
  })

  // Vite の接続インスタンスをミドルウェアとして使用。独自の express ルータ
  // (express.Route()) を利用する場合は、router.use を使用してください
  // (たとえばユーザーが vite.config.js を編集した後に)サーバーが再起動しても、
  // `vite.middlewares` は同じリファレンスのままです(ただし、新しい Vite の内部スタックと
  // プラグインが注入されたミドルウェアが使用されます)。
  // 次のコードは再起動後でも有効です。
  app.use(vite.middlewares)

  app.use('*', async (req, res) => {
    // index.html を提供します - 次にこれに取り組みます。
  })

  app.listen(5173)
}

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

  try {
    // 1. index.html を読み込む
    let template = fs.readFileSync(
      path.resolve(__dirname, 'index.html'),
      'utf-8',
    )

    // 2. Vite の HTML の変換を適用します。これにより Vite の HMR クライアントが定義され
    //    Vite プラグインからの HTML 変換も適用します。 e.g. global preambles
    //    from @vitejs/plugin-react
    template = await vite.transformIndexHtml(url, template)

    // 3. サーバーサイドのエントリーポイントを読み込みます。 ssrLoadModule は自動的に
    //    ESM を Node.js で使用できるコードに変換します! ここではバンドルは必要ありません
    //    さらに HMR と同様な効率的な無効化を提供します。
    const { render } = await vite.ssrLoadModule('/src/entry-server.js')

    // 4. アプリケーションの HTML をレンダリングします。これは entry-server.js から
    //    エクスポートされた `render` 関数が、ReactDOMServer.renderToString() などの
    //    適切なフレームワークの SSR API を呼び出すことを想定しています。
    const appHtml = await render(url)

    // 5. アプリケーションのレンダリングされた HTML をテンプレートに挿入します。
    const html = template.replace(`<!--ssr-outlet-->`, appHtml)

    // 6. レンダリングされた HTML をクライアントに送ります。
    res.status(200).set({ 'Content-Type': 'text/html' }).end(html)
  } catch (e) {
    // エラーが検出された場合は、Vite にスタックトレースを修正させ、実際のソースコードに
    // マップし直します。
    vite.ssrFixStacktrace(e)
    next(e)
  }
})
Kazuki MatsudaKazuki Matsuda

理念

Vite は、すべてのユーザーのすべてのユースケースをカバーするつもりはありません。Vite は、Web アプリケーションを構築するための最も一般的なパターンをすぐにサポートすることを目指しています

Vite は、モダンなコードを書くことを後押しする opinionated な機能を提供します

このスクラップは2024/01/04にクローズされました