TypeScript ことはじめ (Hello world から gulp で複数ファイルをコンパイルするまで)

## 概要

- TypeScript 公式サイトのチュートリアルをベースに Hello world 的なコードを試す
- TypeScript で書かれたコードをコンパイルして JavaScript ファイルに変換する
- gulp を利用して複数ファイルのコンパイルをする

## TypeScript とは

TypeScript は型指定が可能な JavaScript の上位互換プログラミング言語。

[TypeScript \- JavaScript that scales\.](https://www.typescriptlang.org/)

> TypeScript is a typed superset of JavaScript that compiles to plain JavaScript.

[TypeScript \- Wikipedia](https://ja.wikipedia.org/wiki/TypeScript)

> TypeScript はマイクロソフトによって開発され、メンテナンスされているフリーでオープンソースのプログラミング言語である。TypeScriptはJavaScriptに対して、省略も可能な静的型付けとクラスベースオブジェクト指向を加えた厳密なスーパーセットとなっている。

## tsc とは

tsc はコマンドラインで使える TypeScript コンパイラ。
TypeScript で書かれたソースコードを JavaScript に変換することができる。

[TypeScript \- JavaScript that scales\.](https://www.typescriptlang.org/)

> The command-line TypeScript compiler can be installed as a Node.js package.

## TypeScript のインストール

Node.js の npm を利用して TypeScript をインストールする。

今回の Node.js と npm のバージョンを確認。

```
$ node --version
v12.7.0

$ npm --version
6.10.0
```

TypeScript をインストール。
今回のバージョンは 3.5.3 になる。

```
$ npm install -g typescript

$ tsc --version
Version 3.5.3
```

## Hello, world を書く

TypeScript のコードは 拡張子が ts のファイルに書く。
hello.ts というファイルを作成する。

```ts
console.log("Hello, world");
```

tsc コマンドで ts ファイルをコンパイルできる。
コンパイルすると js ファイルが出力される。

```
$ tsc hello.ts
```

hello.js ファイルの中身を確認する。
特に TypeScript らしいコードを書かなかったので中身が hello.ts とまったく同じ・・・

```
console.log("Hello, world");
```

Node.js で js ファイルを実行すると Hello, world が出力される。

```
$ node hello.js
Hello, world
```

## Type annotations (型注釈) を使う

関数の引数が string という型である、ということを指定する。
JavaScript ではできなかったことだが、TypeScript ではできる。
これによって、string 以外の型が来るようなコードを書いたときにコンパイルエラーを出してくれる。

greeter.ts というファイルを作成。

```ts
function greeter(person: string) {
  return "Hello, " + person;
}

let user = "Jane User";

console.log(greeter(user));
```

tsc コマンドで ts ファイルをコンパイルして js ファイルを出力する。

```
$ tsc greeter.ts 
```

出力された greeter.js の中身を確認する。

```js
function greeter(person) {
    return "Hello, " + person;
}
var user = "Jane User";
console.log(greeter(user));
```

Node.js で js ファイルを実行できる。

```
$ node greeter.js 
Hello, Jane User
```

試しに、string 型が必要なところへ数値の配列を渡すように greeter.ts を修正してみる。

```ts
function greeter(person: string) {
  return "Hello, " + person;
}

let user = [0, 1, 2];

console.log(greeter(user));
```

コンパイルしようとするとエラーが出る。

```
$ tsc greeter.ts 
greeter.ts:7:21 - error TS2345: Argument of type 'number[]' is not assignable to parameter of type 'string'.

7 console.log(greeter(user));
                      ~~~~


Found 1 error.
```

これは string 型が必要なところへ、数値の配列が来たため。
JavaScript と違って TypeScript ではこのようなチェックができるようになる。

## interface を使う

TypeScript では、内部構造に互換性がある場合、2つの型は互換性がある。
Person インターフェースを定義しておいて、同じ構造の JavaScript オブジェクトを作って試す。

```ts
interface Person {
  firstName: string;
  lastName: string;
}

function greeter(person: Person) {
  return "Hello, " + person.firstName + " " + person.lastName;
}

let user = {firstName: "Jane", lastName: "User"};

console.log(greeter(user));
```

tsc コマンドで ts ファイルをコンパイルして js ファイルを出力する。

```js
function greeter(person) {
    return "Hello, " + person.firstName + " " + person.lastName;
}
var user = { firstName: "Jane", lastName: "User" };
console.log(greeter(user));
```

Node.js で js ファイルを実行できる。

```
$ node greeter.js 
Hello, Jane User
```

## class を使う

TypeScript ではクラスベースのオブジェクト指向プログラミングが可能。

コンストラクタの引数に public をつけると、自動的にその名前のプロパティが生成される。

また、今回は Person インターフェースと Student クラスで同じ構造の firstName プロパティと lastname プロパティを持っている。そのため、 Student クラスのインスタンスを Person のインスタンスとして扱うことが可能になる。

```ts
interface Person {
  firstName: string;
  lastName: string;
}

class Student {
  fullName: string;
  constructor(public firstName: string, public middleInitial: string, public lastName: string) {
    this.fullName = firstName + " " + middleInitial + " " + lastName;
  }
}

function greeter(person: Person) {
  return "Hello, " + person.firstName + " " + person.lastName;
}

function output(student: Student) {
  console.log("Full name: " + student.fullName);
  console.log("First name: " + student.firstName);
  console.log("Middle initial: " + student.middleInitial);
  console.log("Last name: " + student.lastName);
}

let student = new Student("Jane", "M.", "User");

console.log(greeter(student));

output(student);
```

tsc コマンドで ts ファイルをコンパイルして js ファイルを出力する。

```js
var Student = /** @class */ (function () {
    function Student(firstName, middleInitial, lastName) {
        this.firstName = firstName;
        this.middleInitial = middleInitial;
        this.lastName = lastName;
        this.fullName = firstName + " " + middleInitial + " " + lastName;
    }
    return Student;
}());
function greeter(person) {
    return "Hello, " + person.firstName + " " + person.lastName;
}
function output(student) {
    console.log("Full name: " + student.fullName);
    console.log("First name: " + student.firstName);
    console.log("Middle initial: " + student.middleInitial);
    console.log("Last name: " + student.lastName);
}
var student = new Student("Jane", "M.", "User");
console.log(greeter(student));
output(student);
```

Node.js で js ファイルを実行できる。

```
$ node greeter.js 
Hello, Jane User
Full name: Jane M. User
First name: Jane
Middle initial: M.
Last name: User
```

## gulp で複数の ts ファイルをビルドする

タスクランナーの gulp を使用して複数ファイルをビルドする。

### ファイル一覧

最終的に用意するファイル一覧 (dist と src はディレクトリ)。

```
├── dist
├── gulpfile.js
├── package.json
├── src
│   ├── greet.ts
│   └── main.ts
└── tsconfig.json
```

### package.json

まずは package.json を用意する。
npm init などで生成しても良い。

```json
{
  "name": "greet",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}
```

### gulp-cli をインストールする

コマンドラインで使うための gulp を npm install でグローバルにインストールする。

```
$ npm install -g gulp-cli

$ gulp --version
CLI version: 2.2.0
Local version: Unknown
```

### gulp-typescript などをインストールする

gulp で TypeScript のソースコードをコンパイルするために、必要なパッケージをインストールして package.json に反映する。

```
$ npm install --save-dev typescript gulp@4.0.0 gulp-typescript
```

package.json の devDependencies に gulp-typescript (TypeScript 用の gulp プラグイン) などのパッケージが追加される。

```json
{
  "name": "greet",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "gulp": "^4.0.0",
    "gulp-typescript": "^5.0.1",
    "typescript": "^3.5.3"
  }
}
```

### tsconfig.json

tsconfig.json ファイルを用意する。
tsconfig.json は TypeScript プロジェクトのルートに置く設定ファイル。
コンパイルする ts ファイルやコンパイラオプションを指定できる。

今回は src ディレクトリ以下に main.ts と greet.ts を置く構成にする。

```json
{
  "files": [
    "src/main.ts",
    "src/greet.ts"
  ],
  "compilerOptions": {
    "noImplicitAny": true,
    "target": "es5"
  }
}
```

### gulpfile.js

gulpfile.js ファイルを用意して、実行するタスクをこのファイルに書く。。
gulp を実行すると gulpfile.js が読み込まれてタスクが実行される。

```js
var gulp = require('gulp');
var ts = require('gulp-typescript');
var tsProject = ts.createProject('tsconfig.json');

gulp.task('default', function () {
  return tsProject.src()
    .pipe(tsProject())
    .js.pipe(gulp.dest('dist'));
});
```

### *.ts

TypeScript のソースコードファイルを2つ用意する。

src/greet.ts は呼び出される側のソースコード。

```ts
export function sayHello(name: string) {
  return `Hello, ${name}`;
}
```

src/main.ts は greet.ts を呼び出す方のソースコード。

```ts
import { sayHello } from './greet';

function showHello(name: string) {
  console.log(sayHello(name));
}

showHello('John Doe');
```

### gulp を実行して js ファイルを出力する

gulp を実行して ts ファイルをコンパイルすると js ファイルが出力される。

```
$ gulp
[12:34:56] Using gulpfile ~/hello/gulpfile.js
[12:34:56] Starting 'default'...
[12:34:57] Finished 'default' after 1.6 s
```

dist/greet.js の中身を確認する。

```js
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
function sayHello(name) {
    return "Hello, " + name;
}
exports.sayHello = sayHello;
```

dist/main.js の中身を確認する。

```js
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var greet_1 = require("./greet");
function showHello(name) {
    console.log(greet_1.sayHello(name));
}
showHello('John Doe');
```

Node.js で js ファイルを実行できる。

```
$ node dist/main.js 
Hello, John Doe
```

## 参考資料
- [TypeScript in 5 minutes · TypeScript](https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes.html)
- [Gulp · TypeScript](https://www.typescriptlang.org/docs/handbook/gulp.html)
- [Functions · TypeScript](https://www.typescriptlang.org/docs/handbook/functions.html)
- [Type Compatibility · TypeScript](https://www.typescriptlang.org/docs/handbook/type-compatibility.html)
- [Interfaces · TypeScript](https://www.typescriptlang.org/docs/handbook/interfaces.html)
- [Classes · TypeScript](https://www.typescriptlang.org/docs/handbook/classes.html)
- [tsconfig\.json · TypeScript](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html)
- [JavaScript and Gulpfiles · gulp\.js](https://gulpjs.com/docs/en/getting-started/javascript-and-gulpfiles)
- [gulp\-typescript \- npm](https://www.npmjs.com/package/gulp-typescript)