## 経緯
[[Vue]]で[[tsgo]]の恩恵を受けたいがために[[Vue Tsgo]]を試した。
<div class="link-card-v2">
<div class="link-card-v2-site">
<img class="link-card-v2-site-icon" src="https://publish-01.obsidian.md/access/35d05cd1bf5cc500e11cc8ba57daaf88/favicon-64.png" />
<span class="link-card-v2-site-name">Minerva</span>
</div>
<div class="link-card-v2-title">
📜2026-02-11 Vue Tsgoを試す
</div>
<div class="link-card-v2-content">Nuxt4のVue SFCでtsgo相当の型チェックを行うため、vue-tsgoとTypeScript 7を導入した。専用tsconfigと絶対パスCACHE_DIR指定でNuxt同等のエラー検出を実現し、nuxt typecheck比で約4〜5倍の高速化を確認したが、v-modelの型アサーション使用時にエラーが消失する挙動も判明した。</div>
<img class="link-card-v2-image" src="https://publish-01.obsidian.md/access/35d05cd1bf5cc500e11cc8ba57daaf88/Notes/attachments/activity.webp" />
<a data-href="📜2026-02-11 Vue Tsgoを試す" class="internal-link"></a>
</div>
%%[[📜2026-02-11 Vue Tsgoを試す]]%%
プライベートのプロジェクトでは少々のハックをすることでうまくいったが、仕事のプロジェクトでは動かなかった。そんな折に[[Golar]]を見つけた。
> Status update on Golar ([https://github.com/auvred/golar](https://github.com/auvred/golar)): I decided to temporarily abandon the idea of rewriting `@vue/compiler-core` + `@vue/language-core` in Go and instead built a plugin architecture. The `@golar/vue` plugin uses the official `@vue/language-core` internally, so there should be no loss in type-checking functionality.
>
> I believe `golar --noEmit` can already be used as a drop-in replacement for `vue-tsc --noEmit` (with exception for a few differences in typescript vs typescript-go behavior, but there is nothing we can do about that). Declaration emit (`golar --declaration --emitDeclarationOnly`) is supported as well.
>
> *[TypeScript Native / TypeScript 7 (`@typescript/native-preview`, `tsgo`) Support · Issue #5381 · vuejs/language-tools](https://github.com/vuejs/language-tools/issues/5381#issuecomment-3897864970)*
<div class="link-card-v2">
<div class="link-card-v2-site">
<img class="link-card-v2-site-icon" src="https://github.githubassets.com/favicons/favicon.svg" />
<span class="link-card-v2-site-name">GitHub</span>
</div>
<div class="link-card-v2-title">
GitHub - auvred/golar: Embedded languages framework based on typescript-go
</div>
<div class="link-card-v2-content">
Embedded languages framework based on typescript-go - auvred/golar
</div>
<img class="link-card-v2-image" src="https://opengraph.githubassets.com/4db912f3e62e2a4fba62aca1cc439a42231f19792423a27f3ed4ddd63a47076a/auvred/golar" />
<a href="https://github.com/auvred/golar"></a>
</div>
特にこのへんに共感した。
> There are a few related projects exploring this space:
>
> - [astralhpi/svelte-fast-check](https://github.com/astralhpi/svelte-fast-check/)
> - [pheuter/svelte-check-rs](https://github.com/pheuter/svelte-check-rs)
> - [KazariEX/vue-tsgo](https://github.com/KazariEX/vue-tsgo)
>
> A common pattern in these projects is to copy files into `/tmp`, rewrite custom extensions and their imports to `.ts`, spawn `tsgo` as a subprocess to collect diagnostics, and then map locations back to the original files. This works, but it is hacky.
>
> Another project, [ubugeeei/vize](https://github.com/ubugeeei/vize), takes an interesting approach by using `tsgo`'s LSP, which avoids the `/tmp` file strategy.
>
> Golar goes further by patching `tsgo` so extension-based languages are treated as if they are supported natively. That means no rewriting import extensions or related paths. A `.vue` file is handled like a `.ts` file.
というわけで試してみる。
### 環境
| 対象 | バージョン |
| --------- | ---------- |
| [[Golar]] | 0.0.13 |
## プロジェクト作成
[[Vue Tsgo]]のときと同じものをつくる。
```console
toki nuxt nuxt4-sandbox
cd nuxt4-sandbox
bun dev -o
```
```
$ bun pm ls
/Users/tadashi-aikawa/tmp/nuxt4-sandbox node_modules (737)
├── @fsouza/
[email protected]
├──
[email protected]
├──
[email protected]
├──
[email protected]
├──
[email protected]
└──
[email protected]
```
## コード編集
`app/app.vue`
```html
<script setup lang="ts">
const cnt = ref<string>(0);
const increment = () => {
cnt.value++;
};
</script>
<template>
<div>
<button @click="increment">Increment</button>
<p>Count: {{ cnt }}</p>
<div>
</div>
</template>
```
このコードには3つの問題がある。
```error
~/tmp/nuxt4-sandbox/app/ 3
└╴ app.vue 3
├╴● Argument of type 'number' is not assignable to parameter of type 'string'. ts-plugin (2345) [2, 27]
├╴● An arithmetic operand must be of type 'any', 'number', 'bigint' or an enum type. ts-plugin (2356) [4, 3]
└╴● Element is missing end tag. vue (24) [9, 3]
```
## [[Golar]]のインストール
```console
bun add -D golar @golar/vue --ignore-scripts --minimum-release-age 8640
```
## 実行してみる
```console
$ bun golar --noEmit
```
何も起こらない。[[tsconfig.json]]が[[references (tsconfig)|references]]構成なためっぽい。
`tsconfig.json`
```json
{
// https://nuxt.com/docs/guide/concepts/typescript
"files": [],
"references": [
{
"path": "./.nuxt/tsconfig.app.json"
},
{
"path": "./.nuxt/tsconfig.server.json"
},
{
"path": "./.nuxt/tsconfig.shared.json"
},
{
"path": "./.nuxt/tsconfig.node.json"
}
]
}
```
`--build` フラグをつけるとtemplate側のエラーが出た。
```console
$ bunx golar --build --noEmit
app/app.vue:9:3 - error TS1000000: Element is missing end tag.
9 <div>
~
Found 1 error in app/app.vue:
```
template側の問題を先に修正する。
`app/app.vue`
```html
<script setup lang="ts">
const cnt = ref<string>(0);
const increment = () => {
cnt.value++;
};
</script>
<template>
<div>
<button @click="increment">Increment</button>
<p>Count: {{ cnt }}</p>
</div>
</template>
```
するとscript側のエラーが出る。
```console
$ bunx golar --build --noEmit
app/app.vue:2:25 - error TS2345: Argument of type 'number' is not assignable to parameter of type 'string'.
2 const cnt = ref<string>(0);
~
app/app.vue:4:3 - error TS2356: An arithmetic operand must be of type 'any', 'number', 'bigint' or an enum type.
4 cnt.value++;
~~~~~~~~~
Found 2 errors in the same file, starting at: app/app.vue:2
```
template側の問題は構造が破綻している場合であり、変数参照エラーなどは拾えそう。
`app/app.vue`
```html
<script setup lang="ts">
const cnt = ref<string>(0);
const increment = () => {
cnt.value++;
};
</script>
<template>
<div>
<button @click="increment">Increment</button>
<p>Count: {{ cnt }}</p>
<Hogehoge>fuga</Hogehoge>
<div @hoge="huga">hoge</div>
</div>
</template>
```
```console
$ bunx golar --build --noEmit
app/app.vue:2:25 - error TS2345: Argument of type 'number' is not assignable to parameter of type 'string'.
2 const cnt = ref<string>(0);
~
app/app.vue:4:3 - error TS2356: An arithmetic operand must be of type 'any', 'number', 'bigint' or an enum type.
4 cnt.value++;
~~~~~~~~~
app/app.vue:12:6 - error TS2339: Property 'Hogehoge' does not exist on type '{}'.
12 <Hogehoge>fuga</Hogehoge>
~~~~~~~~
app/app.vue:13:17 - error TS2339: Property 'huga' does not exist on type '{ $: ComponentInternalInstance; $data: {}; $props: {}; $attrs: Data; $refs: Data; $slots: Readonly<InternalSlots>; $root: ComponentPublicInstance | null; ... 184 more ...; cnt: string; }'.
13 <div @hoge="huga">hoge</div>
~~~~
Found 4 errors in the same file, starting at: app/app.vue:2
```
## 実行速度の違い
```console
hyperfine -i --warmup 3 --export-markdown report.md \
'bunx golar --build --noEmit' \
'bunx nuxt typecheck'
```
10倍の差が出た。すごい。
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|:---|---:|---:|---:|---:|
| `bunx golar --build --noEmit` | 231.5 ± 5.2 | 223.5 | 239.3 | 1.00 |
| `bunx nuxt typecheck` | 2408.8 ± 75.3 | 2334.1 | 2607.3 | 10.41 ± 0.40 |
## [[🦉Bruno Report Viewer]]で試す
```console
hyperfine -i --warmup 3 --export-markdown report.md \
'bunx golar --build --noEmit' \
'bunx vue-tsgo --project tsconfig.app.json' \
'bunx vue-tsc -b'
```
[[Vue]]プロジェクトだが、差は3倍くらい。[[Vue Tsgo]]よりはちょっとだけ速い。
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|:---|---:|---:|---:|---:|
| `bunx golar --build --noEmit` | 292.8 ± 4.7 | 282.9 | 297.8 | 1.00 |
| `bunx vue-tsgo --project tsconfig.app.json` | 347.0 ± 3.4 | 341.3 | 353.5 | 1.19 ± 0.02 |
| `bunx vue-tsc -b` | 1177.3 ± 21.3 | 1150.3 | 1221.8 | 4.02 ± 0.10 |
ちなみに [[Vue Tsgo]] だと型エラーを出力しなくなってしまう [[型アサーション (TypeScript)|as cast]] 未対応問題も[[Golar]]なら問題ない。
```console
$ bunx vue-tsgo --project tsconfig.app.json
$ bunx vue-tsc -b
src/components/side/TheSideContents.vue:37:10 - error TS2367: This comparison appears to be unintentional because the types 'BRVStatus' and '"passed2"' have no overlap.
37 (status === "passed2" && filterPassed.value) ||
~~~~~~~~~~~~~~~~~~~~
Found 1 error.
$ bunx golar --build --noEmit
src/components/side/TheSideContents.vue:37:10 - error TS2367: This comparison appears to be unintentional because the types 'BRVStatus' and '"passed2"' have no overlap.
37 (status === "passed2" && filterPassed.value) ||
~~~~~~~~~~~~~~~~~~~~
Found 1 error in src/components/side/TheSideContents.vue:37
```
## 結論
### まとめ
#2026/02/17 時点だと最適解だと感じた。
- 速度差
- [[Nuxt4]]の空プロジェクトだと10倍高速
- [[Vue]]プロジェクトの[[🦉Bruno Report Viewer]]だと5倍高速
- [[Vue Tsgo]]よりも1割くらい速い (誤差)
- シンプル
- [[TypeScript 7]]のインストールは不要
- cacheディレクトリや[[tsconfig.json]]指定/ハックも不要
- 品質
- 出力も[[tsgo]]と遜色なさそう
### 導入方法
```console
bun add -D golar @golar/vue --ignore-scripts
```