qrac / minista

Static site generator with 100% static export from React and Vite.
https://minista.qranoko.jp
165 stars 13 forks source link

本番ビルドのSSGをesbuildからViteに変更 #91

Closed qrac closed 1 year ago

qrac commented 1 year ago

v2のSSGは

  1. esbuildで .tsx.mjs に変換(一時ファイル生成)
  2. nodeで .mjs 読み込む
  3. .html 作成

だったが、v3はViteのSSRを使うことで

  1. .tsx を読み込む
  2. .html 作成

を直接行う。

https://ja.vitejs.dev/guide/ssr.html#pre-rendering-ssg

qrac commented 1 year ago

this.emitFile で追加したHTMLはそこからバンドルを行わないので普通にoutputした場合と同じになる。

qrac commented 1 year ago

HTMLを仮想モジュール化してバンドルに含めることで理想的な処理になりそうだったが this.emitFile() されたHTMLのfileName のパスがおかしくなりビルドが通らない。

Vite及びrollupにもemitした後のリストを修正する機能はないようなので厳しい。

src/pages に一度HTMLファイルを出力して一時ファイルとして使えば通りそうだけど、あまり綺麗ではない...。

仮想モジュール化する場合の実装↓

import type { Plugin } from "vite"
import path from "node:path"
import { fileURLToPath } from "node:url"
import { createServer as createViteServer } from "vite"

import type { ResolvedConfig } from "../config/index.js"
import type { GetSources } from "../server/sources.js"
import type { ResolvedViteEntry } from "../config/vite.js"
import { compileApp } from "../compile/app.js"

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

type HtmlPages = {
  fileId: string
  moduleId: string
  path: string
  html: string
}[]

export function ssgv(config: ResolvedConfig): Plugin {
  const PREFIX = `\0virtual:`
  let htmlPages: HtmlPages = []

  return {
    name: "minista-vite-plugin:ssgv",
    async config(viteConfig) {
      const viteServer = await createViteServer(config.vite)

      await viteServer.listen()

      const { getSources } = (await viteServer.ssrLoadModule(
        __dirname + "/../server/sources.js"
      )) as { getSources: GetSources }
      const { resolvedGlobal, resolvedPages } = await getSources()

      await viteServer.close()

      if (resolvedPages.length === 0) {
        return
      }

      htmlPages = resolvedPages.map((page, index) => {
        const fileId = "__minista_html" + index
        const fileName = page.path.endsWith("/")
          ? path.join(page.path, "index.html")
          : page.path + ".html"
        const moduleId = path.join("src/pages", fileName)
        return {
          fileId,
          moduleId,
          path: page.path,
          html: compileApp({
            url: page.path,
            resolvedGlobal,
            resolvedPages: [page],
            useDevelopBundle: false,
          }),
        }
      })

      const htmlArrayInputs = htmlPages.map((page) => [
        page.fileId,
        page.moduleId,
      ])
      const htmlObjectInputs = Object.fromEntries(htmlArrayInputs)
      const assetInputs = viteConfig.build?.rollupOptions?.input || {}

      const mergedInputs = Object.assign(
        assetInputs,
        htmlObjectInputs
      ) as ResolvedViteEntry

      return {
        build: {
          rollupOptions: {
            input: mergedInputs,
          },
        },
      }
    },
    resolveId(id) {
      const target = htmlPages.find((page) => id === page.moduleId)
      if (target) {
        return PREFIX + id
      }
    },
    load(id) {
      if (id.startsWith(PREFIX)) {
        id = id.slice(PREFIX.length)
        const target = htmlPages.find((page) => id === page.moduleId)
        if (target) {
          return target.html
        }
      }
    },
  }
}
qrac commented 1 year ago

とりあえず、srcへの一時ファイル出力でなんとかした。バグが消えたら作り直したい。

qrac commented 1 year ago

参考:仮想モジュールを追加するViteプラグイン

qrac commented 1 year ago

参考:HTMLを追加するViteプラグインは仮想モジュールを使っておらず、どれも既存のHTMLを使うか生成した後に動かすか消すかを行なっている。

qrac commented 1 year ago

HTMLをエントリーするのではなく、一度SSR用にビルドしてプラグインをページのtsxに反映。nodeでHTMLを生成する。