SpringBootアプリ上にTypescript環境を作ってみた

# はじめに
フロントエンドにTypescriptを導入してみます。
SpringBootのプロジェクトにTypescriptの環境を構築し、フロントエンドを動かしてみます。

# 環境
OS：macOS Ventura 13.6.4
TypeScript：5.3.3
Java：17
SpringBoot：3.2.2
yarn：1.22.21
nodenv：1.4.1
node：16.13.2

# 全体像
```
typescript-sample（SpringBootプロジェクト）
├── src/main
│   ├── java
│   │   └── com.sample.typescript（パッケージ）
│   │         ├── Controller.java
│   │         └── Application.java
│   │  
│   └── resources
│          ├── static
│          │    ├── js（コンパイル後の出力先）
│          │    │    ├── sample.js
│          │    │    └── sampleObj.js
│          │    │
│          │    └── ts（tsファイルの配置先）
│          │         ├── sample.ts
│          │         └── sampleObj.ts
│          │
│          ├── templates
│          │    └── index.html
│          │
│          └── application.yml
│
├── build.gradle
├── gradlew
├── gradlew.bat
├── settings.gradle
│
├── node_modules
├── package.json
├── tsconfig.json
└── yarn.lock
```

# SpringBootプロジェクトを作成
まず、[spring initializr](https://start.spring.io/)でSpringBootプロジェクトを作成します。
Dependenciesでは、以下を選択しておきます。
・Thymeleaf
・Spring Web
・Spring Boot Devtools

## Controllerを実装
続いて、Controllerを実装します。
実装内容は後述で実装するindex.htmlをテンプレートに指定するだけです。

```Controller.java
package com.sample.typescript;

import org.springframework.stereotype.Controller;

@Controller
public class Controller {
  public String index() {
    return "index";
  }

}
```

## Thymeleafテンプレートを実装
簡単にではありますが、テンプレートを用意します。
TypeScriptで生成するjsファイルを指定しておきます。

```index.html
<!doctype html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org">
  <head>
    <meta charset="utf-8">
    <title>sample</title>
  </head>
  <body>
  sample
  <script src="/js/sample.js" th:src="@{/js/sample.js}" type="module"></script>
  </body>
</html>
```

## 作成結果

```
typescript-sample（SpringBootプロジェクト）
├── src/main
│   ├── java
│   │   └── com.sample.typescript（パッケージ）
│   │         ├── Controller.java
│   │         └── Application.java
│   │  
│   └── resources
│          ├── templates
│          │    └── index.html
│          │
│          └── application.yml
│
├── build.gradle
├── gradlew
├── gradlew.bat
└── settings.gradle
```

# TypeScript環境を構築
## node.js、yarnを導入
[以前の投稿](https://qiita.com/kaburankattara/items/1e341bb8830ddcd9b68e)で導入したものを利用します。

## package.jsonを作成
yarnを初期化をしてpackage.jsonを作成します。

```shell
% yarn init -y
yarn init v1.22.21
warning The yes flag has been set. This will automatically answer yes to all questions, which may have security implications.
success Saved package.json
✨  Done in 0.14s.
```

プロジェクトディレクトリの直下にpackage.jsonが生成されれば成功です。

```package.json
{
  "name": "typescript-sample",
  "version": "1.0.0",
  "main": "index.js",
  "repository": "https://github.com/xxxx/typescript-sample.git",
  "author": "xxx <xxx@xxx>",
  "license": "MIT"
}
```

## TypeScriptのインストール
yarnを使ってTypeScriptのパッケージをインストールします。

```shell
% yarn add -D typescript ts-node @types/node
yarn add v1.22.21
info No lockfile found.
・・・
✨  Done in 10.18s.
```

package.jsonに依存関係が追加されて入れれば成功です。

```package.json
{
　・・・
  "devDependencies": {
    "@types/node": "^20.11.17",
    "ts-node": "^10.9.2",
    "typescript": "^5.3.3"
  }
}
```

## TypeScriptのバージョンを確認
package.jsonを見ると、バージョンを確認できます。

```package.json
{
　・・・
  "devDependencies": {
　  ・・・
    "typescript": "^5.3.3"
  }
}
```

また、コマンドからも確認できます。

```shell
% yarn tsc -v
yarn run v1.22.21
$ /Users/xxx/workspace/typescript-sample/node_modules/.bin/tsc -v
Version 5.3.3
✨  Done in 1.10s.
```

## tsconfig.jsonを生成
TypeScriptのパッケージをインストールすると、コンパイラとなるtsc（The TypeScript Compiler）コマンドが使用できるようになります。

tsconfig.jsonを生成するには、tscコマンドに--initオプションを指定します。

```shell
% yarn run tsc --init
yarn run v1.22.21
$ /Users/xxx/workspace/typescript-sample/node_modules/.bin/tsc --init

Created a new tsconfig.json with:                                                                                       
                                                                                                                     TS 
  target: es2016
  module: commonjs
  strict: true
  esModuleInterop: true
  skipLibCheck: true
  forceConsistentCasingInFileNames: true


You can learn more at https://aka.ms/tsconfig
✨  Done in 0.81s.
```

プロジェクトディレクトリの直下にtsconfig.jsonが生成されれば成功です。

## tsconfig.jsonを設定
生成したtsconfig.jsonを設定します。
今回、以下の内容を設定します。
**・moduleにes2015を指定**
**・tsファイルの配置先**
**・コンパイル後のjsファイルの出力先**

### moduleにes2015を指定
jsモジュールのインポートにimport/exportを使用したいため、es2015を指定します。

```tsconfig.json
    "module": "commonjs",
    // ↓
    "module": "es2015",
```

### tsファイルの配置先
tsファイルの配置先には2箇所設定します。
rootDirで、tsファイルを配置するルートディレクトリを設定します。

```tsconfig.json
    // "rootDir": "./",
    // ↓
    "rootDir": "src/main/resources/static/ts",
```

続いて、tsconfig.jsonにinclude項目を追加し、こちらにも配置するルートディレクトリを設定します。

```tsconfig.json
{
  "compilerOptions": {
    /** ・・・ */
  },

  "include": [
    "src/main/resources/static/ts"
  ]
}
```

#### includeを設定していない場合
include項目を追加しなかった場合、tsファイルをコンパイルした時にエラーとなります。

```shell
% yarn tsc
yarn run v1.22.21
$ /Users/xxx/workspace/typescript-sample/node_modules/.bin/tsc
error TS6059: File '/Users/xxx/workspace/typescript-sample/build/resources/main/static/ts/sample.ts' is not under 'rootDir' '/Users/xxx/workspace/typescript-sample/src/main/resources/static/ts'. 'rootDir' is expected to contain all source files.
  The file is in the program because:
    Matched by default include pattern '**/*'


Found 1 error.

error Command failed with exit code 2.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
```

### コンパイル後のjsファイルの出力先
コンパイル後のjsファイルを配置するディレクトリを設定します。
outDir項目で指定できます。

```tsconfig.json
    // "outDir": "./",
    // ↓
    "outDir": "src/main/resources/static/js",
```

## 作成結果
TypeScriptの環境構築は以上です。
構築後は以下のようになります。

```
typescript-sample（SpringBootプロジェクト）
├── src/main
│   └── resources
│          └── static
│               ├── js（コンパイル後の出力先）
│               │
│               └── ts（tsファイルの配置先）
│
├── node_modules
├── package.json
├── tsconfig.json
└── yarn.lock
```

# TypeScriptでフロントエンド実装
TypeScriptで実装してみます。
今回は以下の2ファイルを実装します。
**sample.ts**
　→htmlから呼び出されるtsファイル
**sampleObj.ts**
　→sample.js内で利用するクラスを定義したjsファイル

## sample.ts
htmlから呼び出されるtsファイルを実装します。
後述のsampleObj.tsからSampleObjクラスをimportし、SampleObjを経由した文字列をアラートで表示します。

```sample.ts
import { SampleObj } from "./sampleObj.js";

	
var execSample = function () {
    const sample = SampleObj.createInstance("サンプルクラス名")
    alert(sample.getName());
}

execSample();
```

## sampleObj.ts
こちらではクラスを実装します。
コンストラクタで指定された文字列を保持し、出力するだけのメソッドを実装します。


```sampleObj.ts
class SampleObj {

    static createInstance(name: string) {
        return new SampleObj(name);
    }

    private name: string

    constructor(name: string) {
        this.name = name;
    }

    public getName() {
        return this.name;
    }
};

export { SampleObj };
```

## コンパイル
tsファイルを実装出来たら、いよいよコンパイルをしてみます。

```shell
% yarn tsc                                  
yarn run v1.22.21
$ /Users/xxx/workspace/typescript-sample/node_modules/.bin/tsc
✨  Done in 6.51s.
```

jsファイルが生成されていれば成功です。

## 作成結果
```
typescript-sample（SpringBootプロジェクト）
├── src/main
│   └── resources
│          └── static
│               ├── js（コンパイル後の出力先）
│               │    ├── sample.js
│               │    └── sampleObj.js
│               │
│               └── ts（tsファイルの配置先）
│                    ├── sample.ts
│                    └── sampleObj.ts
│
├── node_modules
├── package.json
├── tsconfig.json
└── yarn.lock
```

# 動作確認
最後にApplication.javaを実行し、SpringBootアプリを起動します。
ブラウザから http://localhost:8080/ にアクセスし、アラートで「サンプルクラス名」と表示されれば成功です。

# 参考文献
https://typescript-jp.gitbook.io/deep-dive/getting-started

https://typescript-jp.gitbook.io/deep-dive/project/compilation-context/files

https://typescript-jp.gitbook.io/deep-dive/styleguide

https://t-cr.jp/memo/29b17a4c47f78c28e

https://zenn.dev/junki555/articles/c21e6e74ea6ffcf074f8