概要
Pugを覚えてReactコンポーネント内のJSXをPugに置き換えようと思い立ったが、TypeScriptとの連携でつまづいた。最終的にワークアラウンドにて対処したため、仔細を覚書として書き残す。
再現
再現用のリポジトリをcloneして yarn start
を実行すると、以下のようなエラー画面が表示される。
検証
エラーメッセージ ReferenceError: React is not defined
の詳細を調べるため、トランスパイル後のソースコードを確認する。なお、先述のリポジトリはCRA@3.0.1で作成したテンプレートをejectしたものを基にしている。
設定、および検証手順
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"
]
}
}
トランスパイル前のソースコードは以下の通り、JSXをPugに書き直しただけのものである。
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;
以下のコマンドにて、トランスパイルを実行する。
$ yarn transpile
結果
ファイル冒頭にて、React
と logo
に対するimport文が存在しないことに注目する。宣言されていない変数の関数を呼び出そうとしたため、ReferenceError: React is not defined
が引き起こされている。
原因
@babel/plugin-transform-typescript removes regular imports #9723によると、TypeScriptはトランスパイルする際に使用されていないimport文を削除する仕様となっている。そしてBabelプラグインの@babel/plugin-transform-typescriptもその仕様を踏襲している。
今回の例では、JSXがPugに置き換わったことで React
が使われなくなったため、これに対するimport文が削除された。また、logo
はPugの中に存在していることが認識されていないため、同様に使われていないものとしてimport文が削除されている。
対策(ワークアラウンド)
使われていないimport文を削除しないよう、Babelプラグインにおける当該のコードを以下のようにコメントアウトする。
// 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
を実行すると、ページが正常に表示される。
考察
ワークアラウンドによって辛くもReact、TypeScript、Pugを連携させたが、不要なimport文を削除してくれる恩恵は失われてしまった。これを失わずに済む方法を考察したい。
BabelのプラグインはほとんどがJavaScript向けに書かれているため、TypeScriptからJavaScriptへの変換は最初に行われなければならない。ここでimport文の削除はして欲しくないため、前述のプラグインを「import文の削除をする機能」と「それ以外の機能」に分割し、Webpackにて
import文の削除以外の処理 ⇨ PugをJSXに変換 ⇨ import文の削除
という流れでトランスパイルを行うと、各ツールの恩恵を失うことなく連携が出来るものと推察される。