react-scripts+TypeScript環境でESLint効かせつつaliasする

メモ。ここでいうaliasってのは@/hogeとかでimportするやつ(Webpackの言う"alias")

↓とかあるけどcreate-react-appで作成してreact-scripts使う時は一工夫要る

qiita.com

TypeScript

何はともあれtsconfig.json
react-scriptsが起動するたびにこいつを書き変えてしまうので、extendsを使ってpathsを切り出す

tsconfig.json

{
  "extends": "./tsconfig.paths.json"
}

tsconfig.paths.json

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

Webpack

react-scriptsの後ろにいるWebpackの設定をいじる方法がない
かといってejectはしたくないので、react-app-rewiredを使う

設定はconfig-overrides.jsに記述する

const path = require('path');

module.exports = function override(config) {
  config.resolve = {
    ...config.resolve,
    alias: {
      '@': path.resolve(__dirname, 'src'),
    },
  };
  return config;
};

ESLint

importまわりのLintをやってくれるeslint-plugin-importsというのがあるが、そのままではaliasをよしなにしてくれない

Webpackの設定を使ってくれるeslint-import-resolver-webpackがあるが、webpack.config.jsが必要となる
config-overrides.jsの内容をコピペしても良いが、うまいこと対応する方法があり、これをやると保守性が高くてよさそう

webpack.config.js

const { paths } = require('react-app-rewired');
const overrides = require('react-app-rewired/config-overrides');
const config = require(paths.scriptVersion + '/config/webpack.config.js');

module.exports = overrides.webpack(config, process.env.NODE_ENV);

TypeScriptプロジェクトの場合は.ts(x)ファイルもESLintに認識してもらう必要がある(1敗)が、eslint-import-resolver-typescriptを使うとその辺をよしなにしてくれるので使うと良い

.eslintrc.json

{
  "extends": [
    "plugin:import/errors",
    "plugin:import/typescript"
  ],
  "settings": {
    "import/resolver": {
      "webpack": {},
      "typescript": {}
    }
  },
  "plugins": [
    "import"
  ]
}

メモ(諸々含めた自分用コピペテンプレート) Prettier用の設定とかも入ってるので自分以外の人はよしなに

↓はreact-app-rewiredとESLint関係とPrettier関係(適宜更新する)

yarn add -D react-app-rewired @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint-config-prettier eslint-import-resolver-typescript eslint-import-resolver-webpack eslint-plugin-import eslint-plugin-react eslint-plugin-react-hooks eslint-plugin-unused-imports prettier

eslintrc.json

{
  "extends": [
    "eslint:recommended",
    "plugin:@typescript-eslint/recommended",
    "plugin:react/recommended",
    "plugin:import/errors",
    "plugin:import/typescript",
    "prettier"
  ],
  "root": true,
  "env": {
    "node": true
  },
  "settings": {
    "react": {
      "version": "detect"
    },
    "import/resolver": {
      "webpack": {},
      "typescript": {}
    }
  },
  "parser": "@typescript-eslint/parser",
  "plugins": [
    "react",
    "react-hooks",
    "@typescript-eslint",
    "unused-imports",
    "import"
  ],
  "rules": {
    "no-var": "error",
    "react-hooks/rules-of-hooks": "error",
    "react-hooks/exhaustive-deps": "warn",
    "sort-imports": "off",
    "unused-imports/no-unused-imports": "error",
    "unused-imports/no-unused-vars": [
      "warn",
      {
        "argsIgnorePattern": "^_"
      }
    ],
    "import/order": [
      "error",
      {
        "alphabetize": {
          "order": "asc"
        }
      }
    ]
  },
  "overrides": [
    {
      "files": [
        "**/*.tsx",
        "**/*.ts"
      ],
      "rules": {
        "react/prop-types": "off",
        "react/display-name": "off",
        "sort-imports": "off",
        "@typescript-eslint/no-unused-vars": "off",
        "@typescript-eslint/no-non-null-assertion": "warn"
      }
    }
  ]
}

.prettierrc

{
  "singleQuote": true,
  "jsxBracketSameLine": true
}

config-overrides.js

/* eslint-disable @typescript-eslint/no-var-requires */
const path = require('path');

module.exports = function override(config) {
  config.resolve = {
    ...config.resolve,
    alias: {
      '@': path.resolve(__dirname, 'src'),
    },
  };
  return config;
};

tsconfig.json

{
  "compilerOptions": {
    "target": "es5",
    "lib": [
      "dom",
      "dom.iterable",
      "esnext"
    ],
    "allowJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx",
    "baseUrl": "./",
    "noFallthroughCasesInSwitch": true
  },
  "include": [
    "src"
  ],
  "exclude": [
    "node_modules"
  ],
  "extends": "./tsconfig.paths.json"
}

tsconfig.paths.json

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

webpack.config.js

/* eslint-disable @typescript-eslint/no-var-requires */
const { paths } = require('react-app-rewired');
const overrides = require('react-app-rewired/config-overrides');
const config = require(paths.scriptVersion + '/config/webpack.config.js');

module.exports = overrides.webpack(config, process.env.NODE_ENV);