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. 独自機能とかないのでまあ、がんばります(がんばりましょう)(一緒にメンテしてください)

Other Works
2024-12-01 Sun.
OpenAPI Spec を出力できる DSL、TypeSpec の実践例
- ドワンゴ教育サービス開発者ブログ

2024-11-16 Sat.
型付き API リクエストを実現するいくつかの手法とその選択
- TSKaigi Kansai 2024

2024-09-10 Tue.
corepack が標準同梱じゃなくなる未来、 mise でパッケージマネージャを管理する
- Zenn

2024-09-10 Tue.
言語環境の管理は *env や *vm を超えて、 mise へ
- Zenn

2024-06-28 Fri.
TypeSpec を使い倒してる
- Kyoto.js 22

2024-05-11 Sat.
Powerfully Typed TypeScript
- TSKaigi 2024

2024-05-10 Fri.
pnpm の node_modules を探検して理解しよう
- ドワンゴ教育サービス開発者ブログ

2024-03-17 Sun.
neverthrow で局所的に Result 型を使い、 try-catch より安全に記述する
- Zenn

2023-12-20 Wed.
レガシーブラウザ向けのビルドオプションを剪定する
- ドワンゴ教育サービス開発者ブログ

2023-05-26 Fri.
Next.js で dynamic import を使い Client だけで動かす Component を実現する
- Zenn

2023-05-02 Tue.
Node.js でファイル名から拡張子を取り除く/取り出すために path.parse を使う
- Zenn

2023-02-27 Mon.
WSL2 で外部からアクセス可能にするために bridge mode を有効にする
- Zenn

2023-01-26 Thu.
init.vim & dein から init.lua & lazy.nvim へ、シンプル設定で移行した
- Zenn

2023-01-13 Fri.
kindle の本をブクログ形式の csv でエクスポートする@2023初春
- Zenn

2023-01-10 Tue.
自宅サーバの移設に際して docker から nerdctl に移行した
- Zenn

2023-01-10 Tue.
自宅サーバを rootless に移行した際のトラブル対応
- Zenn

2021-11-11 Thu.
並列実行した Promise で throw されても全てハンドルしたいときの方法(allSettled, finally, etc...)
- Zenn