じぶんメモ

プログラミングのメモ、日常のメモとか。

typescriptのビルド設定

はじめに

babel7がリリースされましたね! それに伴いtypescriptもサポートされたのでbabel単体でtsのビルドができるようになりました。
しかしbabelはtypescriptから型情報を取り除くだけなので型チェックまではサポートしておらず、
型チェックに関してはtsc経由で行う必要あります。
今回はwebpack + babel + tsc + tslintでtypescriptのビルド+lint+型チェックを行えるようにしたいと思います。

※ソースはこちらです https://github.com/tmzkysk/hello_typescript

必要なmoduleをインストール

まずは適当に作業ディレクトリを作り、 npm(yarn) initでプロジェクトを作成してください。 ※yarnを入れていない人は随時npmで置き換えてください。

$ mkdir hello_typescript && cd hello_typescript
$ yarn init

package.jsonが作成されたら必要なmoduleをインストールしていきます。

$ yarn add typescript webpack webpack-cli webpack-dev-server
$ yarn add -D @babel/core @babel/preset-env babel-loader ts-loader tslint tslint-loader prettier tslint-config-prettier tslint-config-standard tslint-plugin-prettier

ひとつずつ解説すると、

  • webpack:言わずもがな。ファイルバンドリングツール。
  • webpack-cli: webpackをcli上で使用できるようにする。
  • webpack-dev-server:開発時にtsの変更をwatchして自動的にビルドする
  • @babel/core:ES6 -> ES5を行うトランスパイラ
  • @babel/preset-env:サポートされている環境に基づいて必要なBabelプラグインを自動で決定するライブラリ
  • babel-loader:webpack上でbabelを使用するために必要
  • ts-loader:webpack上でtypescriptのビルドをするのに必要
  • tslint:typescriptの構文チェッカー(型チェックはやらないよ)
  • prettier:コードフォーマッター
  • tslint-config-prettier:TSLintの設定のうちPrettierと衝突するものを無効化してくれるパッケージ
  • tslint-plugin-prettier:TSLintのチェックをかけるときに一緒にPrettierをながしてくれる
  • tslint-config-standard:こちらの内容にそってTSLintのチェックをするときに必要
  • tslint-loader:webpackでTSLintを使用するのに必要

結構多い。。。 TSLintとprettierの違いはこちらの記事が参考になります。 https://qiita.com/soarflat/items/06377f3b96964964a65d

typescript側の設定

tsconfig.jsonの作成

typescriptのビルドやチェックのルールを設定するファイルです。 以下のコマンドでディレクトリに作成されます。

$ yarn tsc --init

tsconfig.jsonの設定

configに設定できる項目はたくさんありますが、とりあえずこれらを設定しておけば良いと思います。 型チェックや不要な変数のチェックなどはtsconfig側の設定になります。

{
  "compilerOptions": {
    /* Basic Options */
    "target": "ESNEXT",                    /* tsを書くときにどのversionのESが対象か: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */
    "module": "ESNext",                    /* どのversionのESを生成するか: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
    "jsx": "preserve",                     /* reactを書くときに必要, 'react-native', or 'react'. */
    // "outFile": "./",                    /* 出力するファイル(ファイル出力はwebpackで行うので不要) */
    // "outDir": "./",                     /* 出力するディレクトリ(ファイル出力はwebpackで行うので不要) */
    // "rootDir": "./",                    /* ビルド時のルートディレクトリ(ビルド対象のパス設定はwebpackで行うので不要) */
    // "noEmit": "true",                   /* ビルド時に型ファイルを生成しない(これがあるとts-loaderでエラーが出るので外しています */

    /* Strict Type-Checking Options */
    "strict": true,                        /* すべての strict タイプ・オプション(noImplicitAny, strictNullChecks, noImplicitThis(thisの型チェック), alwaysStrict(strictモードのjs出力)) を 有効化する */
    "noImplicitAny": true,                 /* any型の使用不可 */
    "strictNullChecks": true,              /* nullable型以外でnullを許容しない */

    /* Additional Checks */
    "noUnusedLocals": true,                /* 未使用の変数を許容しない */
    "noUnusedParameters": true,            /* 未使用の変数を許容しない */
    "noImplicitReturns": true,             /* メソッド内で返り値の型があっているかをチェック */

    /* Module Resolution Options */
    "moduleResolution": "node",            /* http://js.studio-kingdom.com/typescript/handbook/module_resolution 参照 */
    "esModuleInterop": true                /* ESModuleと同じ動作をする. */
  }
}

babelの設定

まずは.babelrcを作成します。

$ touch .babelrc

中身はこのような感じです。 (react開発をする場合は@babel/preset-reactが必要です)

{
  "presets": [
    "@babel/preset-env"
  ]
}

tslintの設定

まずは設定ファイルを作ります。

$ touch tslint.json

中身はこのような感じ。 (reactの文法もチェックする場合はtslint-reactが必要です)

{
  "rulesDirectory": [
    "tslint-plugin-prettier"
  ],
  "extends": [
    "tslint-config-standard",
    "tslint-config-prettier"
  ],
  "rules": {
    "prettier": [
      true,
      {
        "singleQuote": true, // 文字列はシングルクオートで囲む
        "semi": false         // 文末のセミコロン(;)は省く。prettierのデフォルトはtrue
      }
    ]
  }
}

webpackの設定

まずは以下のコマンドでファイルを作成します。

$ touch webpack.config.js
module.exports = {
  mode: process.env.NODE_ENV || "development",
  entry: "./src/index.ts",
  output: {
    filename: "bundle.js",
    path: __dirname + "/dist"
  },
  resolve: {
    extensions: [".ts", ".tsx", ".js", ".json"]
  },
  module: {
    rules: [
      {
        test: /\.ts?$/,
        use: [
          // 下から順に処理される
          { loader: "babel-loader" },
          { loader: "ts-loader" },
          {
            loader: 'tslint-loader',
            options: {
              typeCheck: true,
              // tslint時に自動的に修正しない
              fix: false,
              // warningをエラーにすることでその後のビルドを止める
              emitErrors: true
            },
          },
        ],
        exclude: /node_modules/
      }
    ]
  },
};

今回はsrc/index.tsをビルドし、dist/bundle.jsとして出力する前提で記載しています。 複数ファイルの場合は配列やhashでの設定も可能です。 注意する点としてはloaderは下から上の順に実行されるので、

1.tslint-loaderでlintチェック 2.ts-loaderでtypescript -> ES6へ変換 3.babel-loaderでES6 -> ES5へ変換

というようになっています。

ビルド用のtypescriptファイル準備

試しにビルドする用のtypescriptを用意しましょう。 今回はsrc/index.tsを用意し、dist/bundle.jsとして出力したいので準備をします。

$ mkdir src dist
$ touch src/index.ts

中身は適当なtypescriptにしましょう。

const hoge: string = 'test'
console.log(hoge)

package.jsonへscript設定

あとはこれらを実行するのですが、package.jsonのscript項目に記載しておけば、 npm(yarn) run [タスク名] で実行可能です。

{
  "name": "hello_typescript",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "dependencies": {
    "typescript": "^3.1.1",
    "webpack": "^4.20.2",
    "webpack-dev-server": "^3.1.9"
  },
  "scripts": {
    "build": "webpack",
    "watch": "webpack-dev-server",
    "lint" : "yarn run tsc && yarn run tslint",
    "tslint": "tslint --project tsconfig.json"
    "tslint-fix": "tslint --fix --project tsconfig.json"
  },
  "devDependencies": {
    "@babel/core": "^7.1.2",
    "@babel/preset-env": "^7.1.0",
    "babel-loader": "^8.0.4",
    "prettier": "^1.14.3",
    "ts-loader": "^5.2.1",
    "tslint": "^5.11.0",
    "tslint-config-prettier": "^1.15.0",
    "tslint-config-standard": "^8.0.1",
    "tslint-loader": "^3.6.0",
    "tslint-plugin-prettier": "^2.0.0",
    "webpack-cli": "^3.1.2"
  }
}

実行

ようやくビルドできるようになりました。 実行してみましょう。

普通にビルド

$ yarn run build
yarn run v1.5.1
$ webpack
Hash: 8732ceeed4c190294712
Version: webpack 4.20.2
Time: 7067ms
Built at: 2018-10-05 22:46:29
    Asset      Size  Chunks             Chunk Names
bundle.js  3.84 KiB    main  [emitted]  main
Entrypoint main = bundle.js
[./src/index.ts] 52 bytes {main} [built]
✨  Done in 8.76s.

できました! dist/bundle.jsが出来上がっていると思います。

lintは動くか

では、src/index.tsを以下のように書き換えてビルドするとどうなるか・・

const hoge: string="test";
console.log(hoge)
$ yarn run build
yarn run v1.5.1
$ webpack
Hash: 8f442c0fec1e406e6b3d
Version: webpack 4.20.2
Time: 3450ms
Built at: 2018-10-05 22:52:04
    Asset      Size  Chunks             Chunk Names
bundle.js  3.85 KiB    main  [emitted]  main
Entrypoint main = bundle.js
[./src/index.ts] 52 bytes {main} [built] [1 error]

ERROR in ./src/index.ts
Module Error (from ./node_modules/tslint-loader/index.js):
[1, 19]: Replace `="test";` with `·=·'test'`

error An unexpected error occurred: "Command failed.
Exit code: 2
Command: sh
Arguments: -c webpack
Directory: /Users/tmzkysk/hello_typescript
Output:
".

ちゃんとエラーになりました。 こういうときはyarn run tslint-fixとすれば直ります。

$ yarn run tslint-fix
yarn run v1.5.1
$ tslint --fix --project tsconfig.json
Fixed 1 error(s) in /Users/tmzkysk/hello_typescript/src/index.ts

中身を見るとしっかり直ってます。 なお、webpack.config.jsのtslint-loaderのoptionのfixをtrueにすればビルド時に勝手に直してくれます。

$ cat src/index.ts
const hoge: string = 'test'
console.log(hoge)

型チェックとかはどうか

次にtypescriptのコンパイラ側のチェックがちゃんと動くか確認します。 index.tsを以下のようにしてビルドしてみます。

const hoge: number ="test"
console.log(hoge)
$ yarn run build
yarn run v1.5.1
$ webpack
Hash: 8732ceeed4c190294712
Version: webpack 4.20.2
Time: 3322ms
Built at: 2018-10-05 22:58:50
    Asset      Size  Chunks             Chunk Names
bundle.js  3.84 KiB    main  [emitted]  main
Entrypoint main = bundle.js
[./src/index.ts] 52 bytes {main} [built] [1 error]

ERROR in /Users/tmzkysk/hello_typescript/src/index.ts
./src/index.ts
[tsl] ERROR in /Users/hello_typescript/src/index.ts(1,7)
      TS2322: Type '"test"' is not assignable to type 'number'.
error An unexpected error occurred: "Command failed.
Exit code: 2
Command: sh
Arguments: -c webpack
Directory: /Users/tmzkysk/hello_typescript

number型にstring型を代入しているためしっかりエラーになりました。

その他

package.jsonに設定した以下の内容について解説します。

  "scripts": {
    "build": "webpack",
    "watch": "webpack-dev-server",
    "lint" : "yarn run tsc && yarn run tslint",
    "tslint": "tslint --project tsconfig.json"
    "tslint-fix": "tslint --fix --project tsconfig.json"
  },
  • build: webpack.config.jsの設定に沿ってビルドします。
  • watch: webpack.config.jsの設定に沿ってビルドします。ファイルの変更を監視するのでtsファイル変更時に再度ビルドする必要がありません。
  • lint: typescript側のビルド時のチェックとTSLintのチェックをかけます。
  • tslint: TSLintのチェックのみかけます。
  • tslint-fix: TSLintのエラーを修正してくれます。

webpack-dev-serverについて

基本的に開発時はファイル変更と同時にビルドが走って欲しいので、
webpack-dev-serverを使用することになります。
webpack-dev-serverはwebpack-cliと違い、
何も設定していなければ localhost:8080 にビルド後のassetを吐き出すので、
railsでwebpackでビルドしたassetを参照する場合はlocalhost:8080から読み込むようにしてください。

おわりに

@babel/preset-typescritpがtsconfigの内容を踏襲してくれれば楽でしたが、
現状型チェックは提供されていないのでts-loaderをかませるようにしています。
今後のbabelに期待!