かもメモ

自分の落ちた落とし穴に何度も落ちる人のメモ帳

React create-react-app したプロジェクトで paths のエイリアス使おうとして盛大にハマったメモ

create-react-app で作った TypeScript の React アプリで Next.js でやって便利だと感じたパスのエイリアス設定しようとして盛大にハマったのでメモ。

ゴール

create-react-app --template teypescript で作った TypeScript の React アプリで /src ディレクトリのパスのエイリアスを作成して、何処からでも import { XXXX } from '@/components/XXXX' のような形で import できるようにする。../ が連続する相対パスを使わなくて済むようにする

環境
react: ^17.0.2
react-scripts: 4.0.3
typescript: ^1.0.1
eslint: ^7.2.0
/root
 |- /.vscode
 |- /public
 |- /src
 |- .eslintignore
 |- .eslintrc.json
 |- .gitignore
 |- .prettierrc.json
 |- package.json
 |- README.md
 |- tsconfig.json
 |- yarn.lock

🐞 tsconfig.jsonpaths でパスのエイリアスを設定してもアプリ実行時にオプションが消されてしまう問題

Next.js では tsconfig.jsonbaseUrlPaths オプションでパスのエイリアスが設定できました。
同じ様に tsconfig.json に設定を書いてみました

# tsconfig.json
{
  "compilerOptions": {
    "target": "es5",
    "lib": ["dom", "dom.iterable", "esnext"],
+   "baseUrl": './src',
+   "paths": {
+     "@/*": ["./*"]
+   }

これでパスのエイリアスで import できると思ったのですが、 yarn start でアプリを起動すると tsconfig.jsonpaths オプションが丸っと削除されてしまいました。
どうやら react-scripts が自動的に tsconfig.json の内容を書き換えてしまうようです… どうして…

方針

  1. extends を使って別ファイルから paths のオプションを読み込ませる
  2. TypeScript は パスの解決は行わないので、webpack の設定を上書きしてパスの解決を行う
  3. ESLint でのエイリアスのパスを使っていると import/no-unresolved なエラーになる問題を解消する
  4. VS Codeエイリアスのパスで補完が効くようにする

1. extends を使って tsconfig.json に別ファイルから paths のオプションを読み込ませる

tsconfig.json"compilerOptions": {} の内容がビルド時に cleanup されるのは正常な挙動のようです。
このファイルで対処することができないので、 "extends" を使って別ファイルから設定を読み込ませるようにします。

# 読み込ませる設定ファイルを作成 (ファイル名は何でも良い)
$ touch tsconfig.paths.json

tsconfig.paths.jsonエイリアスで使いたいパスの設定を書く

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

tsconfig.jsontsconfig.paths.json を読み込ませる

{
+ "extends": "./tsconfig.paths.json",
  "compilerOptions": {

cf.

この状態で パスのエイリアスが使える様になった思ったのですが yarn start でアプリを起動すると @/components/XXXX Module not found というモジュールが読み込めないエラーが表示されてしまいました。

ビルドされたコードを見てみるとエイリアスのパス部分がそのまま @/componrnts/XXXX と出力されており、エイリアスのパスが正しいパスに解釈されていない = パスの解決がなされてないのが原因のようです。

2. TypeScript は パスの解決は行わないので、webpack の設定を上書きしてパスの解決を行う

tsconfigcompilerOptions.paths オプションで TypeScript のパスのエイリアスの設定はできるが、バンドルする際のパスの解決は TypeScript の責務ではないので、tsconfig に設定を書くだけではダメということっぽい。

create-react-app で作ったアプリは webpack でバンドルされているので、webpack の設定を上書きしてエイリアス設定しているパスの解決をする必要がある。eject することなく webpack の設定などを上書きすることができる react-app-rewired というライブラリを使ってエイリアス設定のパスを解決する設定を作成する。

$ yarn add -D react-app-rewired

ドキュメントを参考に上書きする設定ファイルを config-overrides.js という名前で作成する。
config-overrides.js にパス解決の設定を記述する

const path = require('path');

module.exports = (config) => {
  config.resolve = {
    ...config.resolve,
    alias: {
      ...config.alias,
      // エイリアスにするパスをココに書く
      '@': path.resolve(__dirname, './src/'),
    },
  };

  return config;
};

react の実行を react-scripts から react-app-rewired を変更して設定の上書きを読み込むように変更する

# package.json
  "scripts": {
-   "start": "react-scripts start",
-   "build": "react-scripts build",
-   "test": "react-scripts test",
-   "eject": "react-scripts eject",
+   "start": "react-app-rewired start",
+   "build": "react-app-rewired build",
+   "test": "react-app-rewired test",
+   "eject": "react-app-rewired eject",

config-overrides.js は設定ファイルでアプリからは import しないので .eslintignore に追加して ESLint の対象から外しておく (エラーが表示されてウザいので)

.eslintignore

/config-overrides.js

cf

これでパスの解決がされエイリアスで import できるようになったが、ESLint がエイリアスでのインポートに import/no-unresolved (Unable to resolve path to module '@/component/XXXX' import/no-unresolved) のエラーを表示するので修正する必要があります。ESLintくんの設定めんどい…

3. ESLint でのエイリアスのパスを使っていると import/no-unresolved なエラーになる問題を解消する

.eslintrc.jsparserOptions.project に tsconfig のパスを指定していても、paths の設定を理解させるには settings.import/resolver に TypeScript の設定が別途必要なようです。(理由まで調べてなし)

TypeScript 用の import/resolver のパッケージをインストール

$ yarn add -D eslint-import-resolver-typescript

.eslintrc.js に設定を追加

  settings: {
    'import/resolver': {
+     typescript: {
+       project: './tsconfig.json',
+     },
    },

設定は typescript: {} だけでも上手く動作するっぽい。(デフォルトで tsconfig.json を探してきて読み込むから?)

cf.

ここまで設定するとエイリアスのパスが問題なく使用できるようになり、/src の内のファイルからは何処からでも @//src として import のパスを書けるようになりました!

4. VS Codeエイリアスのパスで補完が効くようにする

目的は達成できていますが、エディタでパスの補完ができれば生産性がよくなるので VS Codeエイリアスのパスの補完が効くように設定します。

./vscode/setting.json

{
  "path-autocomplete.pathMappings": {
    "@": "${folder}/src"
  },
}

cf.

これで import from '@/' と書けば /src からのパスのサジェストが表示されるようになりました! ₍ ᐢ. ̫ .ᐢ ₎ つかれた〜

おわりに

Next.js だとこれだけだったのに…

Next.js のフレームワークがいい感じにしてくれてたのですね。感謝〜

モックとかで新規プロジェクト作ることが多いので一生環境作ってる気がします…


[参考]