2018年後半にスクラッチから作ったReactとReduxのプロジェクトテンプレートを2020年版として色々アップデートしている。

前回はライブラリのアップデートや差し替えをした。

今回はパスエイリアス設定をする。

モジュール解決

import React from 'react';
import Button from '@material-ui/core/Button';
import HogeButton from '../containers/HogeButton';
import Fonts from '../fonts';

こんな感じにモジュールのimportを書いたとき、webpackとかがモジュールの実際のパスを解決する処理をモジュール解決という。

基本的にモジュール解決はimportの仕方によって3通りある:

  • 絶対パス

    import App from '/home/kaitoy/react-redux-scaffold/src/views/App';
    

    こんな感じでimportするモジュールを絶対パスで指定すると、そのままそれがロードされる。

  • 相対パス

    import OkButton from '../../form/OkButton';
    

    こんな感じでimportするモジュールを相対パスで指定すると、importを書いたモジュールから見た相対パスとして解決される。

  • モジュールパス

    import React from 'react';
    

    こんな感じで、ドライブレターとか/とか.で始まらないパスを書くと、それはモジュールパスとして解決される。 モジュールパスは、処理系にモジュールディレクトリとして指定されたディレクトリからのパスになる。 モジュールディレクトリは普通はnode_modules

パスエイリアス

プロジェクトディレクトリ内の自作モジュールをimportするとき、絶対パスでimportするのはプロジェクトディレクトリの可搬性が悪いしパスが長くなるのでダメ。

プロジェクトのソースディレクトリ辺りを(node_modulesに加えて)モジュールディレクトリに指定しておいて、モジュールパスで importするのも、node_modulesのnpmパッケージと見分けがつきにくいし、名前がコンフリクトする可能性もなくはないので微妙。

ということで以前のプロジェクトテンプレートでは相対パスでimportしていたけど、../がたくさん要ったりして書きにくいし読みにくいし、モジュールの移動をするときに書き換えが面倒。

これら3通りのimport方法の欠点をすべて解消するのがパスエイリアス。 モジュールディレクトリに名前を付けて、そこからのパスでimportするモジュールを指定できるような感じ。

import App from '~/views/App';

この~の部分がパスエイリアス。

プロジェクトテンプレートへのパスエイリアス設定

<プロジェクトルート>/src~というパスエイリアスを設定したい。

プロジェクトテンプレートでモジュールの解決をする必要があるのは、バンドルファイルを生成するwebpack、TypeScriptのトランスパイラ、リンティングをするESLintと、Jestの実行時に使うBabel

webpackへのパスエイリアス設定

webpackは標準でパスエイリアスをサポートしているので、設定に書き加えるだけ。

webpack.common.js:

 (snip)
   resolve: {
     extensions: ['*', '.ts', '.tsx', '.js', '.jsx'],
     modules: ['node_modules'],
+    alias: {
+      '~': path.resolve(__dirname, 'src'),
+    },
   },
 (snip)

TypeScriptのパスエイリアス設定

TypeScriptにはPath mappingという機能があってパスエイリアスとして使える。

tsconfig.json:

 {
   "compilerOptions": {
 (snip)
-    // "baseUrl": "./",                       /* Base directory to resolve non-absolute module names. */
-    // "paths": {},                           /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
+    "baseUrl": "./" /* Base directory to resolve non-absolute module names. */,
+    "paths": {
+      /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
+      "~/*": ["./src/*"]
+    },
 (snip)
 }

ESLintのパスエイリアス設定

ESLintは、eslint-import-resolver-webpackというプラグインを使うと、モジュール解決の設定をwebpackの設定から取得してくれる。

npm install -D eslint-import-resolver-webpack

.eslintrc.js:

(snip)
   overrides: [
     {
       files: ['**/*.ts', '**/*.tsx'],
       rules: {
         // Set 'no-unused-vars' to off to suppress errors on importing types.
         // (e.g. error  'FunctionComponent' is defined but never used  no-unused-vars)
         // Unused vars are checked by TypeScript compiler (at-loader) instead.
         'no-unused-vars': 'off',
         'react/prop-types': 'off',
       },
     },
   ],
+  settings: {
+    'import/resolver': {
+      webpack: { config: path.join(__dirname, 'webpack.prod.js') },
+    },
+  },
 };

Babelのパスエイリアス設定

Babelはbabel-plugin-module-resolverを入れるとできる。

npm install -D babel-plugin-module-resolver

babel.config.js:

 module.exports = {
   presets: [
     [
       '@babel/preset-env',
       {
         useBuiltIns: 'usage',
         corejs: 3,
       },
     ],
     '@babel/preset-react',
     '@babel/preset-typescript',
   ],
-  plugins: ["babel-plugin-styled-components", "@babel/plugin-syntax-dynamic-import"],
+  plugins: [
+    'babel-plugin-styled-components',
+    '@babel/plugin-syntax-dynamic-import',
+    [
+      'babel-plugin-module-resolver',
+      {
+        root: ['./'],
+        alias: { '~': './src' },
+      },
+    ]
+  ],
 };

問題点

以上で一通り設定できているはずなんだけど、ユニットテストとか実装途中でまだ使ってないモジュールとか、index.tsxから辿れない(?)やつでパスエイリアスを使うと、VSCode上でTypeScriptコンパイラがCannot find moduleというエラーを表示してくるし、補完が利かない。

何か設定が足らない?