Help us understand the problem. What is going on with this article?

Webpackerはもう要らない〜 Simpacker

やりたいこと

  • フロントエンドはReact、バックエンドはRailsを使いたい。
  • フロントエンドとバックエンドを一つのプロジェクトで実装したい。
  • Webpackの設定を直接書きたい。

Simpackerを使えば全てクリアになります。
クックパッド社が公開したSimpackerとそのサンプルを見て自分なりの最小構成でReactとRailsを組み合わせて見ました。
Webpackの詳細説明は割愛します。

Railsプロジェクトの生成

Railsのプロジェクトのフォルダを生成する。

mkdir react_with_rails
cd react_with_rails
bundle init

Gemfileを開いて「gem "rails"」のところをアンコメントする。

gem "rails"

railsをインストールする。
プロジェクト単位でgemを管理したいので、--pathオプションを付与する。

bundle install --path vendor/bundle --jobs=4

先まではプロジェクトのフォルダを生成してrails gemをインストールしただけ。
本物のプロジェクトを生成する。
Gemfileを上書きするか?のところでYを入力してEnter

-Bは--skip-bundle、gemのインストールは最後で実施するべき
-Sは--skip-sprockets、信じられないけどrails6でもまだ生き残っている。stylesheetのために残しているみたいが、postcssを使いたいので、要らない。
-Tは--skip-test、rspecを使うならminitestは要らない。
-Jは--skip-javascript、rails6ではデフォルトでwebpackerを使っているが、ここではSimpackerを使うので要らない。
-dはデータベースを指定するためのオプション

bundle exec rails new . -B -S -T -J -d mysql

Gemfileを開いてSimpackerを追加する。

gem "simpacker"

gemをインストールする。

bundle

Databaseを生成する。

bin/rails db:create

Reactをインストール

Simpackerでwebpackとwebpack-cliはインストールされるが、webpack-dev-serverはインストールされない。
webpack-dev-serverはwebpackを利用する時必須packageなので、インストールする。

npm i -D webpack-dev-server

React関連packageをインストールする。

npm i -D webpack-dev-server
npm i -S react react-dom
npm i -D babel-loader @babel/core @babel/preset-env @babel/preset-react

Typescript関連packageをインストールする。
ここではtsxは使ってないが、jsxとtsxの両方が使えるようにしたい。
またvimでコード補完のためにもTypescriptが必要(coc-tsserverを使ってる)

npm i -D typescript ts-loader

tsconfig.jsonファイルを生成する。
Typescriptをコンパイルする時の設定ファイル
ここではまだTypescriptを使ってないが、このファイルがない場合はvimのコード補完が作動しない。

{
  "compilerOptions": {
    "target": "es5",
    "lib": ["es2019", "dom", "dom.iterable"],
    "module": "es2015",
    "jsx": "react",
    "moduleResolution": "node",
    "esModuleInterop": true,
    "downlevelIteration": true,
    "sourceMap": true,
    "removeComments": false,
    "noImplicitAny": false,
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    "strictBindCallApply": true,
    "strictPropertyInitialization": true,
    "noImplicitThis": true
  },
  "include": [
    "app/javascript/**/*"
  ]
}

Postcss関連packageをインストールする。

npm i -D css-loader mini-css-extract-plugin postcss-loader postcss-preset-env

Webpackのconfigファイルを編集する。
Webpackerではconfigファイルが隠蔽されているが、SimpackerではWebpackと同じく直接設定しなければならない。

const path = require("path");
const WebpackAssetsManifest = require("webpack-assets-manifest");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

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/javascript/application.jsx")
  },
  output: {
    path: path.resolve(__dirname, "public/packs"),
    publicPath: isProd ? "/packs/" : "//localhost:8081/packs/",
    filename: isProd ? "[name]-[hash].js" : "[name].js"
  },
  resolve: {
    extensions: [".js", ".jsx", ".ts", ".tsx"]
  },
  devServer: {
    contentBase: path.resolve(__dirname, "public"),
    publicPath: "/packs/",
    host: "localhost",
    port: 8081,
    headers: {
      "Access-Control-Allow-Origin": "*"
    }
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          MiniCssExtractPlugin.loader,
          "css-loader",
          {
            loader: "postcss-loader",
            options: {
              plugins: loader => [require("postcss-preset-env")()]
            }
          }
        ]
      },
      {
        test: /\.jsx?$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader",
          options: {
            presets: ["@babel/preset-env", "@babel/preset-react"]
          }
        }
      },
      {
        test: /\.tsx?$/,
        loader: "ts-loader",
        options: {
          transpileOnly: true
        }
      }
    ]
  },
  plugins: [
    new WebpackAssetsManifest({ publicPath: true, writeToDisk: true }),
    new MiniCssExtractPlugin({
      filename: isProd ? "[name]-[hash].css" : "[name].css"
    })
  ]
};

サンプル

Simpackerのgithubにサンプルが用意されている。
gemの公開だけではなく色んなケースに対応するサンプルが忠実に用意されている。
クックパッドさんすごい!

app/javascript/greeter.jsx

import React from "react";

export const Hello = props => <div>Hello {props.name}!</div>;

app/javascript/application.css

:root {
  --mainColor: tomato;
}

body {
  color: var(--mainColor);
}

app/javascript/application.jsx

import React from "react";
import ReactDOM from "react-dom";
import { Hello } from "./greeter";
import "./application.css";

document.addEventListener("DOMContentLoaded", () => {
  ReactDOM.render(
    <Hello name="Rails" />, document.getElementById("app")
  );
})

DOMツリーの構築が完了した時点で発火する必要がある。
そうでなければ、Target container is not a DOM element.というエラーが発生する。
どうしてもDOMContentLoadedイベントを追加したくない場合は、applicationレイアウトからjavascript_pack_tagをbodyタグの一番下に移動する。

app/views/layouts/application.html.erb

<!DOCTYPE html>
<html>
  <head>
    <title>ReactWithRails</title>
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>

    <%= stylesheet_pack_tag 'application' %>
    <%= javascript_pack_tag 'application' %>
  </head>

  <body>
    <%= yield %>
  </body>
</html>

適当なcontrollerとviewを作って確認する。

bin/rails g controller hello world

app/views/hello/world.html.erb

<h1>Hello#world</h1>
<p>Find me in app/views/hello/world.html.erb</p>
<div id="app"></div> # ここだけ追加

production環境ではコンパイルされたjsを使うが、developmentではwebpack-dev-serverを立ち上げる必要がある。
pumaとwebpack-dev-serverを別々に立ち上げるのは面倒なので、Procfileを作成する。

Procfile

server: bin/rails server
assets: ./node_modules/.bin/webpack-dev-server

サーバーを起動する。
ここではovermindを使う。
まだ使ったことがない人はHomebrewでインストールしよう。

overmind s
system | Tmux socket name: overmind-react-with-rails-SlxVtc-lkjdgVfnNPgfz5e
system | Tmux session ID: react-with-rails
system | Listening at /Users/devtopia/apps/study/js/react_with_rails/.overmind.sock
assets | Started with pid 42087...
server | Started with pid 42086...
assets | ℹ 「wds」: Project is running at http://localhost:8081/
assets | ℹ 「wds」: webpack output is served from /packs/
assets | ℹ 「wds」: Content not from webpack is served from /Users/devtopia/apps/study/js/react_with_rails/public
server | => Booting Puma
server | => Rails 6.0.0 application starting in development
server | => Run `rails server --help` for more startup options
server | Puma starting in single mode...
server | * Version 3.12.1 (ruby 2.6.4-p104), codename: Llamas in Pajamas
server | * Min threads: 5, max threads: 5
server | * Environment: development
server | * Listening on tcp://localhost:5000
server | Use Ctrl-C to stop
assets | ℹ 「wdm」: Hash: 1b641e1f282acd737e26
assets | Version: webpack 4.41.2
assets | Time: 1572ms
assets | Built at: 2019-11-03 1:16:33 AM
assets |               Asset       Size       Chunks                   Chunk Names
assets |     application.css  131 bytes  application  [emitted]        application
assets | application.css.map  289 bytes  application  [emitted] [dev]  application
assets |      application.js   1.39 MiB  application  [emitted]        application
assets |  application.js.map   1.61 MiB  application  [emitted] [dev]  application
assets |       manifest.json  266 bytes               [emitted]
assets | Entrypoint application = application.css application.js application.css.map application.js.map
assets | [0] multi (webpack)-dev-server/client?http://localhost:8081 ./app/javascript/application.jsx 40 bytes {application} [built]
assets | [./app/javascript/application.css] 39 bytes {application} [built]
assets | [./app/javascript/application.jsx] 291 bytes {application} [built]
assets | [./app/javascript/greeter.jsx] 146 bytes {application} [built]
assets | [./node_modules/react-dom/index.js] 1.33 KiB {application} [built]
assets | [./node_modules/react/index.js] 190 bytes {application} [built]
assets | [./node_modules/webpack-dev-server/client/index.js?http://localhost:8081] (webpack)-dev-server/client?http://localhost:8081 4.29 KiB {application} [built]
assets | [./node_modules/webpack-dev-server/client/overlay.js] (webpack)-dev-server/client/overlay.js 3.51 KiB {application} [built]
assets | [./node_modules/webpack-dev-server/client/socket.js] (webpack)-dev-server/client/socket.js 1.53 KiB {application} [built]
assets | [./node_modules/webpack-dev-server/client/utils/createSocketUrl.js] (webpack)-dev-server/client/utils/createSocketUrl.js 2.89 KiB {application} [built]
assets | [./node_modules/webpack-dev-server/client/utils/log.js] (webpack)-dev-server/client/utils/log.js 964 bytes {application} [built]
assets | [./node_modules/webpack-dev-server/client/utils/reloadApp.js] (webpack)-dev-server/client/utils/reloadApp.js 1.59 KiB {application} [built]
assets | [./node_modules/webpack-dev-server/client/utils/sendMessage.js] (webpack)-dev-server/client/utils/sendMessage.js 402 bytes {application} [built]
assets | [./node_modules/webpack-dev-server/node_modules/strip-ansi/index.js] (webpack)-dev-server/node_modules/strip-ansi/index.js 161 bytes {application} [built]
assets | [./node_modules/webpack/hot sync ^\.\/log$] (webpack)/hot sync nonrecursive ^\.\/log$ 170 bytes {application} [built]
assets |     + 32 hidden modules
assets | Child mini-css-extract-plugin node_modules/css-loader/dist/cjs.js!node_modules/postcss-loader/src/index.js??ref--4-2!app/javascript/application.css:
assets |     Entrypoint mini-css-extract-plugin = *
assets |     [./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/src/index.js?!./app/javascript/application.css] ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/src??ref--4-2!./app/javascript/application.css 236 bytes {mini-css-extract-plugin} [built]
assets |     [./node_modules/css-loader/dist/runtime/api.js] 2.61 KiB {mini-css-extract-plugin} [built]
assets | ℹ 「wdm」: Compiled successfully.

結果

http://localhost:5000/hello/world

スクリーンショット 2019-11-03 1.33.24.png

参考

park-jh
プルスタックエンジニアになれるまで頑張ろう。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした