## 経緯 [[TypeScript 7]]こと[[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"> 📜2025-12-04 TypeScript 7 Previewをふたたび試す </div> <div class="link-card-v2-content">半年前に試したTypeScript 7 Previewとtsgoの導入経緯を踏まえ、NeovimのLSP設定やグローバルインストールを行い挙動を検証したが、補完やCode Actionの差異からNeovim利用は保留し、Nuxt 4ではvue-tsc未対応のためtypecheck移行は困難と判断した経過を記録した</div> <img class="link-card-v2-image" src="https://publish-01.obsidian.md/access/35d05cd1bf5cc500e11cc8ba57daaf88/Notes/attachments/activity.webp" /> <a data-href="📜2025-12-04 TypeScript 7 Previewをふたたび試す" class="internal-link"></a> </div> %%[[📜2025-12-04 TypeScript 7 Previewをふたたび試す]]%% ただ、このときは[[Nuxt4]]([[Vue]])のコードを解析することができなかった。[[Vue Language Tools]]のissueにも上がっていた。 <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"> TypeScript Native / TypeScript 7 (`@typescript/native-preview`, `tsgo`) Support · Issue #5381 · vuejs/language-tools </div> <div class="link-card-v2-content"> What problem does this feature solve? TypeScript on Go (also known as TypeScript 7 or TypeScript Native) has bee ... </div> <img class="link-card-v2-image" src="https://opengraph.githubassets.com/7daf42becffbd7367a2594013738056d8905b5d0f365fdde912563ef805a6bef/vuejs/language-tools/issues/5381" /> <a href="https://github.com/vuejs/language-tools/issues/5381"></a> </div> 最近これを見返したらこんな投稿があった。 > If you just want a faster cli: [https://github.com/KazariEX/vue-tsgo](https://github.com/KazariEX/vue-tsgo) ということで早速試してみた。 ## プロジェクト作成 前回同様に[[Nuxt4]]向けプロジェクトを[[🦉Toki]]でつくる。 ```console toki nuxt nuxt4-sandbox cd nuxt4-sandbox bun dev -o ``` ``` $ bun pm ls /Users/tadashi-aikawa/tmp/nuxt4-sandbox node_modules (731) ├── @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] ``` `bun tsc` コマンドは[[Vue SFC]]に未対応なのでエラーを出さない。 ```console $ bun tsc ``` `bun nuxt typecheck` ではちゃんとエラーになる。 ```error $ bun nuxt typecheck app/app.vue:2:27 - 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. ERROR Process exited with non-zero status (2) 11:32:12 AM at G._waitForOutput (node_modules/tinyexec/dist/main.js:574:92) at process.processTicksAndRejections (node:internal/process/task_queues:103:5) at async Object.run (node_modules/@nuxt/cli/dist/typecheck-C8wVgBzd.mjs:66:3) at async runCommand (node_modules/citty/dist/index.mjs:196:47) at async runCommand (node_modules/citty/dist/index.mjs:193:21) at async runMain (node_modules/citty/dist/index.mjs:304:10) error: "nuxt" exited with code 1 ``` [[tsgo]]は[[tsc]]に対応するものなのでエラーが出ないのは当然だが、`bun nuxt typecheck` 同等のことがしたい。 ## [[Vue Tsgo]]のインストール ```console bun add -D vue-tsgo ``` ```console $ bun vue-tsgo --version v0.0.1-yggdrasill.11 ``` ## 実行してみる ```console bun vue-tsgo --project .nuxt/tsconfig.app.json ``` エラーになった。 ```error [Vue] Failed to resolve the path of tsgo. Please ensure the @typescript/native-preview package is installed. error: "vue-tsgo" exited with code ``` どうやら [[pnpm]] じゃないと `catalog` が認識されなそう。。出直す ## プロジェクト再作成 ```console toki nuxt-pnpm nuxt-pnpm-sandbox cd nuxt-pnpm-sandbox pnpm dev -o ``` コードも[[#コード編集]]の状態にしておく。 ```console pnpm nuxt typecheck ``` ```error npm warn Unknown env config "verify-deps-before-run". This will stop working in the next major version of npm. app/app.vue:2:27 - 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. ERROR Process exited with non-zero status (2) 11:57:44 AM ``` ## [[Vue Tsgo]]の再インストールと再実行 ```console pnpm i -D vue-tsgo ``` ```console $ pnpm list Legend: production dependency, optional only, dev only nuxt-pnpm-sandbox /Users/tadashi-aikawa/tmp/nuxt-pnpm-sandbox (PRIVATE) dependencies: nuxt 4.3.1 vue 3.5.28 vue-router 4.6.4 devDependencies: @fsouza/prettierd 0.27.0 prettier-plugin-organize-imports 4.3.0 vue-tsgo 0.0.1-yggdrasill.11 ``` 実行してみたが... ```console $ pnpm vue-tsgo --project .nuxt/tsconfig.app.json [Vue] Failed to resolve the path of tsgo. Please ensure the @typescript/native-preview package is installed. ``` これは[[pnpm]]でもいっしょぽい。 `@typescript/navtive-preview` は必要そう。 ## [[TypeScript 7]]をインストール ```console pnpm add -D @typescript/native-preview ``` ```console $ pnpm list Legend: production dependency, optional only, dev only nuxt-pnpm-sandbox /Users/tadashi-aikawa/tmp/nuxt-pnpm-sandbox (PRIVATE) dependencies: nuxt 4.3.1 vue 3.5.28 vue-router 4.6.4 devDependencies: @fsouza/prettierd 0.27.0 @typescript/native-preview 7.0.0-dev.20260209.1 prettier-plugin-organize-imports 4.3.0 vue-tsgo 0.0.1-yggdrasill.11 ``` ## 再び実行 ```console $ pnpm vue-tsgo --project .nuxt/tsconfig.app.json ``` configが見つからなそう。 ```error error TS18003: No inputs were found in config file '/Users/tadashi-aikawa/tmp/nuxt-pnpm-sandbox/node_modules/.ca ... 中略 Found 1 error ``` [[チャッピー]]に聞く。 > [!left-bubble] ![[chappy.webp]] > 原因と対処の整理です。 > > 結論 > vue-tsgo が一時ワークスペースを node_modules/.cache/vue-tsgo/... に作る仕様のため、tsgo(TypeScript 7)が node_modules 配下をワイルドカード探索から除外してしまい、結果として include が 0 件になって TS18003 が出ています。 > > 根拠(手元確認) > > - 実際の tsgo 用 tsconfig は node_modules/.cache/vue-tsgo/4d05ead6/.nuxt/tsconfig.app.json に生成されており、include は node_modules/.cache/vue-tsgo/4d05ead6/app/**/* などです。 > - node_modules/.cache/vue-tsgo/4d05ead6/app/app.vue.ts などの生成ファイルは存在しますが、tsgo が node_modules を無視しているため “No inputs” になります。 > > 対処(コード変更なしで可能) > vue-tsgo のキャッシュ先を node_modules 外に出します。 > empathic/package の CACHE_DIR で上書きできます。 > > 例(ワンショット実行) > > CACHE_DIR=.cache pnpm vue-tsgo --project .nuxt/tsconfig.app.json > > 例(/tmp でも可) > > CACHE_DIR=/tmp/vue-tsgo-cache pnpm vue-tsgo --project .nuxt/tsconfig.app.json > > 恒久化したい場合 > package.json の script に CACHE_DIR=.cache を足すやり方が安全です。 > 必要ならこちらで変更しますが、実装扱いになるので、プラン提示→承認後に進めます。 両方ためしたけど何も変わらない。。 ```console CACHE_DIR=.cache pnpm vue-tsgo --project .nuxt/tsconfig.app.json CACHE_DIR=/tmp/vue-tsgo-cache pnpm vue-tsgo --project .nuxt/tsconfig.app.json ``` 改めて投げる。 > [!left-bubble] ![[chappy.webp]] > 原因がはっきりしました。 > CACHE_DIR を絶対パスにしても TS18003 が出るのは、vue-tsgo が生成する tsconfig の include が壊れているためです。 > > 具体的には、生成された tsconfig.app.json がこうなっています(実ファイル確認済み): > > - include に .../.cache/vue-tsgo/app/**/* が入っている > しかし実際の生成ファイルは .../.cache/vue-tsgo/4d05ead6/app/app.vue.ts > - include に .../.cache/vue-tsgo/4d05ead6/nuxt.d.ts が入っている > 実際は .../.cache/vue-tsgo/4d05ead6/.nuxt/nuxt.d.ts > > つまり パスがずれているので 1 件もマッチせず TS18003 になります。 > これは vue-tsgo が .nuxt/tsconfig.app.json の include を再マップする際に、./ と ../ を誤って解釈しているのが原因です(上流バグ)。 > > ## 回避策(確実) > > ../ を含まない include を持つ専用 tsconfig を作ると動きます。 > 例: tsconfig.vue-tsgo.json を作って extends し、include をプロジェクトルート基準に置き換えます。 > > 提案プラン(実装扱いなので承認が必要です) > > Plan > > 1. tsconfig.vue-tsgo.json を作成 > - extends: "./.nuxt/tsconfig.app.json" > - include をプロジェクトルート基準に置換(app/**/*, .nuxt/**/*.d.ts, modules/*/runtime/**/* など) > 2. pnpm vue-tsgo --project tsconfig.vue-tsgo.json で実行 > 3. 必要なら package.json にスクリプト追加 > 本当かよく分からんけど、とりあえず任せてみる。[[Vue Tsgo]]専用の設定ができること自体は違和感ないので。 変わらずだったので返答。 > [!left-bubble] ![[chappy.webp]] > 原因は CACHE_DIR を相対パスにしたことで、生成された tsconfig の include が キャッシュ内で二重に解釈されてしまう点でした。 > CACHE_DIR を 絶対パスにすると解消します。 > > 実行コマンド(これで通ります) > > CACHE_DIR="$PWD/.cache" pnpm vue-tsgo --project tsconfig.vue-tsgo.json ```console $ CACHE_DIR="$PWD/.cache" pnpm vue-tsgo --project tsconfig.vue-tsgo.json app/app.vue:2:27 - 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 ``` 出た👏 ## 必要だったこと ### 専用の設定ファイル `tsconfig.vue-tsgo.json` ```json { "extends": "./.nuxt/tsconfig.app.json", "include": [ ".nuxt/nuxt.d.ts", "app/**/*", "modules/*/runtime/**/*", "test/nuxt/**/*", "tests/nuxt/**/*", "layers/*/app/**/*", "layers/*/modules/*/runtime/**/*", "shared/**/*.d.ts", "modules/*/shared/**/*.d.ts", "layers/*/shared/**/*.d.ts", "*.d.ts", "layers/*/*.d.ts" ] } ``` ### キャッシュディレクトリ指定のコマンド ```console CACHE_DIR="$PWD/.cache" pnpm vue-tsgo --project tsconfig.vue-tsgo.json ``` `package.json` にコマンドを書いておくとよい。 ```json { "scripts": { "nuxt:typecheck": "nuxt typecheck", "typecheck": "CACHE_DIR=$PWD/.cache vue-tsgo --project tsconfig.vue-tsgo.json", }, } ``` ## 実行速度の違い 一番期待しているのはここだったので確認する。 ```console pnpm nuxt:typecheck 2.98s user 0.51s system 129% cpu 2.695 total ``` ```console pnpm typecheck 0.69s user 0.17s system 217% cpu 0.396 total ``` めっちゃ良い。このボリュームでも既に快適さが違う。 `nuxt typecheck`が遅すぎたので、[[overseer.nvim]]の[[trouble.nvim]]連携は解除していたけど、このスピード感が実際のプロダクトでも出るなら復活も選択肢に入る。 ## [[🦉Bruno Report Viewer]]で試す 大きなプロジェクトではないが、[[Vue]]を使っていてアクティブなプロダクトがこれしかなかったので...。ただ、うまくいかなかった。(エラーがでない。) > [!left-bubble] ![[chappy.webp]] > 結論から言うと、差分の原因は -b(build mode)である可能性が高く、現時点の vue-tsgo では同等のことはできません。理由は2点あります。 > (typescriptlang.org (https://www.typescriptlang.org/docs/handbook/project-references)) > > 1. vue-tsc -b は TypeScript の build mode で、references を辿って関連プロジェクトを順序付きでビルド/型チェックします。 > --project 単体では参照先は自動でビルドされないため、-b の有無で拾えるエラーが変わります。(typescriptlang.org (https://www.typescriptlang.org/docs/handbook/project-references)) > 2. vue-tsgo は内部的に tsgo を使って型チェックするツールです。(github.com (https://github.com/KazariEX/vue-tsgo)) > tsgo 自体が現時点のプレビューで --build(= -b)を未サポート と明言されています。 > そのため vue-tsgo も -b 相当ができません。(devblogs.microsoft.com (https://devblogs.microsoft.com/typescript/announcing-typescript-native-previews/)) > > 実務的な対処案(どれか1つでOK) > > 1. -b を使いたい場合は当面 vue-tsc -b を使う(CI/本番はこれが安全)。(typescriptlang.org (https://www.typescriptlang.org/docs/handbook/project-references)) > 2. vue-tsgo で近いことをするなら、参照先を個別に回す。 > > bun vue-tsgo --project tsconfig.app.json > bun tsgo --project tsconfig.node.json > > 3. まず差分を切り分けたいなら、-b なしの vue-tsc と比較してみる。 > > bun vue-tsc --project tsconfig.app.json > > この結果でエラーが出るなら、tsgo 側の差分(未対応やバグ)の可能性が高いです。(devblogs.microsoft.com (https://devblogs.microsoft.com/typescript/announcing-typescript-native-previews/)) > > もし「vue-tsc -b のどのプロジェクトでエラーが出ているか」を確認したい場合は、--verbose 付きで見ると分かります。(typescriptlang.org (https://www.typescriptlang.org/docs/handbook/project-references)) > > 必要なら、出ているエラーと対象ファイルを教えてくれれば「-b 由来か」「tsgo 由来か」を具体的に切り分けます。 御意。ただ、[[vue-tsc]]で全く同じコマンドを打つとエラーがちゃんと出るので、これは[[Vue Tsgo]]との単なる差分かもしれない...。 ```console $ bun vue-tsgo --project tsconfig.app.json $ bun vue-tsc --project tsconfig.app.json 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 error: "vue-tsc" exited with code 2 ``` > [!left-bubble] ![[chappy.webp]] > • 原因は src/App.vue ではなく、vue-tsgo 側の挙動です。再現確認した結果は以下です。 > > - bun vue-tsgo --project tsconfig.app.json は EXIT:0 で無出力。 > - しかし bunx vue-tsc -p tsconfig.app.json --noEmit だと src/App.vue:21 の型エラーを正しく検出します。 > - TS2345: Argument of type 'string' is not assignable to parameter of type 'number' > - vue-tsgo が生成した仮想ファイルでは、v-model="... as string" 系が __VLS_ctx.xxx as string = $event; になっており、TS1005(構文エラー)を出しています。 > - 例: node_modules/.cache/vue-tsgo/206bb4ea/src/components/main/HeadersTable.vue.ts:127 > - その後 vue-tsgo の診断マッピング処理で、マップできない診断を削除してしまうため、最終的に「エラーなし」になります。 > - node_modules/vue-tsgo/dist/project-7AwPhiYv.mjs:2999 > - node_modules/vue-tsgo/dist/project-7AwPhiYv.mjs:3007 > > 結論として、今の組み合わせでは vue-tsgo が false negative を出しており、App.vue の型エラーが見えません。 > 当面は型チェックを vue-tsc (bun run typecheck / bunx vue-tsc ...) に戻すのが安全です。必要なら、vue-tsgo 回避のための最小修正案(v-model の as を避ける等)をプラン化して出します。 > [!left-bubble] ![[chappy.webp]] > • その疑問は正しくて、通常なら「別エラーは別で出る」はずです。 > 今回消えているのは、vue-tsgo の処理が 2 段あるためです。 > > 1. tsgo 実行段階 > v-model="... as string" 由来で生成された仮想コードに構文エラー(例: node_modules/.cache/vue-tsgo/206bb4ea/src/components/main/HeadersTable.vue.ts:127 の TS1005)が出ます。 > この時点で tsgo 側はその構文エラー群しか出さず、App.vue の型エラー(ref\<number\>('hoge'))は出てきませんでした。 > 2. vue-tsgo の再マップ段階 > vue-tsgo は仮想ファイルの診断を元 .vue にマップできないと削除します(node_modules/vue-tsgo/dist/project-7AwPhiYv.mjs:3007 の diagnostics.splice(i--, 1))。 > TS1005 は生成コード由来でマップ不能なので全削除され、最終的に 0 件になります。 > > つまり「他のエラーまで消した」のではなく、 > > - まず tsgo が他のエラーを出していない > - 出たエラーは vue-tsgo がマップ不能として捨てる > この合成で false negative になっています。 たしかに `as string` をすべて削除したらエラーが表示された。ちなみにパフォーマンスの違いは2.5倍くらい。 ```console bun vue-tsgo --project tsconfig.app.json 0.79s user 0.16s system 261% cpu 0.361 total bun vue-tsc --project tsconfig.app.json 2.02s user 0.13s system 212% cpu 1.013 total ``` ## 結論 ### まとめ - [[Nuxt4]]の空プロジェクトでも4~5倍高速 - シンプルな構成だとそこまで差分は出ない - 実際のプロダクトでどれくらい差が出るかはウォッチしたい - 🚨 **[[v-model]]で[[型アサーション (TypeScript)|as cast]]をしていると結果が空(問題なし)になる** ### [[Nuxt4]]プロジェクトへの導入方法 ```console pnpm add -D vue-tsgo @typescript/native-preview ``` `tsconfig.vue-tsgo.json` ```json { "extends": "./.nuxt/tsconfig.app.json", "include": [ ".nuxt/nuxt.d.ts", "app/**/*", "modules/*/runtime/**/*", "test/nuxt/**/*", "tests/nuxt/**/*", "layers/*/app/**/*", "layers/*/modules/*/runtime/**/*", "shared/**/*.d.ts", "modules/*/shared/**/*.d.ts", "layers/*/shared/**/*.d.ts", "*.d.ts", "layers/*/*.d.ts" ] } ``` ```json { "scripts": { "typecheck": "CACHE_DIR=$PWD/.cache vue-tsgo --project tsconfig.vue-tsgo.json", }, } ``` ### [[Vue]]プロジェクトへの導入方法 ```console pnpm add -D vue-tsgo @typescript/native-preview ``` ```json { "scripts": { "typecheck": "vue-tsgo --project tsconfig.app.json", }, } ```