LoginSignup
5
2

More than 1 year has passed since last update.

【webpack】file-loaderで画像などの出力先を条件分岐させる方法

Last updated at Posted at 2021-02-28

webpackでfile-loaderとhtml-loaderを使用しており、バンドルする際に画像と動画でoutputPathpublicPathを分岐させたかった。
それを実現するためにやったことのまとめ。

※ webpackやローダーのインストール方法や説明は特にしていません。

実行環境

  • macOS Catalina v10.15.7
  • node.js v12.18.1
  • npm v6.14.5

webpackなどのバージョン

  • webpack v4.43.0
  • webpack-cli v3.3.11
  • file-loader v6.0.0
  • html-loader v1.1.0
  • html-webpack-plugin v4.5.1

やりたいこと

file-uploaderとhtml-loaderを使用して、htmlファイルで使用する画像もwebpackでバンドルしている。
バンドル前は画像と動画でディレクトリを分けていたが、バンドル後には一つのディレクトリにまとめて出力されていた。
これを、バンドル前のディレクトリ構成と同じ状態で出力したい。

これを
webpack-img01.jpeg

こうしたい
webpack-img02.jpeg

ディレクトリ構成

以下のように出力したい

.
├── package.json
├── public // 出力先
│   ├── index.html
│   ├── js
│   │   └── bundle.js
│   ├──images
│   │   └── 画像ファイル
│   └──videos
│        └─ 動画ファイル
│
├── src // 編集ファイルのディレクトリ
│   ├── index.html
│   ├── js
│   │   └── app.js
│   ├──images
│   │   └── 画像ファイル
│   └──videos
│        └─ 動画ファイル
└── webpack.config.js

解決法

1.当初のwebpack設定ファイル

webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin'); // プラグイン読み込み

module.exports = {
    mode: 'development',
    entry: './src/js/app.js', // エントリーポイント
    output: { // 出力設定
        path: path.resolve(__dirname, 'public'), // 絶対パス
        filename: 'js/[name].[contenthash].bundle.js', // 出力するファイル名
    },
    module: { // ローダー設定
        rules: [
            {
                test: /\.(jpe?g|gif|png|svg|mp4)$/, // 処理対象とするファイルの指定
                use: [
                    {
                        loader: 'file-loader',
                        options: {
                            name: '[name].[hash].[ext]',
                            outputPath: 'images', // 出力先
                            publicPath: './images' // htmlから読み込まれる際のパス
                        },
                    },
                    'image-webpack-loader',
                ]
            },
            {
                test: /\.html$/, // 処理対象とするファイルの指定
                loader: 'html-loader',
            },
        ]
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: './src/index.html', // テンプレートとなるHTMLを指定(画像バンドルのため)
        }),
    ]
}

file-loaderの設定でoutputPath: 'images'としているので、出力するとimagesディレクトリに画像ファイルも動画ファイルもまとまって出力される。これを画像はimagesディレクトリに、動画はvideosディレクトリに分岐させることが今回の目標。

また、publicPath: './images'としているので、htmlに挿入される際に、このままでは画像ファイルや動画ファイルへのパスが./images/ファイル名となってしまうので、仮にimagesvideosディレクトリに出力を分岐できたとしても、videos配下の動画ファイルを読み込目なくなるので、publicPathも調整する必要がある。

2.outputPathとpublicPathの分岐

webpackの公式ドキュメントのfile-loaderについてのページを見ると、outputPathpuclicPathのとり得る値は、Type: String|Functionとなっているので、関数を使用できることがわかる。
なので、outputPathpublicPathの値には、対象のファイルが置かれているディレクトリに応じて、出力先を分ける処理を追加すればよい。

イメージ

options: {
    name: '[name].[hash].[ext]',
    outputPath: function() {
        if (処理する画像がimagesディレクトリにある) {
            return 'images'
        } else {
            return 'videos'
        }
    },
    publicPath: function() {
        if (処理する画像がimagesディレクトリにある) {
            return './images'
        } else {
            return './videos'
        }
    },
},

2.修正後のwebpack設定ファイル

webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin'); // プラグイン読み込み

module.exports = {
    mode: 'development',
    entry: './src/js/app.js', // エントリーポイント
    output: { // 出力設定
        path: path.resolve(__dirname, 'public'), // 絶対パス
        filename: 'js/[name].[contenthash].bundle.js', // 出力するファイル名
    },
    module: { // ローダー設定
        rules: [
            {
                test: /\.(jpe?g|gif|png|svg|mp4)$/, // 処理対象とするファイルの指定
                use: [
                    {
                        loader: 'file-loader',
                        options: {
                            name: '[name].[hash].[ext]',
                            outputPath:(url, resourcePath) => {
                                // 処理するファイルの絶対パスに「images」にマッチする文字列があるか判定
                                if (/images/.test(resourcePath)) { 
                                    return 'images/' + url;
                                } else {
                                    return 'videos/' + url;
                                }
                            },
                            publicPath:(url, resourcePath) => {
                                // 処理するファイルの絶対パスに「images」にマッチする文字列があるか判定
                                if (/images/.test(resourcePath)) {
                                    return './images/' + url;
                                } else {
                                    return './videos/' + url;
                                }
                            },
                        },
                    },
                    'image-webpack-loader',
                ]
            },
            {
                test: /\.html$/, // 処理対象とするファイルの指定
                loader: 'html-loader',
            },
        ]
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: './src/index.html', // テンプレートになるHTML
        }),
    ]
}

ドキュメントに従って、outputPathpublicPathの値に関数を記述して、if文でoutputPathとpublicPathを分岐させることができた。
注意点としては、デフォルトでoutputPathやpabulicPathを指定する際には、ディレクトリ名のみを設定すればよいが、今回の場合は、ファイル名まで指定しないと正しく処理されなかった。

ここでの関数は、url, resourcePath, contextと3つの引数を取ることができる。

実際に引数を確認してみたところ

  • url => ファイル名
  • resourcePath => ファイルまでの絶対パス
  • context => プロジェクトのディレクトリまでの絶対パス

という内容が渡ってきていた。
この引数を駆使すれば、他にも目的に応じた処理をすることができそう。

5
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
2