yano3nora
7/2/2017 - 9:09 AM

[webpack2: note] webpack2 - frontend build tool. #webpack #js #sass

[dev: webpack] webpack - Frontend module bundler. #webpack #html #js #css #sass

OVERVIEW

webpack/webpack - github.com
webpack.js.org

webpack は WebApp に必要なリソースの依存関係を解決し、アセット(配布物)を生成するビルドツール(要するにコンパイラ)です。JavaScript だけでなく、CoffeeScript や TypeScript、CSS 系、画像ファイルなどを扱うことができます。- webpackでイマドキフロント開発

ざっくりと ./src/index.js をエントリーポイントとして、依存パッケージやアセットをバンドルし、最終的に ./dist/main.js へコンパイルする frontend ビルドツール。

  • dev モードで webpack-dev-server による web サーバ起動 + watch option で変更差分検知して自動リビルド
  • loader でファイルをバンドルする前に加工処理ができる
  • 数多くの や plugin に対応しており入力ソースに対してあれこれできる
  • packages のモジュール解決の他、babel で es6 移行機能に対応したり sass コンパイルしたり ... な用途に使う
  • デフォルトの挙動は webpack.config.js という設定ファイルで鬼カスタマイズできる

利用者が多い分、古い・誤った情報も多い。ドキュメント嫁。

require, import/export.

require()とは何か?何が便利なのか
ES6のexportについて モダンなJSの話──importとexport
[意訳]初学者のためのJavaScriptモジュール講座 Part1

webpack は分割されたモジュール群の結合 = バンドルのためのツール。よって ↓ のような JavaScript のモジュール分割機能について十分に理解してから触れないと「よくわからん」ってなる。

  • ブラウザランタイムでの ES6 の export / import
  • Node.js ランタイムでの require
  • CommonJS や Browserify などサードパーティライブラリの歴史的経緯

Getting Started

Getting Started - webpack.js.org
Configuration > Mode - webpack.js.org

# 自分であれこれ入れるより init の対話で依存入れてもらうのが楽そう
# https://github.com/webpack/webpack-cli/blob/master/packages/generators/INIT.md
#
$ npx webpack-cli init

# 手動で typescript で最小構成にするんだったら多分こう
#
$ npm i -D webpack webpack-cli typescript ts-loader

webpack-cli init 時の注意点

webpack-cli init で作った webpack.config.js について、モードの切り替えをやってるところだけ ↓ のように変えると良い。通常 webpack --mode=production とかでビルドかけるので NODE_ENV 参照じゃなくて module.exports に渡す即時関数の第2引数 (↓ の argv) で mode 見る感じ。

// webpack.config.js
//
const path = require("path");
const config = { /* ... */ };

// ここ
module.exports = (_, argv) => {
  config.mode = argv.mode;

  if (config.mode === 'development') {
    config.devtool = 'inline-source-map';
  }

  return config;
};

Webpack v5 の変更点

webpack v5がリリースされたので、現状をまとめてみた
webpack@5の主な変更点まとめ To v5 from v4 - webpack.js.org

2020.10 に v5 がリリースされた。結構大きく変わっているので注意。

  • Node.js の最低要求バージョンが v10 になった
  • 型提供が標準で入り @types/webpack が不要になった
  • Asset Modules が導入され file-loader とか要らなくなった
  • browserslist に対応した
    • default は target: 'browserslist' で fallback 先は web
  • バンドル時に <script type="module"> で出力する output module 機能が追加された
    • 通情と違い struct モード、遅延ロードが標準で有効になる
  • 生成コードのデフォルトランタイムが一部 es2015 になった
    • 旧環境 (IE とか) サポート時は target: ['es5'] が必要
  • Node.jsのpolyfillの自動挿入がなくなった
  • use loader の違いが厳格に (エラーになるように) なった
    • use: options なし、引数あり
    • loader: option あり、引数なし
  • JSON の named export が禁止になった
    • 一旦 import で取り込んで object にしてから named export する必要がある
  • file:/// http(s):// プロトコルのサポートが追加された
    • http(s):// プロトコルは別途プラグインが必要みたい
  • 実験的機能として top-level-await が導入された (指定する必要ある)

Webpack >= 4

# webpack 本体 & CLI をインストール
# 2021.2 現在 v5 がどれくらい安定してるかわからないので v4 指定してる
#
$ npm i -D webpack@4 webpack-cli

# webpack 設定ファイルを生成 (デフォルトで使うことはほぼない)
$ npx webpack init

SETTINGS

Options > webpack.config.js

より詳細なコンパイル設定は package.json と同階層に設置した webpack.config.js で行う。開発用パッケージのロード・設定、エントリーポイント・出力先、モジュール対象ファイル・処理ローダー ( パッケージ )、開発用サーバー設定など。webpack は JavaScript だけでなく、設定次第で CSS や画像やフォントファイルなどもバンドルできるため、CSS フレームワークを webpack 経由でバンドルしたりと、フロントエンド開発に必要なタスクは大体こなせる。

Watch

Watch and WatchOptions webpack は --watch による Watch モード時に監視対象ファイルの変更を検知して差分ビルドを行ってくれる

// webpack.config.js

module.exports = {
  watchOptions: {
    ignored: ['files/**/*.js', 'node_modules'],
    aggregateTimeout: 300,
    poll: 1000
  },
};

Devtool x source-map

Devtool

// webpack-cli の init で作った例

const isProduction = process.env.NODE_ENV == "production";

const config = {/* ... */};

module.exports = () => {
  if (isProduction) {
    config.mode = "production";
  } else {
    config.mode = "development";
    config.devtool = 'inline-source-map';
  }
  return config;
};

Entry Points x glob

Entry Points
isaacs/node-glob
webpackのentryファイルを複数指定、globパッケージの使い方

webpack は複数のエントリーポイントを設けられるが ./**/*.js みたいなことはできない。これについて node-glob パッケージと掛け合わせてエントリーポイントを複数にして import を減らせるね!やった!!という試み。但し、エントリーポイントは 1 つでそこから他を import する方が設定は楽、webpack で柔軟なバンドルをやりすぎると設定やキメゴトが複雑になるのでほどほどに。

$ npm i -D glob
// webpack.config.js
const glob = require('glob');

module.exports = [
  {
    entry: {
      bundle: [
        './src/index.js',  // ど頭で Polyfill 読んだりする index.js
        ...glob.sync('./src/bases/**/*.js',       { ignored: './**/_*.js' }),
        ...glob.sync('./src/components/**/*.js',  { ignored: './**/_*.js' }),
        ...glob.sync('./src/middlewares/**/*.js', { ignored: './**/_*.js' }),
        ...glob.sync('./src/modules/**/*.js',     { ignored: './**/_*.js' }),
      ],
    },
  }
];

node-sass-glob-importer

https://www.npmjs.com/package/node-sass-glob-importer
Glob online tester

JavaScript と違って CSS / Sass は @import の実行順序が大事。よって webpack 側でまとめるより上記のようなプラグインを使って index.scss みたいなエントリーポイントで順番を指定しながら @import 'hoge/**/*.scss'; とかした方が楽。逆に JavaScript の場合は import 時の変数衝突があるためこの手法は微妙。

$ npm i -D node-sass-glob-importer
// index.scss

// Bootstrap 変数カスタム用 _variables.scss を先頭でインポート
@import 'bases/variables';
// node_modules 配下 Bootstrap のインポート
@import '~bootstrap/scss/bootstrap';

// 以下おれおれモジュール群を glob パターンでインポート
// glob パターンの場合は拡張子指定が必要なので注意
// .{css,scss,sass} とかも一応可能
@import 'bases/**/!(_*).scss';
@import 'components/**/!(_*).scss';

resolve.alias

resolve.alias

デフォルトでは webpack.config.js の存在するアプリルート上の node_modulesimport 時のルート / としてセットされているみたい。React のコンポーネントなど自作 Class の import / export相対パスでなく絶対パスで 指定したい場合は、パス解決のためのエイリアスを resolve.alias で追加できる。

module.exports = {
  //...
  resolve: {
    modules: ['node_modules'],  // デフォ
    extensions: ['.wasm', '.mjs', '.js', '.json', '.jsx'],  // デフォ + .jsx
    alias: {
      // ここやで
      bases:       path.resolve(__dirname, './src/bases'),
      components:  path.resolve(__dirname, './src/components'),
      middlewares: path.resolve(__dirname, './src/middlewares'),
      modules:     path.resolve(__dirname, './src/modules'),
    }
  }
};
// これ ( クソかったるい相対パスで指定していた import ) を ...
import MyComponent from '../../components/my-component';

// こうじゃ ... !
import MyComponent from 'components/my-component';

with TypeScript & ESlint

eslint ちゃんは webpack で alias 設定したって読んでくれないから .tsconfig.json にも同じこと書かんとおこ。

https://stackoverflow.com/questions/40443806/webpack-resolve-alias-does-not-work-with-typescript

{
  "compilerOptions": {
    "paths": {
      "bases/*": ["./src/bases/*"],
      // ... 略
    }

Loader x Babel

Loaders - webpack.js.org
babeljs.io babel(バベル)を使ってすべてのブラウザで動作するようにする
ES2015(ES6)+webpack+babel-loaderで開発環境を構築 Babel@7で構築するReact開発環境

モジュール分割用の import / export なども含め、ES6 以降コードの多くはまだブラウザ間で対応差異がある。これらを解決するために webpack 利用時にあわせて Babel を導入し、ブラウザ間で対応・実行差異のあるシンタックスや API を「サポート対象にしたいブラウザバージョン」に合わせてトランスパイルするのが一般的。つい最近 8 月ごろに最新バージョンの Babel 7 がリリースされており、パッケージ名が @babel/*** になっている。検索の際は 6 系情報と混同しないように注意。

# Install dev dependencies.
$ npm i -D @babel/core @babel/preset-env babel-loader @babel/preset-react 

トランスパイルの設定は .babelrc と呼ばれる設定ファイルで定義するか webpack.config.jspackage.json に記載する。実際のトランスパイルは webpack の Loader 機能によって実現される。

// .babelrc の記載例
{
  presets: [
    ['@babel/preset-env',
      {
        // ブラウザバージョンやシェアは https://browserl.ist を参照
        targets: "> 0.5% in JP, last 2 versions, Firefox ESR, maintained node versions",
        modules: false,
      },
    ],
    ['@babel/preset-react'],
  ],
}
module.exports = [
  //...
  module: {
    rules: {
      {
        test: /\.js$|\.jsx$/,
        exclude: /node_modules/,
        use: 'babel-loader',  // babel-loader を指定
        // ちなみに babel-loader?cacheDirectory でキャッシュ有効化
      },
    },
  },
  //...
];

Browserslist Integration

Browserslist Integration

@babel/preset-env では PostCSS x Autoprefixer の項で後述する Browserslist のクエリを targets に利用できる。また、既に package.json.browserslistrc で記載されているクエリを採用する場合は以下のように記述する。

// webpack.config.js
// package.json の "browserslist" や .browserslistrc を参照する

module.exports = {
  module: {
    rules: {
      {
        test: /\.js$|\.jsx$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: [
              ['@babel/preset-env', { useBuiltIns: 'entry', corejs: 3, modules: false }],
            ],
          },
        },
      }
    }
  }
};

Polyfill

@babel/polyfill
babel-polyfillとbabel-runtimeの使い分けに迷ったので調べた
babel-polyfill / babel-runtime の代わりにcore-jsを直接使うのはアリかナシか

# Install dependencies.
$ npm i -S @babel/polyfill
// index.js
import '@babel/polyfill';  // polyfill 関係は先頭で一度切り読み込む
// webpack.config.jp
module.exports = {
  //...
  entry: {
    js: [
      // 複数エントリー時 Polyfill 類は必ず先頭 JS で一度切り読み込む
      './src/index.js',
      // または ['@babel/polyfill', './src/index.js'] のようにして
      // パッケージを先頭で結合するようなエントリー構造にしてもよい
      './src/another_entry1.js',
      './src/another_entry2.js',
      './src/another_entry3.js',
    ],
  },
  //...
};

babel-loader から node_modules を exclude する

webpackでbabel際はnode_modulesをexcludeし忘れるべからず
webpackでnode_modulesをbabel-loaderの対象から除外すると、ES2015+で書かれたnpmパッケージのモジュールがBabelで変換されない問題

一般的に、npmパッケージのモジュールはES5で書かれている。(ソースがES2015+であってもES5にコンパイルされている)。だが稀にES2015+で書かれたモジュールもある。... 次のように、ES2015+で書かれたモジュールをbabel-loaderの除外対象から除外することで、この問題を回避できる。

module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: {
          // node_modules 中を除外対象に
          include: /node_modules/,
          // foo パッケージのモジュールを除外対象から除外
          // パターンの末尾に \/ がないと全ての foo が
          // 除外対象から除外されてしまうので注意
          exclude: /node_modules\/foo\//,
        },
        use: 'babel-loader',
      },
    ],
  },
};

Loader x postcss x autoprefixer

postcss.org
postcss/autoprefixer
最新版で学ぶwebpack 4入門 – スタイルシート(CSSやSass)を取り込む方法

// webpack.config.js の例
const cssnano      = require('cssnano')
const autoprefixer = require('autoprefixer')

module.exports = {
  module: {
    rules: [
      {
        test: /\.(sa|sc|c)ss$/,
        use: [
          { loader: MiniCssExtractPlugin.loader },
          {
            loader: 'css-loader',
            options: {
              // Deny fetching resource by url().
              url: false,
              importLoaders: 2,  // For PostCSS + Sass.
              sourceMap: (process.env.npm_lifecycle_event === 'build'),
            },
          },
          {
            loader: 'postcss-loader',
            options: {
              plugins: [
                // cssnano({ autoprefixer: false }),
                autoprefixer(),
              ].concat(
                // concat() に空配列を渡すと結合元配列のまま変わらないことを利用して
                // build ( 本番向けビルド ) 時のみ cssnano による minify が実行されるように
                (process.env.npm_lifecycle_event === 'build')
                  ? [cssnano({ autoprefixer: false })]
                  : []
              ),
              sourceMap: (process.env.npm_lifecycle_event === 'build'),
            },
          },
          {
            loader: 'sass-loader',
            options: {
              importer: globImporter(),
              sourceMap: (process.env.npm_lifecycle_event === 'build'),
            },
          },
        ],
      },
    ],
  },
};

.browserslistrc

browserl.ist - .browserslistrc のテストアプリ
browserslist/browserslist - github.com
Autoprefixerの対象ブラウザの選び方
Autoprefixer は Can I Use のブラウザ・サポート情報と StatCounter の全世界のブラウザ利用状況データを参照して Browserslist の記述に当てはまるブラウザを抽出します。

PostCSS の Autoprefixer プラグインや Babel などの サポート対象 ( トランスパイル時の考慮対象 ) として .browserslistrcpackage.json と同階層に設置し、サポート対象ブラウザを指定する仕様になっている。JSON 形式で package.json に書いても良いみたい。

記載方法と対象ブラウザの確認は browserl.ist より。とりま > 0.5% in JP しとけば日本の 90 % 程度はカバーできる。デフォルトは > 1%, last 2 versions, Firefox ESR みたい。

# .browserslistrc

> 0.5% in JP
last 2 versions
Firefox ESR
maintained node versions
{
  "browserslist": [
      "> 0.5% in JP",
      "last 2 versions",
      "Firefox ESR",
      "maintained node versions"
  ]
}

PLUGINS

MiniCssExtractPlugin

MiniCssExtractPlugin 【webpack 4】環境構築からJSとCSSを別出力まで(備忘録)

バンドルしてコンパイルした CSS / Scss / Sass を bundle.js におまとめせず、別ファイル ( style.css ) などに切り出して出力するための整形プラグイン。かつての extract-text-webpack-plugin が担っていたシゴトだが、webpack 4 系からこちらにシフトしたみたい。これに伴い style-loader も不要になった。

$ npm i -D mini-css-extract-plugin
// webpack.config.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
  entry: {
    bundle: [
      './src/index.js',
      './src/index.scss',
    ],
  },
  output: {
    path: path.join(__dirname, 'public/js'),
    filename: '[name].js',
  },
  module: {
    rules: [
      {
        test: /\.(sa|sc|c)ss$/,
        use: [
          { loader: MiniCssExtractPlugin.loader },
          'css-loader',
          'postcss-loader'
          'sass-loader',
        ],
      },
    ],
  },
  plugins: [
    new MiniCssExtractPlugin({
      // パス起点が module.exports.output.path なので注意
      filename: '../css/style.css',
    }),
  ],
};

Watch x Browsersync x BrowserSyncPlugin

Browsersync - browsersync.io
Va1/browser-sync-webpack-plugin - github.com
webpack-dev-serverからbrowsersyncへ
はじめてのbrowserSync
Browsersync で快適なブラウザチェック環境を作る

# 追記
browser-sync-webpack-plugin の更新古いし webpack-dev-server 抜いて、並列で browsersync 単独で立てた方が管理上も楽な気がしてきた。

webpack は development モードで立ち上げた際に自動的に Watch モードになりバンドル対象ファイルの更新を監視 → 更新時に再ビルドするようになる。一般的な構成ではここに webpack-dev-server を導入し、再ビルド時にブラウザをライブリロードしたりする。

Browsersync はこの webpack-dev-server と同じ趣旨のツールで、ローカルに開発用プロキシサーバを作り別途設定した監視対象の「更新時ライブリロード」や「リモートデバッグ ( 実機テスト・デバッグ ) 」などを提供する。webpack と BrowserSync の連携には BrowserSyncPlugin が必要。設定は webpack.config.js に記載する。

$ npm i -D browser-sync-webpack-plugin
new BrowserSyncPlugin({
  host: 'localhost',
  port: 3000,
  cors: true,
  open: false,
  proxy: {
    target: 'https://192.168.99.100',
    cookies: { stripeDomain: false },
    ws: false,  // アプリが WebSocket を利用している際は true へ
  },
  reloadDelay: 0,  // 適宜調整
  watchOptions: {
    awaitWriteFinish : true,  // JS 更新時にビルドを待ってリロード
  },
  files: [
    // 【注意】ここに存在しないパスを書くと正常に動かないよ!!
    'app/**/*.rb',
    'app/**/*.erb',
    'config/**/*.rb',
    'test/**/*.rb',
    'src/**/*.js',
    'public/css/*.css',
  ]
},
{
  // CSS / Sass 更新を Inject するため
  reload: false,
  injectCss: true,
})

この設定では BrowserSync はただのプロキシサーバなので CSS Inject されない JavaScript や、WEB サーバサイドでキャッシュされているテンプレートエンジンファイルの更新など、ブラウザいつも通りキャッシュの影響を受けてしまうことを忘れない。 よって Chrome の DevTools で Network の Disable Cache を true にするなど、キャッシュ対策は別途行うこと。

Server option

server - browsersync.io

ローカルの Static な HTML/CSS/JS だけで完結する静的 WEB ページや、SPA などを開発する際には proxy で VM の IP を指定せず、ローカルのディレクトリおよび index を server に指定してやれば、指定したディレクトリを起点とした開発用 WEB サーバをたててくれる。

server: {
  baseDir: '.',
  index: 'index.html',
}

External URL

Incorrect External URL

Browsersync は localhost に開発用のプロキシサーバを立てる際に、ホスト端末の「最優先なプライベートネットワークカード」を自動検出し、この同一 LAN へ向けた「プロキシサーバのポート解放」を行う。この External URL ( = プライベートネットワーク向け解放アドレス ) に同一 LAN 上からアクセスすることで Charles のように「実機など外部端末 → ホスト端末プロキシサーバ → ゲスト VM 上 WEB サーバ」へ接続できる。

[Browsersync] Proxying: http://192.168.33.10
[Browsersync] Access URLs:
 ----------------------------------
       Local: http://localhost:3000  # ローカル開発マシンからのアクセス
    External: http://10.0.1.7:3000   # 同一ネットワーク上からのアクセス
 ----------------------------------
          UI: http://localhost:3001  # Browsersync 管理画面へのアクセス
 UI External: http://10.0.1.7:3001   # 管理画面へのネットワーク上からのアクセス
 ----------------------------------
[Browsersync] Watching files...

稀に External のアドレスが異常な値になっていることがあるが、大体は localhost の設定ミスか、仮想 VM 用のネットワークカードの優先順位がイーサネットに勝ってしまっていることが原因。ネットワークカードの優先順位は メトリック値 の設定で変更できる。

react-hot-loader x browser-sync

react-hot-loader - github.com / react-hot-loader - npmjs.com
BrowserSync/recipes
webpack, React Hot Loader + Browsersync でクロスブラウジング+ホットリロード開発

index.html から JS を読む SPA 構成のアプリ開発時はこういう構成もできるみたい。Proxy として利用している場合のナレッジはないのかな ... 。

watch で Browsersync がコケる

Error: no parsers registered for: "]a(r)" みたいなこといってコケるときは ignoredfiles の指定がおかしい。

new BrowserSyncPlugin({
  // ...
  watchOptions: {
    // ignored: ['vendor'],       // 理由は不明だがこれだと NG
    ignored: ['laravel/vendor'],  // こっちなら大丈夫 ... なぜ
  },
  files: [
    'laravel/**/*.php',
  ],
})

プロジェクト横断時に POST 処理が死ぬ

BrowserSync を利用している際に、稀に Chrome でプロキシを介さないリクエストは問題ないのに、プロキシを介した localhost なアクセスの POST ( GET 以外の ) リクエストがうんともすんとも言わなくなるケースがある。プロジェクト間を行き来した際に「さっきまで見てた localhost と今の localhost が別物」なときに起きる印象。

スーパーリロード や chrome://restart や Cookie / サイトデータ削除なども効果がなく、Chrome のリクエスト処理が内部で延々と終わらずにハングアップしているようにも見受けられる。

プロキシを介さずに一度何かしらの POST 処理 ( ログインなど ) を行い、そののちに localhost ( BrowserSync ) へアクセスすると改善されることがある。 なぜかは不明。

IgnorePlugin

webpack で moment.js の無駄なロケールファイルを除去する

moment.js のロケールファイルなど、プロダクション環境で全く利用しないものも一緒にバンドルされてしまうとファイルサイズが重たくなってしまう。IgnorePlugin でパッケージの特定ディレクトリを無視するような設定が可能。

plugins: [
  // moment.js では多くの場合デフォルトの en 以外要らない
  new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
]
// 必要になった場合はまた別途 index.js 以下で呼べばよろしい
// 
const moment = require('moment');
require('moment/locale/ja');

moment.locale('ja');

ProvidePlugin

いつまでimport Reactしているつもり? Webpack-ProviderPlugin
暗黙で全てのjsファイルにモジュールをimportする

クラスのオートロード。よく使うやつは予め変数に代入しておくと各ファイルで import ぶっ叩かなくて済む。

// webpack.config.js

// import ClassName from 'package-name';
//  ↓
// ClassName: 'package-name'

// import { ClassName } from 'package-name';
//   ↓
// ClassName: ['package-name', 'ClassName']

plugins: [
  new webpack.ProvidePlugin({
    $: 'jquery',
    jQuery: 'jquery',
    'window.$': 'jquery,
    'window.jQuery': 'jquery,
    moment: 'moment',
    React: 'react',
    ReactDOM: 'react-dom',
    PropTypes: 'prop-types',
    Component: ['react', 'Component'],
  })
]

TIPS & REFERENCES

dotenv で .env ファイル読み込むやつ

motdotla/dotenv - github.com

Node.js ランタイムの process.env.env の Key-value 入れてくれるやつ。環境変数の使い所は Twelve-Factor App あたり読んでよく考えてね。

$ npm i -D dotenv
// webpack.config.js

require('dotenv').config();  // config() 実行で .env ファイルを読込

console.log(process.env.HOGE_ENV);  // 以降は process.env から参照可能

node-sass 関連エラー

node-sass 利用プロジェクトでは、ビルドしたマシンのアーキテクチャやら環境やらが違うとリビルドしろって怒られる。

$ npm rebuild node-sass

上記で Python2 がないよ、みたいなエラーが出る場合は、恐らくプロジェクトの node-sass が古すぎる ( 実行環境の node.js が新しすぎる ) ため必要なビルドパッケージ群を取得しきれずリビルドに失敗している。Windows 環境なら windows-build-tools をぶち込むとリビルドが通るかも。

$ npm i -g windows-build-tools  # 2 ~ 30 分くらいかかるかも
$ npm rebuild node-sass         # ビルドに必要なパッケージを死ぬほどビルドしまくって無理くり通す

FontAwesome などファイルローダが必要なものの導入

FontAwesome などの WEB フォントを含むものや、CSS フレームワーク内部に画像バイナリを抱えているような奴は url-loaderfile-loader を通してやればエンドポイントのメイン CSS をエントリーポイントで import するだけで全部ぶち丸めて JS 化してくれる ( 怖 ) 。

// webpack.config.js
{
  test: /\.css$/,
  include: /node_modules/,
  use: [
    { loader: 'style-loader' },
    { loader: 'css-loader' },
  ],
},
{
  test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
  use: 'url-loader',
},
{
  test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
  use: 'url-loader',
},
// index.js
import 'font-awesome/css/font-awesome.min.css'

performance 警告無視

Webpack2のパフォーマンス警告を制御する

module.exports = [
  {
    //...
    performance: {
      hints: false
    },
    //...
  }
]

Windows 環境で watch 時に Permission エラー

Error: EPERM: operation not permitted, stat 'D:\git\my_app\app\vendor\bin\apigen'

Composer で apigen を利用しているプロジェクトで上記のようなパーミッションエラーが発生して watch がコケていた。Windows のファイルシステムが上記ファイルをロックしていて ( プロセスがゾンビだった? ) watch できなかったよう。端末によっては同環境でも watch できた。

解決策として、メモリキャッシュ npm cache clear -f やら Composer による vendor の再インストールやら npm で node_modules の再インストールやら試したが、結局 BrowserSync の watch 対象から当該ファイルを外すことで解決した。