blog.euxn.me

Webpacker でやっていけるか!? Frontend on Rails

2018-05-22 Tue.

2018/05/23 追記

entry 作る箇所だけ小さく別ライブラリにしました。 euxn23/webpacker-entry

はじめに

この内容は meguro.es#15 での発表を元に内容を整理したものです。

リポジトリ: euxn23/webpacker-pure-config

スライド: slideshare

Rails で JS つらかった問題

  • 標準設定で ES6 が使えない
  • JS ライブラリの gem 移植 hoge-rails の本体追従が遅い
  • gem 経由でインストールされる JS / CSS ライブラリ
  • Sprockets という独自のモジュールバンドラ

Webpacker の誕生

  • Rails 5.1 で標準搭載
  • Webpacker を Rails に合わせて使えるように作られた Ruby / JS ライブラリ
  • 隠蔽されたゼロコンフィグの Webpack
  • npm エコシステムベースのビルドフローが使える
  • Rails 向けの View Helper

普通の JS 開発ができる!!

本当に?

  • 環境変数で分岐するモジュール(Undocumented)
1const createEnvironment = () => {
2 const path = resolve(__dirname, "environments", `${nodeEnv}.js`);
3 const constructor = existsSync(path) ? require(path) : Environment;
4 return new constructor();
5};
  • 独自の config 記法
1// Set nested object prop using path notation
2environment.config.set("resolve.extensions", [".foo", ".bar"]);
3environment.config.set("output.filename", "[name].js");
4
5// Merge custom config
6environment.config.merge(customConfig);
7
8// Delete a property
9environment.config.delete("output.chunkFilename");
  • webpack v4 への未追従

Webpacker makes it easy to use the JavaScript pre-processor and bundler webpack 3.x.x+ to manage application-like JavaScript in Rails. webpack-entry.png

webpacker-npm.png

何がつらいか

  • 独自の設定構文を覚える必要がある
  • 既存の webpack.config をそのまま使えない
  • Undocumented な挙動
  • Webpack 本体への追従の遅さ

webpacker.js 危険なのでは?

脱 webpacker.js

config の要件

  • ruby 側の webpacker のデフォルト設定で動くようにする
    • JS の配置ディレクトリ(public/packs or public/packs-test)
    • キャッシュ回避のためのハッシュ(Rails の作法) webpack-manifest.png
    • ManifestPlugin ベースの key-value の entry 設定 webpack-entry.png

{ [relativePath]: absolutePath }

実装

1const readdirRecursivelySync = (baseDir, options = {}) =>
2 fs
3 .readdirSync(baseDir, options)
4 .map(
5 path =>
6 fs.statSync(`${baseDir}/${path}`).isDirectory()
7 ? readdirRecursivelySync(`${baseDir}/${path}`, options)
8 : `${baseDir}/${path}`
9 )
10
11const flatten = paths => {
12 const flattened = paths.reduce(
13 (prev, next) =>
14 Array.isArray(next) ? [...prev, ...next] : [...prev, next],
15 []
16 )
17 return flattened.filter(Array.isArray).length > 0
18 ? flatten(flattened)
19 : flattened
20}
21
22{
23 mode: 'development',
24 devtool: 'cheap-module-source-map',
25 entry: flatten(readdirRecursivelySync(baseDir))
26 .filter(p => extensions.includes(extname(p)))
27 .reduce(
28 (prev, next) => ({
29 ...prev,
30 [relative(
31 '.',
32 `${dirname(relative(baseDir, next))}/${basename(next, extname(next))}`
33 )]: next
34 }),
35 {}
36 ),
37
38...

煩雑

ライブラリ化

euxn23/webpacker-pure-config

特徴

  • webpack 準拠の config で実装
    • 公式 Docs 準拠に読み書き可能
1return {
2 mode: 'development',
3 devtool: 'cheap-module-source-map',
4 entry: mapPathArrayToEntryHash(
5 readdirRecursivelySyncFlatten(baseDir),
6 baseDir,
7 extensions
8 ),
9 output: {
10 filename: '[name]-[chunkhash].js',
11 chunkFilename: '[name]-[chunkhash].chunk.js',
12 hotUpdateChunkFilename: '[id]-[hash].hot-update.js',
13 path: resolve('./public/packs'),
14 publicPath: '/packs/'
15 },
16 module: {
17 strictExportPresence: true,
18 rules
19 },
20 plugins: [
21 new ManifestPlugin({ publicPath: '/packs/', writeToFileEmit: true })
22 ],
23...
  • 暗黙的な NODE_ENV 依存無し(環境別に提供)
1const { dev } = require("webpacker-pure-config");
2module.exports = dev();
  • Config Object が export されるだけなので Rest Spread で拡張可能
1module.exports = {
2 ...baseConfig,
3 plugins: [
4 ...baseConfig.plugins,
5 new webpack.DefinePlugin({
6 "process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV),
7 }),
8 ],
9};
  • Rails / webpacker.yml 依存無し(ゼロコンフィグ)

Q. Webpack に追従できるの?

A. 独自機能とかないのでまあ、がんばります(がんばりましょう)(一緒にメンテしてください)