Webpack 4.0で静的サイトをつくる2020-06

[Webpackを頑張って設定して、すごい静的サイトジェネレータとして使おう](https://qiita.com/toduq/items/2e0b08bb722736d7968c)をコピペしたのだが動かない。webpackもなんもわからんが2020年でも動く書き方にした。

### 主な変更点


- `cssnano`の利用(css-loaderにminimizeのオプションが[なくなった](http://www.kantenna.com/pg/2018/09/webpack_css-loader.php)ので）
- MiniCssExtractPluginの利用(`extract-text-webpack-plugin`をCSSに使う方法がwebpack 4.0では非推奨となったため([SO](https://stackoverflow.com/questions/50202908/entrypoint-undefined-extract-text-webpack-plugin-output-filename-in-webpack-4), [webpack@4 でCSSを抽出する際は mini-css-extract-plugin を使う](https://numb86-tech.hatenablog.com/entry/2018/10/24/221130)))
- sass→scssにしたり。これは人による
- scss内で`background-image: url(img/image.jpg)`のように`url()`を使いたい場合に画像はスキップするようにした
- 静的ページを`src/pages/index.pug`などに、それらが含む細かい部品を`src/includes/_header.pug`などに書くようにした。そして前者は`apply-loader + pug-loader`、後者は`pug-loader`だけをあてるようにした。参考にした[Github issues](https://github.com/pugjs/pug-loader/issues/105)。できれば前者は`src/index.pug`などとしたいのだができていない…。

今のところ動いているがなんもわかっていないので致命的な間違いがあるかも


```webpack.config.js
// npm i apply-loader autoprefixer @babel/core @babel/preset-env babel-loader copy-webpack-plugin css-loader extract-text-webpack-plugin globule node-sass postcss postcss-loader pug pug-loader sass-loader style-loader webpack webpack-dev-server cssnano
const webpack = require('webpack')
const path = require('path')
const globule = require('globule')
const CopyPlugin = require('copy-webpack-plugin')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')

const opts = {
  sourceDirectory: path.join(__dirname, 'src'),
  destinationDirectory: path.join(__dirname, 'dist'),
}

const extensionsToConvert = {
  pug: 'html',
  sass: 'css',
  js: 'js',
}

// list files to transpile
// files starting with _ won't be exported: instead, they are treated as files that are imported from elsewhere
const files = {}
Object.keys(extensionsToConvert).forEach((from) => {
  const to = extensionsToConvert[from]
  globule
    .find([`**/*.${from}`, `!**/_*.${from}`], { cwd: opts.sourceDirectory })
    .forEach((filename) => {
      files[
        filename.replace(new RegExp(`.${from}$`, 'i'), `.${to}`)
      ] = path.join(opts.sourceDirectory, filename)
    })
  console.log(files)
})

const pugLoader = [
  'apply-loader',
  { loader: 'pug-loader', options: { pretty: true } },
]

const sassLoader = [
  { loader: 'css-loader' },
  {
    loader: 'postcss-loader',
    options: {
      ident: 'postcss',
      plugins: [
        require('cssnano')({ preset: 'default' }),
        require('autoprefixer')(),
      ],
    },
  },
  { loader: 'sass-loader' },
]

const jsLoader = {
  loader: 'babel-loader',
  query: {
    presets: ['@babel/preset-env'],
  },
}

const config = {
  context: opts.sourceDirectory,
  entry: files,
  output: {
    filename: '[name]',
    path: opts.destinationDirectory,
  },
  module: {
    rules: [
      {
        test: /\.pug$/,
        exclude: path.resolve(__dirname, 'src/includes'),
        use: ExtractTextPlugin.extract(pugLoader),
      },
      {
        test: /\.pug$/,
        exclude: path.resolve(__dirname, 'src/pages'),
        use: 'pug-loader',
      },
      {
        test: /\.scss$/,
        oneOf: [
          {
            // pugから `require('./hoge.sass?inline')` のように呼ばれた時は、ExtractTextPluginをかけない
            resourceQuery: /inline/,
            use: sassLoader,
          },
          {
            // それ以外の時は、単純にファイルを生成する
            use: [MiniCssExtractPlugin.loader, ...sassLoader],
          },
        ],
      },
      {
        test: /\.js$/,
        exclude: /node_modules(?!\/wepack-dev-server)/,
        use: jsLoader,
      },
      {
        test: /\.(png|jpe?g|gif)$/i,
        exclude: /node_modules/,
        use: [
          {
            loader: 'url-loader',
            options: {
              limit: 8192,
            },
          },
        ],
      },
    ],
  },
  plugins: [
    new ExtractTextPlugin('[name]'),
    new MiniCssExtractPlugin({ filename: 'style.css' }),
    new CopyPlugin({
      // extensionsToConvertに含まれていないファイルは、単純にコピーする
      patterns: [
        {
          from: '**/*',
          globOptions: {
            dot: true,
            ignore: Object.keys(extensionsToConvert).map(
              (extension) => `*.${extension}`
            ),
          },
        },
      ],
    }),
    new webpack.DefinePlugin({
      'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
    }),
  ],
  devServer: {
    contentBase: opts.destinationDirectory,
    watchContentBase: true,
  },
}

if (process.env.NODE_ENV === 'production') {
  config.plugins = config.plugins.concat([
    new webpack.optimize.UglifyJsPlugin(),
    new webpack.optimize.OccurrenceOrderPlugin(),
    new webpack.optimize.AggressiveMergingPlugin(),
  ])
}

module.exports = config


```