💍

TypeScript x Reactアプリの雛形を自作する(webpack, babelなど)

2021/05/09に公開

create-react-appやNextjsなど、中身がよくわかっていなくても最適化してくれるフレームワークが台頭している中、わざわざ自力でReactのアプリの雛形を組んでみようという取り組みです。

背景としては、

  • webpackやbabelなどを使ってみたい
  • create-react-appで作られたプロジェクトに配属された際、中身をわかっていないと細かい部分に対応できない
    などがありました。

create react appとは

create-react-appとは、Ruby on Railsのraild new、Vueにおけるvue createのような新規プロジェクトの雛形を作ってくれるコマンドです。
元々の開発元はReactを作っているFacebookではないそうですが、React公式にも推奨されているコマンドになっています(Create a New React App)。

create-react-appを実行すると、以下の3つのパッケージがインストールされます。

  • react
  • react-dom
  • react-scripts

reactとreact-domについてはreactのブログに詳しいことは書いてありますが、簡単に引用してみます。

react-native、react-art、react-canvas、react-threeなどのパッケージを見ていくと、Reactの美しさと本質はブラウザやDOMとは関係ないことが明らかになってきました。 このことをより明確にし、Reactがレンダリングできる環境をより簡単に構築するために、メインのreactパッケージをreactとreact-domの2つに分割しています。これにより、Web版のReactとReact Nativeで共有できるコンポーネントを書く道が開けます。アプリ内のすべてのコードが共有されることは期待していませんが、プラットフォーム間で同じ動作をするコンポーネントを共有できるようにしたいと考えています。

reactパッケージがプラットフォームをまたぐReactの本体、react-domパッケージがreactからdomを操作できるようにしたものといったイメージです。

また、react-scriptsについては、

  • ES6や TypeScriptなどを古いブラウザでも動くレガシーなJavaScriptにコンパイルしたり、
  • ソースコードを最適化したり、
  • ホットリロードのようなことをしてくれていたりします。

当記事ではそんなreact-scriptsがやってくれている上記の作業を自分でやっていこうと思います。
また今回は、もはやReactでのプロダクト開発のスタンダードとなっているTypeScriptを使用します。

初期設定をする

では実際にやっていきましょう。

まずはプロジェクト用のフォルダを作り、そこに移動します。

$ mkdir react-project && cd $_

使用するパッケージマネージャーはnpmyarnがありますが、ここではyarnを使うことにします。

$ yarn init -y

(-yを指定することで初期化する際の質問を全て「Yes」で進行)

プロジェクトのフォルダ直下にpackage.jsonというファイルができます。
ここにはアプリケーションにインストールするパッケージやアプリケーションの情報を保存されます。

package.json
{
  "name": "react-project",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT"
}

プロジェクト内でsrcフォルダとpublicフォルダを作成します。

$ mkdir src public

publicフォルダ内にindex.htmlを作ります。

$ touch public/index.html
public/index.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>React App</title>
</head>
<body>
  <div id="root"></div>
</body>
</html>

ここで大事なのがid="root"と書かれた部分です。
SPA(Single Page Application)ではこの一枚のhtmlにアプリケーションの実体が紐づけられます。

react, react-domを導入する

では紐付けるreactパッケージをインストールしましょう。

$ yarn add react react-dom
package.json
{
  "name": "react-project",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
+  "dependencies": {
+    "react": "^17.0.2",
+    "react-dom": "^17.0.2"
+  }
}

package.jsonに各パッケージ情報が追記されました。

次にsrcフォルダの配下にApp.tsxindex.tsxを作ります。

$ touch src/App.tsx src/index.tsx
src/App.tsx
import React from 'react';

function App() {
  return (
    <div>
      Hello world
    </div>
  );
}

export default App;
src/index.tsx
import React from 'react'
import ReactDom from 'react-dom'
import App from './App'

ReactDom.render(
  <App />,
  document.getElementById('root')
)

コードをみてみます。
ReactDOM.render()は

  • 第一引数である、Reactのコンポーネント(今回はAppコンポーネントを簡単に作成)をDOMに描画して、
  • 第二引数に指定されたHTML要素に上書きしています。

こちらが先ほど書いたindex.html<div id="root"></div>に対応しているわけですね。
(なおCRAで作成したプロジェクトには非推奨になったAPIなどを警告してくれる、Strictモードを有効にするラッパーが存在しますが、ここでは割愛します。)

TypeScriptを導入する

さて、いよいよReactのプロジェクトにTypeScriptを導入していきます。

$ yarn add -D typescript @types/react @types/react-dom
  • @types/は型定義ファイルになります。
package.json
...
+  "devDependencies": {
+    "@types/react": "^17.0.5",
+    "@types/react-dom": "^17.0.3",
+    "typescript": "^4.2.4"
+  }
}

(なお、型定義ファイルをインストールすると、App.tsxやindex.tsxに警告が出る場合がありますが、無視してください。
次の設定で直していきます。)

TypeScriptプロジェクトのコンパイラ設定を保存しておくためのファイルである、tsconfig.jsonを設定します。

$ touch tsconfig.json
tsconfig.json
{
  "compilerOptions": {
    "target": "es5", // コンパイル先のJSのバージョンを指定する
    "lib": [ // コンパイルに含むライブラリを指定する
      "dom",
      "dom.iterable",
      "esnext"
    ],
    "allowJs": true, // jsファイルのimportを許可するか
    "skipLibCheck": true, // 宣言ファイルの型チェックをスキップするか
    "esModuleInterop": true, // CommonJSとESモジュール間との相互運用を可能にする
    "allowSyntheticDefaultImports": true, // esModuleInteropと同様. こちらはコンパイル時のみに影響
    "strict": true, // noImplicitAnyなどの厳密な型チェックがまとめてONになる
    "forceConsistentCasingInFileNames": true, // 大文字小文字を区別してファイルを参照するか
    "noFallthroughCasesInSwitch": true, // switch文のbug検出用
    "module": "esnext", // コンパイル後のモジュール構文をどのモジュールシステム形式にするか設定
    "moduleResolution": "node", // モジュール解決の方法をnodeにする
    "resolveJsonModule": true, // jsonのimportを可能にするか
    "isolatedModules": true, // 全てのファイルを単一のモジュールとしてコンパイルする
    "noEmit": true, // ファイルを出力しないようにする.TSのコンパイルはbabelで行う
    "jsx": "react-jsx" // JSX 構文をそのままにしておくか React の構文に書き換えるかを指定するためのオプション
  },
  "include": [ // コンパイル対象にするファイルを指定する
    "src"
  ],
  "exclude": ["node_modules", "build"] // コンパイル対象から外すファイルを指定
}

コメントで軽く説明を加えましたが、ドキュメントに詳細が載っています。

Babel

では次にBabelについてみていきます。
BabelとはJavaScriptのコンパイラです。
主に、es2015以降の新しい構文をes5の構文に変換したり、JSX構文やTypeScriptの変換に使用されています。

まずは各パッケージをインストールします。

$ yarn add -D @babel/core @babel/cli @babel/preset-env @babel/preset-typescript @babel/preset-env @babel/preset-react

先頭二つはdefaultでほぼ必要になります。

  • @babel/core
    • Babel本体
  • @babel/cli
    • Babelをターミナルから利用できるようにするもの

presetとはコンパイルに必要なプラグインのまとまりです。

  • @babel/preset-env
    • ECMAScript用のプラグインのまとまり
  • @babel/preset-typescript
    • TypeScript用のプラグインのまとまり
  • @babel/preset-react
    • React用のプラグインのまとまり

また、CRAではnode_modules/@babelで実体を確認できます。

Babelのインストールができたので、設定をしていきます。

$ touch .babelrc
.babelrc
{
  "presets": [
    "@babel/preset-env", "@babel/preset-react", "@babel/preset-typescript"
  ]
}

こちらも色々と設定できそうですが、簡単にやってみました。

webpack

次にwebpackについてみていきます。
webpackとは、JavaScriptモジュールを一つまたは複数の指定したファイルにバンドル(束ねる)してくれるツールです。
JavaScriptと言いましたが、ローダーを入れることでCSSや画像などもバンドルしてくれます。

$ yarn add -D webpack webpack-cli webpack-dev-server babel-loader html-webpack-plugin
$ touch webpack.config.js
webpack.config.js
const path = require('path'); // outputパスに絶対パスを指定するため
const HtmlWebpackPlugin = require('html-webpack-plugin'); // plugin

module.exports = {
  entry: path.resolve(__dirname, "src", "index.tsx"), // ビルドを始める際の開始点となるファイルを指定. srcフォルダのindex.tsxを起点としている
  output: { // bundleファイルの出力先を指定. distフォルダのbundle.jsに吐き出すようにしている
    path: path.resolve(__dirname, "dist"),
    filename: "bundle.js",
  },
  mode: "development", // development or production or nodeの指定が可能. それぞれに最適化されてwebpackが実行される
  module: { // 各種ローダーの設定を行う
    rules: [
      {
        test: /\.[jt]sx?$/, // jsx or tsxで終わるファイルがあればbundleファイルに追加する前にbabel-loaderで変換する
        use: ["babel-loader"],
        exclude: /node_modules/,
      },
    ],
  },
  plugins: [ // ビルドする前の変換処理にタスクを追加する
    new HtmlWebpackPlugin({ // webpackバンドルに対応するhtmlを自動生成するプラグイン
      template: path.resolve(__dirname, "./public/index.html"),
    }),
  ],
  resolve: { // .jsxを解決できるようにする
    extensions: ['.js', '.jsx', '.ts', '.tsx'],
  },
};

実際に動かしてみましょう。
package.jsonに次のように追記します。

package.json
+ "scripts": {
+    "start": "webpack serve --config webpack.config.js --env env=dev",
+    "build": "webpack --config webpack.config.js --env env=prod"
+  },

webpack serveでlocalにserverが立ち上がります。
--config webpack.config.jsでserveするwebpackファイルを指定しています。
--env env=devでwebpackを実行する環境を指定します。

これらをスクリプトとしてstartとして登録することで実行することができます。

$ yarn start

ローカルでReactのアプリケーションが立ち上がりました!

ここからはおまけです。

prettier, eslint

まずはコードフォーマッターであるprettierをインストールしていきます。
主にコードの形をチームで強制するのに使用されます。

$ yarn add -D prettier eslint-config-prettier eslint-plugin-prettier
$ touch prettierrc.js
prettierrc.js
module.exports = {
  semi: false, // セミコロンをつけるか
  jsxSingleQuote: true, // jsxでsingle quotationを使うか
  singleQuote: true, // single quotationを使うか
  printWidth: 120, // 折り返す行の長さを指定
  arrowParens: "always", // アロー関数が()を常につけるか
  trailingComma: "all" // 末尾にカンマをつけるか
};

設定オプションはたくさんあるので好みになるかと思います。
私はこの構成でいつもコードを書いています。

eslintの設定もしていきましょう。
eslintはコードフォーマッターと構文チェックができるツールです。
prettierと併用することでそれぞれの良いところどりをして利用されることが多いです。

$ yarn add -D eslint @typescript-eslint/eslint-plugin eslint-plugin-react eslint-plugin-react-hooks eslint-plugin-import @typescript-eslint/parser  eslint-plugin-jsx-a11y
$ touch .eslint.js
eslint.js
module.exports = {
  env: {
    browser: true,
    node: true
  },
  extends: [ // 上から順番に適用され、どんどん上書きされるため優先度の高いものは一番下に指定する
    "plugin:react/recommended",
    "plugin:react-hooks/recommended",
    "plugin:@typescript-eslint/recommended",
    "plugin:import/errors",
    "plugin:import/warnings",
    "plugin:import/typescript",
    "plugin:jsx-a11y/recommended",
    "plugin:prettier/recommended",
  ],
  parser: "@typescript-eslint/parser",
  parserOptions: {
    ecmaVersion: 2020,
    sourceType: "module",
  },
  rules: {
    "no-unused-vars": "off",
    "@typescript-eslint/no-unused-vars": ["error"],
    "@typescript-eslint/no-var-requires": "off",
    "react-hooks/rules-of-hooks": "error",
    "react-hooks/exhaustive-deps": "warn",
    "react/prop-types": "off",
    "react/jsx-uses-react": "off",
    "react/react-in-jsx-scope": "off",
    "@typescript-eslint/explicit-module-boundary-types": "off",
  },
};

vscodeを使っている方はeslintやprettierの拡張機能を入れるとより開発体験が向上すると思います。
ここまでお付き合いいただきありがとうございました!
拙い点や間違いなどございましたが、コメントをいただけると嬉しいです🙏

参考資料

りあクト!シリーズ

https://oukayuka.booth.pm/

yarn

https://qiita.com/sugamondo/items/da1fd6c020301f5cd60a

tsconfig.json

https://omochizo.netlify.app/posts/2020/08/commonjs-esm/

babel

https://babeljs.io/docs/en/usage

webpack

https://webpack.js.org/concepts/
https://zenn.dev/yuri/books/4391b9280f823061932c
https://github.com/webpack-contrib/webpack-serve
https://webpack.js.org/api/cli/#environment-options

prettier

https://prettier.io/docs/en/options.html

eslint

https://eslint.org/

Discussion