Vite 8 に上げたあと vite build がこの行で止まります。
TypeError: manualChunks is not a function
vite.config.js の manualChunks は関数ではなくオブジェクトで書いてあるはずです。それで正しく、Vite 7 までは動いていました。Vite 8.0.0 で本番ビルドのバンドラが Rollup から Rolldown に替わり、Rolldown はオブジェクト形式の manualChunks を受け取りません。
同じ行の少し上に、種明かしになる警告が出ています。
Warning: Invalid output options (1 issue found)
- For the "manualChunks". Invalid type: Expected Function but received Object.
検索で使う異表記:
manualChunks is not a functionInvalid type: Expected Function but received ObjectInvalid output options (1 issue found)- Vite 8.2.1 では
error during build:/Build failed with 1 error:の下に出ます。Vite 8.0.0 では捕捉されず、出力の末尾がNode.js v22.23.1のような素の例外で終わります(行そのものは同じ)。
直し方
output.codeSplitting に移します。Rolldown が用意している置き換え先です。
// vite.config.js
export default {
build: {
rolldownOptions: {
output: {
codeSplitting: {
groups: [
{ name: 'vendor', test: /node_modules/ }
]
}
}
}
}
}
groups の各要素は name(チャンク名)と test(モジュールIDに当てる正規表現)を取ります。CodeSplittingOptions は minSize / maxSize / minShareCount / includeDependenciesRecursively などの絞り込みも受け取ります。
置き場所は build.rollupOptions.output の下でも通りますが、そちらは非推奨です。 Vite 8 の移行ガイドは build.rollupOptions を build.rolldownOptions へ改名したものとして挙げており、設定リファレンスも rollupOptions を deprecated(rolldownOptions のエイリアス)と書いています。旧名でもビルドは通り、非推奨の警告も出ません。直すついでに新しい名前へ移すほうが、次の移行が 1 回減ります。同じアプリで両方を試すと、出るチャンクは名前もサイズも同じでした(vendor 189.23 kB)。
移行前の設定はこうでした。
// Vite 7 まで。Vite 8 ではこれが TypeError になる
manualChunks: {
vendor: ['react', 'react-dom']
}
ただし、この書き換えは「同じ設定の言い換え」ではありません。 分けるモジュールの選び方が変わるので、vendor の中身が移行前と変わることがあります。次の節がその話です。
移行後に vendor の中身が変わることがある
manualChunks のオブジェクト形式と codeSplitting の test は、チャンクに入れるモジュールの選び方が違います。 前者は列挙したモジュールとその依存、後者はモジュールIDに正規表現を当てて一致したものです。両者が同じ集合になるとは限らないので、同じ「vendor を分ける」つもりでも結果が変わることがあります。
React を使う最小のアプリで測った例です。まず Vite 7.3.6 のまま、列挙する名前だけを変えます。
vendor: ['react', 'react-dom'] → vendor 11.21 kB / index 181.17 kB
vendor: ['react', 'react-dom/client'] → vendor 191.81 kB / index 0.86 kB
このアプリが import しているのは react-dom/client で、これは react-dom とは別のモジュールです。react-dom を列挙しても、そこから辿れる依存に react-dom/client は含まれない(import している向きが逆)ため、react-dom 本体は vendor に入らず index に残りました。
これを codeSplitting の test: /node_modules/ に移すと、node_modules 配下が一致するので、同じアプリで vendor は 189.23 kB になりました。['react', 'react-dom'] を test: /node_modules/ に置き換えるのは、同じ指定の言い換えではありません。
必ず変わるという話ではありません。 旧い列挙と新しい test が同じモジュール集合を指すなら、成果物は変わりません。実際、Vite 8 の関数形式(id.includes('node_modules'))と codeSplitting(test: /node_modules/)は、同じアプリでチャンク名もサイズも一致しました。変わるかどうかは、自分の旧い列挙と新しい正規表現が、同じモジュールを指しているかで決まります。
移行したら dist/assets の一覧を移行前と突き合わせてください。ファイル名にハッシュが付く構成では、チャンクの中身が変わるとファイル名も変わります。CDN のキャッシュやエラー監視のソースマップがファイル名に紐づいているなら、そこが影響を受けます。
関数形式の manualChunks
エラー文が Expected Function but received Object と言うので、オブジェクトを関数に書き換える手が最初に出てきます。実際それでビルドは通ります。
// 通る。が、これも非推奨
manualChunks(id) {
if (id.includes('node_modules')) return 'vendor';
}
Vite 8.2.1 でこの形は終了コード 0 で完走し、vendor チャンクも出ます。ビルド出力に非推奨の告知は 1 行も出ません。
告知が出ないだけで、非推奨であること自体は配布物に書いてあります。node_modules/rolldown の型定義が manualChunks にこう付けています。
* Note that unlike Rollup, object form is not supported.
* @deprecated
* Please use output.codeSplitting instead.
つまり manualChunks は関数形式も含めて codeSplitting へ寄せる途中の API で、オブジェクト形式だけが先に受理されなくなった状態です。関数形式への書き換えはビルドを通しますが、移行ガイドと型定義が置き換え先として挙げているのは codeSplitting のほうなので、いずれもう一度書き換えることになります。
manualChunks と codeSplitting を両方書いたとき
codeSplitting が勝ち、manualChunks は無視されます。このときは警告が出ます。
WARN manualChunks option is ignored because the codeSplitting option is specified.
段階的に移行して両方が残った場合、ビルドは成功し、チャンクは codeSplitting の側の名前で出ます。
切り分け
vite buildでは出るがvite devでは出ない:manualChunksとcodeSplittingはどちらも本番ビルドのチャンク分割の設定です。同じオブジェクト形式の設定のまま Vite 8.2.1 でvite devを起動すると、開発サーバは待ち受けに入り、このTypeErrorは出ませんでした。設定を直したかどうかはvite buildでしか分かりません。- エラーは出ないが
vendorチャンクが消えた:manualChunksを消してcodeSplittingを足し忘れた形です。codeSplittingの既定はtrue(自動分割)で、どちらも書かずにビルドすると終了コード 0 でindexチャンク 1 つだけが出ます(同じアプリで 189.98 kB)。ビルドは成功し、名前を付けたチャンクだけが無くなります。 - ビルドは通るのに、本番に
console.logが残る:同じ Vite 8 移行で、止まらない側の変更を踏んでいます。esbuild: { drop: ['console'] }は Vite 8 では黙って無視されます(Vite 8 で console.log が本番バンドルに残る)。 Invalid type: Expected ... but received ...がmanualChunks以外の名前で出る:Rolldown の出力オプション検証は同じ形の警告を出します。entryFileNamesに数値を入れると- For the "entryFileNames". Invalid type: Expected (string | Function) but received 123.になりました(このときビルドは終了コード 1 で失敗します。警告の形は同じでも、止まるかどうかはオプションによって違います)。For the "..."の引用符の中を読んで、どのオプションかを特定してください。vite buildが別のTypeError: ... is not a functionで止まる:Vite 8 は CommonJS の default import の解決も変えています。条件が別(importer の拡張子や対象パッケージの持つフィールドで決まります)で、本記事では再現していません。移行ガイドの該当節を確認してください。- 依存を上げたら同期呼び出しが関数でなくなった:ツールの API が同期から非同期に変わる型は prettier3-resolveconfig-sync-removed が扱っています。あちらは版を戻さず
awaitを足して直す形で、本記事のようにオプションの置き場所が変わる話ではありません。