mcpField
E
experience-lwc-typescript-migrate

experience-lwc-typescript-migrate

github.com/forcedotcom開発1.1k2.3k 回インストール1 日前に更新
リポジトリ

experience-lwc-typescript-migrate について

Lightning Web Components(LWC)を JavaScript から TypeScript に移行する Salesforce 開発者向けのスキルです。Git の履歴を保ったリネームと、公開された @api メンバーのみを含む .d.ts の生成を行い、コンパイルとテストで確認します。

主な機能

  1. 01コンポーネントと補助ファイルを .ts に変更
  2. 02公開・内部メンバーへの型付け
  3. 03@api メンバーのみの .d.ts 生成
  4. 04イベントと非同期メソッドの型指定
  5. 05TypeScript コンパイルと Jest テストの実行

活用シーン

  1. 01単一の LWC を JavaScript から TypeScript に移行
  2. 02既存 LWC の公開型宣言を作成
  3. 03リネーム済みコンポーネントに型を追加

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/forcedotcom/sf-skills --skill experience-lwc-typescript-migrate

インストール後、Claude Code で/experience-lwc-typescript-migrateを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

assets/
dts-template.ts
type-patterns.ts
scripts/
find-consumers.sh

詳細

LWC を TypeScript に変換する

Lightning Web Component バンドルを JavaScript から TypeScript に変換します。成果物は、型が完全に付けられた .ts 実装と、@api メンバー(他の LWC が利用する公開 API)のみを公開する .d.ts ファイルです。

このスキルを使うタイミング

  • 単一のコンポーネントまたはコンポーネントのフォルダーを .js から .ts に移行したい場合。
  • 既存の LWC 用に .d.ts が必要で、他のコンポーネント(または外部の TypeScript ホスト)から安全にインポートできるようにしたい場合。
  • すでに .ts に名前を変更したものの、適切に型が付けられていない LWC に型注釈を追加したい場合。
  • JSDoc 形式の型ヒントを、実際の TypeScript の型にアップグレードしたい場合。

前提条件

  • 現在、コンポーネントが JavaScript で正しくビルドされ、動作していること。
  • git が利用可能であること(名前の変更時に git mv を使い、履歴を保持する必要があります)。
  • TypeScript コンパイラーがビルドに組み込まれていること(SFDX TS パイプライン、またはスタンドアロンの tsc ステップ)。

ワークフロー

ステップ 1 — コンポーネントを確認する

バンドル内のすべてのファイルを開きます。

componentName/
├── componentName.js
├── componentName.html
├── componentName.css
└── (possibly) __tests__/, __utam__/, existing .d.ts

次の点を確認します。

  • LightningElement を継承しているものは何ですか? クラス名は何ですか?
  • @api デコレーターが付いているフィールドとメソッドはどれですか?
  • 既存の JSDoc が付いているプロパティやメソッドはありますか?(型ヒントの出発点として使いますが、実際の使用状況と照らし合わせて検証してください。JSDoc が誤っていることもあります。)
  • コード内での呼び出し方から、どのパラメーターや戻り値の型を推測できますか?
ステップ 2 — git mv を使って .js を .ts に名前変更する
git mv componentName/componentName.js componentName/componentName.ts

バンドル内にヘルパーの .js ファイルがあれば、同様に名前を変更します(すでに .ts の場合を除く)。決して通常の mv を使わないでください。TypeScript のレビュアーが頼りにしている履歴とのつながりが失われます。

ステップ 3 — .ts に型注釈を追加する

公開 API の契約が確かなものになった時点で作業を止められるよう、次の優先順位で型を適用します。

  1. まず @api プロパティとメソッドに型を付けます。 JSDoc がなければ追加し、JSDoc の型を TypeScript の構文(string、number、boolean、Promise<T>)に変換します。JSDoc の記述を信頼する前に、コードと照らし合わせて検証してください。
  2. 複雑な形状には interface または type エイリアスを使います。 同じインラインの型をあちこちで繰り返さないでください。
  3. オプショナルメンバーには ? を使います。 ただし、値が実際に undefined になり得る場合に限ります。用心のためだけに ? を付けないでください。
  4. プライベート/内部状態にも型を付けますが、その型はエクスポートしません。 利用側から決して触れられてはならないメンバーには private を使います。
  5. イベントハンドラー — 正確な DOM イベント型を優先します。
    • onclick(およびその他のクリック系ハンドラー)には MouseEvent を使います。キーボード操作によるクリックを含め、click は MouseEvent としてディスパッチされます。そのため、PointerEvent として型を付けると、ハンドラーがその場合には undefined となるポインター専用フィールド(pointerType、pressure など)に依存できてしまいます。
    • ポインター固有のフィールドが実際に意味を持つ onpointerdown/onpointerup/onpointermove やその他の pointer* ハンドラーには PointerEvent を使います。
    • LWC のカスタムイベントには CustomEvent<{ detail: ... }> を使います。
    • Event は最終手段とし、使用する場合はその理由を記載してください。
  6. 非同期メソッドは常に Promise<T> を返します。T だけを返すことはありません。
  7. any は避けます。 本当に型を付けられない場合は unknown を使い、型ガードで絞り込みます。
参考パターン

[[assets/type-patterns.ts|assets/type-patterns.ts]] を読み込み、プロパティ、メソッド、イベントハンドラーの型を示すインラインの例として使います。

ステップ 4 — .d.ts の生成

.ts の隣に componentName.d.ts を作成します。次の条件を満たす必要があります。

  • @api メンバーのみを含めること — プライベートな状態、内部メソッド、それ自体が @api でない限りライフサイクルフックは含めません。
  • 各宣言の直前に @api JSDoc をそのまま保持すること(@type、@required、@default、@param、@returns タグを含む)。
  • LWC モジュール名前空間 c/componentName(または異なる場合は組織の名前空間)を宣言すること。

テンプレート: [[assets/dts-template.ts|assets/dts-template.ts]] を開始時の .d.ts の形として読み込みます。

コンポーネントに @api メンバーがない場合でも、公開サーフェスがないことを説明するコメントとともにモジュール宣言を生成してください — ファイルをスキップしないでください。

ステップ 5 — コンパイルとテスト
  • TypeScript コンパイラ(tsc --noEmit またはビルドの同等のもの)を実行します。完了とする前にすべてのエラーを解決してください。@ts-ignore による回避は禁止です。
  • コンポーネントの既存の Jest テストを実行します。動作は同一であるべきです。
  • 同梱の consumer-finder を無条件で実行します — 出力が空でも有効な結果であり、スキップする理由にはなりません。このスクリプトは sfdx-project.json の packageDirectories(またはフォールバックとして <project-root>)から検索パスを解決し、プロジェクトルートを脱出するエントリを拒否し、LWC インポートの検索を内部で実行するため、呼び出しは完全に決定論的です。
"<skill_dir>/scripts/find-consumers.sh" "<project-root>" "<componentName>"

各一致について、コンシューマーが期待する型が新しい .d.ts の公開サーフェスと依然として整合していることを確認します。

ステップ 6 — 最終的なバンドルの想定形状
componentName/
├── componentName.ts          # Main TypeScript implementation
├── componentName.html        # Template (unchanged)
├── componentName.css         # Styles (unchanged)
└── componentName.d.ts        # Type definitions (new)

検証チェックリスト

変換前:

  • コンポーネントが有効な JS であり、すべてのテストがパスする。
  • すべての @api メンバーとその意図する型を特定済みである。

変換後:

  • 履歴を保持するために git mv を使用した。
  • .ts 内のすべての変数とパラメーターが具体的な型を持つ(暗黙の any がない)。
  • 複雑なオブジェクト形状は interface / type エイリアスにあり、インラインで繰り返されていない。
  • オプションの ? は本当にオプションのフィールドにのみ付いている。
  • .d.ts が存在し、c/componentName を宣言し、LightningElement を継承し、@api メンバーのみを含む。
  • すべての @api JSDoc が .d.ts にそのまま保持されている。
  • tsc がエラーゼロでパスする。回避策として @ts-ignore や any を使用していない。
  • Jest テストが依然としてパスする。

よくある落とし穴

  • エラーを黙らせるために any を使う。 実際の型を解決してください。値が本当に未知の場合は、unknown + 型ガードを使用します。
  • .d.ts にプライベートメンバーを含める。 .d.ts は公開契約です。内部のライフサイクルとヘルパーが漏れてはいけません。
  • リネーム中に JSDoc を失う。 前後でスキャンしてください — @api メンバーの JSDoc コメントは .ts と .d.ts の両方に現れる必要があります。
  • git mv をスキップする。 レビューが悲惨になり、blame が混乱します。
  • async の戻り値の型を忘れる。 async キーワードを伴う foo() は常に Promise を返します。宣言してください。
  • onclick を PointerEvent と型付けする。 click は MouseEvent(キーボードでトリガーされたクリックを含む)であるため、pointerType のような PointerEvent のフィールドはそれらのイベントでは undefined になります。onclick は MouseEvent と型付けし、PointerEvent は onpointer* ハンドラー用に取っておいてください。MouseEvent | TouchEvent は、コードが TouchEvent で明確に分岐する場合にのみ使用します。

サポートリソース

コメント