【UI5】TypeScriptを使ってみる

##はじめに
今年の7月に、UI5でTypeScriptが使えるようになったとアナウンスがありました。

- UI5Conセッション：[Enhanced TypeScript support for UI5 apps - A quick intro](https://www.youtube.com/watch?v=5jfHNKQ48w8&list=PLHUs_FUbq4dXRIbRPiXmzXmV2BAcA8zAR&index=5)
- ブログ：[Getting Started with TypeScript for UI5 Application Development](https://blogs.sap.com/2021/07/01/getting-started-with-typescript-for-ui5-application-development/)

具体的には、[@sapui5/ts-types-esm](https://www.npmjs.com/package/@sapui5/ts-types-esm)または[@openui5/ts-types-esm](https://www.npmjs.com/package/@openui5/ts-types-esm)というモジュールでUI5の型情報が提供されており、使うことができます。ただし、両モジュールとも**Beta State**となっており、今後互換性のない変更の可能性があります。よって、**個人で試してみるのはいいが、プロジェクトで使うにはまだ早い**という段階です。

このブログでは、以下の3点について書きたいと思います。TypeScriptに対応したUI5アプリについては、以下に詳細な説明があります。このブログではエッセンスをかいつまんでお伝えします。
[Using TypeScript in UI5 Apps](https://github.com/SAP-samples/ui5-cap-event-app/blob/typescript/docs/typescript.md)

1. TypeScriptに対応したUI5プロジェクトの特徴
1. プロジェクトの作成方法
1. BTPにデプロイする方法（オプション）

※3.はTypeScriptか否かにかかわらず、UI5プロジェクトをBTPのLaunchpadで使えるようにする一般的な設定です。

##1. TypeScriptに対応したUI5の特徴
###1.1. そもそも、TypeScriptとは
[こちら](https://github.com/SAP-samples/ui5-cap-event-app/blob/typescript/docs/typescript.md#brief-typescript-introduction)から引用・翻訳します。

>- JavaScriptに型が追加されたもの
- 開発時にだけで使われる（ブラウザには理解されないので、実行前にビルドのステップが必要）
- Microsoftによって開発されたが、オープンソースで広く使われている

特徴は、ソースの中で変数や関数の宣言時に型を明示する必要があるということです。宣言で指定された型と、実際に割り当てようとした変数の型が違った場合、エディタ上でエラーが出ます。これによって、従来は動かしてみないとわからなかった不具合を開発時に知ることができます。

```
var suomeNumber: number;
```
始めてTypeScriptを触るという方には、以下のドキュメントがお勧めです。短くまとまっていてコードも載っているのでわかりやすいと思います。
[TypeScript for JavaScript Programmers](https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes.html)

###1.2. UI5はどうやってTypeScriptに対応しているのか
UI5では、[@sapui5/ts-types-esm](https://www.npmjs.com/package/@sapui5/ts-types-esm)または[@openui5/ts-types-esm](https://www.npmjs.com/package/@openui5/ts-types-esm)というモジュールによって型情報が提供されます。モジュールの中を見てみると、`*.d.ts`というファイルがあり、この中でUI5の部品の型定義がされています。型定義はUI5の[ドキュメント](https://openui5.hana.ondemand.com/api)をベースに生成されています。ドキュメントが完全でないと型の定義がされないので、「問題があれば以下にissuesを上げてください」ということになっています。
https://github.com/SAP/openui5/blob/master/CONTRIBUTING.md#report-an-issue

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/b48c27d8-c3ac-80f3-d1d0-c77cb403daef.png)
TypeScriptで書かれたソースは、Babelのプラグインである[babel-plugin-transform-modules-ui5](https://github.com/r-murphy/babel-plugin-transform-modules-ui5)によって、従来のUI5のコードに変換されます。このプラグインはTypeScriptからJavaScriptへの変換だけでなく、ES6で書かれたコードからES5への変換も行ってくれます。つまり、プラグインを使うことでTypeScriptが使えるだけでなく、**ES6でのモダンな書き方もできるように**なります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/76ed3c7b-7bca-2a32-1a68-df4acbbedfea.png)
図：[Getting Started with TypeScript for UI5 Application Development](https://blogs.sap.com/2021/07/01/getting-started-with-typescript-for-ui5-application-development/)より引用

UI5に対応させるために必要な設定ファイルや各種モジュールのインストール方法は以下に載っています。
[A Detailed Guide to Create a UI5 TypeScript App From Scratch in Five to Ten Steps](https://github.com/SAP-samples/ui5-typescript-helloworld/blob/main/step-by-step.md)

ただし、[generator-easy-ui5](https://www.npmjs.com/package/generator-easy-ui5)を使えばTypeScriptに対応したプロジェクトを自動で生成してくれるので、裏の仕組みを全て知らなくてもよいと思います。

###1.3. TypeScriptに対応したUI5の特徴
TypeScript (+ES6)に対応したUI5のソースには以下のような特徴があります。

####モジュールのロードは`import xxx from ...`の形で行う
ES6に対応したおかげで、以下のような書き方ができるようになりました。

```javascript
//従来の書き方
sap.ui.define(["sap/m/MessageBox"],
	function(MessageBox) {
}

//新しい書き方
import MessageBox from "sap/m/MessageBox";
```
####コントローラーはクラスとして定義する
ES6に対応したおかげで、以下のような書き方ができるようになりました。

```javascript
//従来の書き方
return Controller.extend("namespace.projectName.controller.App", {})

//新しい書き方
export default class AppController extends Controller {}
```
####メソッド定義で引数、および返り値の型を指定する
このように型を明示するところがTypeScriptの特徴です。

```javascript
//従来の書き方
	getMessage: function (name) {
		return `Hello, ${name}`;
	}

//新しい書き方
	public getMessage(name:string): string {
		return `Hello, ${name}`;
	}
```

####get系のメソッド呼び出しでは、必要に応じて`as xxx`でキャストして型を明示する
以下はgenerator-easy-ui5で生成したTypeScriptのプロジェクトに入っているサンプルソースです。

```javascript
//従来の書き方
this.getOwnerComponent().getContentDensityClass()

//新しい書き方
import AppComponent from "../Component";
...
(this.getOwnerComponent() as AppComponent).getContentDensityClass()
```
キャストをしなかった場合、以下のように`getContentDensityClass`がComponentに存在しないというエラーになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/f0ae81d4-c79d-3277-3a76-ccbcc0409dbf.png)
**背景**
[getOwnerComponent()](https://sapui5.hana.ondemand.com/#/api/sap.ui.core.mvc.Controller%23methods/getOwnerComponent)の返り値はsap.ui.core.Component型です。ただし、sap.ui.core.ComponentにはgetContentDensityClassというメソッドがありません。このメソッドは、sap.ui.core.Componentを拡張したsrc/Component.tsで定義されたものだからです。そこで、getOwnerComponent()で取れるComponentの型を明示することによって、エラーを回避しています。

##2. プロジェクトの作成方法
###2.1. プロジェクトを生成

[generator-easy-ui5](https://www.npmjs.com/package/generator-easy-ui5)を使ってプロジェクトを作成します。`ts-app`というのがTypeScriptに対応したプロジェクト用のテンプレートです。

```
yo easy-ui5 ts-app
```

以下のように質問に答えます。UI5のバージョンは、デフォルトでは1.90.0が設定されています。[UI5Conのセッション](https://www.youtube.com/watch?v=5jfHNKQ48w8&list=PLHUs_FUbq4dXRIbRPiXmzXmV2BAcA8zAR&index=4)の中で、TypeScriptがサポートされるのは1.90以降と言われていました。

```
? How do you want to name this application? ui5TsDemo
? Which namespace do you want to use? miyasuta
? Which framework do you want to use? SAPUI5
? Which framework version do you want to use? 1.90.0
? Who is the author of the library? Mio Yasutake
? Would you like to create a new directory for the application? Yes
```

プロジェクトの構造は以下のようになります。通常のUI5プロジェクトだとwebappフォルダ配下にview, controllerなどのリソースが配置されますが、TypeScriptだとsrcフォルダになっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/308aac35-a0fc-af28-d580-f39b906a07f9.png)

easy-ui5を使うと、サンプルソースがあらかじめ入った状態でプロジェクトが生成されます。
src/controller/App.controller.tsを見てみると、[TypeScriptに対応したUI5の特徴](https://qiita.com/tami/items/fa703b212982a094b7f5#13-typescript%E3%81%AB%E5%AF%BE%E5%BF%9C%E3%81%97%E3%81%9Fui5%E3%81%AE%E7%89%B9%E5%BE%B4)で紹介した特徴があります。

```javascript
import MessageBox from "sap/m/MessageBox";
import Controller from "sap/ui/core/mvc/Controller";
import AppComponent from "../Component";

/**
 * @namespace miyasuta.ui5TsDemo.controller
 */
export default class AppController extends Controller {

	public onInit() : void {
		// apply content density mode to root view
		this.getView().addStyleClass((this.getOwnerComponent() as AppComponent).getContentDensityClass());
	}

	public sayHello() : void {
		MessageBox.show("Hello World!");
	}
}
```

###2.2. 実行
package.jsonには、"start"のコマンドが以下のように定義されています。

```json
"start": "npm-run-all --parallel watch:ts start:ui5"
```

`npm start`でそのまま実行してみましょう。
以下のブラウザ画面が立ち上がり、"Say Hello"ボタンを押すとダイアログが出力されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/dabaf28a-f36b-9ede-1aa5-8f06829ba504.png)

###2.3. ビルド
以下のコマンドでビルドします。

```
npm run build
```
この結果、distフォルダにビルドされたファイルが作成されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/9c2d63f8-da8e-4e6b-a762-1a7892690d68.png)
App-dbg.controller.jsを見てみると、従来のUI5の書き方になっていることがわかります。

```javascript
sap.ui.define(["sap/m/MessageBox", "sap/ui/core/mvc/Controller"], function (MessageBox, Controller) {
  /**
   * @namespace miyasuta.ui5TsDemo.controller
   */
  const AppController = Controller.extend("miyasuta.ui5TsDemo.controller.AppController", {
    onInit: function _onInit() {
      // apply content density mode to root view
      this.getView().addStyleClass(this.getOwnerComponent().getContentDensityClass());
    },
    sayHello: function _sayHello() {
      MessageBox.show("Hello World!");
    }
  });
  return AppController;
});
```
##3. BTPにデプロイする方法（オプション）
generator-easy-ui5で生成したTypeScriptのプロジェクトには、デプロイに必要な設定が入っていません。BTPのLaunchpadから利用できるようにするため、以下の設定を行います。

###3.1. index.htmlを修正
デフォルトでは、sap-ui-core.jsをローカルのリソースフォルダから持ってくる設定になっています。Launchpadから実行する場合はこのままでよいですが、HTML5 Appsメニューから実行する場合はindex.htmlが使われるので、取得元を以下のように変更する必要があります。

元の状態

```html
src="resources/sap-ui-core.js"
```
これを、CDNからの取得に変更します。

```html
src="https://sapui5.hana.ondemand.com/resources/sap-ui-core.js"
```

###3.2. xs-app.json
srcフォルダの直下にxs-app.jsonファイルを追加します。

```json
{
    "welcomeFile": "/index.html",
    "routes": [ 
      {
        "source": "^(.*)",
        "target": "$1",
        "authenticationType": "xsuaa",
        "service": "html5-apps-repo-rt"
      }
    ]
  }
```

###3.3. manifest.jsonを修正
Launchpadで使うため、crossNavigation、およびsap.cloud.serviceの設定を追加します。

```json
	"sap.app": {
		...,
		"crossNavigation": {
		  "inbounds": {
			"intent1": {
			  "signature": {
				"parameters": {},
				"additionalParameters": "allowed"
			  },
			  "semanticObject": "TypeScriptSample",
			  "action": "display",
			  "title": "TypeScript Sample"
			}
		  }
		}
		...
	},
	"sap.cloud": {
	  "service": "ui5tsdemo.service"
	}
```

###3.4. ui5-task-zipperの設定
ビルドした結果をzipするための設定です。まず、devDependenciesにui5-task-zipperを追加します。

```
npm i ui5-task-zipper --save-dev
```
インストールされたら、package.jsonのui5.dependenciesにも追加します。

```json
  "ui5": {
    "dependencies": [
      "ui5-middleware-livereload",
      "ui5-task-zipper"
    ]
  }
```
ui5.yamlに以下の設定を追加します。

```yaml
builder:
  customTasks:
    -  name: ui5-task-zipper
       afterTask: uglify
       configuration:
         includeDependencies: false
         archiveName: ui5tsdemo  
```

###3.5. mta.yamlを作成
デプロイするための設定です。mta.yamlファイルを新規作成します。
※以下の`ui5tsdemo`は必要に応じて、ご自身のプロジェクト名に置き換えてください

```yaml
ID: ui5tsdemo
_schema-version: 3.2.0
version: 0.0.1
parameters:
  enable-parallel-deployments: true
modules:
  - name: ui5tsdemo_deployer
    type: com.sap.application.content
    path: .
    requires:
      - name: ui5tsdemo_html5_repo_host
        parameters:
          content-target: true
    build-parameters:
      build-result: resources
      requires:
        - name: ui5tsdemo
          artifacts:
            - dist/ui5tsdemo.zip
          target-path: resources/
  - name: ui5tsdemo
    type: html5
    path: .
    build-parameters:
      builder: custom
      commands:
        - npm install
        - npm run build
      supported-platforms: []  

  - name: ui5tsdemo_destination-content
    type: com.sap.application.content
    build-parameters:
      no-source: true
    requires:
      - name: ui5tsdemo_uaa
        parameters:
          service-key:
            name: ui5tsdemo_uaa-key
      - name: ui5tsdemo_html5_repo_host
        parameters:
          service-key:
            name: ui5tsdemo_html5_repo_host-key
      - name: ui5tsdemo_destination
        parameters:
          content-target: true
    parameters:
      content:
        instance:
          existing_destinations_policy: update
          destinations:
            - Name: ui5tsdemo_html5_repo_host
              ServiceInstanceName: ui5tsdemo_html5_repo_host
              ServiceKeyName: ui5tsdemo_html5_repo_host-key
              sap.cloud.service: ui5tsdemo.service
            - Name: ui5tsdemo_uaa
              Authentication: OAuth2UserTokenExchange
              ServiceInstanceName: ui5tsdemo_uaa
              ServiceKeyName: ui5tsdemo_uaa-key
              sap.cloud.service: ui5tsdemo.service 

resources:
  - name: ui5tsdemo_destination
    type: org.cloudfoundry.managed-service
    parameters:
      service-plan: lite
      service: destination
      config:
        HTML5Runtime_enabled: true
        version: 1.0.0
  - name: ui5tsdemo_html5_repo_host
    type: org.cloudfoundry.managed-service
    parameters:
      service-plan: app-host
      service: html5-apps-repo
      config:
        sizeLimit: 2
  - name: ui5tsdemo_uaa
    type: org.cloudfoundry.managed-service
    parameters:
      path: ./xs-security.json
      service-plan: application
      service: xsuaa                           
```

プロジェクト直下にxs-security.jsonも作成しておきます。

```json
{
    "xsappname": "ui5tsdemo",
    "tenant-mode": "dedicated"
}
```

###3.6. ビルド、デプロイ
以下のコマンドでビルド、デプロイします。

```
mbt build
cf deploy mta_archives/ui5tsdemo_0.0.1.mtar
```
HTML5 Applicationのメニューからアプリを開けるようになっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/bcc0ccd2-e584-e0af-cdd1-f546b3756e78.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/08a19354-3a3c-16ff-d303-dc26c1f2daf8.png)
設定をすればLaunchpadで開くこともできます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/275cc54c-b454-efff-dd32-beaa045b094d.png)

##思うこと
Babelのプラグインのおかげで、ES6に対応した書き方ができるようになったということが開発者にとってはうれしいのかな、と思います。これで、バックエンドとフロントエンドでコードの書き方を統一することができます。
また、私自身TypeScriptはほとんど使ったことがなかったので、慣れるため個人開発ではしばらくTypeScriptで書いてみようと思います。

##参考
###TypeScriptについて
- [The TypeScript Handbook](https://www.typescriptlang.org/docs/handbook/intro.html)
- [TypeScript for JavaScript Programmers ](https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes.html)

###UI5とTypeScript
- UI5でTypeScriptを使うための詳細なガイド：[Using TypeScript in UI5 Apps](https://github.com/SAP-samples/ui5-cap-event-app/blob/typescript/docs/typescript.md)
- TypeScriptで書かれた本格的なUI5アプリ：[ui5-cap-event-app](https://github.com/SAP-samples/ui5-cap-event-app/tree/typescript)
- generatorを使わずにTypeScriptに対応させる方法：[A Detailed Guide to Create a UI5 TypeScript App From Scratch in Five to Ten Steps](https://github.com/SAP-samples/ui5-typescript-helloworld/blob/main/step-by-step.md)
- UI5Con ：[Enhanced TypeScript support for UI5 apps - A quick intro](https://www.youtube.com/watch?v=5jfHNKQ48w8&list=PLHUs_FUbq4dXRIbRPiXmzXmV2BAcA8zAR&index=5)
- ブログ：[Getting Started with TypeScript for UI5 Application Development](https://blogs.sap.com/2021/07/01/getting-started-with-typescript-for-ui5-application-development/)