React #1 Advent Calendar 2017 2日目の記事です。
もう2018年になって一月経ちましたが空いていたので埋めます。

webpack 4 のbeta版が2018/01/25 JSTにプレリリースされました。
おそらく2月か3月中には正式リリースされるでしょう。

(追記 2/25)
正式リリースされました:raised_hands:
正式版でもこの記事の内容に問題が無いことを確認しました。
https://github.com/webpack/webpack/releases/tag/v4.0.0

今回のwebpackは変更点が多く、速度改善もされているようです。
また設定ファイルも不要となり、webpack.config.js書くのが嫌な方には朗報です。
この記事では設定ファイル無しでビルドするところまで簡単な例を用いて紹介いたします。

webpack 4について

今回はReactのビルドに関する記事なのでwebpack4の変更点をすべて載せる予定はありません。
それらについては以下の記事が詳しいので拝見してください。

拙者が試したプロジェクトの周辺環境

  • babel-preset-env
  • babel-preset-react
  • flow
  • webpack 3.8

これまでのwebpackの内容

jsjsxファイルをミニファイして一つにバンドルしているだけです。

const path = require('path');
const webpack = require('webpack');

module.exports = {
  entry: {
    'index': [
      path.resolve(__dirname, 'src/index.js')
    ]
  },
  output: {
    filename: '[name].js',
    path: path.resolve(__dirname, 'public'),
    publicPath: '/',
  },
  plugins: [
    new webpack.DefinePlugin({
      'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV)
    }),
    new webpack.optimize.UglifyJsPlugin(),
  ],
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        use: 'babel-loader',
      },
    ]
  },
  resolve: {
    extensions: ['.js', '.jsx'],
  },
};

webpack 4への移行

準備

webpack 4とwebpack-cliをインストールします。
webpackをCLIで実行するのにこれまではwebpackだけで実行できたのですが、
webpack 4からはwebpack-cliに分離しました。

npm install --save-dev webpack webpack-cli

設定ファイルを使ったビルド

設定ファイルありバージョンを先に紹介します。
上記で紹介したwebpack.config.jsではwebpack4を使ったビルドは通りません。

上記の設定のままwebpackを実行すると、
TypeError: webpack.optimize.UglifyJsPlugin is not a constructor
とエラーが出ました。

これはmodeという新しいオプションが関係します。
modeにはproductiondevelopmentがあります。
productionは最適化オプションであるwebpack.optimizationのプラグインが有効になります。
そのため、今まで指定していたwebpack.optimize.UglifyJsPluginは不要となります。
developmentでは最適化はされず、eval-source-mapsが有効になります。
またwatchで動かす場合はdevelopmentを指定しましょう。
modeを指定せずにwebpackを実行した場合、どちらかを指定するように警告が出ます。
なので、modeも設定しましょう。

修正したwebpack.config.jsは以下の通りです。

const path = require('path');
const webpack = require('webpack');

module.exports = {
  mode: 'production', // 追加
  entry: {
    'index': [
      path.resolve(__dirname, 'src/index.js')
    ]
  },
  output: {
    filename: '[name].bundle.js',
    path: path.resolve(__dirname, 'public'),
    publicPath: '/',
  },
  plugins: [
    new webpack.DefinePlugin({
      'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV)
    })
    // webpack.optimize.UglifyJsPluginを削除
  ],
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        use: 'babel-loader',
      },
    ]
  },
  resolve: {
    extensions: ['.js', '.jsx'],
  },
};

設定ファイルを使わないビルド

今回の目玉でもあるでしょう設定ファイルwebpack.configを使わないビルドについて紹介します。

module-bind

まず何も考えずにオプションを付けずにwebpackを実行してみると、以下のようなエラーが出ました。

Module parse failed: Unexpected token (15:6)
You may need an appropriate loader to handle this file type.
|   render() {
|     return (
|       <div>

これはbabelが利いておらず、JSX文法を解析できていないためです。
webpack.config.jsでは以下のように指定していた部分です。

module: {
  rules: [
    {
      test: /\.(js|jsx)$/,
      use: 'babel-loader',
    },
  ]
},

webpack.config内で指定していたbabel-loaderを指定するには--module-bindオプションを使います。
--module-bind 拡張子=ローダー名で使うことができます。

webpack --module-bind 'js=babel-loader' --module-bind 'jsx=babel-loader'

※jsとjsxを一つにまとめてjs,jsx=babel-loaderということができたら良いのですが、うまくいきませんでした。もし知っている方がいればコメントお願い致します。

resolve-extensions

上記--module-bindオプションをつけて実行すると別のエラーがでました。

Module not found: Error: Can't resolve './Foo' in '/path/to/src'

これはimport先のモジュールが読み込めていないエラーです。
以下のように拡張子を省いている場合、ファイル解決ができません。

import Foo from 'components/Foo'

これをwebpack.config.jsでは以下で設定することで解決していました。

resolve: {
  extensions: ['.js', '.jsx'],
},

CLIにもこれと同じことができるresolve-extensionsというオプションがあります。
--resolve-extensions .js,.jsxのように拡張子を指定します。
先程のmodule-bindと組み合わせると以下のようになります。

webpack --module-bind 'js=babel-loader' --module-bind 'jsx=babel-loader' --resolve-extensions .js,.jsx 

mode

このままでは警告が出ます。というのも前述したmodeを指定していないためです。
modeオプションをつけると警告は消えます。以下のようになります。

webpack --module-bind 'js=babel-loader' --module-bind 'jsx=babel-loader' --resolve-extensions .js,.jsx --mode production

entry

エントリポイントを指定することもできます。
--entry srcのように指定するとsrcディレクトリ内にあるindex.jsをエントリポイントとします。
--entry src/entry.jsのようにファイルを指定することもできます。

output

出力先を指定することもできます。
--output publicとするとpublicディレクトリにエントリポイントのファイルと同じファイル名のファイルができます。
--output public/index.bundle.jsのようにファイル名を指定することもできます。
また、-o public/index.bundle.js--output-oとすることもできます。

最終形

上記で紹介したオプションをすべて反映させて最終的には以下のようにすることで、
前述したwebpack.config.jsを使ったビルドと同じことができます。

webpack --module-bind 'js=babel-loader' --module-bind 'jsx=babel-loader' --resolve-extensions .js,.jsx --mode production --entry ./src --output public/index.bundle.js

他オプション

webpack --help でオプションの一覧が見れます。
各オプションについては別の記事で紹介するかもしれません。

所感

webpackでもparcelのように設定ファイル無しでビルドすることができました。しかし数多くのオプションを指定しなければいけません。
webpack.config.jsを用いる方がわかりやすいかどうか好みが別れるとこかと思いますが、個人的には選択肢が増えて良いかと思います。
もっと複雑な構成になると設定ファイル無しでどこまでできるかまだ検証できていませんが、これからはできる限り設定ファイル無しで開発できたらいいなと考えています。
また、今回のwebpackはビルド速度の改善も大幅にされています。今回試した感じではParcelより早いかもと感じました。
Keep webpack Fast: A Field Guide for Better Build Performanceがビルド速度改善に関して詳しく書かれています。

参考

最後までお読み頂きありがとうございました。
不備や質問がありましたらコメントお願い致します。

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.