LT(ライトニングトーク) - TECH PLAY - TECH PLAY

TECH PLAY

LT(ライトニングトーク)

ライトニングトーク(LT)とは、通常5分から10分程度の短時間でテンポよく行われるプレゼンテーションの一種です。
稲妻のように素早く伝えることを目的としているため、「ライトニング」トークと呼ばれています。

将来は大きなカンファレンスで登壇してみたいとお考えの方も、まずはLT会などに参加して発表の経験を積むことをお勧めします。

イベント

マガジン

技術ブログ

2026年11月22日(日)に開催される、JSConf JP 2026にて、ミイダスは プレミアムスポンサー を務めさせていただきます! 当日は ブース出展 を行いますので、ぜひご来場ください。
こんにちは。ファインディで Findy を開発している大石( @bicstone )です。 2026年11月1日に開催される TSKaigi Sendai 2026 で、 「TS 7.0 で baseUrl が使えなくなった今、パスエイリアスをどうするか」 というタイトルで登壇します。本記事は5分のLTでは時間の都合で端折った内容も含めた拡張版としてお届けします。 sendai.tskaigi.org speakerdeck.com 今まで広く使われてきたbaseUrlオプションはTypeScript 6.0で非推奨となり、7.0では廃止されました。7.0へのアップグレードを進める場合、この対応を避けて通れません。 この記事では実際に @/ (paths)からsubpath importsの #/ へ移行した経験をもとに、baseUrlからの移行事例を解説します。 TypeScript 6.0からは #/ で始まるsubpath importsがサポートされ、Node.jsのモジュール解決を活用したパスエイリアスへ乗り換えられるようになりました。 baseUrl廃止の背景、pathsへの置き換えとの比較、移行方法の選び方、移行作業で踏んだハマりポイントに触れます。プロジェクトでbaseUrlの廃止をスムーズに進めるためのヒントになれば幸いです。 baseUrlオプションとは baseUrlオプションが廃止された理由 pathsへの置き換え Vite builderを使用したStorybookにおけるトラブル tsconfig.jsonの探索でStorybookが起動不可に webpack builderを使用したStorybookにおけるトラブル subpath importsへの置き換え テストランナーとの相性 どちらを選ぶか ① Node.jsのType StrippingでTypeScriptを直接実行するか ② パッケージ名風のエイリアスを使うモノレポか ③ 実行・テストに使うNode.jsが24.14以上(25.4以上)で、Bunを使用していないか 移行前に確認すること おわりに baseUrlオプションとは baseUrlは、パスエイリアスの基点を決める設定だと思われがちです。しかし本来はブラウザでAMDローダーを使っていた時代に、相対でないモジュール指定をbaseUrlのディレクトリから探すための機能でした *1 。 それでもAMDローダーと関係のないプロジェクトでbaseUrlが広く使われたのは、 ../../ を書かずに済む便利な設定だったからです。 "baseUrl": "src" と1行書くだけで、 import Button from "components/Button" のようにsrcを起点にしたimportを書けます。 Create React Appはこの書き方を「Absolute Imports」として案内し *2 、Next.jsも2020年5月のバージョン9.4でbaseUrlだけで絶対パスのimportを使えるようにしました *3 。バンドラの設定を触らなくてもエディタがエイリアスを認識できるので、手軽な便利ツールとして定着していきました。 もう1つの理由は、TypeScript 4.0まではpathsを使うのにbaseUrlが必須だったことです *4 。 @/ のようなエイリアスを書きたいだけでも、baseUrlをセットで書く必要がありました。 TypeScript 4.1からは、pathsを使うのにbaseUrlは不要になりました。baseUrlがない場合、pathsの値はそのpathsを宣言したtsconfig.jsonのディレクトリを基点に解決されます *5 。 それでも一度書いたbaseUrlはそのまま残り、テンプレートやジェネレーターもbaseUrlを出力し続けていました。 たとえばモノレポツールのNxは、2026年4月にリリースされた22.7.0より前のバージョンでは、生成するtsconfig.base.jsonに "baseUrl": "." を書いていました *6 。 baseUrlオプションが廃止された理由 baseUrlには便利さの反面、思わぬ挙動がありました。baseUrlはpathsの基点としてだけでなく、モジュール探索のルートとしても働きます。そのため、pathsで定義していないモジュール指定までTypeScriptが解決してしまいます。 TypeScript 6.0のリリースノートでは、次のような例が挙げられています *7 。 baseUrl: "./src" で、pathsに "@app/*" と "@lib/*" だけを定義している場合、次のimportはpathsの設定に関係なく src/someModule.js に解決される可能性があります。 import * as someModule from "someModule.js" ; 型チェックは通るのに、実行時には解決できないimportを書けてしまうという課題がありました。そこで、2026年3月にリリースされたTypeScript 6.0でbaseUrlが非推奨になりました。 6.0では "ignoreDeprecations": "6.0" を書けばエラーを抑制できました *8 。しかし2026年7月にリリースされたTypeScript 7.0ではignoreDeprecationsで抑えられず、エラーになります *9 。 "baseUrl": "." を残したまま7.0で型チェックすると、次のエラーになります。 error TS5102: Option 'baseUrl' has been removed. Please remove it from your configuration. Use '"paths": {"*": ["./*"]}' instead. baseUrlを使わずにパスエイリアスを書くには、主にTypeScriptにおけるpathsオプションへの置き換えと、Node.jsにおけるsubpath importsへの置き換えの2つの選択肢があります。 ちなみに、どちらもECMAScriptの仕様ではありません。ECMAScriptはモジュール指定をどう解決するかを実行環境に任せていて *10 、2つの選択肢はそれぞれTypeScriptとNode.jsが決めた解決ルールです。 pathsへの置き換え 1つ目は、tsconfig.jsonのpathsの値を ./ から始まる相対パスに書き換えて、baseUrlだけを消す方法です。TypeScriptのリリースノートが推奨しているのもこの方法です。 { "compilerOptions": { - "baseUrl": ".", "paths": { - "@/*": ["src/*"] + "@/*": ["./src/*"] } } } ファインディでは2025年11月から2026年8月にかけて、複数のプロダクトのフロントエンドでbaseUrlから置き換えました。 動機はTypeScript 7でのbaseUrlの廃止そのものより、型チェックを速くしたいためでした。tsgoのプレビュー版はすでにbaseUrlを受け付けなくなっていたため、tsgoを試すにはbaseUrlを消す必要があったのです。移行後、型チェックはCIで3分弱から1分未満になりました。 tsconfig.jsonの置き換え自体は上の差分のとおりで、すべてのpathsをsedコマンドで一括変換しました。移行ツール ts5to6 の --fixBaseUrl でも、同じ変換ができます *11 。 しかし、動作確認を進める中で、いくつかのパスが解決できなくなってハマりました。中でも一番時間を取られたのがStorybookでした。 Vite builderを使用したStorybookにおけるトラブル @storybook/nextjs-viteのビルドで、 @app/* のエイリアスがすべて解決できなくなりました。このリポジトリのtsconfig.jsonは次のように多段でextendsしていて、pathsはtsconfig.base.jsonにだけ書かれています。 libs/ui/tsconfig.lib.json → libs/config/typescript-config/tsconfig.lib.json → libs/config/typescript-config/tsconfig.json → tsconfig.base.json(paths はここだけ) デバッグすると、エイリアスは <root>/libs/components/... ではなく、 <root>/libs/config/typescript-config/libs/components/... に解決されてしまいました。 原因は、@storybook/nextjs-viteが内部で使う vite-tsconfig-paths の処理でした。baseUrlがないとき、TypeScriptはpathsを宣言したtsconfig.jsonのディレクトリを基点にします。vite-tsconfig-pathsはこれをextendsチェーンで最初にpathsを持つtsconfig.jsonとして模倣します。 ところがバージョン5では tsconfck のキャッシュを全プロジェクトで共有します。そのため、中間の共有tsconfig.jsonが先に読まれていると、継承したpathsを自分で宣言しているように見え、中間のディレクトリが基点に選ばれていました。この時は置き換えをやめ、各ライブラリのIssueをウォッチして対応を待ちました。 現在は、vite-tsconfig-pathsをバージョン6に上げれば解決します。バージョン6はtsconfckのキャッシュを共有しなくなったためです *12 。 また、2026年3月にリリースされたVite 8でVite本体にresolve.tsconfigPathsが追加され、@storybook/nextjs-viteはVite 8以上ではvite-tsconfig-pathsの代わりにこの設定を使うので、この問題は起きません *13 。 なお、subpath importsに書き換えた場合、これらの問題が発生せずにエイリアスを解決できます。 tsconfig.jsonの探索でStorybookが起動不可に 2026年10月には、Storybookが「Storybook ready」の直後にOOMで起動できなくなりました。 原因を追究すると、vite-tsconfig-pathsの既定の探索(eager)がワークスペース配下のtsconfig.jsonを見つけ、referencesまで展開して1,000を超えるファイルを読んでいました。さらに、Storybookがアプリのvite.config.tsを自動で読み込むため、プラグインが二重に起動していました。 そこで、読み込むtsconfig.jsonを projects: ["tsconfig.base.json"] で1つに絞ると、OOMが起きなくなり、2分以上かかっていた起動も数秒になりました。 pathsはツールがtsconfig.jsonを探して読み直す仕組みなので、モノレポが大きくなるほどこのコストも大きくなります。 webpack builderを使用したStorybookにおけるトラブル 社内の別のリポジトリでは、webpackをbuilderに設定している@storybook/nextjsでも同じようにエイリアスを解決できなくなりました。 こちらは内部で使う tsconfig-paths が、pathsを宣言したtsconfig.base.jsonではなく、それをextendsしたアプリのディレクトリを基点にしてしまうためです。 当時は、Storybookの設定にtsconfig-paths-webpack-pluginのTsconfigPathsPluginを足し、tsconfig.base.jsonを明示して回避しました。 現在は、2026年9月にリリースされたStorybook 10.6以上にアップグレードすることで解決します。tsconfig-pathsの既定の基点に任せず、Storybook自身がextendsチェーンをたどってpathsを宣言したtsconfig.jsonのディレクトリを求め、tsconfig-pathsに渡すようになりました *14 。 subpath importsへの置き換え 2つ目はNode.jsのsubpath importsを使う方法です *15 。package.jsonのimportsに、#から始まるエイリアスを定義します。 { " imports ": { " #/* ": " ./src/* " } } import { formatDate } from "#/utils/formatDate.ts" ; subpath importsはNode.js独自のモジュール解決の仕様です。importsフィールド自体はNode.js 12.19.0と14.6.0からありますが、キーを #/ で始められるようになったのは25.4.0と24.14.0からです *16 。 TypeScriptも6.0から、moduleResolutionがbundlerかnodenextのときに #/ を解決できるようになりました *17 。 また、副次的な効果として、importsには条件を書けるので、Storybookやテストのときだけモックに切り替えることもできます *18 。testの条件は、テストランナーごとに有効にする設定が必要ですが、一度の設定で済みます。 社内の事例では前述のとおりpathsへの置き換えを選びましたが、プライベートのリポジトリではsubpath importsへの置き換えを実施しました。以前に使用されていた @/ を #/ に移行していきました。 importへの拡張子の明示と、Node.jsや各ツールチェーンのバージョンアップをしたうえで、sedでの一括置き換えで移行が完了しました。pathsのときのようにツールごとにエイリアスの解決が食い違うトラブルはありませんでした。 テストランナーとの相性 subpath importsに置き換える場合、テストランナーの相性が問題になります。 subpath importsはNode.jsの仕様なので、 #/ を使えるかは、テストランナーがモジュールをどう解決するかと、実行するNode.jsのバージョンで決まります。 自前のresolverを持つVitest 5.0.5とJest 30.5.2は、Node.js 22でも #/ を解決できます。 一方、エイリアスの解決をNode.jsのローダーに任せるnode:test、Mocha、AVA、tsx、Playwright Testなどは、Node.js 24.14未満(25.4未満)では ERR_INVALID_MODULE_SPECIFIER などのエラーが発生し #/ が解決できません。 実行時のNode.jsのバージョンが重要になるため、ローカルのNode.js 26ではテストが通るのに、CIではNode.js 22だったので落ちる、ということが起こりえます。 Node.jsのバージョンを揃えることはこの例に限らず重要ですが、トラブルが起きた際には実行するNode.jsのバージョンを疑ってみてください。 なお、Bun 1.4.4で検証したところ、 #/ を解決できませんでした *19 。 どちらを選ぶか 2つの方法の違いは、どのツールがエイリアスを解決するかです。 tscはpathsを設定してもimport文を書き換えないので、pathsはTypeScriptの中だけで完結する設定になります。そのため、バンドラやツールチェーンがそれぞれtsconfig.jsonを読みにいく必要があります。 一方、subpath importsはNode.jsのモジュール解決の仕様なので、各ツールはpackage.jsonを読みにいき、Node.jsと同じ挙動を実装する必要があります。 2つの方法を表で比較すると、次のとおりです。 観点 pathsへの置き換え subpath imports 定義する場所 tsconfig.jsonのcompilerOptions.paths package.jsonのimports 解決するツール TypeScriptと、tsconfig.jsonを読む各ツール Node.jsと、Node.jsの規則を実装した各ツール 実行時 Node.jsは解決しない ランタイムが解決する エイリアス名 自由( @/ 、 ~/ など) #から始まる名前に限定 適用される範囲 tsconfig.jsonで指定したファイル importした側から最も近いpackage.json 条件による切り替え できない Storybookやテストなどの条件で解決先を切り替えられる ここまでの検証から、どちらの方法にするかは、次の3つの基準をもとに選ぶのがよいと考えています。 ① Node.jsのType StrippingでTypeScriptを直接実行するか Node.jsのType StrippingでTypeScriptを直接実行する構成では、subpath importsが唯一の選択肢となります。 Node.jsはtsconfig.jsonを無視するため、pathsが効きません *20 。 ② パッケージ名風のエイリアスを使うモノレポか @org/pkg/featureのようなパッケージ名風のエイリアスを使うモノレポでは、pathsへの置き換えが現実的です。 subpath importsのキーは#から始める必要があり、パッケージ名風の名前を表現できません。 またimportsは最も近いpackage.jsonをもとに解決するため、Nxなどのモノレポでよく見る、package.jsonを持たないライブラリが並ぶ構成とは相性がよくありません。 将来的には、workspaceパッケージ化してexportsで公開する方法が考えられます。 ③ 実行・テストに使うNode.jsが24.14以上(25.4以上)で、Bunを使用していないか 実行・テストに使うNode.jsが24.14以上(25.4以上)で、Bunを使用していない場合は、subpath importsに置き換えるのがおすすめです。 tsconfig.jsonを読み直すツールが減るので、前述のようなツールチェーンの課題も構造的に避けられます。 @/ から #/ に移ると、importの書き方が変わります。 @/ が #/ になるだけでなく拡張子も明示することになるので、気になる人もいるかもしれません。ただ、書き換えは機械的に一括でできますし、最近はimportをAIに書かせることも多いので、書き方の違いは大きな負担にならないと思います。 それよりも、ツールチェーンの構造的な課題を避けられることのほうが、メリットは大きいと考えています。ただし、ランタイムの条件を満たさない場合は正しく動作しないため、影響範囲を小さくする観点からpathsへの置き換えが候補になります。 移行前に確認すること どちらの方法を選ぶ場合も、移行前に次の点を確認しておくとトラブルが減ります。 現在使用しているランタイム・ツールチェーン・バンドラのバージョンが新しいパスエイリアスの処理に対応しているか tsconfig.jsonのextendsが多段になっているか、pathsをどのファイルで宣言しているか pnpm 10までのuse-node-versionや、pnpm 11以降のdevEngines.runtimeのように、シェルとは別にNode.jsを固定する設定がないか shadcn/uiのCLIのように、エイリアスを読んでコードを生成するツールがあるか おわりに baseUrlからpathsへの置き換えは、tsconfig.jsonの差分だけを見ると小さな変更です。しかし実際には、pathsを読むツールも対応する必要がありました。今日ではツールを最新のバージョンにアップデートすれば、pathsへの置き換えだけでも移行できるケースが多いです。 それでも私は、条件を満たすならsubpath importsへの置き換えをおすすめします。 tsconfig.jsonを読み直すツールが減り、同じ種類の問題を構造的に避けられるからです。importの書き方は変わりますが、書き換えは一括でできます。 TypeScript 7への移行を進めるうえで、この記事がパスエイリアスの選び方の参考になれば幸いです。 TSKaigi Sendai 2026では、ファインディから甲斐さんが「 100以上のプロジェクトを持つNxモノレポのTypeScript 7移行の実践 」というタイトルで登壇します。詳細を解説した記事も公開していますのでぜひご覧ください。 tech.findy.co.jp ファインディでは一緒に会社を盛り上げてくれるメンバーを募集中です。興味を持っていただいた方はこちらのページからご応募お願いします。 herp.careers *1 : TSConfig Reference - baseUrl https://www.typescriptlang.org/tsconfig/#baseUrl *2 : Create React App - Absolute Imports https://create-react-app.dev/docs/importing-a-component/#absolute-imports *3 : Next.js 9.4 - Absolute Imports and Aliases https://nextjs.org/blog/next-9-4#absolute-imports-and-aliases *4 : TypeScript 4.1 Release Notes - paths without baseUrl https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-1.html#paths-without-baseurl *5 : TypeScript Modules Reference https://www.typescriptlang.org/docs/handbook/modules/reference.html *6 : nrwl/nx#34965 https://github.com/nrwl/nx/pull/34965 *7 : TypeScript 6.0 Release Notes https://www.typescriptlang.org/docs/handbook/release-notes/typescript-6-0.html *8 : Announcing TypeScript 6.0 https://devblogs.microsoft.com/typescript/announcing-typescript-6-0/ *9 : Announcing TypeScript 7.0 https://devblogs.microsoft.com/typescript/announcing-typescript-7-0/ *10 : ECMAScript Language Specification - HostLoadImportedModule https://tc39.es/ecma262/#sec-HostLoadImportedModule *11 : Announcing TypeScript 6.0 https://devblogs.microsoft.com/typescript/announcing-typescript-6-0/ *12 : vite-tsconfig-paths v6.0.0 src/index.ts https://github.com/aleclarson/vite-tsconfig-paths/blob/v6.0.0/src/index.ts#L110-L113 *13 : Vite 8.0 is out! https://vite.dev/blog/announcing-vite8 *14 : storybookjs/storybook#35959 https://github.com/storybookjs/storybook/pull/35959 *15 : Node.js Documentation - Subpath imports https://nodejs.org/api/packages.html#subpath-imports *16 : nodejs/node#60864 https://github.com/nodejs/node/pull/60864 *17 : microsoft/TypeScript#62844 https://github.com/microsoft/TypeScript/pull/62844 *18 : Storybook Docs - Mocking modules https://storybook.js.org/docs/writing-stories/mocking-data-and-modules/mocking-modules *19 : oven-sh/bun#28995 https://github.com/oven-sh/bun/issues/28995 *20 : Node.js Documentation - TypeScript https://nodejs.org/api/typescript.html

動画

書籍