-ao- ramune blog

©2022 unio / GO2直営からふるラムネ
2018年12月18日 (更新:2019年07月28日)

Webpack Encoreをwebpack4に書き直す

Webpack Encoreを外す

この記事は Symfony Advent Calendar 2018 の18日目です。

今年2018年11月で、ようやく Webpack Encore がwebpack4に対応しました。 今回のようにWebpack Encoreは本家webpackの追従にタイムラグがありいろいろ歯痒い思いをしたので、 これを期に(?)ピュアな webpack.config.js に書き換えてみました。 今回書き換えるWebpack Encoreのバージョンは 0.22.2 です。

で、失敗したやつ

さっそくですが、一番最初に試して失敗した方法です。 Webpack Encoreはざっくり言うとwebpackコンフィグビルダーなので、 Encore.getWebpackConfig()の結果をwebpack.config.jsに書けばいけるはずです。

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

                    Encore
                        ...
                    ;

                    console.log(JSON.stringify(Encore.getWebpackConfig()));
                
            

Encoreで作成した設定のJSONをデシリアライズするだけでいけると思いましたが、 modulepluginsのオブジェクトがうまく展開されずに撃沈しました。

Webpack Encore vs. webpack4

仕方がないので、Webpack Encoreに用意されている関数を調べながら書き換えていきます。 下記はWebpack Encoreで書いた webpack.config.js のサンプルです。 たぶんよく使われるであろう設定をいくつか抜粋しました。

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

                    Encore
                        .setOutputPath('public/build/')
                        .setPublicPath('/build')
                        .cleanupOutputBeforeBuild()
                        .addEntry('hoge', './assets/hoge/index.js')
                        .enableSourceMaps(!Encore.isProduction())
                        .enableSingleRuntimeChunk()
                        .splitEntryChunks()
                        .autoProvidejQuery()
                        .enablePostCssLoader()
                        .enableSassLoader()
                        .enableVueLoader()
                    ;

                    module.exports = Encore.getWebpackConfig();
                
            

書き換え結果

先程のWebpack Encore設定を書き換えたものが以下です。 サンプルのEncoreのコードに比べると(当然ですが)記述量はかなり増えました。 実際は不要な設定もあるかと思いますが、一旦Webpack Encoreとほぼ同じ動作をする形に記述しています。 それぞれの設定の意味は、各パッケージサイトやwebpack本家サイトをご覧いただくとして、 いくつか注意点を抜粋して説明いたします。

webpack.config.js
                
                    // 以下はdevelopmentビルド時のwebpack設定です。
                    // 実際は NODE_ENV や、Symfony4で定義した .env を使って、設定を切り替える仕組みが必要です。

                    const path = require('path');

                    // 以下は@symfony/webpack-encoreをremoveした際に個別でaddが必要
                    const webpack = require('webpack');
                    const MiniCssExtractPlugin = require('mini-css-extract-plugin');
                    const ManifestPlugin = require('webpack-manifest-plugin');
                    const CleanWebpackPlugin = require('clean-webpack-plugin');
                    const VueLoaderPlugin = require('vue-loader/lib/plugin');
                    const AssetsWebpackPlugin = require('assets-webpack-plugin');

                    module.exports = {
                        mode: 'development',
                        context: __dirname,
                        // Encore.addEntryの設定
                        entry: {
                            hoge: './assets/hoge/index.js'
                        },
                        // Encore.setOutputPathとEncore.setPublicPathの設定
                        output: {
                            path: path.resolve(__dirname, 'public/build'),
                            filename: '[name].js',
                            publicPath: '/build/',
                            pathinfo: true
                        },
                        module: {
                            rules: [
                                // Babel7適用ルール
                                // v0.22.2ではデフォルト適用される
                                {
                                    test: /\.jsx?$/,
                                    exclude: /(node_modules|bower_components)/,
                                    use: {
                                        loader: 'babel-loader',
                                        options: {
                                            cacheDirectory: true,
                                            presets: ['@babel/preset-env'],
                                            plugins: ['@babel/plugin-syntax-dynamic-import']
                                        }
                                    }
                                },
                                // CSS適用ルール
                                // Encore.enablePostCssLoaderの設定
                                {
                                    test: /\.css$/,
                                    use: [
                                        path.resolve(__dirname, 'node_modules/mini-css-extract-plugin/dist/loader.js'),
                                        {loader: 'css-loader', options: {minimize: false, sourceMap: true, importLoaders: 1}},
                                        {loader: 'postcss-loader', options: {sourceMap: true}}
                                    ]
                                },
                                {
                                    test: /\.(png|jpg|jpeg|gif|ico|svg|webp)$/,
                                    loader: 'file-loader',
                                    options: {name: 'images/[name].[hash:8].[ext]', publicPath: '/build/'}
                                },
                                {
                                    test: /\.(woff|woff2|ttf|eot|otf)$/,
                                    loader: 'file-loader',
                                    options: {name: 'fonts/[name].[hash:8].[ext]', publicPath: '/build/'}
                                },
                                // Sass、Scss適用ルール
                                // Encore.enablePostCssLoaderとEncore.enableSassLoaderの設定
                                {
                                    test: /\.s[ac]ss$/,
                                    use: [
                                        path.resolve(__dirname, 'node_modules/mini-css-extract-plugin/dist/loader.js'),
                                        {loader: 'css-loader', options: {minimize: false, sourceMap: true, importLoaders: 1}},
                                        {loader: 'postcss-loader', options: {sourceMap: true}},
                                        {loader: 'resolve-url-loader', options: {sourceMap: true}},
                                        {loader: 'sass-loader', options: {sourceMap: true}}
                                    ]
                                },
                                // Vue.js適用ルール
                                // Encore.enableVueLoaderの設定
                                {
                                    test: /\.vue$/,
                                    loader: 'vue-loader'
                                }
                            ]
                        },
                        plugins: [
                            new MiniCssExtractPlugin({filename: '[name].css', chunkFilename: '[name].css'}),
                            new ManifestPlugin({
                                publicPath: null,
                                basePath: 'build/',
                                fileName: 'manifest.json',
                                transformExtensions: /^(gz|map)$/i,
                                writeToFileEmit: true,
                                seed: {},
                                filter: (file) => {
                                    return (!file.isChunk || !['_tmp_shared', '_tmp_copy'].includes(file.chunk.id));
                                },
                                map: null,
                                generate: null,
                                sort: null
                            }),
                            new webpack.LoaderOptionsPlugin({
                                debug: true,
                                options: {
                                    context: __dirname,
                                    output: {path: path.resolve(__dirname, 'public/build')}
                                }
                            }),
                            new webpack.ProvidePlugin({'$': 'jquery', jQuery: 'jquery', 'window.jQuery': 'jquery'}),
                            // Encore.cleanupOutputBeforeBuildの設定
                            new CleanWebpackPlugin(['**/*'], {
                                root: path.resolve(__dirname, 'public/build'),
                                verbose: false,
                                allowExternal: false,
                                dry: false
                            }),
                            new webpack.DefinePlugin({'process.env': {NODE_ENV: 'development'}}),
                            new VueLoaderPlugin(),
                            new AssetsWebpackPlugin({
                                path: path.resolve(__dirname, 'public/build'),
                                filename: 'entrypoints.json',
                                includeAllFileTypes: true,
                                entrypoints: true,
                                // Webpack Encoreで定義された関数をそのままコピペ
                                processOutput: (assets) => {
                                    for (const entry of ['_tmp_shared', '_tmp_copy']) {
                                        delete assets[entry];
                                    }
                                    delete assets.entrypoints;
                                    for (const asset in assets) {
                                        for (const fileType in assets[asset]) {
                                            if (!Array.isArray(assets[asset][fileType])) {
                                                assets[asset][fileType] = [assets[asset][fileType]];
                                            }
                                        }
                                    }
                                    return JSON.stringify({
                                        entrypoints: assets
                                    }, null, 2);
                                }
                            }),
                        ],
                        // splitChunks
                        // Encore.enableSingleRuntimeChunkの設定
                        optimization: {
                            namedModules: true,
                            runtimeChunk: 'single',
                            splitChunks: {chunks: 'all'}
                        },
                        devtool: 'inline-source-map',
                        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,
                            builtAt: false
                        },
                        resolve: {
                            extensions: ['.wasm', '.mjs', '.js', '.json', '.jsx', '.vue', '.ts', '.tsx'],
                            alias: {'vue$': 'vue/dist/vue.esm.js'}
                        },
                        externals: {}
                    };
                
            

@symfony/webpack-encore依存を切り離す

Webpack Encoreが独自に提供しているプラグインを切り離します。

クラス名 Encore独自機能 説明
FriendlyErrorsWebpackPlugin NO Encore独自のフィルタを使っているため除外
AssetOutputDisplayPlugin YES FriendlyErrorsWebpackPluginと連携して、asset情報を表示するプラグイン
DeleteUnusedEntriesJSPlugin YES addStyleEntryでcssを登録する際に、webpack本体で生成される同名のjsを削除するプラグイン

最後に@symfony/webpack-encoreを削除しますが、本家 package.json のdependenciesに記載されている各種パッケージを追加するのを忘れないでください。 loader名やpluginsの設定を参考に、必要なパッケージを個別にaddします。

Webpack Encoreを削除する

webpack-encore-bundlecomposer removeで削除すると webpack.config.js も一緒に削除されてしまいます。 削除の前に必ずwebpack.config.jsのバックアップを行ってください。

まとめ

Webpack Encoreを使うと簡単に webpack.config.js を用意できます。 しかしあくまでもラッパーのため、本家バージョンアップへの追従に時間がかかる、複雑な設定になると結局本家webpackを調べないといけない、などのデメリットがあります。 もし特段の事情がなければ Webpack Encore ではなくピュアなwebpack構成でプロジェクトを進めたほうが後々つぶしが効くと思います。