背景
Summer'25で認定JavaScript デベロッパーの更新時にLWCのTypescriptの開発者プレビューに関する話が出ました。
今まではJSで書いていましたがやはりLWCでもTypescriptで書けるといいですよね。
特に規模が大きいプロジェクトほど、型安全性や補完、保守性の向上といった TypeScript の恩恵が大きいと感じています。
実際に試す中でいくつかハマりどころがあったため、その対処法をまとめます。
ゴール
TypeScriptで実装したLWCのソースを、スクラッチ組織へデプロイできるようにする。
前提
- sfdxプロジェクトを作成していること
- Salesforce開発の各種拡張機能がインストールされていること
- スクラッチ組織が作成済みであること
今回使うpackage.jsonの依存関係に以下を追加します。
"devDependencies": {
"lwc": "^8.21.6",
"@salesforce/lightning-types": "^0.9.0",
"typescript": "^5.8.3"
}
手順
背景に記載のTrailheadに書いてある手順に基本的に沿って行います。
1. LWCのTypescriptプレビューの設定を有効にする
vscodeの設定ファイルに以下を追加します。
{
"salesforcedx-vscode-lwc.preview.typeScriptSupport": true
}
ファイルを保存すると、lwc フォルダー (またはコンポーネントが保存されているフォルダー) にtsconfig.json ファイルが自動的に作成されます。すぐに表示されない場合は、VS Code を再起動します。
とありますが、実際にこの設定を有効にすることで作成されるのは.sfdx\tsconfig.sfdx.jsonでした。これに関しては重要な役割があります。(後述します)
tsconfig.jsonは以下の条件の時に自動で作成されていそうです。
- lwc配下にtypescriptファイルがある
- 拡張機能起動時(vscode立ち上げも含む)
とりあえず自動作成されたら次に進みます。
2. lightning-typesをプロジェクト全体で読み込む
ルートに以下を追加します。
import '@salesforce/lightning-types';
3. 2.で作成したファイルを参照する
includeに以下を追加します。
"include": [
"../../../../types/salesforce.d.ts"
]
4. トランスパイル+デプロイ
Trailheadの手順のままだ型エラーやデプロイエラーなどでうまくデプロイできずこけました。
以下に解決方法を記載しています。
この対応をするとうまくデプロイできます。
> npx tsc --project force-app\main\default\lwc
> sf project deploy start
Deployed Source
┌─────────┬─────────────────┬──────────────────────────┬────────────────────────────────────────────────────────────────────────┐
│ State │ Name │ Type │ Path │
├─────────┼─────────────────┼──────────────────────────┼────────────────────────────────────────────────────────────────────────┤
│ Changed │ sampleComponent │ LightningComponentBundle │ force-app\main\default\lwc\sampleComponent\sampleComponent.html │
│ Changed │ sampleComponent │ LightningComponentBundle │ force-app\main\default\lwc\sampleComponent\sampleComponent.js │
│ Changed │ sampleComponent │ LightningComponentBundle │ force-app\main\default\lwc\sampleComponent\sampleComponent.js-meta.xml │
└─────────┴─────────────────┴──────────────────────────┴────────────────────────────────────────────────────────────────────────┘
エラー時の解消
デコレーターのエラー
wireやapiはコンパイルエラーになってしまうと思います。
これは解消法として@ts-ignoreや@ts-expect-errorを付ける必要があります。と書かれていました。
余りつけたくないですが仕方ないですね。
また、Salesforce VS Code 拡張機能にデフォルトで LWC の型が含まれていますが、experimentalDecorators 設定を無効にすると互換性がなくなります。この設定を有効にしておく必要がありますが、デコレーターを使用すると TypeScript エラーが発生します。この問題を回避するために、デコレーターを使用する行のすぐ上に // @ts-ignore または // @ts-expect-error を追加します。
デプロイエラー
トランスパイルするとcommonjsの形式で出力されてしまうのでtsconfig.jsonは以下に書き換えておきましょう。
{
"include": [
"**/*.ts",
"../../../../.sfdx/typings/lwc/**/*.d.ts",
"../../../../types/*.d.ts",
"**/*.d.ts"
],
"exclude": [
"**/__tests__/**"
],
"compilerOptions": {
"target": "ESNext", //tsconfig.sfdx.jsonがnodenextで書いているためトランスパイルされたjsの書き方がスクラッチ組織にデプロイでエラーになってしまう。targetをESNextにする必要がある
"module": "ESNext",
"moduleResolution": "bundler",
"skipLibCheck": true,
"experimentalDecorators": false
},
"extends": "../../../../.sfdx/tsconfig.sfdx.json" //拡張機能が生成した設定を読み込む
}
コンポーネントのパスエラー
LWCでは、jsファイル内で別のコンポーネントを使う場合はc/{コンポーネント名}で指定する必要があります。
例としてsampleModalというlightningModalを継承したコンポーネントをsamplePageから呼び出したいとします。
import { api } from 'lwc';
import LightningModal from 'lightning/modal';
export default class SampleModal extends LightningModal {
// @ts-ignore
@api
prop: string = '';
}
samplePage内ではsampleMpdalをインポートします。
import { LightningElement } from 'lwc';
import SampleModal from 'c/sampleModal';
export default class SamplePage extends LightningElement {
async openModal() {
const result = await SampleModal.open({
label: 'モーダル',
size: 'large'
});
}
}
この時にモジュール 'c/sampleModal' またはそれに対応する型宣言が見つかりません。とエラーが出てしまいますがここは拡張機能側再起動時にtsconfig.sfdx.jsonへ書き込まれるのでもし新しいコンポーネントを追加してインポートの部分でエラーになったら拡張機能の再起動をかけてあげましょう。
まあこの例だとlightningModalの型定義にstatic open<T, R = any>(prop?: Prop<T>): Promise<R>;のような型定義がないのでこれもエラーになってしまいますが... ここの解決方法は別で書きます。
{
"compilerOptions": {
"skipLibCheck": true,
"target": "ESNext",
"module": "NodeNext",
"paths": {
"c/sampleModal": [
//コンポーネントへのフルパス
],
"c/samplePage": [
//コンポーネントへのフルパス
],
}
}
}
最後に
今までTypescriptでLWCを書く話は何度か聞いていましたが実際に試せていなかったのでうまくできて良かったです。
今後も少しずつLWCに触れていこうと思います。