mcpField
U
ui5-typescript-conversion

ui5-typescript-conversion

github.com/cline開発40950 回インストール15 週間前に更新
リポジトリ

ui5-typescript-conversion について

SAPUI5/OpenUI5 プロジェクトを JavaScript から TypeScript に移行するための手順ガイドです。プロジェクト設定、アプリケーションコード、テストを扱い、コメントの保持と具体的な UI5 型の使用を重視します。

主な機能

  1. 01プロジェクト設定と tsconfig の指針
  2. 02UI5 クラスとモジュールの変換
  3. 03メソッド、変数、イベントの型付け
  4. 04共有型定義の作成
  5. 05コメント保持のルール

活用シーン

  1. 01UI5 プロジェクトの TypeScript 移行計画
  2. 02UI5 クラスとモジュール定義の変換
  3. 03UI5 コントロールとイベントの型指定

インストール

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

$npx skills add https://github.com/cline/skills --skill ui5-typescript-conversion

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

同梱ファイルreferences/test_conversion.md

詳細

UI5 TypeScript 変換ガイドライン

このドキュメントでは、UI5(SAPUI5/OpenUI5)プロジェクトを TypeScript に変換する方法について説明します。次の内容で構成されています。

  1. 重要な一般ルール
  2. プロジェクトのセットアップで変更が必要な点
  3. コード自体の変換
  4. テストの変換(別ファイルを参照)

変換に関する一般ルール

すべてのコメントを保持する

既存の JSDoc、ドキュメント、コメントは必ず保持してください。変換中に JSDoc やコメントを削除してはいけません。

入力例:

/**
 * My cool controller, it does things.
 */
return Controller.extend("com.myorg.myapp.controller.BaseController", {
    /**
     * Convenience method for accessing the component of the controller's view.
     * @returns {sap.ui.core.Component} The component of the controller's view
     */
    getOwnerComponent: function () {
        // comment
        return Controller.prototype.getOwnerComponent.call(this);
    },
    ...
});

誤った出力:

export default class BaseController extends Controller {
    public getOwnerComponent(): UIComponent {
        return super.getOwnerComponent() as UIComponent;
    }
}

正しい出力:

/**
 * My cool controller, it does things.
 * @namespace com.myorg.myapp.controller
 */
export default class BaseController extends Controller {
    /**
     * Convenience method for accessing the component of the controller's view.
     * @returns {sap.ui.core.Component} The component of the controller's view
     */
    public getOwnerComponent(): UIComponent {
        // comment
        return super.getOwnerComponent() as UIComponent;
    }
}
慎重に対応する

このドキュメントに記載されたすべてのガイドラインを注意深く守ってください(必要に応じて適切に調整してください)。変換の各ステップを始める前に、このドキュメントにある関連事項をすべて検討してください。

ステップごとに進める

TypeScript プロジェクトのセットアップから始め、ほかのファイルが依存する中心的なファイルを優先して、プロジェクトをステップごとに変換してください。中心的なファイルも変換されれば、ほかのファイルから型付きのバージョンを利用できます。必要に応じて、tsconfig.json の compilerOptions にある "allowJs": true を設定すると、変換途中のプロジェクトを実行しやすくなる場合があります。

any 型を避ける

安易な方法を取らず、any の代わりに適切な型を見つけるか、インターフェースを作成してください。

悪い例:

(this.getOwnerComponent() as any).getContentDensityClass();

良い例:

(this.getOwnerComponent() as AppComponent).getContentDensityClass()
unknown のキャストを避ける

代わりに実際の UI5 コントロール型をインポートして使用してください(基本クラスの sap/ui/core/Control、または該当するプロパティにアクセスするために必要な、より具体的なクラスを使用します)。XMLView を確認し、コントローラーで this.byId(...) を呼び出したときに、実際にどのコントロール型が返されるかを調べてください。 ルーティングイベントには、たとえば Route$PatternMatchedEvent のような固有のイベント型を使用することも忘れないでください。

キャストの例

悪い例:

(this.byId("form") as unknown as {setVisible: (v: boolean) => void}).setVisible(false);

良い例:

import SimpleForm from "sap/ui/layout/form/SimpleForm";
(this.byId("form") as SimpleForm).setVisible(false);
共有型定義を作成する

作成した型定義の多くは、複数のファイルで役立ちます。src/types/ 内のファイルなど、中心となる場所に作成してください。

プロジェクトセットアップの変換

1. package.json

まだ存在しない場合は、次の dev 依存関係を package.json ファイルに追加する必要があります(非常に重要です)。

{{dependencies}}

ただし、依存関係がすでに package.json に存在する場合は、そのメジャーバージョンを上げないでください。 既存の依存関係は削除せず、新しい設定のみを追加してください。型が見つかることを確認するため、早めに依存関係をインストールしてください。

重要:さらに、UI5 プロジェクトとバージョンが一致する @sapui5/types(または @openui5/types)パッケージを、dev 依存関係として必ず追加してください。フレームワークの型とバージョンは ui5.yaml で確認するか、get_project_info MCP ツールを使用して確認できます。

また、依存関係またはそのバージョンに変更があった場合に限り、変更された依存関係をプロジェクトに反映するため、npm install / yarn install(プロジェクトで使用しているもの)を実行するようにしてください(またはユーザーに実行を伝えてください)。

typescript-eslint 依存関係が関係するのは、プロジェクトにすでに eslint のセットアップがある場合のみです(詳細は後述)。

また、TypeScript のエラーを簡単に確認できるように、package.json に "ts-typecheck": "tsc --noEmit" スクリプトを追加してください。

2. tsconfig.json

tsconfig.json ファイルを追加してください。次のサンプルを参考にしつつ、パスのマップを調整するなど、現在のプロジェクトの要件に合わせてください。

{
	"compilerOptions": {
		"target": "es2023",
		"module": "es2022",
		"moduleResolution": "node",
		"skipLibCheck": true,
		"allowJs": true,
		"strict": true,
		"strictNullChecks": false,
		"strictPropertyInitialization": false,
		"outDir": "./dist",
		"rootDir": "./webapp",
		"types": ["@sapui5/types", "@types/jquery", "@types/qunit"],
		"paths": {
			"com/myorg/myapp/*": ["./webapp/*"],
			"unit/*": ["./webapp/test/unit/*"],
			"integration/*": ["./webapp/test/integration/*"]
		}
	},
	"exclude": ["./webapp/test/e2e/**/*"],
	"include": ["./webapp/**/*"]
}
3. ui5.yaml

ui5.yaml ファイルを更新して ui5-tooling-transpile-task と ui5-tooling-transpile-middleware を使用するようにし、少なくとも次の設定が含まれていることを確認してください。

builder:
  customTasks:
    - name: ui5-tooling-transpile-task
      afterTask: replaceVersion
server:
  customMiddleware:
    - name: ui5-tooling-transpile-middleware
      afterMiddleware: compression
    - name: ui5-middleware-livereload
      afterMiddleware: compression

生成された ui5.yaml ファイルが有効であることを確認してください。重複するエントリーは避け、各ルート設定は一度だけ記述してください。 server のような設定がすでにある場合は、2つ目のエントリーを追加せず、既存の設定に追加してください。

4. Eslint の設定

プロジェクトで eslint が設定されている場合に限り、TypeScript 固有の設定を eslint 設定に追加します。package.json に依存関係がなく、eslint 設定ファイルもない場合は、何もしないでください。 eslint v9 と互換性のある完全な eslint.config.mjs ファイルは、たとえば次のようになりますが、実際の内容はプロジェクトによって異なるため、必ず適宜調整してください。

import eslint from "@eslint/js";
import globals from "globals";
import tseslint from "typescript-eslint";

export default tseslint.config(
	eslint.configs.recommended,
	...tseslint.configs.recommended,
	...tseslint.configs.recommendedTypeChecked,
	{
		languageOptions: {
			globals: {
				...globals.browser,
				sap: "readonly"
			},
			ecmaVersion: 2023,
			parserOptions: {
				project: true,
				tsconfigRootDir: import.meta.dirname
			}
		}
	},
	{
		ignores: ["eslint.config.mjs"]
	}
);

アプリケーションコードの変換

ステップ 1:独自の UI5 クラス構文を標準の ES クラス構文に変更する

すべての UI5 クラス定義(SuperClass.extend(...))を、標準の JavaScript class に変換する必要があります。 UI5 クラス設定オブジェクト(extend の第 2 引数)のプロパティは、標準の JavaScript クラスのメンバーになります。 逆変換で名前空間を再追加できるよう、JSDoc コメントでクラスに名前空間を注釈することが重要です。この @namespace コメントは、クラス宣言の直前に記述する必要があります。 名前空間とは、クラス名より前にある完全なパッケージ名+クラス名(extend の第 1 引数)の部分です。

変換前(例):

[... other code, e.g. loading the dependencies "App", "Controller" etc. ...]
 
var App = Controller.extend("ui5tssampleapp.controller.App", {
    onInit: function _onInit() {
        // apply content density mode to root view
        this.getView().addStyleClass(this.getOwnerComponent().getContentDensityClass());
    }
});

変換後(例。このコードをそのまま使用しないでください):

[... other code, e.g. loading the dependencies "App", "Controller" etc. ...]
 
/**
* @namespace ui5tssampleapp.controller
*/
class App extends Controller {
    public onInit() {
        // apply content density mode to root view
        this.getView().addStyleClass((this.getOwnerComponent()).getContentDensityClass());
    };
};
ステップ 2:ECMAScript モジュールとインポートに変更する

TypeScript の UI5 アプリでは、最新の ES モジュールとインポートを使用する必要があります。 そのため、すべての UI5 モジュール定義および依存関係読み込みの呼び出し(sap.ui.require(...)、sap.ui.define(...)) を、インポートを使用する ES モジュールに変換します(sap.ui.define の場合はモジュールのエクスポートも行います)。

上記の例では、次のようになります。

変換前:

sap.ui.define(["sap/ui/core/mvc/Controller"], function (Controller) {
    /**
     * @namespace ui5tssampleapp.controller
     */
    class App extends Controller {
        ... // as above
    };
 
  return App;
});

変換後:

import Controller from "sap/ui/core/mvc/Controller";
 
/**
* @namespace ui5tssampleapp.controller
*/
export default class App extends Controller {
    ... // as above
};

sap.ui.require はインポートのみに変換し、エクスポートは行わないでください。 インポートしたモジュール名が重複しないようにしてください。

ヒント:sap/ui/core/Core をインポートしても、ほとんどの他の UI5 モジュールと同様にクラスが提供されるのではなく、UI5 Core のシングルトンインスタンスが提供されます。そのため、JavaScript でシングルトンを返す sap.ui.getCore() を呼び出す代わりに、インポートしたモジュールを byId(...) などのメソッドに直接使用できます。

sap.ui.require が動的に使用されている場合(たとえば、メソッド本体の中で sap.ui.require(["sap/m/MessageBox"], function(MessageBox) { ... }) のように使用されている場合)は、これを import("sap/m/MessageBox").then((MessageBox) => { ... }) のような動的インポートに変換します。

ステップ 3:TypeScript コードへの標準的な適応

JavaScript コードを TypeScript に変換する際の一般的な知識を適用します。特に、次の点に注意してください。

  • 必要に応じて、メソッドのパラメーターと変数に型情報を追加します。
  • クラス定義の先頭に、不足しているクラスの private メンバー変数(型情報を含む)を追加します。(JavaScript では、クラスインスタンスの存続期間中に、後からその場で作成されることがよくあります。)
  • .bind(...) が使われている場合は、通常の function をアロー関数に変換します。TypeScript は、バインドされた「this」コンテキストの型を関数本体に伝播しないようです。
  • 該当する場合は、コード内で必要となる追加の型や構造を定義します。

重要:変数やメソッドのパラメーター/戻り値の型注釈などで UI5 の型を使う場合、グローバル名前空間付きの UI5 型(sap.m.Button や sap.ui.core.Popup など)は使用しないでください。代わりに、それぞれのモジュール(sap/m/Button や sap/ui/core/Popup など)から UI5 の型をインポートして、インポートした型を使用します(必要に応じてインポートを追加してください)。

例:

誤り:

const b: sap.m.Button;
function getPopup(): sap.ui.core.Popup  { ... }

正しい例:

import Button from "sap/m/Button";
import Popup from "sap/ui/core/Popup";
 
const b: Button;
function getPopup(): Popup  { ... }

ヒント:UI5 コントロールのイベントハンドラーでは、Event や MouseEvent のようなブラウザーイベントではなく、実際の UI5 コントロールイベントを使用してください。UI5 のイベントはブラウザーイベントとは異なります。たとえば、sap/m/Button の press イベントを処理する場合は、sap/m/Button モジュールの Button$PressEvent と Button$PressEventParameters を使用します。

注:UI5 コントロール ABC のイベント XYZ には、ABC$XYZEvent や ABC$XYZEventParameters のような型が用意されています。

例:

変更前:

sap.ui.define(["./BaseController"], function (BaseController) {
    return BaseController.extend("my.app.controller.Main", {
        onPress: function(oEvent) {
            const button = oEvent.getSource();
        },
        
        onSelectionChange: function(oEvent) {
            const items = oEvent.getParameter("selectedItems");
        }
    });
});

変更後:

import BaseController from "./BaseController";
import Button from "sap/m/Button";
import {Button$PressEvent} from "sap/m/Button";
import {Table$RowSelectionChangeEvent} from "sap/ui/table/Table";

export default class Main extends BaseController {
    onPress(oEvent: Button$PressEvent): void {
        const button = oEvent.getSource() as Button;
    }
    
    onRowSelectionChange(oEvent: Table$RowSelectionChangeEvent): void {
        const selectedContext = oEvent.getParameter("rowContext");
    }
}

ヒント:必要なプロパティをすべて備えた、最も具体的な型を使用してください。例:

  • ブラウザーイベントには、単なる Event ではなく、KeyboardEvent や MouseEvent などの具体的な型を使用します。
  • sap/ui/base/Event ではなく、sap/m/Button モジュールの Button$PressEvent を使用します。
  • イベントに限らず、すべての型に同じことが当てはまります。
ステップ 4:ジェネリックメソッドの戻り値の型キャスト

document.getElementById(...) や someUI5Control.getModel()、またはコントローラー内の this.byId() のようなジェネリックなゲッターメソッドは、実際には通常、特定のサブタイプ(たとえば HTMLAnchorElement、sap.ui.model.odata.v4.ODataModel、sap.m.Input)である場合でも、可能な型すべてのスーパータイプ(この例では HTMLElement、sap.ui.model.Model、sap.ui.core.Element)を返します。

多くの場合、戻り値を特定の型にキャストしないと使用できません。実際の型は通常、コンテキストから判断できます。判断できない場合は、誤った型を推測するより、キャストを避けてください。また、戻り値の型がすでに sap.ui.model.Model であるのに、スーパータイプである sap.ui.model.Model にキャストすることも避けてください。

同じことは、次のような複数の UI5 メソッドにも当てはまります。

  • core.byId() / view.byId()
  • control.getBinding()
  • ownerComponent.getModel()
  • event.getSource()
  • component.getRootControl()
  • this.getOwnerComponent()

このキャストでは、型(上記の ODataModel など)を認識させるために、追加のモジュールインポートが必要になる場合もあります。

上記のアプリコントローラーの例では、アプリのコンポーネント(AppComponent という名前)の追加インポートが必要になります。これにより、onInit の実装内で必要な型キャストを行えます。この型キャストがない場合、getOwnerComponent の戻り値は sap.ui.core.Component となり、アプリのコンポーネントで定義されている getContentDensityClass メソッドを使用できません。

変更前:

import Controller from "sap/ui/core/mvc/Controller";
 
/**
* @namespace ui5tssampleapp.controller
*/
export default class App extends Controller {
 
    public onInit() {
        // apply content density mode to root view
        this.getView().addStyleClass(this.getOwnerComponent().getContentDensityClass());
    };
 
};

変更後:

import Controller from "sap/ui/core/mvc/Controller";
import AppComponent from "../Component";
 
/**
* @namespace ui5tssampleapp.controller
*/
export default class App extends Controller {
 
    public onInit() : void {
        // apply content density mode to root view
        this.getView().addStyleClass((this.getOwnerComponent() as AppComponent).getContentDensityClass());
    };
 
};

(注:メソッドの戻り値の型を void と定義することは TypeScript で必須ではありませんが、たとえばリンティングの設定によっては有益です。)

ステップ 5:残っている問題の解決

この時点で、残っている TypeScript エラーの数は大幅に減っているはずです。 明らかに認識できるエラーがあれば修正します。判断に迷う場合は、最後まで残った問題を開発者に伝えてください。

UI5 コントロールの TypeScript 変換ガイドライン

このセクションでは、UI5 のカスタムコントロールを JavaScript から TypeScript に変換する方法を説明します。これは、アプリケーション内の単一のカスタムコントロールにも、コントロールライブラリにも適用されます。

カスタム UI5 コントロールを TypeScript に変換するには、一般的な TypeScript 変換(独自の UI5 クラスや構文の変換)に加えて、特定のパターンが必要です。

実行時に生成されるメソッドの問題(重大)

理解しておくべき最も重要な点です。

UI5 は、すべてのプロパティ、集約、関連付け、イベントに対するゲッター/セッター(およびその他の)メソッドを実行時に生成します。そのため、TypeScript は開発時にこれらのメソッドを認識できず、型エラーが発生します。

問題

メタデータで text プロパティを定義したコントロールの場合:

static readonly metadata: MetadataOptions = {
    properties: {
        "text": "string"
    }
};

生成されたメソッドを使用しようとすると、TypeScript はエラーを表示します。

rm.text(control.getText());  // ERROR: Property 'getText' does not exist on type 'MyControl'

また、TypeScript はコントロールを初期化するためのコンストラクターシグネチャの構造も認識しません。

new MyControl("myId", {text: "Hello"}); // TypeScript doesn't know about the settings object structure

これは次の項目に影響します。

  • プロパティのゲッター/セッター:getText()、setText()、bindText()
  • 集約のメソッド:addItem()、removeItem()、getItems()、…
  • 関連付けのメソッド:getLabel()、setLabel()
  • イベントのメソッド:attachPress()、detachPress()、firePress()
  • コンストラクターの設定オブジェクトの構造
解決策:@ui5/ts-interface-generator

インターフェイスジェネレーターのツールを開発依存関係としてインストールします。

npm install --save-dev @ui5/ts-interface-generator@{{ts-interface-generator-version}}

以降の開発を容易にするため、package.json に次のようなスクリプトを追加します。

{
    "scripts": {
        "watch:controls": "npx @ui5/ts-interface-generator --watch"
    }
}

注意:コントロールに関連する tsconfig ファイルは、インターフェイスジェネレーターを実行するディレクトリと同じディレクトリに置く必要があります。プロジェクトのルートで実行し、TypeScript コントロールを対象とする tsconfig がサブディレクトリにある場合、またはファイル名が tsconfig.json と異なる場合は、 npx @ui5/ts-interface-generator --watch --config path/to/tsconfig.json のように呼び出してください。

すべてのコントロールを TypeScript に変換した後、ジェネレーターを一度実行して、必要なコントロールインターフェイスを生成します。

npm run watch:controls

これにより、実行時に生成されるすべてのメソッドを含む TypeScript インターフェイスが記述された *.gen.d.ts ファイル(例:MyControl.gen.d.ts)が生成されます。TypeScript はこれらのインターフェイスをコントロールクラスとマージします。

これらの生成ファイルはバージョン管理にコミットし、手動では決して編集しないでください。

必須のコンストラクターシグネチャ(重大な手動作業)

インターフェイスジェネレーターの実行後、ターミナル出力からコンストラクターシグネチャを手動でコピーし、該当するコントロールクラスに貼り付ける必要があります。

ジェネレーターは次のような出力を行います。

===== BEGIN =====
// The following three lines were generated and should remain as-is to make TypeScript aware of the constructor signatures 
constructor(id?: string | $MyControlSettings);
constructor(id?: string, settings?: $MyControlSettings);
constructor(id?: string, settings?: $MyControlSettings) { super(id, settings); }
===== END =====

これらの行を、メタデータ定義より前のクラス本体の先頭にコピーします。

export default class MyControl extends Control {
    // The following three lines were generated and should remain as-is to make TypeScript aware of the constructor signatures 
    constructor(id?: string | $MyControlSettings);
    constructor(id?: string, settings?: $MyControlSettings);
    constructor(id?: string, settings?: $MyControlSettings) { super(id, settings); }

    static readonly metadata: MetadataOptions = {
        // ...
    };
}
コントロールのメタデータの型付け

コントロールのメタデータは MetadataOptions として型付けする必要があります。

import type { MetadataOptions } from "sap/ui/core/Element";

export default class MyControl extends Control {
    static readonly metadata: MetadataOptions = {
        properties: {
            "text": "string"
        }
    };
}

重要な点:

  • コントロールの場合は sap/ui/core/Element から MetadataOptions をインポートします(または最も近い基底クラスからインポートします。sap/ui/core/Object、sap/ui/core/ManagedObject、sap/ui/core/Component でも利用できます)
  • import ではなく import type を使用します(設計時のみで、実行時への影響はありません)
  • MetadataOptions は UI5 1.110 以降で利用できます。それ以前のバージョンでは object を使用してください
  • 型付けにより、コントロールを継承する際の問題を防げます(継承したプロパティを重複して記述する必要はありません)
Namespace アノテーションが必須

トランスフォーマーが正しい UI5 クラス名を生成するには、@namespace JSDoc アノテーションが必須です。

/**
 * @namespace ui5.typescript.helloworld.control
 */
export default class MyControl extends Control {
    // ...
}
エクスポートパターン

クラスを定義する際は、すぐに export default を使用する必要があります。そうしないと、ts-interface-generator が失敗します。

// CORRECT:
export default class MyControl extends Control {
    // ...
}

// WRONG - separate export:
class MyControl extends Control {
    // ...
}
export default MyControl;
メタデータとレンダラーの静的メンバー

メタデータとレンダラーは、どちらも static クラスメンバーとして定義します。

import RenderManager from "sap/ui/core/RenderManager";

export default class MyControl extends Control {
    static readonly metadata: MetadataOptions = {
        properties: {
            "text": "string"
        }
    };

    static renderer = {
        apiVersion: 2,
        render: function (rm: RenderManager, control: MyControl): void {
            rm.openStart("div", control);
            rm.openEnd();
            rm.text(control.getText());
            rm.close("div");
        }
    };
}

レンダラーは別ファイルに分けることもできます(ライブラリで一般的です)。その場合、TypeScript への変換後も別ファイルのままにしてください。

次の JavaScript コードは、

sap.ui.define([
    "sap/ui/core/Control",
    "./MyControlRenderer"
], function (Control, MyControlRenderer) {
    "use strict";

    return Control.extend("com.myorg.myapp.control.MyControl", {
        ...
        renderer: MyControlRenderer,
        ...

次の TypeScript コードに変換されます。

import Control from "sap/ui/core/Control";
import type { MetadataOptions } from "sap/ui/core/Element";
import MyControlRenderer from "./MyControlRenderer";

/**
 * @namespace com.myorg.myapp.control
 */
export default class MyControl extends Control {
    ...
    static renderer = MyControlRenderer;
    ...
コントロールの完全な例
JavaScript(変換前):
sap.ui.define([
    "sap/ui/core/Control",
    "sap/ui/core/RenderManager"
], function (Control, RenderManager) {
    "use strict";
    
    var MyControl = Control.extend("ui5.typescript.helloworld.control.MyControl", {
        metadata: {
            properties: {
                "text": "string"
            },
            events: {
                "press": {}
            }
        },
        
        renderer: function (rm, control) {
            rm.openStart("div", control);
            rm.openEnd();
            rm.text(control.getText());
            rm.close("div");
        },
        
        onclick: function() {
            this.firePress();
        }
    });

    return MyControl;
});
TypeScript(変換後):
import Control from "sap/ui/core/Control";
import type { MetadataOptions } from "sap/ui/core/Element";
import RenderManager from "sap/ui/core/RenderManager";

/**
 * @namespace ui5.typescript.helloworld.control
 */
export default class MyControl extends Control {
    // The following three lines were generated and should remain as-is to make TypeScript aware of the constructor signatures 
    constructor(id?: string | $MyControlSettings);
    constructor(id?: string, settings?: $MyControlSettings);
    constructor(id?: string, settings?: $MyControlSettings) { super(id, settings); }

    static readonly metadata: MetadataOptions = {
        properties: {
            "text": "string"
        },
        events: {
            "press": {}
        }
    };

    static renderer = {
        apiVersion: 2,
        render: function (rm: RenderManager, control: MyControl): void {
            rm.openStart("div", control);
            rm.openEnd();
            rm.text(control.getText());
            rm.close("div");
        }
    };

    onclick(): void {
        this.firePress();
    }
}
ライブラリ固有のガイドライン

個々のアプリ内のコントロールだけでなく、コントロールライブラリ全体を変換する場合は、追加の手順が必要です。

列挙型を含むライブラリモジュール(XSS の問題を回避するために重要です!)

library.ts では、UI5 ランタイムとの互換性を確保するため、列挙型をグローバルライブラリオブジェクトに割り当てる必要があります。

import ObjectPath from "sap/base/util/ObjectPath";

// Define enum as TypeScript enum
export enum ExampleColor {
    Red = "Red",
    Green = "Green",
    Blue = "Blue"
}

// CRITICAL: Attach to global library object
const thisLib = ObjectPath.get("com.myorg.myui5lib") as {[key: string]: unknown};
thisLib.ExampleColor = ExampleColor;

ライブラリ内のすべての列挙型で、これが重要な理由:

  • コントロールのプロパティでは、型をグローバル名として参照します。例:type: "com.myorg.myui5lib.ExampleColor"
  • UI5 ランタイムは、このグローバルパスを通じて列挙型を見つける必要があります
  • これがないと、UI5 はプロパティの型を検証できません
  • 型チェックが機能しなくなり、検証されていないコンテンツが予期せず HTML に書き込まれることで、XSS の脆弱性が生じる可能性があります
tsconfig.json でのパスマッピング

ライブラリの場合は、ライブラリの名前空間のパスマッピングを追加します。

{
    "compilerOptions": {
        "paths": {
            "com/myorg/mylib/*": ["./src/*"]
        }
    }
}
コントロール変換チェックリスト

コントロールを JavaScript から TypeScript に変換する場合:

  1. 通常の UI5 モジュールと同様に、ES6 クラス/モジュールに変換します
  2. @namespace JSDoc アノテーションを追加します
  3. クラス定義の直後に export default を使用します
  4. メタデータを MetadataOptions として型付けします(適切な基底クラスからインポートしてください)
  5. メタデータとレンダラーを static メンバーとして定義します
  6. @ui5/ts-interface-generator をインストールして実行します
  7. 生成された出力からコンストラクターのシグネチャをコピーしてクラスに追加します
  8. ライブラリの場合:列挙型をグローバルライブラリオブジェクトに手動で割り当てます
  9. すべての JSDoc コメントとドキュメントを保持します

テストの変換

UI5 のテストコードを JavaScript から TypeScript に変換するには、重要で一見わかりにくいパターンがあります。テストを変換する必要がある場合は、詳細についてテスト変換のドキュメントを参照してください。

コメント