webpack-dev-serverで開発用サーバを立てる

webpackでは、開発用サーバを簡単に立てることが出来る。

前回の記事でwebpack2の導入に成功したので、今度はそこにwebpack-dev-serverを導入してみる。

前回の記事の最後で、Reactをビルドできる状態になった。
その時はhtmlファイルを直接開いていたが、ローカルサーバを立ち上げてそこでhtmlを表示できるようにするのが、今回の目的。

なお、この記事で使用しているバージョンは、webpack@2.4.1webpack-dev-server@2.4.5である。

サーバを起動するまでの手順

まずは必要なnpmパッケージをインストール。

$ npm i -D webpack-dev-server

次に、webpack.config.jsを編集して、以下の内容にする。

const path = require('path');

const config = {
  context: path.resolve(__dirname, 'src'),
  entry: './main.js',
  output: {
    path: path.resolve(__dirname, 'public'),
    filename: 'bundle.js',
  },
  module: {
    rules: [{
      test: /\.js$/,
      include: path.resolve(__dirname, 'src'),
      use: [{
        loader: 'babel-loader',
        options: {
          presets: [
            'es2015', 'react',
          ],
        },
      }],
    }],
  },
  devServer: {
    contentBase: path.resolve(__dirname, 'public'),
    port: 3000,
  },
};

module.exports = config;

devServerの設定を追加している。
contentBaseで指定したディレクトリが、サーバの起点になる。省略した場合は、カレントディレクトリが起点になる。
portはその名の通り、ポート番号を指定する。省略した場合は8080となる。

最後に、package.jsonscriptsを編集。

"scripts": {
  "start": "webpack-dev-server --inline",
  "build": "webpack -p"
},

この状態で$ npm startを実行すると、ローカルサーバが立ち上がる。
http://localhost:3000/にアクセスすると、Hello, React!と表示されているのが確認できる。

そして、src/parts.jsの内容を編集して保存すると、自動的にビルドが行われ、さらにブラウザのリロードも自動的に行われるのが分かる。

ディレクトリの場所に注意

contentBaseで指定するディレクトリと、バンドルファイルをoutputするディレクトリは、同一のものにする必要がある。
上記の例では両方共publicになっているので、問題ない。

例えばこれを変更してみる。

serverというディレクトリを作り、そこにindex.htmlというファイルを置く。

<!DOCTYPE html>
<html>
<head>
  <title>webpack</title>
</head>
<body>
  <div id="app"></div>
  <script src="../public/bundle.js"></script>
</body>
</html>

このファイルを直接開くと、問題なくbundle.jsを読み込め、正しく表示される。

では、ローカルサーバでアクセスするとどうなるのか。
contentBase: path.resolve(__dirname, 'server'),とした上で、$ npm startしてみる。 すると、bundle.jsを読み込めず、上手くいかない。

ビルドしたファイルは出力されない

webpack-dev-serverを実行していると自動的にビルドされることは既に述べた。
だが、ビルドしたファイルはメモリ上に保存されるらしく、実際にbundle.jsとして出力されるわけではない。

試しに、サーバを起動させた状態でsrc/parts.jsHello, React!となっている部分をHello, Server!に書き換えてみる。
自動的にビルドされ、Hello, Server!と描画される。

では、直接htmlファイルを開くとどうなるか。
Hello, React!のままである。つまり、bundle.jsの内容は、変更前のままである。

つまり、webpack-dev-serverを実行しても、bundle.jsには出力されないというである。
実際にファイルに出力するには、webpackを実行する必要がある。

参考資料

webpack2の初歩

今まで、複数のファイルのバンドルにはbrowserifyを使っていた。
特に不満はなかったのだが、webpackに乗り換えていくことにした。

ネット上の記事を見てもwebpackを使っているケースは多いし、webpack-dev-serverという開発用サーバーも簡単に立てられるらしい。

今年の1月にv2が正式リリースされたので、それに準拠した内容を学んでいく予定。
この記事では、2.4.1を使っている。

初期設定

まずはwebpackのインストールから。

$ npm install -D webpack

package.jsonを編集して、npm scriptでwebpackを実行できるようにする。

{
  ...
  "scripts": {
    "start": "webpack -p --watch",
    "build": "webpack -p"
  },
  ...
}

--watchオプションをつけておくと、ファイルの変更を検知して都度、自動的にリビルドしてくれる。

最後に、webpack.config.jsという設定ファイルをルートディレクトリに作成する。

// webpack.config.js
const path = require('path');

const config = {
  context: path.resolve(__dirname, 'src'),
  entry: './main.js',
  output: {
    path: path.resolve(__dirname, 'public'),
    filename: 'bundle.js',
  },
};

module.exports = config;

このように設定すると、src/main.jsをエントリポイントとして、バンドルしたファイルをpublic/bundle.jsとして出力するようになる。

ファイルのバンドル

早速、使ってみる。

まず、バンドルしたファイルを読み込むhtmlファイルを作っておく。

<!-- public/index.html -->
<!DOCTYPE html>
<html>
<head>
  <title>webpack</title>
</head>
<body>
  <div id="app"></div>
  <script src="bundle.js"></script>
</body>
</html>

次に、エントリポイントとなるsrc/main.js

// src/main.js
const elem = require('./parts');

const app = document.querySelector('#app');
app.innerHTML = elem;

最後に、src/main.jsが読み込んでいるsrc/parts.jsを作成する。

// src/parts.js
module.exports = 'hogehoge';

この状態で$ npm run buildを実行してpublic/index.htmlを開くと、画面にhogehogeと表示されており、バンドルが上手くいったことを確認できる。

Babelの導入

ただファイルをバンドルするだけならこれで十分なのだが、現実的には、Babelによるトランスパイルも行うことが多いと思う。
ファイルの読み込みにしても、requireではなくimport/exportを使うことが主流になっている。

browserifyの場合は、babelifyを使うことでトランスパイルを同時に行っていた。
もちろんwebpackでも同じことが出来る。

ただその前に、ちょっと気になることがあったので記録しておく。

import/exportはトランスパイルしないと使えないと理解しているが、webpack2では、トランスパイルなしでも使えたのだ。

先程のsrc/main.jssrc/parts.jsを、以下のように書き換えた。

// src/main.js
import elem from './parts';

const app = document.querySelector('#app');
app.innerHTML = elem;
// src/parts.js
export default 'test test';

この状態で$ npm run buildしたところ、問題なくビルドできた。
そのため、import/exportを使いたいだけなら、Babelを導入しなくても問題ない、ということになる。

ただ、やはりきちんとBabelを入れておいたほうがいいだろう。
後述するReactのビルドなどでは結局必要になるし、敢えて入れない理由は何もないと思う。

ということでまずは、ES2015のトランスパイルを行えるようにしていく。

まず、必要なパッケージをインストールする。

$ npm i -D babel-core babel-loader babel-preset-es2015

次に、Babelの設定ファイルである.babelrcを作成。

{
  "presets": ["es2015"]
}

最後に、webpack.config.jsの内容を変更する。

// webpack.config.js
const path = require('path');

const config = {
  context: path.resolve(__dirname, 'src'),
  entry: './main.js',
  output: {
    path: path.resolve(__dirname, 'public'),
    filename: 'bundle.js',
  },
  module: {
    rules: [{
      test: /\.js$/,
      include: path.resolve(__dirname, 'src'),
      use: [{
        loader: 'babel-loader',
        options: {
          presets: [
            'es2015',
          ],
        },
      }],
    }],
  },
};

module.exports = config;

この状態でwebpackを実行すると、バンドルとトランスパイルを同時に行える。

Reactのビルド

Babelを使えるので、Reactのビルドも当然行える。

$ npm i -D babel-preset-reactでプリセットをインストールし、.babelrcwebpack.config.jsのプリセットの設定を書き換えるだけである。

["es2015"]
↓
["es2015", "react"]

これでビルドできるようになったので、実際にReactのコードを書いてみる。

$ npm i -S react react-dom
// src/main.js
import React from 'react';
import ReactDOM from 'react-dom';

import Hello from './parts';

ReactDOM.render(<Hello />, document.querySelector('#app'));
// src/parts.js
import React from 'react';

export default function () {
  return (
    <div>
      Hello, React!
    </div>
  );
}

そして$ npm run build$ npm startでwebpackを使うと、ビルドされる。
public/index.htmlを開くと、Hello, React!と表示されているはずである。

参考資料