【React + TypeScript + Pug】JSXをPugに置き換える際のワークアラウンド

# 概要
Pugを覚えてReactコンポーネント内のJSXをPugに置き換えようと思い立ったが、TypeScriptとの連携でつまづいた。最終的にワークアラウンドにて対処したため、仔細を覚書として書き残す。

# 再現
[再現用のリポジトリ](https://github.com/takuyahara/pug-in-react-ts)をcloneして `yarn start` を実行すると、以下のようなエラー画面が表示される。

<img width="1016" alt="Screen Shot 2019-07-05 at 3.47.43.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/286440/24e229a3-ba6d-cd0f-f1e3-33f219bb7ca9.png">

# 検証
エラーメッセージ `ReferenceError: React is not defined` の詳細を調べるため、トランスパイル後のソースコードを確認する。なお、先述のリポジトリは[CRA@3.0.1](https://github.com/facebook/create-react-app/releases/tag/v3.0.1)で作成したテンプレートをejectしたものを基にしている。

## 設定、および検証手順

<br />package.jsonにおける検証用の設定は以下の通りである。

```json:package.json
{
  "dependencies": {
    "@babel/cli": "^7.4.4",
    "babel-plugin-transform-react-jsx": "^6.24.1",
    "babel-plugin-transform-react-pug": "^7.0.1",
    "pug": "^2.0.4"
  },
  "scripts": {
    "transpile": "babel src/App.tsx --out-file App.js"
  },
  "eslintConfig": {
    "extends": [
      "react-app",
      "plugin:react-pug/all"
    ],
    "plugins": [
      "react-pug"
    ]
  },
  "babel": {
    "presets": [
      "@babel/preset-typescript"
    ],
    "plugins": [
      "transform-react-pug",
      "transform-react-jsx"
    ]
  }
}
```

<br />トランスパイル前のソースコードは以下の通り、JSXをPugに書き直しただけのものである。

```typescript:src/App.tsx
import React from 'react';
import logo from './logo.svg';
import './App.css';

const App: React.FC = () => {
  return pug`
    div.App
      header.App-header
        img.App-logo(
          src=logo
          alt="logo"
        )
        p
          | Edit 
          code src/App.js
          |  and save to reload.

        a.App-link(
          href="https://reactjs.org" 
          target="_blank" 
          rel="noopener noreferrer"
        ) Learn React
  `;
}

export default App;
```

<br />以下のコマンドにて、トランスパイルを実行する。

```bash
$ yarn transpile
```

## 結果
ファイル冒頭にて、`React` と `logo` に対するimport文が存在しないことに注目する。宣言されていない変数の関数を呼び出そうとしたため、`ReferenceError: React is not defined` が引き起こされている。

![Screen Shot 2019-07-05 at 3.46.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/286440/5e63e814-54f8-8555-91f5-324ca73641ef.png)


# 原因
[@babel/plugin-transform-typescript removes regular imports #9723](https://github.com/babel/babel/issues/9723#issuecomment-475018627)によると、TypeScriptはトランスパイルする際に使用されていないimport文を削除する仕様となっている。そしてBabelプラグインの[@babel/plugin-transform-typescript](https://babeljs.io/docs/en/babel-plugin-transform-typescript)もその仕様を踏襲している。

今回の例では、JSXがPugに置き換わったことで `React` が使われなくなったため、これに対するimport文が削除された。また、`logo` はPugの中に存在していることが認識されていないため、同様に使われていないものとしてimport文が削除されている。

# 対策（ワークアラウンド）
使われていないimport文を削除しないよう、Babelプラグインにおける当該のコードを以下のようにコメントアウトする。

```javascript:node_modules/@babel/plugin-transform-typescript/lib/index.js
        // for (const stmt of path.get("body")) {
        //   if (_core().types.isImportDeclaration(stmt)) {
        //     if (stmt.node.specifiers.length === 0) {
        //       continue;
        //     }

        //     let allElided = true;
        //     const importsToRemove = [];

        //     for (const specifier of stmt.node.specifiers) {
        //       const binding = stmt.scope.getBinding(specifier.local.name);

        //       if (binding && isImportTypeOnly(file, binding, state.programPath)) {
        //         importsToRemove.push(binding.path);
        //       } else {
        //         allElided = false;
        //       }
        //     }

        //     if (allElided) {
        //       stmt.remove();
        //     } else {
        //       for (const importPath of importsToRemove) {
        //         importPath.remove();
        //       }
        //     }
        //   }
        // }
```

## 検証
再度トランスパイルを行うと、`React` と `logo` に対するimport文が残っていることが確認できる。この状態で `yarn start` を実行すると、ページが正常に表示される。

![Screen Shot 2019-07-05 at 3.50.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/286440/4ad41278-2905-4d1b-10e7-11adda4e8c0d.png)


# 考察
ワークアラウンドによって辛くもReact、TypeScript、Pugを連携させたが、不要なimport文を削除してくれる恩恵は失われてしまった。これを失わずに済む方法を考察したい。

BabelのプラグインはほとんどがJavaScript向けに書かれているため、TypeScriptからJavaScriptへの変換は最初に行われなければならない。ここでimport文の削除はして欲しくないため、前述のプラグインを「import文の削除をする機能」と「それ以外の機能」に**分割**し、Webpackにて

import文の削除**以外**の処理 ⇨ PugをJSXに変換 ⇨ import文の削除

という流れでトランスパイルを行うと、各ツールの恩恵を失うことなく連携が出来るものと推察される。