たかぎとねこの忘備録

プログラミングに関する忘備録を自分用に残しときます。マサカリ怖い。

tscの代わりにBabelにトランスパイルをまかせてみた

モノリポでパッケージを作る際にtscの代わりにbabelを使ってみたかったので、これを機に色々調べてみた。

参考にしたのはこのサイト。

Using Babel with TypeScript | Learn TypeScript

Babelってなんだ

Babelは主にECMAScript 2015+のコードを、古いブラウザや環境での後方互換性をもったバージョンのJavaScriptに変換するために使用されるツール。

Babelがtscに代わって使われる理由はJSXをJavaScriptに変換できるから。 tscは変換できない。なのでReactをプロジェクトで使っている場合はBabelが必要になる。

必要なパッケージのインストール

とりあえず、TypeScriptやデコレーターだったりを使いたいので、それらをトランスパイルするのに必要なパッケージをまとめてみた。

  • @babel/core
    • Babelのコアとなるライブラリ
  • @babel/preset-env
    • 最新のJavaScriptの機能を使いつつ、それらをサポートしていないブラウザをターゲットにするためのプラグインの集まり。
  • @babel/preset-typescript
    • TypeScriptのコードをJavaScriptに変換するためのプログラインの集まり
  • @babel/cli
  • babel-plugin-transform-typescript-metadata
  • @babel/plugin-proposal-decorators
  • @babel/plugin-proposal-class-properties
    • 静的なクラスプロパティとプロパティ初期化構文で宣言されたプロパティを変換するプラグイン
  • babel-plugin-module-resolver
  • tsc-alias
yarn add -D @babel/core @babel/preset-env @babel/preset-typescript @babel/cli @babel/plugin-proposal-class-properties @babel/plugin-proposal-decorators babel-plugin-module-resolver babel-plugin-transform-typescript-metadata

設定ファイルを作成する

.babelrcというファイルをルートディレクトリに作成する。

中身は次のようにする。

{
  "presets": ["@babel/preset-env", "@babel/preset-typescript"],
  "plugins": [
    "babel-plugin-transform-typescript-metadata",
    ["@babel/plugin-proposal-decorators", { "version": "legacy" }],
    "@babel/plugin-proposal-class-properties",
    [
      "module-resolver",
      {
        "root": ["."],
        "alias": {
          "~": "./src"
        }
      }
    ],
  ]
}

babel-plugin-transform-typescript-metadata@babel/plugin-proposal-decoratorsよりも前に記述しないといけないので注意する。

トランスパイルはBabelに任せて、tscでは型チェックのみを行いたいので、noEmitfalseに、declarationtrueに、そしてemitDeclarationOnlytrueに設定する。

...
"compilerOptions": {
  ...
  "outDir": "dist",
  ...
  "noEmit": false, // 型定義ファイルは出力したいのでfalse 
  "declaration": true, // 型定義ファイルの生成を指示する
  "emitDeclarationOnly": true, // 型定義ファイルのみ生成する
  ...
},
...

outDirはBabelの出力先と一致させておく。ここではdistを設定する。

スクリプトを追加する

buildスクリプトとしてpackage.jsonに次のコマンドを追加する。

...
"scripts": {
  "build": "tsc -p tsconfig.json -d && tsc-alias -p tsconfig.json && npx babel ./src --extensions '.ts,.tsx' --out-dir ./dist",
  ...
}
...

--out-file dist/index.jsではなく--out-dir ./distを設定することに注意する。

もし--out-file dist/index.jsを指定してしまうと、./src/index.tsのみがトランスパイルされて、distディレクトリに出力される。たとえ./src/index.ts内で./src/classes/Sample.tsをインポートしていたとしてもdist/classesSample.jsが出力されることはない。

tsc-aliasを使わないと~/classes/...のようなエイリアスパスがそのまま型定義ファイルに出力されてしまう。なのでそれを防ぐためにtscによる型定義ファイルの出力後に、tsc-aliasを使って型定義ファイル中のエイリアスパスを相対パスに置き換える必要がある。

module-resolverの設定

tsconfig.jsonで次のようにエイリアスを設定している場合

...
"paths": {
  "~/*": ["./src/*"],
},
"baseUrl": ".",
...

module-resolverの設定は次のようにする。

...
[
  "module-resolver",
  {
    "root": ["."],
    "alias": {
      "~": "./src"
    }
  }
],
...

まとめ

Reactなどのフレームワークを触り始めたばかりのころに、WebpackやらBabelやらという当時の自分にはわからない単語が多すぎたせいで、なぜか今でもBabelを敬遠してしまっていた。

今回改めて自分で設定ファイル書いたりプラグイン追加してみたりしたけど、案外難しいことはなかった。

やっぱり自分で使ってみるのが一番ですね。

参考

Using Babel with TypeScript | Learn TypeScript

TypeScript: Documentation - Using Babel with TypeScript

TypeScript で型を検査する|【React/Redux】カンバンボードを実装して Web フロントエンド上級者を目指そう!|Techpit

GitHub - microsoft/tsyringe: Lightweight dependency injection container for JavaScript/TypeScript

GitHub - tleunen/babel-plugin-module-resolver: Custom module resolver plugin for Babel

Next.js + TypeScript + デコレータ + reflect-metadata を動かす

GitHub - justkey007/tsc-alias: Replace alias paths with relative paths after typescript compilation