express + webpack + BabelでHMRを使いながらIE11対応する

この記事は、「expressとwebpackでHMRする最小構成を試す」の記事にBabelを追加で組み合わせるための記事です。

expressとは?

Node.jsのフレームワークです。今回は、開発環境向けにWEBサーバを簡単に立ち上げるために使用します。
公式サイト

webpackとは?

複数ファイルに分けて管理しているjs(モジュール)を一つのファイルにまとめてくれるツール(モジュールバンドラー)です。
(正確には1つのファイルとは限りませんが、役割に応じてファイルをまとめられます)

別の記事が参考になります。

Babelとは?

ブラウザがまだ対応していない最新のJavaScriptソースコードを、ブラウザが対応しているソースコードへ変換してくれるツール (トランスパイラ)です。

別の記事が参考になります。

HMR(Hot Module Replacement)とは?

CSS/JSのソースコードを編集した時に、リロード操作(F5を押す等)無しで編集結果がブラウザに反映される仕組みを提供してくれるwebpackの機能です。

別の記事が参考になります。

expressとwebpackとBabelを組み合わせてHMRを使用した開発をする利点

WEBサーバー(express)からwebpackを使用することで、コマンドラインからwebpackを起動させる必要がなくなり、webpackからBabelを使用することでBabelをコマンドラインから起動させる必要が無くなります。

つまり、コンパイル(モジュールをバンドル化し、トランスパイルする)工程が自動化されます。 そして、HMRを使用することでリロードの作業も省くことで開発の高速化が可能になります。

今回は、上記で述べた技術を組み合わせて、IE11対応の開発を行います。

手を動かして、HMRを使いながらIE11対応した開発をする

今回作成したプロジェクトのリポジトリ

以下がインストールされていることを前提とします。

  • node.js
  • npm

以下が手順概要になります。

  1. npmでプロジェクトを作成する
  2. express、webpack、Babel等をインストールする
  3. 必要なファイルを手動で作成する
  4. コマンドラインでWEBサーバー(express)を立ち上げる
  5. IE11でアクセスして確認する
  6. JSファイルを編集し、IE11に反映された結果を確認する

1. npmでプロジェクトを作成する

以下のコマンドを実行します

mkdir express-webpack-babel-hmr-demo
cd express-webpack-babel-hmr-demo
npm init -y

2. express、webpack、Babel等をインストールする

npm install -D webpack webpack-cli webpack-dev-middleware webpack-hot-middleware
npm install -D babel-loader @babel/core @babel/preset-env
npm install --save @babel/polyfill express lodash

3. 必要なファイルを手動で作成する

以下のファイルを手動で作成します。

  • ./babel.config.js
  • ./index.html
  • ./server.js
  • ./webpack.config.js
  • ./src/index.js
  • ./src/foo.js
  • ./src/bar.js

以下のようなディレクトリ構造になります。

│  babel.config.js
│  index.html
│  package.json
│  server.js
│  webpack.config.js
│
├─node_modules
│  └─etc...
└─src
    bar.js
    foo.js
    index.js

./package.jsonを以下のように書き換えます。

  • - "main": "index.js",
  • + "private": true,
  • + "dev": "node server",
{
  "name": "express-webpack-babel-hmr-demo",
  "version": "1.0.0",
  "description": "",
  "private": true,
  "scripts": {
    "dev": "node server",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "MIT",
  "devDependencies": {
    "webpack": "^4.29.6",
    "webpack-cli": "^3.3.0",
    "webpack-dev-middleware": "^3.6.1",
    "webpack-hot-middleware": "^2.24.3"
  },
  "dependencies": {
    "express": "^4.16.4",
    "lodash": "^4.17.11"
  }
}

./babel.config.jsの内容を次のようにし、babelの動作を設定します。

const presets = [
  [
    "@babel/env",
    {
      targets: {
        ie: '11'
      },
      useBuiltIns: "usage"
    }
  ]
];

module.exports = { presets };


./webpack.config.jsの内容を次のようにし、webpackの動作を設定します。

const webpack = require('webpack');

module.exports = {
  mode: 'development',
  entry:  ['webpack-hot-middleware/client', './src/index.js'], // 2つのファイルをwebpackの基点とする
  output: {
    filename: 'main.js', // 出力されるファイル名
    // 出力されるファイルを読み込めるパスを指定する
    // ファイルはメモリ上に出力され、そのファイルをロードするためのパス
    // https://webpack.js.org/configuration/output/#outputpublicpath
    // https://webpack.js.org/guides/public-path/
    publicPath: '/example_public_path/'
  },
  module: {
    rules: [
      {
        test: /\.js$/, // 拡張子が.jsであり
        exclude: /node_modules/, // ディレクトリがnode_modules以外であれば
        use: ['babel-loader'] // Babelの対象とする
      }
    ]
  },
  plugins: [
    new webpack.HotModuleReplacementPlugin(), // HMRを使用するためのプラグイン
  ]
};


./server.js
WEBサーバー(express)の動作を記述します。

const express = require('express');
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');
const webpackHotMiddleware = require('webpack-hot-middleware');
const WebpackConfig = require('./webpack.config');

const app = express();
const compiler = webpack(WebpackConfig);

// メモリ上にファイルをコンパイルする
// ファイルを監視して、変更されていれば再コンパイルする
app.use(webpackDevMiddleware(compiler, {
    publicPath: WebpackConfig.output.publicPath,
}));

// クライアントに変更を通知する
// クライアント側でHMRに対応している箇所はリロードせずに更新される
app.use(webpackHotMiddleware(compiler));

// index.htmlを静的に配置しているディレクトリを指定する
app.use(express.static(__dirname));

const port = process.env.PORT || 8080;
app.listen(port, () => {
    console.log(`Server listening on http://localhost:${port}, Ctrl+C to stop`);
});


./index.html

バンドルファイル(main.js)以外に、ブラウザ(IE11)用のポリフィルを読み込んでいます。
webpackからポリフィルもまとめてバンドルしてみたのですが、現在(2019/03/24)では動作が不安定になるため、CDNを使用しました。

<!doctype html>
<html>
  <head>
    <title>express + webpack + babelでHMR</title>
  </head>
  <body>
    <script src="https://www.promisejs.org/polyfills/promise-7.0.4.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/event-source-polyfill/0.0.9/eventsource.min.js"></script>
    <script src="./example_public_path/main.js"></script>
  </body>
</html>


./src/foo.js

export default () => {
  return 'from foo!';
}


./src/bar.js

export default () => {
  return 'from bar!';
}


./src/index.js
下部にてHMRを実行するための記述をしています。
ファイルが編集されるとサーバーから変更の通知を受け取り、module.hot.accept()の第1引数で指定したJSが変更されていた場合に第2引数のコールバックが実行されます。
コールバックによってbodyタグを再描画しています。

import _ from 'lodash';
import foo from './foo.js'
import bar from './bar.js'


const expressAndWebpack = () => {
  let body = document.querySelector('body');
  body.innerHTML = '';
  body.innerHTML = _.join(['Hello', 'express', '+', 'webpack', '+' , 'Babel'], ' ');
  body.innerHTML += `<br>${foo()}`;
  body.innerHTML += `<br>${bar()}`;
  return body;
}
expressAndWebpack();

if (module.hot) {
  module.hot.accept(['./foo.js', './bar.js'], () => {
    expressAndWebpack();
  });
};

4. コマンドラインでWEBサーバー(express)を立ち上げる

以下のコマンドを実行して、WEBサーバーを立ち上げます。

npm run dev

上記のコマンドは./package.jsonに定義してあり、以下のコマンドと同等です。

node server

5. IE11でアクセスして確認する

以下のURLにアクセスします。

http://localhost:8080/

以下のように表示されていれば、expressとwebpackとBabelの組み合わせは成功です。

Hello express + webpack + Babel + Babel
from foo!
from bar! 

6. JSファイルを編集し、IE11に反映された結果を確認する

WEBサーバーを立ち上げたまま、JSファイルを編集します。 以下のJSファイルを編集して保存します。

./src/foo.js

export default () => {
  return 'from foo!';
}

↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓

export default () => {
  return 'FROM FOO!!';
}

表示が以下のように即座に変更されればIE11上でのHMRが成功です。

Hello express + webpack + Babel
from foo!
from bar!

↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓

Hello express + webpack + Babel
FROM FOO!!
from bar!