gulp.distをCopyWebpackPluginで再現する


近年ただのモジュールバンドラであるはずのwebpackをgulpのように扱う傾向があるように感じます。
その流行に乗っかってgulpをwbpackに完全replaceしてみたのですが、htmlやimageを /dist などにコピーするという単純な処理に手間取ったので、解決法をここに記しておこうと思います。

ディレクトリ構成

root
    webpack.config.js
    - dist
    - src
        index.html
        - under
            - index.html
        - assets
            - images
                - test.png

目標

  • これらのファイルを構造を維持しつつ、distへコピーする.
  • webpack-dev-serverを使いながらでも、ファイルが更新されるようにする.
  • watch中にファイルが新しく追加されてもコピーされるようにする.

ライブラリのインストール

$ yarn add -D webpack webpack-cli webpack-dev-server copy-webpack-plugin write-file-webpack-plugin

webpackwebpack-cliwebpack-dev-server の説明は割愛します。
copy-webpack-plugin が今回の主役です。名前の通り、webpackでの単純なファイルコピーを実現します.

また、 webpack-dev-server を使うなら write-file-webpack-plugin もあると楽ちんです。
通常 webpack-dev-server の起動時、更新されたファイルはメモリ上でのみ展開されるのですが、 write-file-webpack-plugin を使うことによって、実ファイルが更新されるようになるのです。

webpack.config.js

const CopyWebpackPlugin = require('copy-webpack-plugin')
const path = require('path');
const webpack = require('webpack');
const WriteFilePlugin = require('write-file-webpack-plugin');


module.exports = {
  mode: process.env.NODE_ENV || "development",
  watch: false,
  watchOptions: {
    ignored: /node_modules/
  },
  devtool: 'source-map',
  output: {
    path: path.join(__dirname, './dist/assets'),
    publicPath: '/assets',
    filename: 'js/[name].js'
  },
  plugins: [
    new CopyWebpackPlugin(
      [
        {
          from: '',
          to: '../',
          ignore: [
            '!*.html'
          ]
        },
      ],
      { context: 'src' }
    ),
    new CopyWebpackPlugin(
      [
        {
          from: '',
          to: 'images/',
        },
      ],
      { context: 'src/assets/images' }
    ),
    new WriteFilePlugin(),
  ],
  devServer: {
    contentBase: path.resolve(__dirname, 'dist'),
    port: 8080,
  },
};

こうなります。

これだけの目的なら output のpathはそうじゃなくてよくね?と思われそうですが、この webpack.config.js はjsやcssをコンパイルする想定で書いています。

今回は見やすいようにjsやcssのloaderについての記述は省いています。

CopyWebpackPluginの設定

まずやりがちなのが、fromに src/**/*.html とか書いちゃうことですが、そのように書くとコピーされた時 dist/src/index.html のように from のパスをすべて保ったまま吐き出されてしまいます。
最悪です。

さらに注意したいのが、 from**/*.html のようにファイル形式を指定してしまうと、watc中に新規でファイルを追加した時にwebpackの監視対象から外れてしまいます。
その対策として、 from'' とだけ書き、 contextto で何を対象とし、どこにコピーするかを記述する必要があります。

あと to についての補足ですが、 tooutputのpathの情報を引き継ぎます。

これらを踏まえた上で、画像とhtmlのコピーを例にとって設定方法を説明していきます。

例1: 画像のコピー

source: src/assets/images/**/*
dist: dist/assets/images/**/*

まず contextを設定しましょう。contextとは、fromの基準をどのパスとするかの設定です。
画像は dist/assets/images に入る想定です。
そしてfromが '' ということは、contextは src/assets/images となりますね。

そして、toは output.pathの dist/assets を基準パスとした時、 images/ になりますね。

つまり画像をコピーするための設定は

new CopyWebpackPlugin(
  [
    {
      from: '',
      to: 'images/',
    },
  ],
  { context: 'src/assets/images' }
)

となります。

例2: htmlのコピー

source: src/**/*.html
dist: dist/**/*.html

まずconextを考えましょう. fromが '' ということは、 contextは src となります。
そして、to は output.pathの dist/assets を基準パスとした時、 ../ になります。

ここまではいいのですが、このままだとどこにもファイル形式についての指定がないので、 src の中身が そのまま dist へ コピーされてしまいます。

そうではなく、htmlのみをコピーしたいので、 fromではないどこかにファイル形式の指定をする必要があります。

そこで、 ignore の出番です。
ignore は無視するファイルを glob形式で記述できるのですが、ここで html以外のような指定をすると、htmlだけがコピーされるようになります。

それらを合わせた結果、このような設定になります。

new CopyWebpackPlugin(
  [
    {
      from: '',
      to: '../',
      ignore: [
        '!*.html'
      ]
    },
  ],
  { context: 'src' }
)

このように、少し工夫した設定をすることで、無事gulp.distをCopyWebpackPluginで再現することができました。
果たしてこんなに頑張ってまでgulpから脱却する必要があるのかはいささか疑問ですが、それはそれとしましょう。