どう足掻こうとNULL

nullに対してのメソッドコール? なんだかしらんがとにかくヨシ!

TypeScript+Reactで小規模SPAの開発環境を構築する②――ユーティリティ編

要約

  • これは技術記事である。
    • ただし参照する際はこちらを確認してリスクを理解した上で扱うこと。
  • ESLint、Jest等を導入
  • スタイルはairbnb
  • Chakra Uiを使うのでstylelintは不要

目次

始めに

当記事には前回が存在する。その記事が読まれていることを前提に話が進むので、可能な限りはこちらに目を通して頂けると有難い。
前回では、主にコードを動かす、という面での環境構築を行った。しかしlintingがまだ強力とは言い難いし、test環境も整っていない。そこで、当記事ではeslint及びjestの設定を中心に解説していく。しかしながら、ここからは特に好みが出る部分だ。チームでのコード規則にも関わってくる難しい箇所なので、筆者の設定は参考程度に留めておいて、それぞれが自身の内情を鑑みながら利用して頂きたい。
その上で、本環境では以下のような機能要件が追加で挙げられる。

  • テストが記述できる環境
  • 強力なlint
  • フロントエンドでの表示要素の修飾

これらを満たすライブラリを選択して明示した後、環境構築へ移ることにする。 では以下からがこの記事の本文だ。

マシン環境

筆者の環境は以下のように、操作用のWindowsと開発及び実行用の仮想環境であるUbuntuの二つとなる。エディタはVSCodeで、SSH接続を利用することでWindowsからテキストの操作を行っている。参考の一助として欲しい。

role CPU cores memory OS
host Ryzen 5 6 16GB Windows
gest ---- 4 6GB Ubuntu 20.04

ところで、当記事でのコードのパス等は全て開発環境であるUbuntuに準拠していることに留意されたい。

代表的な技術選定

前回の環境に追加で導入するライブラリは、大きく分類して以下のようになる。

  • ESLint
  • Prettier
  • Jest
  • React Testing Library
  • Chakra UI

このライブラリ群を見て分かる通り、今回は明確にはCSSを記述しない。よってlintの対象にCSSは存在しないので、stylelintに関しては除外されている。その他に関してはChakra UIに目が行くものの、概ねスタンダードな構成だろう。
以下にそれぞれ意図等を述べていく。

ESLint

Nodeにおけるlinterの代表格だ。
今回はeslint-plugin-prettierは用いずに、別途prettierを動作させる。勿論これによってエディタでの設定コストが増える上、設定がコードで行える、つまりgitで差分管理が可能であるというメリットが失われてしまう。これを尊重したい場合、たとえばエディタを選ばずlintを導入したいと考えている場合は前述のプラグインの利用を検討すべきだろう。
しかし、プラグインから半ば無理やりPrettierを動作させるよりもformatterとして分離させたほうが動作が素早くあるし、Prettierの公式が推奨している手法であるという安心感もある。これらはどちらが優れているというよりかはケースバイケースであるが、筆者の場合は開発は主に少人数で行うこと、チームの間でエディタに明確な差異はないこと等から分離する方向を取ることとなった。
なお、いわゆるスタイルはairbnbのものを採用している。ラムダ周りでセミコロンがどうしても必要なケースがあるとのことだったので、いっそのこと全てに付けてしまおうという算段だ。別段formatterが自動で挿入してくれるので、コーダとしてはマメにフォーマットすればよいだけの話だと考えている。わざわざ手打ちするつもりは筆者にはない。

Prettier

筆者にとって、このライブラリはESLintとよくペアとなってコードを整えている印象が強い。それにやや引きずられるような形でESLintのペアとしてこれを導入した。
特別意図があって利用している訳ではない。強いて言うなれば、筆者は仮想環境で開発しているので、どうしてもネイティブのそれよりも速度面で劣る。故にコストのかかりづらい範囲で最適化を行いたい、という目的がある。
本環境の設定に関してもESLintのそれよりかはかなりシンプルで、ほぼ初期設定のまま利用するとしている。

Jest

JavaScriptにおけるテストライブラリの最有力の一つだ。本環境ではコードは設定ファイルを除いて全てTypeScriptで書くので、TS-Jestから利用する形になる。
ところで、前回のWebpackにおいてTypeScriptのパス問題はtsconfig-paths-webpack-pluginを用いて解決できたが、Jestでは不可能だ。試しにコードからJestCLIをコールするような形のTSファイルを、tsconfig-paths/registerを渡しつつTS-Nodeで実行してみたがパス解決は為されなかった。結局Jest内部でパス解決しなければならないのだから当然と言えば当然であるが。
よってこの問題は、TS-JestのユーティリティであるpathsToModuleNameMapperを使い対応する。具体的な方法については後述するのでそちらを参照されたい。

React Testing Library

実のところ、この名前がライブラリ名として適切かは筆者には分かっていない。パッケージマネージャにてインストールする際は@testing-library/reactという名前になる。
それはともかくとして、これはブラウザの環境を疑似的に再現することでReactのコンポーネントを動作させるものだ。@testing-library/jest-domと組み合わせ、expect(someElement).toBeInTheDocument();と記述するのが一般的なテスト方式となる。
競合ライブラリであるEnzymeでなくこちらを採用した理由としては、厳密なUIのテストを行わない要素から来ている。DOMを直接検索するのではなく表示されているテキストを利用するのは、筆者が始めて利用したときなど斬新さを大いに感じたものだった。
フロントエンドの都合上、厳密なロジックのテストよりもUIのテストが優先される。そういった側面において、このテスト手法は非常に効率がよい。軽くスタイルシートのためにDOM構成を変えただけではテストが崩れない、という要素は変更の多いフロントエンドの要求に合致していると言えるだろう。  

Chakra UI

Material UIなどと同じく、スタイルがすでに宛がわれたコンポーネントやテーマを提供するライブラリだ。
これを選んだ理由としては、そのユーザが求めるスタイリングを簡単に受け入れてしまえる自由度が挙げられる。
Material UIやBootstrapなどは、それらの特色が強く出てしまうためにオリジナリティを出すことがかなり難しい。正にフレームワークといったもので、制作物の雰囲気をほぼ決定づけてしまう個性が魅力とも欠点とも言えるだろう。
その点、Chakra UIはライブラリ然としていて、扱いやすさがとても良い。コンポーネントのプロパティとしてJSSを受け取るそのやり方には筆者などいたく感動したものだ。
本環境ではこれを用いて画面を構成する。よって、CSS部分に関してはコンポーネントのコードと分離するのがやや難しいことに留意したい。

環境構築

必要なライブラリのインストール

前回構築した環境のディレクトリをカレントディレクトリとした上で、以下のコマンドを叩く。

yarn add -D \
  eslint \
  eslint-config-prettier \
  @typescript-eslint/eslint-plugin \
  @typescript-eslint/parser \
  eslint-plugin-import \
  eslint-plugin-jsx-a11y \
  eslint-plugin-react \
  eslint-plugin-react-hooks \
  eslint-config-airbnb-typescript \
  eslint-config-airbnb \
  eslint-config-prettier-react \
  eslint-plugin-html \
  eslint-plugin-jest \
  prettier \
  @testing-library/dom \
  @testing-library/jest-dom \
  @testing-library/react \
  jest \
  ts-jest \
  @chakra-ui/react

yarn run check-peer-dependencies --install

前回の通り、check-peer-dependenciesでインストールされたものはdependenciesに入るが、今回インストールしたものは全て稼働には必要ないのでdevDependenciesに移しておく。その後yarn run sort-package-jsonだ。

ESLintの設定

以下のように.eslintrc.jsを記述する。

module.exports = {
  parser: "@typescript-eslint/parser",
  parserOptions: {
    ecmaFeatures: {
      jsx: true,
    },
    sourceType: "module",
    project: "./tsconfig.json",
  },
  globals: {
    fetch: false,
    AbortController: false,
  },
  env: {
    node: true,
    es6: true,
    browser: true,
  },
  plugins: ["@typescript-eslint"],
  extends: [
    "airbnb-typescript",
    "plugin:@typescript-eslint/eslint-recommended",
    "plugin:@typescript-eslint/recommended",
    "plugin:@typescript-eslint/recommended-requiring-type-checking",
    "plugin:jest/recommended",
    "prettier",
  ],
  rules: {
    "@typescript-eslint/explicit-module-boundary-types": "off",
    "import/prefer-default-export": "off",
    "no-console": "off",
    "import/no-extraneous-dependencies": "off",
    "@typescript-eslint/no-explicit-any": "off",
    "@typescript-eslint/quotes": "off",
    "@typescript-eslint/return-await": "off",
    "consistent-return": "off",
    "jsx-a11y/click-events-have-key-events": "off",
    "react/prop-types": "off",
    "@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }],
  },
};

airbnbは直接利用するのではなく、それのtypescript版を使う形となる。
他ルール項目で切っているのはpettierと競合したり、開発がしづらくなるという理由からのものだ。その辺りは各自で自由にカスタマイズするとよいだろう。

Prettierの設定

.prettierrc.jsは以下の通りだ。

module.exports = {
  tabWidth: 2,
  trailingComma: "all",
};

至って単純、スペースインデントの数とセミコロンを付けることしか記述していない。余談だが、公式のドキュメントを読んでいると、linterが表示するに留め、調整は手で行うべきようなものも頻出している。この辺りはできるだけでやらないほうがいい、ということの典型例だろうか。

Jestの設定

jest.config.jsは以下のようになる。

/* eslint-disable @typescript-eslint/no-var-requires */
const { pathsToModuleNameMapper } = require("ts-jest/utils");
const { compilerOptions } = require("./tsconfig.json");

module.exports = {
  preset: "ts-jest",
  roots: ["./tests"],
  testMatch: ["**/?(*.)+(spec|test).+(ts|tsx|js)"],
  transform: {
    "^.+\\.(ts|tsx)$": "ts-jest",
  },
  moduleFileExtensions: ["ts", "tsx", "js"],
  moduleDirectories: ["node_modules", __dirname],
  globals: {
    "ts-jest": {
      tsconfig: "tsconfig.json",
    },
  },
  moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths),
};

pathsToModuleNameMapperに注目して頂きたい。これにtsconfig.jsonのpathsを渡すことで問題なく動作させることができる。これをもし手で書いていた場合はこちらに修正すべきだろう。でないと重複した二つの記述を調整するため、開発しているうちに非常にストレスが溜まる。よろしくない傾向と言えるだろう。
またmoduleDirectoriesにdirnameを追加しているのは、tsconfig-pathsが行っていた、非相対パスのモジュールをルートディレクトリからも検索する部分を、Jestにも行えるようにする記述だ。
なお、筆者が試しに
dirnameでなく.と入力した場合、全てのrequireの返り値が空オブジェクトとなるらしいことが分かった。

諸注意

上記の設定を終えれば、ひとまずボイラーテンプレートとでも呼ぶべきリポジトリが出来上がることだろう。試しにその構成で、Chakra UIを利用して修飾したHelloWorldでもブラウザに表示させてみるとよい。
ただし注意点として、今回の構成ではバンドルされたファイルのサイズが非常に大きくなる。分割して並列にダウンロードしないと、とてもではないが実用には耐えないだろう。
各自、課題に気付いたときは修正して見て欲しい。なお、前回も紹介したこちらのテンプレートリポジトリにプルリクエストを送ってくれるとより一層有難い。

というところで、今回の記事は以上である。

終わりに

かなりの駆け足で環境構築を行ってきた。おそらく環境構築というハンズオンを想起させるタイトルよりかは、環境紹介という形に則ったほうがよかったかもしれない、と薄々考え始めている。
しかしそういった言い分はナンセンスであるのかもしれない。その話を持ち出すならば、世の開発環境系列の記事は軒並みテンプレートリポジトリのURLを貼り、Readmeに書いた解説を読め、で終わってしまう。
やや冗長であることも技術記事の良さなのかもしれない。この辺りに関しては、ぜひとも読者諸君の考えを伺いたいところだ。 ところで、文末にはなってしまうが、当記事も前回と同じくgoochy氏に協力して頂いた。非常に参考にさせて頂いたので、ここにて感謝したい。