Migration

ここではminista v3からv4への移行方法について説明します。

Table of Contents

Overview

v4は設計が大きく変わっています。v3まではオールインワンのライブラリでしたが、v4からは本体がViteのラッパーとして動作し、SSGや画像最適化などの機能はすべてViteプラグインとして提供されます。

Changes

  • すべての機能をViteプラグイン化
  • コンフィグをVite公式に変更
  • インポートパスを細分化して変更
  • SvgrをSvgコンポーネントに変更
  • IconをSpriteに変更
  • Partial HydrationをIslandに変更
  • Delivery(納品リスト)を廃止

Agent Mode

まず、エディタのエージェントモードを使用して効率的に移行する方法を検討してください。GitHub上にこのページのMarkdownデータがありますので、エージェントへの指示としてコピーして使用(またはダウンロードして指示に添付)し、その上でうまくいかなかった箇所を修正してみてください。

Packages

最初にパッケージを更新します。v4では vite もpeerDependenciesになりました。

npm install --save-dev minista@latest vite react react-dom

TypeScriptを使用している場合は @types/react @types/react-dom も更新。

npm install --save-dev @types/react @types/react-dom

Partial HydrationやSearchの移行には @vitejs/plugin-react が必要です。

npm install --save-dev @vitejs/plugin-react

Config

次にコンフィグファイルを更新します。v4はminista独自のコンフィグを廃止しVite公式と同一になりました。ファイルは vite.config.{ts,js} または minista.config.{ts,js} のどちらでも動作します。

基本設定はVite公式に沿って移行。minista独自の機能はViteプラグイン化されているため、必要なプラグインをインポートして plugins 配列に追加します。

注意点として、v4はViteのSSRビルドと通常ビルドを連続で行うことを意識してください。ビルド用に設定した内容がSSRビルドを壊してしまう可能性があります。これは設定をSSRビルド用・通常ビルド用に切り分けることで解消できます。

Before
import { defineConfig } from "minista"

export default defineConfig({
  out: "dist",
  assets: {
    images: {},
  },
})
After
import { defineConfig, pluginSsg, pluginImage } from "minista"

export default defineConfig(({ command, isSsrBuild }) => {
  const isDev = command === "serve"
  const isSsr = command === "build" && (isSsrBuild ?? false)
  const isBuild = command === "build" && !(isSsrBuild ?? false)
  return {
    plugins: [pluginSsg()],
    build: { outDir: isBuild ? "dist" : undefined },
  }
})

root

※新旧ともにViteと同じなので変更なし

base

※新旧ともにViteと同じなので変更なし

public

Viteの publicDir に変更してください。

Before
export default { public: "public" }
After
export default { publicDir: "public" }

out

Viteの build.outDir に変更してください。

Before
export default { out: "dist" }
After
export default { build: { outDir: "dist" } }

assets.outDir

Viteの build.assetsDir に変更してください。

Before
export default { assets: { outDir: "assets" } }
After
export default { build: { assetsDir: "assets" } }

assets.outName

Viteの build.rolldownOptions でそれぞれ設定する必要があります。

  • CSS・画像・フォント: build.rolldownOptions.output.assetFileNames
  • JavaScript(chunk): build.rolldownOptions.output.chunkFileNames
  • JavaScript(entry): build.rolldownOptions.output.entryFileNames

※これらを設定した場合 build.assetsDir は無視されます。

Before
export default { assets: { outName: "pjt-[name]" } }
After
export default {
  build: {
    rolldownOptions: {
      output: {
        assetFileNames: "assets/pjt-[name][extname]",
        chunkFileNames: "assets/pjt-[name].js",
        entryFileNames: "assets/pjt-[name].js",
      },
    },
  },
}

assets.images

outDir, outName

Viteの build.rolldownOptions で設定する必要があります。後述のSeparate asset directoriesを参照して設定してください。

remoteName

ministaのプラグイン pluginImage のオプションに変更してください。v3では固定だった [index] プレースホルダーの位置を指定できます。

Before
export default { assets: { images: { remoteName: "remote" } } }
After
import { pluginImage } from "minista"

export default {
  plugins: [pluginImage({ optimize: { remoteName: "remote-[index]" } })],
}

optimize

ministaのプラグイン pluginImage のオプションに変更してください。v3では固定だった出力画像の名前([name]-[width]x[height])を変更できます。

Before
export default { assets: { images: { optimize: { layout: "constrained" } } } }
After
import { pluginImage } from "minista"

export default {
  plugins: [
    pluginImage({
      optimize: { outName: "[name]-[width]x[height]", layout: "constrained" },
    }),
  ],
}

assets.svgr

svgrOptions

SVGRが提供するオプションの中で svgoConfig のみ pluginSvg のオプションとしてそのまま移行できます。その他のオプションはサポートされていないため、後述のSvgr移行を参照して対応してください。

Before
export default {
  assets: { svgr: { svgrOptions: { svgoConfig: { plugins: [] } } } },
}
After
import { pluginSvg } from "minista"

export default { plugins: [pluginSvg({ config: { plugins: [] } })] }

assets.icons

後述によりIconはSpriteに移行となりますが、事前にオプションの移行先を記載します。

srcDir, outName

オプションとして指定することはなくなり、テンプレートに設置したSpriteの src により実質的に複数のディレクトリをターゲットにできるようになりました。また、src のファイルが存在するディレクトリ名が出力名になります。

Before
export default {
  assets: { icons: { srcDir: "src/assets/icons", outName: "[dirname]" } },
}
After (template)
import { Sprite } from "minista/assets"

export default function () {
  return <Sprite src="/src/assets/icons/square.svg" />
}

outDir

Viteの build.rolldownOptions で設定する必要があります。後述のSeparate asset directoriesを参照して設定してください。

svgstoreOptions

依存関係のSVG最適化ライブラリが svgstore から svgo に変更となったため、同様のオプションは存在しません。pluginSpritesvgo オプションを渡すことで最適化を図ってください。

Before
export default {
  assets: {
    icons: {
      svgstoreOptions: {
        cleanSymbols: ["fill", "stroke", "stroke-linejoin", "stroke-width"],
      },
    },
  },
}
After
import { pluginSprite } from "minista"

export default {
  plugins: [
    pluginSprite({
      config: {
        plugins: [
          {
            name: "removeAttrs",
            params: {
              attrs: ["fill", "stroke", "stroke-linejoin", "stroke-width"],
            },
          },
        ],
      },
    }),
  ],
}

assets.fonts

outDir, outName

Viteの build.rolldownOptions で設定する必要があります。後述のSeparate asset directoriesを参照して設定してください。

assets.bundle

outName

ministaのプラグイン pluginBundle のオプションに変更してください。

Before
export default { assets: { bundle: { outName: "bundle" } } }
After
import { pluginBundle } from "minista"

export default { plugins: [pluginBundle({ outName: "bundle" })] }

assets.partial

後述によりPartial HydrationはIslandに移行となりますが、事前にオプションの移行先を記載します。

usePreact

自動切り替えは廃止されたため、Viteの resolve.alias を使用してください。

Before
export default { assets: { partial: { usePreact: true } } }
After
const preactAlias = {
  react: "preact/compat",
  "react-dom": "preact/compat",
}

export default defineConfig(({ command, isSsrBuild }) => {
  const isDev = command === "serve"
  const isSsr = command === "build" && (isSsrBuild ?? false)
  const isBuild = command === "build" && !(isSsrBuild ?? false)
  return {
    resolve: {
      alias: isBuild ? preactAlias : undefined,
    },
  }
})

preact のインストールも必要となります。

$ npm install preact

useIntersectionObserver

IntersectionObserverを使うかどうかはコンフィグではなくコンポーネントのディレクティブで指定するようになりました。client:visible を使用してください。

Before
export default { assets: { partial: { useIntersectionObserver: true } } }
After (template)
import { Counter } from "../components/counter"

export default function () {
  return <Counter client:visible />
}

outName

ministaのプラグイン pluginIsland のオプションに変更してください。v3では固定だった [index] プレースホルダーの位置を指定できます。

Before
export default { assets: { partial: { outName: "hydrate" } } }
After
import { pluginIsland } from "minista"

export default { plugins: [pluginIsland({ outName: "hydrate-[index]" })] }

rootAttrSuffix, rootValuePrefix

rootAttrSuffix はministaのプラグイン pluginIsland のオプションに変更してください。rootValuePrefix で設定していた識別子の接頭辞は廃止され、HTMLには数字のみ記載されます。

Before
export default {
  assets: {
    partial: { rootAttrSuffix: "partial-hydration", rootValuePrefix: "ph" },
  },
}
After
import { pluginIsland } from "minista"

export default {
  plugins: [pluginIsland({ rootAttrName: "partial-hydration" })],
}

ただし、ハイドレーション対象のルート要素に付与される属性名は機能の細分化に伴い変更されているので、同一の値を渡しても異なる名前になります。CSSやJavaScriptで参照している場合は注意してください。

Before
<div data-partial-hydration="ph-1">...</div>
After
<div
  data-partial-hydration-client-directive="load"
  data-partial-hydration-client-directive-params
  data-partial-hydration-client-snippet="1"
>
  ...
</div>

rootDOMElement, rootStyle

ministaのプラグイン pluginIsland のオプションに変更してください。

Before
export default {
  assets: {
    partial: { rootDOMElement: "div", rootStyle: { display: "contents" } },
  },
}
After
import { pluginIsland } from "minista"

export default {
  plugins: [
    pluginIsland({ rootDOMElement: "div", rootStyle: { display: "contents" } }),
  ],
}

intersectionObserverOptions

IntersectionObserverの設定はそれが使用されるコンポーネントの client:visible ディレクティブで指定するようになりました。

Before
export default {
  assets: { partial: { intersectionObserverOptions: { rootMargin: "0px" } } },
}
After (template)
import { Counter } from "../components/counter"

export default function () {
  return <Counter client:visible={{ rootMargin: "0px" }} />
}

resolve.alias

※新旧ともにViteと同じなので変更なし

css

※新旧ともにViteと同じなので変更なし

markdown

Viteプラグイン pluginMdx のオプションに変更となります。v3でministaが用意していた useRemarkGfm useRehypeHighlight は廃止されたため、必要なRemark / Rehypeプラグインをインストールして remarkPlugins rehypePlugins に渡してください。

useRemarkGfm, remarkGfmOptions

remark-gfm を直接設定してください。

Before
export default {
  markdown: {
    useRemarkGfm: true,
    remarkGfmOptions: {},
  },
}
After
import { pluginMdx } from "minista"
import remarkGfm from "remark-gfm"

export default {
  plugins: [pluginMdx({ remarkPlugins: [[remarkGfm, {}]] })],
}

useRehypeHighlight, rehypeHighlightOptions

rehype-highlight を直接設定してください。また、見た目の調整まで含めたい場合は rehype-pretty-code など別のRehypeプラグインへの移行も検討してください。

Before
export default {
  markdown: {
    useRehypeHighlight: true,
    rehypeHighlightOptions: {},
  },
}
After
import { pluginMdx } from "minista"
import rehypeHighlight from "rehype-highlight"

export default {
  plugins: [pluginMdx({ rehypePlugins: [[rehypeHighlight, {}]] })],
}

mdxOptions

mdxOptions の中身は pluginMdx の直下に移動してください。

Before
export default {
  markdown: {
    mdxOptions: {
      remarkPlugins: [],
      rehypePlugins: [],
    },
  },
}
After
import { pluginMdx } from "minista"

export default {
  plugins: [pluginMdx({ remarkPlugins: [], rehypePlugins: [] })],
}

ministaのプラグイン pluginSearch のオプションに変更してください。検索UIはIslandとして動作するため、pluginIsland@vitejs/plugin-react も併用します。

Before
export default {
  search: {
    outDir: "assets",
    outName: "search",
    include: ["**/*"],
    exclude: ["/404"],
    baseUrl: "",
    trimTitle: "",
    targetSelector: "[data-search]",
  },
}
After
import { pluginIsland, pluginSearch } from "minista"
import react from "@vitejs/plugin-react"

export default {
  plugins: [
    pluginIsland(),
    pluginSearch({
      outName: "search",
      src: ["**/*.html"],
      ignore: ["404.html"],
      trimTitle: "",
      targetSelector: "[data-search]",
    }),
    react(),
  ],
}

outDir

検索用JSONもViteのアセットとして出力されるため、個別の outDir は廃止されました。出力先を変える場合は build.rolldownOptions.output.assetFileNames で制御してください。

include, exclude

includesrcexcludeignore に変更してください。対象はSSG後のHTMLファイルなので、"/404" のようなURLパスではなく "404.html" のようなファイル名のglobを指定します。

baseUrl

baseUrl は廃止されました。URLの基準はViteの base から解決されます。

hit

hit の中身は同じ名前で pluginSearch に移行できます。

Before
export default {
  search: {
    hit: {
      minLength: 3,
      number: false,
      english: true,
      hiragana: false,
      katakana: true,
      kanji: true,
    },
  },
}
After
import { pluginSearch } from "minista"

export default {
  plugins: [
    pluginSearch({
      hit: {
        minLength: 3,
        number: false,
        english: true,
        hiragana: false,
        katakana: true,
        kanji: true,
      },
    }),
  ],
}

delivery

Delivery(納品リスト)は廃止されました。include exclude trimTitle sortBy に対応する移行先はありません。

archives で圧縮ファイルを生成していた場合は、ministaのプラグイン pluginArchive に移行してください。

Before
export default {
  delivery: {
    archives: [{ srcDir: "dist", outName: "dist" }],
  },
}
After
import { pluginArchive } from "minista"

export default {
  plugins: [
    pluginArchive({
      archives: [{ srcDir: "dist", outName: "dist" }],
    }),
  ],
}

beautify

ministaのプラグイン pluginBeautify のオプションに変更してください。v3の useHtml useAssets は廃止され、対象ファイルは src で指定します。

Before
export default {
  beautify: {
    useHtml: true,
    useAssets: false,
    htmlOptions: {},
    cssOptions: {},
    jsOptions: {},
  },
}
After
import { pluginBeautify } from "minista"

export default {
  plugins: [
    pluginBeautify({
      src: ["**/*.{html,css,js}"],
      htmlOptions: {},
      cssOptions: {},
      jsOptions: {},
    }),
  ],
}

vite

v4ではministaのコンフィグ自体がViteのコンフィグになったため、vite の中身は直下に移動してください。

Before
export default {
  vite: {
    server: { port: 3000 },
  },
}
After
export default {
  server: { port: 3000 },
}

Separate asset directories

Viteのデフォルト設定では、すべてのアセットが同じディレクトリに出力されます。CSS・JavaScript・画像・フォントを別々のディレクトリに出力したい場合は、以下のプロパティを設定してください。

  • CSS・画像・フォント: build.rolldownOptions.output.assetFileNames
  • JavaScript(chunk): build.rolldownOptions.output.chunkFileNames
  • JavaScript(entry): build.rolldownOptions.output.entryFileNames

※これらを設定した場合 build.assetsDir は無視されます。

assetFileNames は拡張子ごとに分けることができます。

./minista.config.ts
import type { PreRenderedAsset } from "rolldown"
import { normalizePath } from "vite"

const assetFileNames = (assetInfo: PreRenderedAsset) => {
  const name = assetInfo.name ?? ""
  const originalNames = assetInfo.originalFileNames ?? []
  const isSprite = originalNames.some((file) =>
    normalizePath(file).includes("/.minista/sprite/"),
  )
  if (name.endsWith(".css")) {
    return "assets/css/[name][extname]"
  }
  if (isSprite) {
    return "assets/sprites/[name][extname]"
  }
  if (/\.(png|jpe?g|gif|bmp|svg|webp|avif)$/.test(name)) {
    return "assets/images/[name][extname]"
  }
  if (/\.(woff2?|ttf|otf|eot)$/.test(name)) {
    return "assets/fonts/[name][extname]"
  }
  return "assets/others/[name][extname]"
}

export default {
  build: {
    rolldownOptions: {
      output: {
        assetFileNames,
        chunkFileNames: "assets/js/[name].js",
        entryFileNames: "assets/js/[name].js",
      },
    },
  },
}

Import Paths

v4では機能ごとにインポートパスが分かれています。主な移行先は以下です。

  • コンフィグ・プラグイン: minista
  • アセット系コンポーネント: minista/assets
  • Headコンポーネント: minista/head
Before
import { Head, Image } from "minista"
After
import { Image } from "minista/assets"
import { Head } from "minista/head"

Svgr to Svg

v3のSVGRは、SVGをReactコンポーネントとしてimportしていました。v4では pluginSvg<Svg> コンポーネントに移行してください。

Before
import Logo from "../assets/logo.svg"

export default function () {
  return <Logo className="logo" />
}
After
import { Svg } from "minista/assets"

export default function () {
  return <Svg src="/src/assets/logo.svg" className="logo" />
}

Icon to Sprite

v3の Icon はv4の Sprite に移行してください。name で指定していたアイコン名は、SVGファイルへのルートパスを src に指定する形へ変わります。

Before
import { Icon } from "minista"

export default function () {
  return <Icon name="square" />
}
After
import { Sprite } from "minista/assets"

export default function () {
  return <Sprite src="/src/assets/icons/square.svg" />
}

Partial Hydration to Island

v3のPartial Hydrationはv4のIslandに移行してください。v3ではコンポーネントのimportパス末尾に ?ph を付与していましたが、v4では通常のimportに戻し、使用箇所に client:* ディレクティブを付与します。

v3のPartial Hydration対象コンポーネントは隔離されていたためpropsを渡せず、named exportにも対応していませんでした。v4のIslandではpropsを渡せるため、必要に応じてコンポーネント設計も見直してください。

Before
import BlockCounter from "../../components/block-counter?ph"

export default function () {
  return <BlockCounter /> // You can't pass Props.
}
After
import BlockCounter from "../../components/block-counter"

export default function () {
  return <BlockCounter client:load />
}

v3のデフォルト設定では、ブラウザでコンポーネントが画面に表示されるとReact Appとして復元されていました。同じタイミングに寄せる場合は client:visible を使用してください。

After
import BlockCounter from "../../components/block-counter"

export default function () {
  return <BlockCounter client:visible />
}