Mobile Factory Tech Blog

技術好きな方へ!モバイルファクトリーのエンジニアたちが楽しい技術話をお届けします!

Vite+時代のVueプロジェクトのLinter/Formatter設定 2026年5月

こんにちは、駅メモ!開発チームエンジニアの id:hayayanai です!

最近、 VoidZero から Vite+ がリリースされました。 Vite+ は Vite 8、Vitest、Oxlint、Oxfmt などを統合した「Web のための統合ツールチェーン」です。

駅メモ!は現在 Vue + Vite 7 + Vitest + ESLint(チーム独自ルール有り)+ Prettier + Stylelint で開発されており、Vite+ のツールはまだ導入していません。 AI で開発が高速化した現代、数十倍速いと謳う Vite+ のツールチェーンは気になります。

ただ、そもそも各ツールがどれくらい Vue 対応しているのか、どう設定すれば良いのかがわからなかったので、テンプレを使って確認することにしました。 Vite+ 経由の vp create vue と従来の pnpm create vue@latest で生成されるプロジェクト設定を比較し、Vue プロジェクト特有の注意点を整理します。

検証環境

  • vp v0.1.16 (Vite+)
  • create-vue v3.22.2 (pnpm create vue@latest)
  • Node.js v24.14.1
  • pnpm v10.33.0

プロジェクトの作成

Vite+ 経由

vp create vue vue-viteplus
◇ Which package manager would you like to use?
  pnpm

◇ pnpm@10.33.0 installed

◇ Which agents are you using?
  Claude Code

◇ Which editor are you using?
  VSCode

◇ Set up pre-commit hooks to run formatting, linting, and type checking with auto-fixes?
  Yes


Generating project…

Running: pnpm dlx create-vue

┌  Vue.js - The Progressive JavaScript Framework
│
◇  Project name (target directory):
│  vue-viteplus
│
◇  Use TypeScript?
│  Yes
│
◇  Select features to include in your project:
│  Vitest (unit testing), Linter (error prevention), Prettier (code formatting)
│
◇  Select experimental features to include in your project:
│  Replace Prettier with Oxfmt
│
◇  Skip all example code and start with a blank Vue project?
│  No

Scaffolding project in /Users/yanai/project/vue-viteplus...
│
└  Done.

✔ Merged vue-viteplus/.oxlintrc.json into vue-viteplus/vite.config.ts
✔ Merged vue-viteplus/.oxfmtrc.json into vue-viteplus/vite.config.ts

Wrote agent instructions to CLAUDE.md

Rewrote imports in 4 files

✔ Merged staged config into vue-viteplus/vite.config.ts
◇ Dependencies installed
◇ Code formatted
◇ Scaffolded vue-viteplus

出力を見ると Running: pnpm dlx create-vue とあり、内部で create-vue を呼んでいることが分かります。create-vue でプロジェクトを生成した後に、Vite+ が以下の変換をかけています。

  • .oxlintrc.jsonvite.config.tslint ブロックにマージ
  • .oxfmtrc.jsonvite.config.tsfmt ブロックにマージ
  • vite / vitest の import パスを vite-plus に書き換え
  • pre-commit フック(vp staged)の設定を統合

つまり vp create vue は create-vue のラッパーで、生成物を Vite+ 向けに変換しているだけのようです。

従来の create-vue

pnpm create vue@latest vue-create-vue
┌  Vue.js - The Progressive JavaScript Framework
│
◇  Use TypeScript?
│  Yes
│
◇  Select features to include in your project:
│  Vitest (unit testing), Linter (error prevention), Prettier (code formatting)
│
◇  Select experimental features to include in your project:
│  none
│
◇  Skip all example code and start with a blank Vue project?
│  No

Scaffolding project in /Users/yanai/project/vue-create-vue...
│
└  Done.

こちらは従来通りのシンプルな Scaffold です。create-vue 側でも「Replace Prettier with Oxfmt」の選択肢が出ますが、今回は Oxfmt との比較のため Prettier を選びました。

生成される設定ファイルの比較

以降のコードブロックは主要部分の抜粋です。

Vite+ プロジェクトの構成

package.json

{
  "scripts": {
    "dev": "vp dev",
    "build": "run-p type-check \"build-only {@}\" --",
    "build-only": "vp build",
    "type-check": "vue-tsc --build",
    "test:unit": "vp test",
    "lint": "run-s lint:*",
    "lint:oxlint": "vp lint . --fix",
    "lint:eslint": "eslint . --fix --cache",
    "format": "vp fmt src/"
  },
  "devDependencies": {
    "eslint": "^10.1.0",
    "eslint-plugin-vue": "~10.8.0",
    "eslint-plugin-oxlint": "~1.57.0",
    "eslint-config-prettier": "^10.1.8",
    "vite": "catalog:",
    "vite-plus": "catalog:",
    "vitest": "catalog:"
  }
}

vite.config.ts:

import { defineConfig } from "vite-plus"
import vue from "@vitejs/plugin-vue"

export default defineConfig({
  staged: {
    "*": "vp check --fix",
  },
  fmt: {
    semi: false,
    singleQuote: true,
  },
  lint: {
    plugins: ["eslint", "typescript", "unicorn", "oxc", "vue", "vitest"],
    env: { browser: true },
    categories: { correctness: "error" },
    options: { typeAware: true, typeCheck: true },
  },
  plugins: [vue()],
})

defineConfig'vite-plus' からインポートしていて、Vite の設定に加え lint(Oxlint)、fmt(Oxfmt)、staged(pre-commit フック)の設定が1つのファイルにまとまっています。 vitevitest は、pnpm-workspace.yamlcatalog: により @voidzero-dev のものに解決されています。

従来の create-vue プロジェクトの構成

package.json

{
  "scripts": {
    "dev": "vite",
    "build": "run-p type-check \"build-only {@}\" --",
    "build-only": "vite build",
    "type-check": "vue-tsc --build",
    "test:unit": "vitest",
    "lint": "run-s lint:*",
    "lint:oxlint": "oxlint . --fix",
    "lint:eslint": "eslint . --fix --cache",
    "format": "prettier --write --experimental-cli src/"
  },
  "devDependencies": {
    "eslint": "^10.1.0",
    "eslint-plugin-vue": "~10.8.0",
    "eslint-plugin-oxlint": "~1.57.0",
    "eslint-config-prettier": "^10.1.8",
    "oxlint": "~1.57.0",
    "prettier": "3.8.1",
    "vite": "^8.0.3",
    "vitest": "^4.1.2"
  }
}

.oxlintrc.json:

{
  "plugins": ["eslint", "typescript", "unicorn", "oxc", "vue", "vitest"],
  "env": { "browser": true },
  "categories": { "correctness": "error" }
}

.prettierrc.json:

{
  "$schema": "https://json.schemastore.org/prettierrc",
  "semi": false,
  "singleQuote": true,
  "printWidth": 100
}

共通: eslint.config.ts

前述の通り vp create vue は内部で create-vue を実行しているため、eslint.config.ts は両プロジェクトで同一です。

import {
  defineConfigWithVueTs,
  vueTsConfigs,
} from "@vue/eslint-config-typescript"
import pluginVue from "eslint-plugin-vue"
import pluginVitest from "@vitest/eslint-plugin"
import pluginOxlint from "eslint-plugin-oxlint"
import skipFormatting from "eslint-config-prettier/flat"

export default defineConfigWithVueTs(
  { name: "app/files-to-lint", files: ["**/*.{vue,ts,mts,tsx}"] },
  ...pluginVue.configs["flat/essential"],
  vueTsConfigs.recommended,
  { ...pluginVitest.configs.recommended, files: ["src/**/__tests__/*"] },
  ...pluginOxlint.buildFromOxlintConfigFile(".oxlintrc.json"),
  skipFormatting
)

ただし、Vite+ プロジェクトではこの eslint.config.ts に落とし穴があります。 Vite+ の公式ガイド では .oxlintrc.json の使用は推奨されておらず、vite.config.tslint ブロックへ設定を集約する方針です。実際、vp create vue.oxlintrc.jsonvite.config.ts へマージされた後に削除されています。 しかし eslint.config.tsbuildFromOxlintConfigFile(".oxlintrc.json") はそのまま残っています。存在しないファイルを参照すると eslint-plugin-oxlint: could not find oxlint config file: .oxlintrc.json と警告が出て空配列を返すため、ルール重複の無効化が効きません。 つまり、Oxlint と ESLint で同じ違反が重複報告される状態になります。

回避策は2つあります。

1つ目は vite.config.ts から lint ブロックを直接インポートする方法です。 eslint.config.ts は TypeScript ですから、vite.config.ts の default export から .lint を取り出して buildFromOxlintConfig に渡せます。

// eslint.config.ts
import viteConfig from './vite.config'

// 変更前: ファイルが存在しないため機能しない
...pluginOxlint.buildFromOxlintConfigFile(".oxlintrc.json"),

// 変更後: vite.config.ts の lint ブロックをそのまま渡す
...pluginOxlint.buildFromOxlintConfig(viteConfig.lint),

2つ目は vite.config.tslint ブロックを削除し、.oxlintrc.json に設定を一本化する方法です。 vite-plus の issue によると、現状の実装では .oxlintrc.json 等の専用設定ファイルが優先され、vite.config.ts はフォールバックとして使われます。 .oxlintrc.json があればそちらが読み込まれます。

{
  "plugins": ["eslint", "typescript", "unicorn", "oxc", "vue", "vitest"],
  "env": { "browser": true },
  "categories": { "correctness": "error" },
  "options": { "typeAware": true, "typeCheck": true }
}

eslint.config.ts の修正が不要で済みますが、Vite+ の「vite.config.ts に集約する」方針とは外れます。

共通: Lint 実行順

2026年4月時点で、create-vue は Oxlint をデフォルトで同梱しています。前述の package.json にある通り、pnpm lintrun-s lint:*)で lint:oxlintlint:eslint の順に直列実行されます。 create-vue 側では eslint-plugin-oxlint.oxlintrc.json を読み取り、Oxlint と重複する ESLint ルールを自動で無効化してくれます。

# Vite+
vp run lint
# → vp lint . --fix       ... Oxlint(vp経由)
# → eslint . --fix --cache ... ESLint(直接呼び出し)

# create-vue
pnpm lint
# → oxlint . --fix        ... Oxlint(直接呼び出し)
# → eslint . --fix --cache ... ESLint(直接呼び出し)

vp lint は Oxlint だけを実行する組み込みコマンドで、ESLint は Vite+ に統合されていません。 そのため、テンプレートでは ESLint を eslint コマンドで直接呼ぶ構成になっています。

Vite+ のタスクランナーを活用したい場合は、vite.config.tsrun.tasks に定義を移行すると良さそうです。 run.tasks で定義したタスクはデフォルトでキャッシュが有効なため、入力ファイルに変更がなければ再実行がスキップされます。 なお、run.tasks のタスク名は package.jsonscripts と重複できないため、移行する場合は package.json 側の lint スクリプトを削除します。

// package.json の lint 関連スクリプトを run.tasks に移行する例
run: {
  tasks: {
    lint: {
      command: 'vp lint . --fix && eslint . --fix --cache',
      input: [{ auto: true }, '!.eslintcache'],
    },
  },
},

eslint--cache を使うと .eslintcache が書き出され、vp run がそれを入力の変更と見なしてタスクキャッシュがヒットしません。 input'!.eslintcache' を指定し、キャッシュファイルを変更検知の対象外にすることで併用できます。 キャッシュ機能については ESLint ではなくタスクランナー側のもので十分かもしれませんが、--cache の有無による差異は今回未検証です。

Linter 比較: Oxlint vs ESLint

検証用コンポーネント

検証用に、意図的に Lint 違反を仕込んだ Vue コンポーネントを用意しました。

<script setup lang="ts">
import { ref } from "vue"

// unused expression (correctness)
const x = 1
x

// prefer-as-const (typescript)
let y = "hello" as "hello"

const items = ref([
  { id: 1, name: "Apple" },
  { id: 2, name: "Banana" },
])
</script>

<template>
  <!-- v-for without :key -->
  <li v-for="item in items">{{ item.name }}</li>

  <!-- v-if and v-for on same element -->
  <div v-for="item in items" v-if="item.id > 0" :key="item.id">
    {{ item.name }}
  </div>
</template>

<style scoped>
.unused-class {
  color: redd;
}
</style>

Oxlint の結果

# Vite+
vp lint src/components/LintTest.vue

  x eslint(no-unused-expressions): Expected expression to be used
   ,-[src/components/LintTest.vue:6:1]
 5 | const x = 1
 6 | x
   : ^
   `----

  x typescript-eslint(prefer-as-const): Expected a `const` assertion instead
    of a literal type annotation.
   ,-[src/components/LintTest.vue:9:20]
 8 | // prefer-as-const (typescript)
 9 | let y = 'hello' as 'hello'
   :                    ^^^^^^^
   `----

Found 0 warnings and 2 errors.
Finished in 369ms on 1 file with 132 rules using 10 threads.

# create-vue
pnpm exec oxlint -c .oxlintrc.json src/components/LintTest.vue

  ...(同一の2件)

Found 0 warnings and 2 errors.
Finished in 24ms on 1 file with 116 rules using 10 threads.

Oxlint は <script> 内の違反を検出しましたが、<template> / <style> の問題はスルーされています。Oxlint は .vue ファイルの <script> ブロックしか Lint しないためです。

oxc の互換性ページ にもある通り、Vue/Svelte/Astro 等のフレームワークでは script ブロックのみが対象です。 vue プラグインを有効にしても、script ブロック内の Vue 関連ルール(ref の使い方など)しか動きません。 SFC テンプレートの Lint 対応は oxc#15761 で追跡されていますが、まだ実装されていません。

また、Oxlint には ESLint の JS プラグインを読み込む JS plugins 機能もありますが、eslint-plugin-vue は動きません。 JS plugins の 制限事項 に「Custom file formats and parsers (e.g. Svelte, Vue, Angular)」は未対応と明記されています。 eslint-plugin-vue はカスタムパーサー(vue-eslint-parser)で .vue ファイル全体をパースしてテンプレートの AST をルールに渡す仕組みのため、この制限に該当しています。

ルール数の差(132 vs 116)は、typeAware: true で型情報を使ったチェック(floating promise の検出等)が追加されるためです。

なお、Vite+ テンプレートの typeCheck: true は Vue プロジェクトでは実質的に使えないようです。 vp lint src/ のようにディレクトリを指定すると .ts ファイルもチェック対象になります。 しかし、.ts から .vue をインポートしている箇所で tsgo がモジュール解決に失敗し TS2307: Cannot find module エラーが出ます。 上の検証でファイルを直接指定しているのはこの問題を回避するためです。

ESLint の結果

# Vite+(eslint-plugin-oxlint が機能していない)
vp exec eslint src/components/LintTest.vue

src/components/LintTest.vue
   6:1  error  Expected an assignment or function call and instead saw
        an expression
        @typescript-eslint/no-unused-expressions
   9:5  error  'y' is never reassigned. Use 'const' instead
        prefer-const
   9:5  error  'y' is assigned a value but never used
        @typescript-eslint/no-unused-vars
   9:20 error  Expected a `const` instead of a literal type assertion
        @typescript-eslint/prefer-as-const
  19:3  error  Elements in iteration expect to have 'v-bind:key' directives
        vue/require-v-for-key
  22:30 error  The 'items' variable inside 'v-for' directive should be replaced
        with a computed property that returns filtered array instead.
        You should not mix 'v-for' with 'v-if'
        vue/no-use-v-if-with-v-for

✖ 6 problems (6 errors, 0 warnings)

# create-vue(eslint-plugin-oxlint が正常動作)
pnpm exec eslint src/components/LintTest.vue

src/components/LintTest.vue
   9:5  error  'y' is never reassigned. Use 'const' instead
        prefer-const
   9:5  error  'y' is assigned a value but never used
        @typescript-eslint/no-unused-vars
  19:3  error  Elements in iteration expect to have 'v-bind:key' directives
        vue/require-v-for-key
  22:30 error  The 'items' variable inside 'v-for' directive should be replaced
        with a computed property that returns filtered array instead.
        You should not mix 'v-for' with 'v-if'
        vue/no-use-v-if-with-v-for

✖ 4 problems (4 errors, 0 warnings)

Vite+ 側は Oxlint で検出されている no-unused-expressionsprefer-as-const も ESLint から報告されて6件。前述の通りルール重複の無効化が効いていません。 create-vue 側は eslint-plugin-oxlint が正常動作し、Oxlint との重複ルールが ESLint 側で無効化されるため4件。 どちらも、eslint-plugin-vue により <template> 内の Vue 固有の問題も検出されています。

CSS/Style Lint について

検証用コンポーネントの <style>color: redd; というタイポを仕込みましたが、Oxlint でも ESLint でも引っかかりませんでした。 どちらのテンプレートも CSS の Lint は対象外のようです。

Vue 公式のツーリングガイドの Linting セクション でも案内されているのは eslint-plugin-vue による JavaScript/テンプレートの Lint だけで、CSS/Style の Lint には触れていません。

CSS の Lint が必要なら、これまで同様 Stylelint と Vue プラグインを別途入れることになりそうです。

型チェックの違い

pnpm type-check はどちらも vue-tsc --build で同じです。 Vite+ 側は vite.config.tstypeAware: true(型認識 Lint ルールの有効化)と typeCheck: true(tsgo 経由の型チェック同時実行)の設定があります。 ただし前述の通り tsgo は .vue を読めないため、.vue の型チェックには引き続き vue-tsc が必要です。

テスト

どちらも Vitest です。 Vite+ ではインポートパスが 'vitest' から 'vite-plus/test' に、コマンドが vitest から vp test に変わりますが、設定内容やテストの書き方は同じです。

Formatter 比較: Oxfmt vs Prettier

Oxfmt は Prettier との出力互換を謳っており、JavaScript/TypeScript の conformance test を 100% パスしています。

Vue SFC のフォーマット対応

<template><style> もフォーマットできるのか気になったため、わざと崩した Vue ファイルで試しました。

<!-- フォーマット前 -->
<template><div class="foo"    ><p   v-if="true"  >hello</p></div></template>
<style scoped>.foo{color:red;font-size:16px;display:flex;justify-content:center}</style>
<!-- フォーマット後(Oxfmt / Prettier どちらも同一の結果) -->
<template>
  <div class="foo"><p v-if="true">hello</p></div>
</template>
<style scoped>
.foo {
  color: red;
  font-size: 16px;
  display: flex;
  justify-content: center;
}
</style>

Oxfmt でも Prettier でも <template><style> をフォーマットでき、出力結果は同一でした。乗り換えて問題なさそうです。

公式ドキュメントによると Prettier の約30倍の速度とのこと。小規模プロジェクトだと体感差はありませんが、大規模プロジェクトでは差が出そうです。

全体比較表

項目 Vite+ (vp create vue) create-vue (pnpm create vue@latest)
Linter Oxlint (vp lint) + ESLint Oxlint + ESLint
ESLint 設定 同一(ただし eslint-plugin-oxlint の修正が必要) 同一
Vue template lint ESLint 経由で対応 ESLint 経由で対応
CSS lint なし なし
typeCheck tsgo が .vue を読めず実質使えない なし
テスト Vitest (vp test) Vitest
Formatter Oxfmt (vp fmt) Prettier (Oxfmtも案内される)
ビルド Vite (vp build) Vite (vite build)
設定の統合 vite.config.ts に集約 個別ファイル (.oxlintrc.json, .prettierrc.json)
Pre-commit フック .vite-hooks/pre-commitvp staged なし (要別途設定)

まとめ

vp create vuepnpm create vue@latest で生成されるプロジェクトを比較した結果をまとめます。

Linter

  • Oxlint は .vue<script> ブロックしか Lint できない
    • <template><style> は対象外
  • Vue template の Lint には引き続き ESLint(eslint-plugin-vue)が必要
    • これは Vite+ でも create-vue でも同じ
  • create-vue は Oxlint をデフォルトで同梱している
  • ESLint 連携の落とし穴
    • Vite+ テンプレートでは .oxlintrc.json がマージ後に削除されることへの対応が無く、ルール重複の無効化が壊れる
      • vite.config.tslint ブロックを import するか、.oxlintrc.json に一本化することで対処できる
  • Lint 実行
    • どちらも Oxlint → ESLint の直列実行

型チェック

  • typeCheck: true は Vue プロジェクトでは実質使えない
    • tsgo が .vue をインポートした .ts ファイルで TS2307 エラーを出すため
  • .vue の型チェックには引き続き vue-tsc が必要

Formatter

  • Oxfmt は Prettier と同一の出力
    • <template> / <style> もフォーマットできる
  • Prettier からの乗り換えで困ることはなさそう

CSS Lint

  • どちらのテンプレートも対象外
  • 必要なら Stylelint を別途入れることになる

Vite+ を選ぶメリットは Linter/Formatter 単体の差分よりも、vite.config.ts への設定一元化と vp コマンドによる統合にありそうでした。 Vue 固有の Lint や型チェックについてはまだ ESLint + vue-tsc 頼りなため、Oxlint の Vue テンプレート対応や tsgo の .vue サポートが整えば、設定が楽になりそうです。

今回はテンプレートの設定比較だけでしたが、気になるのはやはり実際のプロジェクトでの速度差です。 次回は Vue ファイルが約2000個存在する駅メモ!のフロントエンドで、Lint/Format/ビルドがどれくらい速くなるか実測してみます。お楽しみに!