simpackerでRails + React + Typescript環境構築

## はじめに
#### simpackerとは
webpackerを使わず、シンプルなwebpackでRailsを開発するgem。
クックパッド技術部の方が作られたgemです。
https://github.com/hokaccha/simpacker
[Simpacker: Rails と webpack をもっとシンプルにインテグレーションしたいのです](https://techlife.cookpad.com/entry/2019/07/08/100000#fnref:1)
Githubと、このクックパッドの開発者ブログを見れば、simpackerが何なのかほとんどわかると思います。

## なぜsimpacker?
Rails6以降、webpackerが標準搭載になっていますが、webpackerは独自のDSLになっており、webpack本来の設定などが見えなくなっています。簡単にwebpackを扱えますが、設定のカスタマイズには独自DSLを学ぶコストがついてきます。またwebpackの細かい設定などは調整しにくかったりできないこともあるので、純粋なwebpackを使いたいならsimpackerの導入をおすすめします。
またwebpackerの独自DSLを学ぶなら、webpackそのものを学んだ方が今後の開発に活かせる気がします。私は初めて現場で触れたのがsimpackerの方で、webpackerのことはよくわかってなかったりします。笑

## Rails + React + TypeScriptで環境構築
バックエンドはRails、フロントエンドはReact + TypeScriptで構成された開発手順をメモしたいと思います。

## 開発環境
ruby 3.0.0
rails 6.1.1
yarn 1.22.10
tsc 4.1.3

## 手順
グローバルを汚さないRails環境構築やMySQLのコンテナ化は別記事に解説してますので、よければ参考にしてください。
[Dockerでコンテナ化したMySQLを使用してRails環境構築](https://qiita.com/revanira_siitake/items/eff8ec9720be86241148)
[Railsの環境構築(グローバル環境を汚さずに)](https://qiita.com/revanira_siitake/items/0dd50671fcf5f5635763)

##### railsプロジェクト立ち上げ

```
bundle exec rails new my_app --skip-javascript
bundle exec rails db:create
```
webpackerを入れたくないので、`--skip-javascript`を付けます。

##### simpackerをGemfileに追加

```:Gemfile
gem 'simpacker'
```
##### gemインストール

```
bundle install
```
##### simpacker初期化コマンド実行

```
bundle exec rails simpacker:install
```

##### React, TypeScript、必要なパッケージをインストール

```
yarn add -D react react-dom
yarn add -D typescript ts-loader
```

yarnを使っていますが、npmでも問題ありません。
ts-loaderはtypescriptをjavascriptにトランスパイルするためのパッケージです。


##### webpack.config.jsを作成

ここにwebpackの設定を書いていきます。
純粋なwebpackと全く同じ書き方です。

```js:webpack.config.js
const path = require("path");
const WebpackAssetsManifest = require("webpack-assets-manifest");

const { NODE_ENV } = process.env;
const isProd = NODE_ENV === "production";

module.exports = {
  mode: isProd ? "production" : "development",
  devtool: "source-map",
  entry: {
    application: path.resolve(__dirname, "app/frontend/js/packs/application.tsx"),
  },
  output: {
    path: path.resolve(__dirname, "public/packs"),
    publicPath: "/packs/",
    filename: isProd ? "[name]-[hash].js" : "[name].js",
  },
  resolve: {
    extensions: [".js", ".ts", ".jsx", "tsx"],
  },
  module: {
    rules: [
      {
        test: /\.(js|ts|jsx|tsx)$/,
        exclude: /node_modules/,
        use: [
          {loader: "ts-loader"}
        ]
      }
    ]
  },
  plugins: [
    new WebpackAssetsManifest({
      publicPath: true,
      output: "manifest.json",
    }),
  ],
};

```
`const path = require("path");`
node.jsのpathモジュールを読んでます。

`const WebpackAssetsManifest = require("webpack-assets-manifest");
manifest.json`を生成してくれるパッケージです。

`const { NODE_ENV } = process.env;`
こちらで任意の環境変数を読み込んでいます。

`mode:`
modeによって出力ファイルの形式が変わります。productionだと圧縮され、develomentだとみやすく整形されて出力されます。

`devtool:`
ソースマップを指定できます。ソースマップを有効にすると、ブラウザコンソールでエラーを確認するときに、エラー箇所を特定できるため必須だと思います。

`entry:`
webpackに読み込ませるエントリポイントを指定します。
`path.resolve(__dirname, "")`と言う表記は環境に依存しない絶対pathを取得できるっぽいです。

`output:`
ファイルに出力先を指定します。
`publicPath`は本番環境での解決pathを指定しています。
railsではデフォルトでpublic配下がドキュメントルートなので、`/packs/`を指定しています。



`extensions:`
importするファイルの拡張子を省略できます。同じファイル名の異なる拡張子ファイルが存在した場合、配列の先頭のものが読み込まれます。

```ts
import File from '../path/to/file';
```

`rules:`
トランスパイルするローダーの設定を書きます。
`test:`で対象ファイルを指定します。
`use`で使用するローダーをしてします。複数書いた場合は後ろから実行されます。

`WebpackAssetsManifest`:
manifest.jsonを作成するwebpack用ライブラリです。

webpackの設定はやはり公式を参照すべきだと思います。
[https://webpack.js.org/concepts/](https://webpack.js.org/concepts/)
こちらも充実しています。
[webpack 4 入門 - Qiita](https://qiita.com/soarflat/items/28bf799f7e0335b68186)



##### tsconfig.jsonを作成

typescriptの設定を書いていきます。

```json:tsconfig.json
{
  "compilerOptions": {
    "target": "es5",
    "module": "es2015",
    "jsx": "react",
    "allowJs": true,
    "moduleResolution": "node",
    "sourceMap": true,
    "strict": true,
    "noImplicitAny": false,
  },
  "include": [
    "app/frontend/js/**/*"
  ],
  "exclude": [
    "**/*.(spec|test).ts",
    "**/setup.jest.ts",
  ]
}

```

`target:`
出力するjsのバージョンを指定します。

`module:`
使用するモジュールを指定します。

targetやmoduleについてまだ詳しく理解していないので、今後勉強していきたいと思ってます。

`jsx:`
reactを使用する場合、reactを指定します。

`allowJs:`
trueでjsファイルもトランスパイルしてくれます。

`moduleResolution:`
とりあえずnodeにしておけばいい？

`strict:`
全ての型チェックを有効にします。

tsconfig.jsonについては、
公式にコンパクトにまとまっています。
https://typescript-jp.gitbook.io/deep-dive/project/compilation-context/tsconfig
日本語だと、こちらの記事でかなり詳細に解説してくださっています。
[tsconfig.jsonの全オプションを理解する（随時追加中） - Qiita](https://qiita.com/ryokkkke/items/390647a7c26933940470)


##### Reactコンポーネントを作成

エントリーポイントのファイルを作成していきます。

```tsx:app/frontend/js/packs/application.tsx
import * as React from 'react';
import * as ReactDOM from 'react-dom';

import Index from '../pages/Index';

const appElement = document.getElementById('app');

if (appElement) {
  ReactDOM.render((
    <Index />
  ), appElement);
}

```

レンダーするコンポーネント

```tsx:app/frontend/js/pages/Index.tsx
import * as React from 'react';

interface Props {}

const Index: React.FC<Props> = () => {
  return(
    <div>Hello React</div>
  )
};

export default Index;

```

これをwebpackでビルドしてjsファイルを出力します

```
yarn webpack
```
`/public/packs/`配下に`application.js`, `application.js.map`, `manifest.json`が作成されていると思います。

これらをRails側で読み込みましょう。

```
bundle exec rails g controller Top
```

```rb:app/controllers/top.rb
class TopController < ApplicationController
  def index; end
end

```

```erb:app/views/top/index.html.erb
<div id="app"></div>
```

```erb:app/views/layouts/application.html.erb

<!DOCTYPE html>
<html>
  <head>
    <title>Sample</title>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>
  </head>

  <body>
    <%= yield %>
    <%= javascript_pack_tag 'application' %>
  </body>
</html>
```
simpackerが良い感じに`<%= javascript_pack_tag 'application' %> `を解釈して読み込んでくれます。

`http://localhost:3000/index`にアクセスしてHello Reactが表示されれば成功です。

## 終わりに
simpackerはシンプルにwebpackを扱えるようにしてくれます。
ただ、今のRailsのフロント周りのgemはwebpackerを前提に作られているものが多いので、そういうgemを利用する際には、かなり苦労することもありますので、少なからずデメリットも存在します。