Webpack4でjsとは別に複数CSSファイルを出力する

やりたいこと

外部CSSフレームワークのCSSを自分のjsにbundleせずに読み込みたい。

例えばよくあるのがBootstrapのCSSを読み込んで、かつ自分のReactコンポーネントではcss-modulesreact-css-modulesを使いたいという場合の忘備録。

  1. 必要なものをインストールする
npm install --save webpack webpack-cli css-loader extract-text-webpack-plugin@next react-css-modules css-modules
  1. vendorのscssファイルをまとめたものを用意する。
    /vendor/css/import.scss
  2. 自分のscssファイルは各自コンポーネントで読み込む。
    src/index.jsとかでimport styles from './index.scss';で使う。
    e.g.
import React from 'react';
import ReactDOM from 'react-dom';
import styles from './index.scss';

class App extends React.Component {
  render() {
    return (
      <div className={styles.main}>
        <h1>Hello React World</h1>
      </div>
    );
  }
}
ReactDOM.render(<App />, document.querySelector('#main'));
  1. webpackのentryとoutputを編集。
  entry: {
    bundle: './src/index.js',
    vendor: './vendor/css/import.scss',
  },
  output: {
    path: dist,
    filename: '[name].js',
  },
  1. cssのruleを設定する。vendorのcssはmodulesをfalseにして、extract-text-webpack-plugin でjsとcssを分割する。
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const extractCSS = new ExtractTextPlugin({ filename: '[name].css', allChunks: true });
// bundle.jsのcss用の設定
const bundleSassRule = {
  test: /\.(css|scss)$/,
  exclude: fs.realpathSync('./vendor'),
  use: extractCSS.extract({
    fallback: 'style-loader',
    use: [
      {
        loader: 'css-loader',
        options: {
          importLoaders: 1,
          modules: true,
          url: true,
          localIdentName: '[name]__[local]--[hash:base64:5]',
        },
      },
      {
        loader: 'sass-loader',
        options: {
          sourceMap: true,
          outputStyle: 'expanded',
          sourceComments: true,
          indentWidth: 2,
        },
      },
    ],
  })
};
// vendorのcssはmodules化しない
const vendorSassRule = {
  test: /\.(css|scss)$/,
  include: fs.realpathSync('./vendor'),
  use: extractCSS.extract({
    fallback: 'style-loader',
    use: [
      {
        loader: 'css-loader',
      },
      {
        loader: 'sass-loader',
        options: {
          sourceMap: true,
          outputStyle: 'expanded',
          sourceComments: true,
          indentWidth: 2,
        },
      },
    ],
  })
};
module.exports = {
  module: {
    rules: [jsRule, bundleSassRule, vendorSassRule ...],
  },
};

これで以下のファイルが出力されるはずです。

bundle.js
bundle.css
vendor.js
vendor.css

あとはhtmlでbundle.jsとbundle.css、vendor.cssを読み込めばOK。

その他

entry で vendor: './vendor/css/import.scss',を指定しているのにvendor.jsができちゃう問題はissueにも上がっていますが、解決方法はないようです( ´ω` ;)