## 経緯 [[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 ```