2021-10-06

RailsからWebpackerを外してpureなwebpack構成にしてみる その1

Railsに導入することで、フロントエンドエコシステムの知識がなくてもある程度モダンな構成でフロント開発ができるようになるWebpackerさん
巷では色々言われているけど、僕は正直WebpackやBabelなどのエコシステムに精通していたわけではないので、よくわからんけど色々任せられて結構ありがたいgemだった
しかし、やはり自分たちにとって最適な構成でアセットのビルドフローを構築するためには、pureな状態でWebpackとかを扱えた方がいいのは間違いないので、いざという時のために外すという作業を一度やってみる
(よく言われている独自のDSLで設定を書くのは正直結構面倒なのは同意する)

ゴリゴリに使い込まれたWebpackerではなく、最近デフォルトで導入されるようになった新規Railsアプリに対してやってみる

まずは新規Railsアプリの作成

この辺はWebpackerとは関係がないのでサクサクいく

// Gemfileの作成
bundle init

// 生成されたGemfileを編集し、最新のrailsが入るように書き換える(今回は6.1.4.1)
code Gemfile

bundle

// 今回はwebpackerにしか関心がないので、最小構成でアプリを作成する(DBは何となくポスグレ)
bundle exec rails new . --minimal --webpack=react -d postgresql

// とりあえず適当な画面が欲しいので作成
bin/rails g scaffold users name:string admin:boolean

Reactは使えるようになっているので、適当に画面に表示されるようにapp/javascriptのエントリポイントをいじる
Reactコンポーネントが画面に表示できたら準備完了
Webpackerを除去していく

Webpackerが提供しているデフォルトのwebpack.configを見る

除去していくといっても、いきなり何の用意もなくひっぺがすのは無理なので、デフォルトで提供されているwebpack向けの設定をバックアップしておく
設定を抜き出すのは簡単で、 config/webpack/development.js に以下の行を追加して bin/webpack を実行すればよい

// 正規表現の部分が欠落してしまうのを回避するための設定
// https://github.com/survivejs/webpack-merge/issues/80
Object.defineProperty(RegExp.prototype, 'toJSON', {
  value: RegExp.prototype.toString,
})

const fs = require('fs')
fs.writeFileSync('./resultToWebpackConfig.txt', JSON.stringify(environment.toWebpackConfig()))

そうすると以下のようなとんでもねぇ巨大なJSONが得られる
これがWebpackerがデフォルトで提供している設定
ただし、一部どうしても設定が欠落して空のハッシュとかになってしまうので、Webpackerのソースと一緒に見るといいと思う

{
    "mode": "development",
    "output": {
        "filename": "js/[name]-[contenthash].js",
        "chunkFilename": "js/[name]-[contenthash].chunk.js",
        "hotUpdateChunkFilename": "js/[id]-[hash].hot-update.js",
        "path": "/Users/hakozaru/projects/myprj/remove_webpacker_sample/public/packs",
        "publicPath": "/packs/"
    },
    "resolve": {
        "extensions": [
            ".jsx",
            ".mjs",
            ".js",
            ".sass",
            ".scss",
            ".css",
            ".module.sass",
            ".module.scss",
            ".module.css",
            ".png",
            ".svg",
            ".gif",
            ".jpeg",
            ".jpg"
        ],
        "plugins": [
            {
                "topLevelLoader": {}
            }
        ],
        "modules": [
            "/Users/hakozaru/projects/myprj/remove_webpacker_sample/app/javascript",
            "node_modules"
        ]
    },
    "resolveLoader": {
        "modules": [
            "node_modules"
        ],
        "plugins": [
            {}
        ]
    },
    "node": {
        "dgram": "empty",
        "fs": "empty",
        "net": "empty",
        "tls": "empty",
        "child_process": "empty"
    },
    "devtool": "cheap-module-source-map",
    "entry": {
        "application": "/Users/hakozaru/projects/myprj/remove_webpacker_sample/app/javascript/packs/application.js",
        "hello_react": "/Users/hakozaru/projects/myprj/remove_webpacker_sample/app/javascript/packs/hello_react.jsx"
    },
    "module": {
        "strictExportPresence": true,
        "rules": [
            {
                "test": "/(.jpg|.jpeg|.png|.gif|.tiff|.ico|.svg|.eot|.otf|.ttf|.woff|.woff2)$/i",
                "use": [
                    {
                        "loader": "file-loader",
                        "options": {
                            "esModule": false,
                            "context": "app/javascript"
                        }
                    }
                ]
            },
            {
                "test": "/\\.(css)$/i",
                "use": [
                    {
                        "loader": "style-loader"
                    },
                    {
                        "loader": "css-loader",
                        "options": {
                            "sourceMap": true,
                            "importLoaders": 2,
                            "modules": false
                        }
                    },
                    {
                        "loader": "postcss-loader",
                        "options": {
                            "config": {
                                "path": "/Users/hakozaru/projects/myprj/remove_webpacker_sample"
                            },
                            "sourceMap": true
                        }
                    }
                ],
                "sideEffects": true,
                "exclude": "/\\.module\\.[a-z]+$/"
            },
            {
                "test": "/\\.(scss|sass)(\\.erb)?$/i",
                "use": [
                    {
                        "loader": "style-loader"
                    },
                    {
                        "loader": "css-loader",
                        "options": {
                            "sourceMap": true,
                            "importLoaders": 2,
                            "modules": false
                        }
                    },
                    {
                        "loader": "postcss-loader",
                        "options": {
                            "config": {
                                "path": "/Users/hakozaru/projects/myprj/remove_webpacker_sample"
                            },
                            "sourceMap": true
                        }
                    },
                    {
                        "loader": "sass-loader",
                        "options": {
                            "sourceMap": true,
                            "implementation": {
                                "info": "dart-sass\t1.42.1\t(Sass Compiler)\t[Dart]\ndart2js\t2.14.2\t(Dart Compiler)\t[Dart]",
                                "types": {},
                                "NULL": {},
                                "TRUE": {
                                    "value": true
                                },
                                "FALSE": {
                                    "value": false
                                }
                            },
                            "sassOptions": {
                                "includePaths": []
                            }
                        }
                    }
                ],
                "sideEffects": true,
                "exclude": "/\\.module\\.[a-z]+$/"
            },
            {
                "test": "/\\.(css)$/i",
                "use": [
                    {
                        "loader": "style-loader"
                    },
                    {
                        "loader": "css-loader",
                        "options": {
                            "sourceMap": true,
                            "importLoaders": 2,
                            "modules": {
                                "localIdentName": "[name]__[local]___[hash:base64:5]"
                            }
                        }
                    },
                    {
                        "loader": "postcss-loader",
                        "options": {
                            "config": {
                                "path": "/Users/hakozaru/projects/myprj/remove_webpacker_sample"
                            },
                            "sourceMap": true
                        }
                    }
                ],
                "sideEffects": false,
                "include": "/\\.module\\.[a-z]+$/"
            },
            {
                "test": "/\\.(scss|sass)$/i",
                "use": [
                    {
                        "loader": "style-loader"
                    },
                    {
                        "loader": "css-loader",
                        "options": {
                            "sourceMap": true,
                            "importLoaders": 2,
                            "modules": {
                                "localIdentName": "[name]__[local]___[hash:base64:5]"
                            }
                        }
                    },
                    {
                        "loader": "postcss-loader",
                        "options": {
                            "config": {
                                "path": "/Users/hakozaru/projects/myprj/remove_webpacker_sample"
                            },
                            "sourceMap": true
                        }
                    },
                    {
                        "loader": "sass-loader",
                        "options": {
                            "sourceMap": true
                        }
                    }
                ],
                "sideEffects": false,
                "include": "/\\.module\\.[a-z]+$/"
            },
            {
                "test": "/\\.(js|mjs)$/",
                "include": "/node_modules/",
                "exclude": "/(?:@?babel(?:\\/|\\\\{1,2}|-).+)|regenerator-runtime|core-js|^webpack$|^webpack-assets-manifest$|^webpack-cli$|^webpack-sources$|^@rails\\/webpacker$/",
                "use": [
                    {
                        "loader": "babel-loader",
                        "options": {
                            "babelrc": false,
                            "presets": [
                                [
                                    "@babel/preset-env",
                                    {
                                        "modules": false
                                    }
                                ]
                            ],
                            "cacheDirectory": true,
                            "cacheCompression": false,
                            "compact": false,
                            "sourceMaps": false
                        }
                    }
                ]
            },
            {
                "test": "/\\.(js|jsx|mjs|ts|tsx)?(\\.erb)?$/",
                "include": [
                    "/Users/hakozaru/projects/myprj/remove_webpacker_sample/app/javascript"
                ],
                "exclude": "/node_modules/",
                "use": [
                    {
                        "loader": "babel-loader",
                        "options": {
                            "cacheDirectory": true,
                            "cacheCompression": false,
                            "compact": false
                        }
                    }
                ]
            }
        ]
    },
    "plugins": [
        {
            "keys": [
                "BUNDLER_ORIG_PATH",
                "RBENV_VERSION",
                "STARSHIP_SHELL",
                "MANPATH",
                "NODENV_SHELL",
                "TERM_PROGRAM",
                "NODENV_DIR",
                "GEM_HOME",
                "BUNDLER_ORIG_MANPATH",
                "SHELL",
                "TERM",
                "BUNDLER_ORIG_GEM_HOME",
                "TMPDIR",
                "HOMEBREW_REPOSITORY",
                "TERM_PROGRAM_VERSION",
                "BUNDLER_ORIG_BUNDLER_VERSION",
                "ORIGINAL_XDG_CURRENT_DESKTOP",
                "TERM_SESSION_ID",
                "NODENV_ROOT",
                "NODENV_HOOK_PATH",
                "USER",
                "HOMEBREW_SHELLENV_PREFIX",
                "COMMAND_MODE",
                "RBENV_ROOT",
                "SSH_AUTH_SOCK",
                "__CF_USER_TEXT_ENCODING",
                "RBENV_HOOK_PATH",
                "BUNDLER_ORIG_RUBYLIB",
                "BUNDLER_ORIG_RUBYOPT",
                "PATH",
                "RBENV_ORIG_PATH",
                "__CFBundleIdentifier",
                "PWD",
                "BUNDLER_ORIG_GEM_PATH",
                "LANG",
                "ITERM_PROFILE",
                "XPC_FLAGS",
                "NODE_ENV",
                "RBENV_SHELL",
                "XPC_SERVICE_NAME",
                "HOME",
                "SHLVL",
                "COLORFGBG",
                "BUNDLE_GEMFILE",
                "APPLICATION_INSIGHTS_NO_DIAGNOSTIC_CHANNEL",
                "VSCODE_GIT_ASKPASS_MAIN",
                "RAILS_ENV",
                "LC_TERMINAL_VERSION",
                "HOMEBREW_PREFIX",
                "WEBPACKER_CONFIG",
                "RBENV_DIR",
                "ITERM_SESSION_ID",
                "BUNDLER_ORIG_BUNDLE_GEMFILE",
                "LOGNAME",
                "STARSHIP_SESSION_KEY",
                "NODENV_VERSION",
                "GEM_PATH",
                "VSCODE_GIT_IPC_HANDLE",
                "INFOPATH",
                "HOMEBREW_CELLAR",
                "GIT_ASKPASS",
                "VSCODE_GIT_ASKPASS_NODE",
                "LC_TERMINAL",
                "RUBYOPT",
                "BUNDLER_ORIG_BUNDLE_BIN_PATH",
                "BUNDLE_BIN_PATH",
                "BUNDLER_ORIG_RB_USER_INSTALL",
                "RUBYLIB",
                "BUNDLER_VERSION",
                "COLORTERM"
            ],
            "defaultValues": {
                "BUNDLER_ORIG_PATH": "/Users/hakozaru/.rbenv/versions/2.7.3/bin:/opt/homebrew/Cellar/rbenv/1.2.0/libexec:/Users/hakozaru/.nodenv/shims:/Users/hakozaru/.rbenv/shims:/opt/homebrew/opt/git:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin:/Users/hakozaru/.nodenv/shims:/Users/hakozaru/.rbenv/shims:/opt/homebrew/opt/git:/opt/homebrew/bin:/opt/homebrew/sbin",
                "RBENV_VERSION": "2.7.3",
                "STARSHIP_SHELL": "zsh",
                "MANPATH": "/usr/share/man:/usr/local/share/man:/opt/homebrew/share/man",
                "NODENV_SHELL": "zsh",
                "TERM_PROGRAM": "vscode",
                "NODENV_DIR": "/Users/hakozaru/projects/myprj/remove_webpacker_sample/node_modules/.bin",
                "GEM_HOME": "/Users/hakozaru/.rbenv/versions/2.7.3/lib/ruby/gems/2.7.0",
                "BUNDLER_ORIG_MANPATH": "/usr/share/man:/usr/local/share/man:/opt/homebrew/share/man",
                "SHELL": "/bin/zsh",
                "TERM": "xterm-256color",
                "BUNDLER_ORIG_GEM_HOME": "BUNDLER_ENVIRONMENT_PRESERVER_INTENTIONALLY_NIL",
                "TMPDIR": "/var/folders/vh/3y0sscdj1w3fx545k2848mbm0000gn/T/",
                "HOMEBREW_REPOSITORY": "/opt/homebrew",
                "TERM_PROGRAM_VERSION": "1.60.2",
                "BUNDLER_ORIG_BUNDLER_VERSION": "BUNDLER_ENVIRONMENT_PRESERVER_INTENTIONALLY_NIL",
                "ORIGINAL_XDG_CURRENT_DESKTOP": "undefined",
                "TERM_SESSION_ID": "w0t0p0:238D346E-C7C8-4CE7-9730-CB89F51142A9",
                "NODENV_ROOT": "/Users/hakozaru/.nodenv",
                "NODENV_HOOK_PATH": "/Users/hakozaru/.nodenv/nodenv.d:/opt/homebrew/Cellar/nodenv/1.4.0/nodenv.d:/opt/homebrew/etc/nodenv.d:/etc/nodenv.d:/usr/lib/nodenv/hooks",
                "USER": "hakozaru",
                "HOMEBREW_SHELLENV_PREFIX": "/opt/homebrew",
                "COMMAND_MODE": "unix2003",
                "RBENV_ROOT": "/Users/hakozaru/.rbenv",
                "SSH_AUTH_SOCK": "/private/tmp/com.apple.launchd.QfX1Ds0HzH/Listeners",
                "__CF_USER_TEXT_ENCODING": "0x1F5:0x1:0xE",
                "RBENV_HOOK_PATH": "/Users/hakozaru/.rbenv/rbenv.d:/opt/homebrew/Cellar/rbenv/1.2.0/rbenv.d:/opt/homebrew/etc/rbenv.d:/usr/local/etc/rbenv.d:/etc/rbenv.d:/usr/lib/rbenv/hooks",
                "BUNDLER_ORIG_RUBYLIB": "/opt/homebrew/Cellar/rbenv/1.2.0/rbenv.d/exec/gem-rehash:",
                "BUNDLER_ORIG_RUBYOPT": "BUNDLER_ENVIRONMENT_PRESERVER_INTENTIONALLY_NIL",
                "PATH": "/Users/hakozaru/.nodenv/versions/14.18.0/bin:/opt/homebrew/Cellar/nodenv/1.4.0/libexec:/Users/hakozaru/.rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/bin:/Users/hakozaru/.rbenv/versions/2.7.3/bin:/opt/homebrew/Cellar/rbenv/1.2.0/libexec:/Users/hakozaru/.nodenv/shims:/Users/hakozaru/.rbenv/shims:/opt/homebrew/opt/git:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin:/opt/homebrew/bin:/opt/homebrew/sbin",
                "RBENV_ORIG_PATH": "/Users/hakozaru/.nodenv/shims:/Users/hakozaru/.rbenv/shims:/opt/homebrew/opt/git:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin:/Users/hakozaru/.nodenv/shims:/Users/hakozaru/.rbenv/shims:/opt/homebrew/opt/git:/opt/homebrew/bin:/opt/homebrew/sbin",
                "__CFBundleIdentifier": "com.googlecode.iterm2",
                "PWD": "/Users/hakozaru/projects/myprj/remove_webpacker_sample",
                "BUNDLER_ORIG_GEM_PATH": "BUNDLER_ENVIRONMENT_PRESERVER_INTENTIONALLY_NIL",
                "LANG": "ja_JP.UTF-8",
                "ITERM_PROFILE": "Default",
                "XPC_FLAGS": "0x0",
                "NODE_ENV": "development",
                "RBENV_SHELL": "zsh",
                "XPC_SERVICE_NAME": "0",
                "HOME": "/Users/hakozaru",
                "SHLVL": "3",
                "COLORFGBG": "15;0",
                "BUNDLE_GEMFILE": "/Users/hakozaru/projects/myprj/remove_webpacker_sample/Gemfile",
                "APPLICATION_INSIGHTS_NO_DIAGNOSTIC_CHANNEL": "true",
                "VSCODE_GIT_ASKPASS_MAIN": "/Applications/Visual Studio Code.app/Contents/Resources/app/extensions/git/dist/askpass-main.js",
                "RAILS_ENV": "development",
                "LC_TERMINAL_VERSION": "3.4.10",
                "HOMEBREW_PREFIX": "/opt/homebrew",
                "WEBPACKER_CONFIG": "/Users/hakozaru/projects/myprj/remove_webpacker_sample/config/webpacker.yml",
                "RBENV_DIR": "/Users/hakozaru/projects/myprj/remove_webpacker_sample/bin",
                "ITERM_SESSION_ID": "w0t0p0:238D346E-C7C8-4CE7-9730-CB89F51142A9",
                "BUNDLER_ORIG_BUNDLE_GEMFILE": "/Users/hakozaru/projects/myprj/remove_webpacker_sample/Gemfile",
                "LOGNAME": "hakozaru",
                "STARSHIP_SESSION_KEY": "7299325103251177",
                "NODENV_VERSION": "14.18.0",
                "GEM_PATH": "/Users/hakozaru/.rbenv/versions/2.7.3/lib/ruby/gems/2.7.0:/Users/hakozaru/.gem/ruby/2.7.0",
                "VSCODE_GIT_IPC_HANDLE": "/var/folders/vh/3y0sscdj1w3fx545k2848mbm0000gn/T/vscode-git-5c46353762.sock",
                "INFOPATH": "/opt/homebrew/share/info:",
                "HOMEBREW_CELLAR": "/opt/homebrew/Cellar",
                "GIT_ASKPASS": "/Applications/Visual Studio Code.app/Contents/Resources/app/extensions/git/dist/askpass.sh",
                "VSCODE_GIT_ASKPASS_NODE": "/Applications/Visual Studio Code.app/Contents/Frameworks/Code Helper (Renderer).app/Contents/MacOS/Code Helper (Renderer)",
                "LC_TERMINAL": "iTerm2",
                "RUBYOPT": "-r/Users/hakozaru/.rbenv/versions/2.7.3/lib/ruby/2.7.0/bundler/setup",
                "BUNDLER_ORIG_BUNDLE_BIN_PATH": "BUNDLER_ENVIRONMENT_PRESERVER_INTENTIONALLY_NIL",
                "BUNDLE_BIN_PATH": "/Users/hakozaru/.rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/bundler-2.1.4/libexec/bundle",
                "BUNDLER_ORIG_RB_USER_INSTALL": "BUNDLER_ENVIRONMENT_PRESERVER_INTENTIONALLY_NIL",
                "RUBYLIB": "/opt/homebrew/Cellar/rbenv/1.2.0/rbenv.d/exec/gem-rehash",
                "BUNDLER_VERSION": "2.1.4",
                "COLORTERM": "truecolor"
            }
        },
        {
            "options": {},
            "logger": {},
            "pathCache": {},
            "fsOperations": 0,
            "primed": false
        },
        {
            "options": {
                "filename": "css/[name]-[contenthash:8].css",
                "ignoreOrder": false,
                "chunkFilename": "css/[name]-[contenthash:8].chunk.css"
            }
        },
        {}
    ]
}

どう見ても全ての設定を継承する必要はなさそうなくらいこってりしているので、上からざっと眺めて必要そうな設定だけを引き継いだwebpack.config.jsを作成する

何を引き継ぐべきか

何を引き継ぐべきなのかは1つ1つ設定を見ていくしかないので地道にやっていく
実際のソースではどこに定義されているのかもメモしておく
なお、@rails/webpackerのバージョンは5.4.3を使っている

"mode": "development"

定義はここ => https://github.com/rails/webpacker/blob/e0c998e2aa7f096709eaa2a7f2d7e29d413abed3/package/environments/development.js#L11

modeは必須の設定なので確実に必要
どうやってビルドするのかにもよるが、process.env.NODE_ENVを指定したり、--modeオプションを指定したりすればOK

"output": {
  "filename": "js/[name]-[contenthash].js",
  "chunkFilename": "js/[name]-[contenthash].chunk.js",
  "hotUpdateChunkFilename": "js/[id]-[hash].hot-update.js",
  "path": "/Users/hakozaru/projects/myprj/remove_webpacker_sample/public/packs",
  "publicPath": "/packs/"
}

定義はここ => https://github.com/rails/webpacker/blob/e0c998e2aa7f096709eaa2a7f2d7e29d413abed3/package/environments/base.js#L95-L101

filenameとchunkFilenameは他にチームで規約とかなければ、そのまま流用してよさそう
ただ、WebpackerデフォルトではsplitChunkの設定がされていないので、chunkFilenameは使われない(が、chunkは将来絶対使うと思うので、とりあえず指定しておくのが良さそう)

hotUpdateChunkFilenameはwebpackが提供するHot Module Replacement(HMR)に関する設定らしいけど、ちょっとよくわからない
とりあえずdev環境にだけ関係があるっぽいのと、HMRにアプリが対応していないと使えないので、対応していなければ引き継がなくて問題なさそう
(参考: https://qiita.com/haradakunihiko/items/9840f1ea2ffa0ee0874d)

pathは出力先なので必ず必要で、そのまま引き継いだ方が良さそう
publicPathはpathと似ているが、それぞれ以下の違いがある

  • pathは単にwebpackに生成したファイルの格納先を伝える設定
  • publicPathはwebpackのプラグインが利用する設定で、ビルド時にCSSやHTMLファイル内のURLを更新する

例えばCSSの中で ./hoge.png というファイルを指定した場合、ローカルでは読み込みに成功しても、本番の場合はCDNに存在しているためそのままでは読み込みに失敗する可能性があるため、環境に応じてURLを書き換えないといけない
しかし手動ではやってられないので、publicPathを認識できるプラグインを使うことで、本番ビルド時にURLを自動で更新したファイルを吐き出してくれるようになる
なのでこの設定は引き継いだ方が良さそう

"resolve": {
  "extensions": [
    ".jsx",
    ".mjs",
    ".js",
    ".sass",
    ".scss",
    ".css",
    ".module.sass",
    ".module.scss",
    ".module.css",
    ".png",
    ".svg",
    ".gif",
    ".jpeg",
    ".jpg"
  ],
  "plugins": [
    {
      "topLevelLoader": {}
    }
  ],
  "modules": [
    "/Users/hakozaru/Desktop/remove_webpacker_sample/app/javascript",
    "node_modules"
  ]
}

定義はここ => https://github.com/rails/webpacker/blob/e0c998e2aa7f096709eaa2a7f2d7e29d413abed3/package/environments/base.js#L103-L106

extenshionsはrequireなどする際に、拡張子を省略できるようになる設定
(hoge.jsを読み込みたい時にrequire('hoge')みたいに.jsを省略できるようになる)
そのまま使ってもいいけど、多分全部使わないと思うので必要に応じて削ったものを引き継げば良いと思う

pluginsはその名の通りプラグインを指定するところなんだけど、topLevelLoaderはなんなのかわからなかったのと、設定がうまく書き出せていなくて実際はapplyとかmakePluginみたいなキーが複数設定されていて、関数が渡されている模様
ソースを見た感じPnpWebpackPluginというプラグインが指定されているっぽい
これはyarn2.0から導入された、でかいnode_modulesの代わりに.pnp.jsという単一のファイルを読み込むことで70%ほど高速化できる機能のためのプラグインの模様(参考)
ただ、↑の参考にもあるようにまだ恩恵が受けられないっぽいので今回はスルー(あまり細かく調べてないので、実務でやるならしっかり時間使って調べて欲しい)

modulesはimportとかrequireした時にファイルを探索するパス
自分が作成したJSファイルと node_modules が指定されていて問題なさげなので、これはそのまま使えば良さそう
(うっかりnode_modules指定を消してしまうと、外部ライブラリを一切使えなくなるので注意)

"resolveLoader": {
  "modules": [
    "node_modules"
  ],
  "plugins": [
    {}
  ]
}

定義はここ => https://github.com/rails/webpacker/blob/e0c998e2aa7f096709eaa2a7f2d7e29d413abed3/package/environments/base.js#L108-L111

またpuluginsがうまく書き出せていないが、ソースを見るとここもPnpWebpackPluginを使っている箇所なので、↑と同じ理由でスルー
よってresolveLoaderは特に移植しなくてもデフォルトで問題なさそう

"node": {
  "dgram": "empty",
  "fs": "empty",
  "net": "empty",
  "tls": "empty",
  "child_process": "empty"
}

定義はここ => https://github.com/rails/webpacker/blob/e0c998e2aa7f096709eaa2a7f2d7e29d413abed3/package/environments/base.js#L113-L120

この設定本当に意味がわからないんだけど、どうやら各モジュールにモックを貼っているらしい
これを書くことで空のオブジェクトを指すようになるらしい
(例えば、fs.readFileはundefinedになるという感じ)

webpackはデフォルトでブラウザ向けにビルドするので、とりあえず何か問題がない限りは指定しない方が無難な気がする
よって今回は除外しておいて、何か問題があれば移植を検討する

"devtool": "cheap-module-source-map"

定義はここ => https://github.com/rails/webpacker/blob/e0c998e2aa7f096709eaa2a7f2d7e29d413abed3/package/environments/development.js#L12

devtoolはソースマップの生成方法について設定できる
ソースマップはトランスパイル後の人間が読みづらいコードと、トランスパイル前の自分たちが書いたコードをマッピングしてデバッグしやすくするためのお馴染みの機能
devでは必要だけど、prodでは不要なやつ

いくつか指定できるが、ちょっと調べたくらいでは違いがいまいちわからなかったので、今回はそのまま使うことにする
(どうやらパフォーマンスに関わってくるらしい)

"entry": {
  "application": "/Users/hakozaru/projects/myprj/remove_webpacker_sample/app/javascript/packs/application.js",
  "hello_react": "/Users/hakozaru/projects/myprj/remove_webpacker_sample/app/javascript/packs/hello_react.jsx"
}

定義はここ => https://github.com/rails/webpacker/blob/e0c998e2aa7f096709eaa2a7f2d7e29d413abed3/package/environments/base.js#L162

entryはバンドルを実行するための起点となるファイルを指定する
ここで指定されたファイルを起点として(ここではapplication.jsとhello_react.jsx)、それぞれのファイルでimport/requireされている依存を解決し、outputの設定に従って吐き出される

--webpacker=react で作成したRailsアプリでは、エントリポイントとなる app/javascript/packs にapplication.jsとhello_react.jsxがあるので、2つエントリポイントとして指定されている
状況によるがapplication.jsだけで問題ないので、移植する際はそちらだけを残しておくことにする
application.jsだけをviewのlayoutで指定し、そこを起点に全てのJSをロードするような構成を想定している
場合にもよるけど、経験上エントリポイントを大量に作ってjavascript_pack_tagを自由に埋め込みまくるとどこかで死ぬので、エントリポイントは1つにしておいた方がいいと思っている

"module": {
  "strictExportPresence": true,
  "rules": [
    "あまりに巨大すぎるので略"
  ]
}

定義はここ => https://github.com/rails/webpacker/blob/e0c998e2aa7f096709eaa2a7f2d7e29d413abed3/package/environments/base.js#L164-L167

いよいよ出てきた問題の箇所

strictExportPresenceはエクスポートが不足している場合に警告ではなくエラーにするオプションで、これはあった方が安全そう

rulesに関してはデフォルトの設定は参考程度に留めておいて、1から自分たちで必要な構成を作っていった方がいいと感じた
まず間違いなくTSを使うようにするから、対象ブラウザによってはbabelではなくtscで事足りるかもしれないし、そうなるとbabel入れないということになってrulesの構成も相当変わってくるというのが一番の理由
(postcssとかよーわからんし、使う時になったら考えればいい)

"plugins": [
  "やっぱり巨大なので略"
]

定義はここ => https://github.com/rails/webpacker/blob/e0c998e2aa7f096709eaa2a7f2d7e29d413abed3/package/environments/base.js#L29-L52

自分で抜き出したjsonでは大量の環境変数と、謎のオブジェクトしか出力されてないが、ソースを見にいくと
EnvironmentPlugin CaseSensitivePathsPlugin MiniCssExtractPlugin WebpackAssetsManifest の4つがプラグインとして入れられていることがわかる
それぞれ何をやってくれるプラグインなのか見ていく

  • EnvironmentPlugin

    • 名前からまんまだけど環境変数を定義するためのプラグインで、DefinePluginの定義からちょっと省略して書ける
        new webpack.EnvironmentPlugin({
            NODE_ENV: 'development', // use 'development' unless process.env.NODE_ENV is defined
            DEBUG: false,
        })
      
      ほとんどのアプリで環境変数を使うことはあると思うので、dotenvなりこのプラグインなりなんらかの仕組みは必要だと思われる
      けど今回は特に環境変数を必要としていないので、必要になったら考えることにして入れないでおく
  • CaseSensitivePathsPlugin

    • macOSではファイルシステムがcase insentive(大文字小文字を区別しない)であるのに対して、Linux環境ではcase sensitive(大文字小文字を区別する)だったりして、異なる認識をされる可能性がある(macのバージョンや、Linuxのカーネルの違いによって挙動が異なる可能性まである)
    • JSのimport分で、ファイル名がHogeComponentなのに import { Hoge } from 'hogeComponent' と書くと、mac(dev環境)ではビルドが通るのに、Linux(stg環境)ではビルドが通らない。みたいなことが起こる
    • そんな大文字小文字の間違いをエラーで教えてくれるようになるのがこのプラグインで、無駄なハマりを起こさないように入れておいた方が良さそう
  • MiniCssExtractPlugin

    • cssをjsに含めないで、個別にcssファイルとして書き出せるようになるプラグインらしい
    • フォントも画像もcssも何でもJSに入れたくない人向けのパッケージなんだろうか?
      • 他のプラグインとかもそうだけど、「何ができる」よりも「何が嬉しい」のか書いて欲しい。フロント疎いのでCSSをファイルとして分割すると何が嬉しいのかいまいちわからん
  • WebpackAssetsManifest

    • JSやCSSの元のファイル名と、ダイジェストが付与されたファイルを一致させるJSONファイルを作成するプラグイン
    • 要するにmanifest.jsonを出力してくれるもの
    • Railsでビルドしたアセットをヘルパー使って読み込む時、ヘルパーに渡されたファイル名からmanifest.jsonを探索して、ビルド済みのファイルを見つけ出す。という作業は絶対に必要そうなので、このプラグインは必須

ということでWebpackerが提供している巨大な設定を簡単にだが見て行った
基本的な作戦は自分のチームとアプリに必要な設定を書いていき、必要に応じて上記設定を参考にする感じ
長くなってきたので次の記事でwebpack.config.jsの作成と、Railsとの繋ぎ込みをやってく