TypeScript プロジェクト用サンドボックスで簡単コード検証

TypeScript を使うと JavaScript のコードに型を導入することができ、コードの安全性や生産性を高めることができます。いっぽうで TypeScript は JavaScript にコンパイルしてから実行する必要があり、ちょっとしたコードの確認をするにしても一手間かかります。
そこで検証を簡単にするため、プロジェクト内のファイル変更を監視し、ファイルの変更があったら自動的に再コンパイル&実行してくれる環境を作ります。

TypeScript では、ちょっとしたコードを試したいときでもtscでコンパイルしてnodeで実行してと、コンソールでの作業が必要となります。コードの動きを試したかったり、ネットで見つけたおもしろいコードを動かしたいといっただけでも手間がかかります。
今回は TypeScript 用のサンドボックス・プロジェクトを用意して、手軽にコードを試せるようにします。(サンドボックスというとセキュリティモデル的な感じもしますが、今回はお砂場遊び的な感覚。最近はプレイグラウンドと言う?)

環境
本記事の開発環境は以下となります。

  • Windows 10 64bit + WSL Ubuntu 18.04.1 LTS
  • Visual Studio Code
  • Node.js 12.2.0
  • Yarn 1.15.2
  • TypeScript 3.4.5

TypeScript プロジェクトの作成

いろいろなモジュールが組み合わさるので、順を追って作っていきます。
まずは基本となる TypeScript プロジェクトを作成します。

プロジェクトのディレクトリを作成し/package.jsonファイルを作ります。(今回はsandboxという名前のディレクトリ)

  • devDependenciesは、常に最新版のモジュールを使いたいためにバージョンは"*"としています。
  • scripts: cleanは、最新のモジュールを使いやすいようにnode_modulesなどを削除します。OS に依存しないようnpx rimrafで削除しています。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    {
    "name": "sandbox",
    "private": true,
    "dependencies": {},
    "devDependencies": {
    "@types/node": "*",
    "typescript": "*"
    },
    "scripts": {
    "clean": "npx rimraf yarn.lock node_modules dist",
    "setup": "yarn clean && yarn install --ignore-optional"
    }
    }

同じくプロジェクト直下にtsconfig.jsonを作ります。
こちらは普段使う TypeScript の設定に合わせておくとよいでしょう。今回はesnextで最新仕様を使い、"strict": trueでチェックを厳しくしています。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
{
"compilerOptions": {

/* Basic Options */
"target": "esnext", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */
"module": "esnext", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
"outDir": "./dist", /* Redirect output structure to the directory. */
"removeComments": true, /* Do not emit comments to output. */

/* Strict Type-Checking Options */
"strict": true, /* Enable all strict type-checking options. */
"noUnusedLocals": true, /* Report errors on unused locals. */
"noUnusedParameters": true, /* Report errors on unused parameters. */
"noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
"noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */

/* Module Resolution Options */
"moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
"baseUrl": "./", /* Base directory to resolve non-absolute module names. */
"paths": { "~/*": [ "./src/*" ] }, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */

/* Experimental Options */
"experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
"emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */

/* Additional Options */
"forceConsistentCasingInFileNames": true, /* Disallow inconsistently-cased references to the same file. */
"newLine": "LF", /* Use the specified end of line sequence to be used when emitting files: "crlf" (windows) or "lf" (unix). */
"resolveJsonModule": true, /* Allows for importing, extracting types from and generating .json files. */

/* Specify library files to be included in the compilation. */
"lib": [
"esnext"
]
},
"include": [ "./src/**/*.ts", "./test/**/*.ts" ]
}

これで/srcディレクトリ以下の TypeScript ファイルをコンパイルして実行する環境ができました。
/src/index.ts

1
console.debug('Hello World !');

コンパイルして実行。

1
2
3
$ yarn tsc -p .
$ node dist/index.js
Hello World !

ts-node で TypeScript を直接実行

コンパイルの手間をなくして、TypeScript をコンソールから直接実行できるようにします。

1
$ yarn add -D ts-node@* tsconfig-paths@*

/package.jsonに以下のstartスクリプトを追加します。(scriptsのみ抜粋)

  • ts-nodesrc/index.tsを実行。(以降、プログラム実行の起点はindex.tsになります)
  • TypeScript の alias path をts-nodeで使えるように-r tsconfig-paths/registerを登録します。
    1
    2
    3
    4
    5
    6
    7
    {
    "scripts": {
    "clean": "npx rimraf yarn.lock node_modules dist",
    "setup": "yarn clean && yarn install --ignore-optional",
    "start": "ts-node -r tsconfig-paths/register src/index.ts"
    }
    }

実行!新たに1行追加しましたがtscなしで動作します。また先ほど作られたdistディレクトリは削除して大丈夫です。
/src/index.ts

1
2
console.debug('Hello World !');
console.debug('Hello TypeScript !!');
1
2
3
$ yarn start
Hello World !
Hello TypeScript !!

nodemon でファイルの変更を検知して TypeScript を直接実行

さらにファイルの変更を検出して、自動的にts-nodeを実行できるようにします。

1
$ yarn add -D nodemon@*

/package.jsonstartスクリプトを修正します。(scriptsのみ抜粋)

  • nodemon -w srcで、nodemonから起動しsrcディレクトリを監視します
  • -x ts-nodeで、nodemonから実行するプログラムts-nodeを指定しています
    1
    2
    3
    4
    5
    6
    7
    {
    "scripts": {
    "clean": "npx rimraf yarn.lock node_modules dist",
    "setup": "yarn clean && yarn install --ignore-optional",
    "start": "nodemon -w src -x ts-node -r tsconfig-paths/register src/index.ts"
    }
    }

実行!今回はyarn startから先に始めます。
先ほどまでの Hello World, TypeScript がコンソールに表示され、その後にnodemonが変更待ちをしている状態になります。

1
2
3
4
5
$ yarn start
...(省略)
Hello World !
Hello TypeScript !!
[nodemon] clean exit - waiting for changes before restart

ここで/src/index.tsを変更し、保存します。

1
2
3
console.debug('Hello World !');
console.debug('Hello TypeScript !!');
console.debug('Hello Sandbox Project !!!');

するとファイルの変更を検知して、コンソールに新しい結果が自動的に出力されます。

1
2
3
4
5
6
...(省略)
[nodemon] restarting due to changes...
[nodemon] starting `ts-node -r tsconfig-paths/register -r dotenv/config src/index.ts`
Hello World !
Hello TypeScript !!
Hello Sandbox Project !!!

コードの検証で利用

実際にコードの検証で使ってみます。

最近気になったのが、こちらソート可能なUUID互換のulidが便利そう - Qiitaの記事。
よく UUID v4 でランダムな ID 発行を使っています。それに対してソート可能なランダムな ID 発行できるというのは興味深いです。記事は Python ですが Node.js/TypeScript にもulid/javascriptモジュールがあります。

モジュールを導入し、サンドボックス・プロジェクトを起動しておきます。

1
2
3
$ yarn add uuid ulid
$ yarn add -D @types/uuid
$ yarn start

単なるコンソール出力ですがコードを書いて保存するだけで結果が見れます。

1
2
3
4
5
import { ulid } from 'ulid';
import * as uuid from 'uuid';

console.debug(`ULID: ${ ulid() }`);
console.debug(`UUID: ${ uuid.v4() }`);

ランダムな値を生成するので保存するたびに変わるのがわかります。
“また UUID との 128 ビット互換性” ですが、表現方法は異なるようです。ソートの必要があるかはシステム次第ですが、文字数が詰まるのは良さそうです。また TypeScript 対応されているのも Good!

1
2
ULID: 01DB2S8RCY1EM7XBJG6P6A2KFE
UUID: 67d6e72b-97d9-4a65-ac92-35c476e02f7b

もうひとつ、こちら日付時刻操作ライブラリをmomentからdayjsへ乗り換えた - Qiitaの記事。

普段、日時を扱う場合には Moment.js を使っていますが、もっと軽量のiamkun/dayjsがあるとのこと。
サーバサイドの開発では軽量に越したことはありませんがシビアに考えることは少ないですが、フロントでは大事なポイントです。

さっそくモジュールを導入し、サンドボックス・プロジェクトを起動しておきます。

1
2
$ yarn add dayjs moment
$ yarn start

ここではコードをまとめて書いていますが、保存しながら書き足している感じで作業しています。コーディングしながらCtrl + Sして、コンソールが流れている間に次のコードといった感じです。
デフォルトのタイムゾーンとフォーマットが異なるものの、通常は ISO 文字列で使ったり、フォーマットのテンプレートを指定したりするので問題はないでしょう。2KB の軽さは魅力的です。

1
2
3
4
5
6
7
8
9
10
11
import dayjs from 'dayjs';
import moment from 'moment';

console.debug(`Day.js: ${ dayjs() }`);
console.debug(`Moment.js: ${ moment() }`);

console.debug(`Day.js: ${ dayjs().unix() }`);
console.debug(`Moment.js: ${ moment().unix() }`);

console.debug(`Day.js: ${ dayjs().toISOString() }`);
console.debug(`Moment.js: ${ moment().toISOString() }`);

徐々にコンソール出力が増えていくのを確認しながら、動きの差異などを確認しています。

1
2
3
4
5
6
Day.js:    Fri, 17 May 2019 13:52:02 GMT
Moment.js: Fri May 17 2019 22:52:02 GMT+0900
Day.js: 1558101122
Moment.js: 1558101122
Day.js: 2019-05-17T13:52:02.637Z
Moment.js: 2019-05-17T13:52:02.637Z

以上、ちょっとしたコードの検証に使えるサンドボックス、お砂場プロジェクトでした。

このようなプロジェクトを1つ作っておくと、TypeScript で型を調べたり、新しいライブラリの使い方を確認したりといった際に便利です。

nodemon まで入ったところで Git にコミットしておくと、散らかってもクリーンな環境へ簡単に戻せます。
私は AWS Lambda で環境変数を使うことが多いので、さらにmotdotla/dotenvも追加しています。また、TSLint もかけています。また Strict なコーディングの確認や練習のためにTSLintも設定しています。(ESLint へ引っ越さないと💦)
ソースはこちらlulzneko/sandbox