Symfonyでwebpackを使いたい


内容

最近Symfony2アプリケーションにwebpackを導入したくなったのでその方法を検討する。
主な内容はwebpackかWebpack-Encoreどちらを使うかということになるのでSymfonyのバージョンはあまり関係ないです。

webpackかWebpack-Encoreか

Symfonyでwebpackを使うには、普通にwebpackを導入する(数多の記事で説明されているような)方法の他にもWebpack-Encore(アンコール)というツールを使う手もある。
http://symfony.com/doc/current/frontend.html

以下、前者の方法を単にwebpack、後者をencoreと呼ぶことにする。

Webpack-Encoreとは

Symfonyプロジェクトが作っているwebpackの薄いラッパーライブラリ。だからといってSymfony専用というわけではない。純粋なNodeパッケージなのでSymfony以外の種々のwebフレームワークで利用可能だと思う。
Symfonyのバージョン3以上のドキュメントに導入方法が書かれているが、関係なくSymfony2でも使うことができる。

これを書いている時点では週に5000くらいダウンロードされている模様。
https://www.npmjs.com/package/@symfony/webpack-encore

設定の記述

webpackの動作にはwebpack.config.jsという設定ファイルを記述する必要があるがwebpackとencoreではこの書き方が大きく異なる。

webpack

多数のプロパティを持ったjsオブジェクトとして記述。サンプルコードはwebを漁ったりwebpackのドキュメントを読みまくれば何かしら見つかる。

webpack.config.js(webpackの通常の記述)
var path = require('path');
module.exports = {
  entry: './assets/js/app.js',
  output: {
    path: path.resolve(__dirname, 'web/build'),
    filename: 'app.js'
  },
  // ...
};

encore

対してencoreは基本的なプロパティのsetterメソッドを介して設定を書いていく。
https://symfony.com/doc/3.3/frontend/encore/simple-example.html

webpack.config.js(encore用)
var Encore = require('@symfony/webpack-encore');

Encore
    .setOutputPath('web/build/')
    .setPublicPath('/build')
    .addEntry('app', './assets/js/app.js')
    // ...
;
module.exports = Encore.getWebpackConfig();

最後にexportしているEncore.getWebpackConfig()でwebpackの設定オブジェクトを生成していて、console.logで覗いてみると設定で書いた以外にもいろいろ吐き出されているのがわかる。

{ context: '/app/symfony2-webpack-example',
  entry: { app: './assets/js/app.js' },
  output:
   { path: '/app/symfony2-webpack-example/web/build',
     filename: '[name].js',
     publicPath: '/build/',
     pathinfo: true },
  module: { rules: [ [Object], [Object], [Object], [Object] ] },
  plugins:
   [ ExtractTextPlugin { filename: '[name].css', id: 1, options: [Object] },
     DeleteUnusedEntriesJSPlugin { entriesToDelete: [] },
     ManifestPlugin { opts: [Object] },
     LoaderOptionsPlugin { options: [Object] },
     NamedModulesPlugin { options: {} },
     DefinePlugin { definitions: [Object] },
     FriendlyErrorsWebpackPlugin {
       compilationSuccessInfo: [Object],
       onErrors: undefined,
       shouldClearConsole: false,
       formatters: [Array],
       transformers: [Array] },
     AssetOutputDisplayPlugin { outputPath: 'web/build', friendlyErrorsPlugin: [Object] } ],
  performance: { hints: false },
  stats:
   { hash: false,
     version: false,
     timings: false,
     assets: false,
     chunks: false,
     maxModules: 0,
     modules: false,
     reasons: false,
     children: false,
     source: false,
     errors: false,
     errorDetails: false,
     warnings: false,
     publicPath: false },
  resolve:
   { extensions: [ '.js', '.jsx', '.vue', '.ts', '.tsx' ],
     alias: {} } }

逆に言えばwebpackの設定はこういうのを自分で書いていく必要がある。encoreの設定は汎用的になっているので自分のプロジェクトには不要なものも多数含まれることになると思う。
またencoreのAPIにない設定を書きたい場合はこのオブジェクトを直接いじることもできる。
https://symfony.com/doc/3.3/frontend/encore/advanced-config.html

どちらを使うか

設定はwebpackの書き方で

上記のとおりencoreは裏で「だいたいこれ必要でしょ?」って感じの設定をデフォルトでやってくれている。だから簡潔に少数のsetterメソッドで設定を書いていくことができるわけだけど、そういうのはwebpack初心者にとってもwebpackに慣れた人にとってもブラックボックスになりやすい。例えばビルドでエラーが出たときその原因がencoreの設定ファイルから読み取れるだろうか?
幸いencoreは通常のwebpackの書き方をしても動作してくれるので、それなら後でencoreを捨てたくなっても何も問題ない。

encore使う意味ないのでは

encore最大の特徴は設定を簡潔に書けることだと思うので、それをしないとなるとencoreを使う意味はないように思えるし、実際のところないと思う。
強いてencoreを使用する理由をつけるなら以下の点だろうか。

さっと試したい場合

とりあえずwebpackを触ってみたいという場合はyarn add @symfony/webpack-encore --devでめぼしいパッケージをだいたい一緒にインストールしてくれるので楽。

dev / prodが簡単に判別できる

webpackでdev, prodの判別をするのは少し手間がかかる。
公式で推奨されているのは以下の3ファイルを用意してコマンドのパラメータでdev, prodのどちらかを指定するという方法。

  • dev用の設定ファイル
  • prod用の設定ファイル
  • 共通の設定ファイル

またはコマンドに--env.dev, --env.prodのようなパラメータをつけて設定ファイル側で受け取るという方法もある。

いずれにしても面倒だけれどencoreでは簡単に取得することができる。これは便利。

var Encore = require('@symfony/webpack-encore');
Encore.isProduction();

cache busting versioning

余談にはなるがSymfony2でwebpackがビルドしたファイルにcache busting versioning(index-1u2b42.jsのようにハッシュ値をつけて配信する仕組み)をしたい場合は少しワークアラウンドをする必要がありそう。

Symfony3以上の場合は設定でwebpackが吐くmanifest.jsonファイルを指定すればOKな仕組みがある。
https://symfony.com/doc/3.3/frontend/encore/versioning.html

残念ながらSymfony2にはこの仕組みがないのでAsseticのフローに乗せる必要がある。
つまりfilterなしでAsseticに記述してハッシュ値だけつけてもらうという作戦。
Symfony3にバージョンアップしたらこれは殲滅する。

{% javascripts 'build/bundle.js'
    output="assets/main.js" %}
    <script src="{{ asset_url }}"></script>
{% endjavascripts %}