ExpressをWebpackでバンドルする【前編】

# 記事に従ってやってみた。
[Creating a Node Express-Webpack App with Dev and Prod Builds](https://medium.com/@binyamin/creating-a-node-express-webpack-app-with-dev-and-prod-builds-a4962ce51334) を翻訳しながら進める。(google翻訳頼みのため間違ってたら指摘ください。)
> 原文にないコメントは引用で書きます。


# 私が作りたいもの
開発ビルドとプロダクションビルドを分ける。

開発ビルド

* ES6+のトランスパイル。
* lint
* 単体テスト
* カバレッジレポート
* HMR(Hot Module Reloading)
* minifyしない
* imageやcssをBase64エンコードせずに保持する。

プロダクションビルド

* minifyする。
* uglify する。
* imageとcssはBase64エンコードする。

開発とプロダクションで別々のExpressサーバーファイルとWebpack設定ファイルで管理している。(不要なインポートを避けるため)

# Tech Stack(技術スタック)
* Express -- server
* Webpack 4 -- bundling
* Jest -- testing
* Babel -- ES6+ transpilation
* ESlint -- Linting
* Webpack Dev Middleware -- Bundle code in memory instead of in a file
* Webpack Hot Middleware -- Enables Hot Module Reloading (HMR)
* UglifyJS -- uglifies code
* mini-css-extract-plugin -- minifies CSS

# OK,Let's Begin

## Step 1: The Express Server

**筆者の検証環境**

* macOS Sierra 10.12.6
* Node v10.0.0
* NPM 6.0.0
* Webpack 4
* Express 4.16.3

**自分の検証環境**

* Windows8.1
* Node v10.14.1
* NPM 6.4.1
* Webpack 4
* Express  4.16.4

検証環境のディレクトリ作成

```console
mkdir express-webpack
cd express-webpack
```

package.jsonを作成

```console
npm init -y
```

Expressをインストール

```console
npm install --save express
```

package.jsonに以下を追加してください。

```json:pakage.json
"scripts": {
  "start": "node ./server.js"
},
```

基本的なExpressサーバー・ファイルをプロジェクト・ルート・ディレクトリserver.jsに書き込んで、動作することをテストしましょう

```javascript:server.js
const path = require('path')
const express = require('express')
const app = express(),
            DIST_DIR = __dirname,
            HTML_FILE = path.join(DIST_DIR, 'index.html')
app.use(express.static(DIST_DIR))
app.get('*', (req, res) => {
    res.sendFile(HTML_FILE)
})
const PORT = process.env.PORT || 8080
app.listen(PORT, () => {
    console.log(`App listening to ${PORT}....`)
    console.log('Press Ctrl+C to quit.')
})
```

そしてもちろん、 "Hello"と言う素敵なシンプルなHTMLファイル。(index.htmlを作成)

```html:index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>Express and Webpack App</title>
    <link rel="shortcut icon" href="#">
</head>
<body>
    <h1>Expack</h1>
    <p class="description">Express and Webpack Boilerplate App</p>
</body>
</html>
```
さて、それが動作することをテストするには、`npm start` を実行し、http：// localhost：8080にナビゲートしてください。ページにHTMLを正しく表示する必要があります。
> `npm start` を実行してブラウザで http://localhost:8080 にアクセスする。
>**実行結果**
![hello world.jpg](https://qiita-image-store.s3.amazonaws.com/0/160966/33e8689e-2f92-e18d-2937-bef7478422f4.jpeg)


## Step 2: Webpackをインストールして有効にする(Install and Enable Webpack)

webpackと関連するパッケージをインストールする。

* `webpack` -- version 4
* `webpack-cli` -- cliツール
* `webpack-node-externals` --  node-modules をサーバーサイドでbundleする。

```terminal

npm install --save-dev webpack webpack-cli webpack-node-externals
```

ES6+をES5にトランスパイルするため `Babel` をインストールする。

```terminal
npm install --save-dev babel-core babel-loader babel-preset-env
```

index.htmlファイルをdistディレクトリにコピーするには、html-loaderとhtml-webpack-pluginもインストールする必要があります。

```terminal
npm install  --save-dev html-loader html-webpack-plugin
```

Webpack設定ファイル -- webpack.config.jsを作成する必要があります。

```javascript:webpack.config.js
const path = require('path')
const webpack = require('webpack')
const nodeExternals = require('webpack-node-externals')
const HtmlWebPackPlugin = require("html-webpack-plugin")
module.exports = {
  entry: {
    server: './server.js',
  },
  output: {
    path: path.join(__dirname, 'dist'),
    publicPath: '/',
    filename: '[name].js'
  },
  target: 'node',
  node: {
    // Need this when working with express, otherwise the build fails
    __dirname: false,   // if you don't put this is, __dirname
    __filename: false,  // and __filename return blank or /
  },
  externals: [nodeExternals()], // Need this to avoid error when working with Express
  module: {
    rules: [
      {
        // Transpiles ES6-8 into ES5
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader"
        }
      },
      {
        // Loads the javacript into html template provided.
        // Entry point is set below in HtmlWebPackPlugin in Plugins 
        test: /\.html$/,
        use: [{loader: "html-loader"}]
      }
    ]
  },
  plugins: [
    new HtmlWebPackPlugin({
      template: "./index.html",
      filename: "./index.html",
      excludeChunks: [ 'server' ]
    })
  ]
}
```
Note:
excludeChunksはサーバーと呼ばれるファイルを除外します。これはWebファイルであり、アプリケーション自体には必要ないため、HTMLファイルに含めたくないファイルです。

`require` の代わりにserver.jsにES6 +のインポート構文を設定して、Babelの転送が正しく行われているかどうかをテストする必要があります。

```server.js
import path from 'path'
import express from 'express'
const app = express(),
            DIST_DIR = __dirname,
            HTML_FILE = path.join(DIST_DIR, 'index.html')
app.use(express.static(DIST_DIR))
app.get('*', (req, res) => {
    res.sendFile(HTML_FILE)
})
const PORT = process.env.PORT || 8080
app.listen(PORT, () => {
    console.log(`App listening to ${PORT}....`)
    console.log('Press Ctrl+C to quit.')
})
```

あなたのルートに.babelrcという名前のファイルを作成し、このコードで入力してください

```javascript:.babelrc
{
  'presets': ['env']
}
```

package.jsonのscriptを次のように変更します。

Unix系OSの場合

```package.json
"scripts": {
  "build": "rm -rf dist && webpack --mode development",
  "start": "node ./dist/server.js"
},
```
>Windowsの場合

>```package.json
  "scripts": {
    "build": "(if exist dist rd dist /s /q) && webpack --mode development",
    "start": "node ./dist/server.js"
  },
```

こうすることで、常に新しいdistフォルダから始め、コマンドラインから開発モードを宣言的に設定します。

これで、`npm run build`と`npm start`を実行し、http：// localhost：8080にナビゲートしてテストすることができます。
この時点で、エラーはないはずです。私はこの記事を書いているので、このステップを段階的に構築しています。

> 実行したところエラーが発生

>```terminal
npm run build
Version: webpack 4.27.1
Time: 312ms
Built at: 2018-12-06 20:08:10
       Asset       Size  Chunks             Chunk Names
./index.html  278 bytes          [emitted]
   server.js   7.16 KiB  server  [emitted]  server
Entrypoint server = server.js
[./server.js] 3.12 KiB {server} [built] [failed] [1 error]
ERROR in ./server.js
Module build failed (from ./node_modules/babel-loader/lib/index.js):
Error: Cannot find module '@babel/core'
 babel-loader@8 requires Babel 7.x (the package '@babel/core'). If you'd like to use Babel 6.x ('babel-core'), you should install 'babel-loader@7'.
    at Function.Module._resolveFilename (internal/modules/cjs/loader.js:580:15)
    at Function.Module._load (internal/modules/cjs/loader.js:506:25)
    at Module.require (internal/modules/cjs/loader.js:636:17)
    at require (e:\00_Development\src\NodeJS\express-webpack\node_modules\v8-compile-cache\v8-compile-cache.js:159:20)
    at Object.<anonymous> (e:\00_Development\src\NodeJS\express-webpack\node_modules\babel-loader\lib\index.js:10:11)
    at Module._compile (e:\00_Development\src\NodeJS\express-webpack\node_modules\v8-compile-cache\v8-compile-cache.js:178:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:699:10)
    at Module.load (internal/modules/cjs/loader.js:598:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:537:12)
    at Function.Module._load (internal/modules/cjs/loader.js:529:3)
    at Module.require (internal/modules/cjs/loader.js:636:17)
    at require (e:\00_Development\src\NodeJS\express-webpack\node_modules\v8-compile-cache\v8-compile-cache.js:159:20)
    at loadLoader (e:\00_Development\src\NodeJS\express-webpack\node_modules\loader-runner\lib\loadLoader.js:13:17)
    at iteratePitchingLoaders (e:\00_Development\src\NodeJS\express-webpack\node_modules\loader-runner\lib\LoaderRunner.js:169:2)
    at runLoaders (e:\00_Development\src\NodeJS\express-webpack\node_modules\loader-runner\lib\LoaderRunner.js:362:2)
    at NormalModule.doBuild (e:\00_Development\src\NodeJS\express-webpack\node_modules\webpack\lib\NormalModule.js:280:3)
    at NormalModule.build (e:\00_Development\src\NodeJS\express-webpack\node_modules\webpack\lib\NormalModule.js:427:15)
    at Compilation.buildModule (e:\00_Development\src\NodeJS\express-webpack\node_modules\webpack\lib\Compilation.js:633:10)
    at moduleFactory.create (e:\00_Development\src\NodeJS\express-webpack\node_modules\webpack\lib\Compilation.js:1019:12)
    at factory (e:\00_Development\src\NodeJS\express-webpack\node_modules\webpack\lib\NormalModuleFactory.js:405:6)
    at hooks.afterResolve.callAsync (e:\00_Development\src\NodeJS\express-webpack\node_modules\webpack\lib\NormalModuleFactory.js:155:13)
    at AsyncSeriesWaterfallHook.eval [as callAsync] (eval at create (e:\00_Development\src\NodeJS\express-webpack\node_modules\tapable\lib\HookCodeFactory.js:32:10), <anonymous>:6:1)
    at AsyncSeriesWaterfallHook.lazyCompileHook (e:\00_Development\src\NodeJS\express-webpack\node_modules\tapable\lib\Hook.js:154:20)
    at resolver (e:\00_Development\src\NodeJS\express-webpack\node_modules\webpack\lib\NormalModuleFactory.js:138:29)
    at process.nextTick (e:\00_Development\src\NodeJS\express-webpack\node_modules\webpack\lib\NormalModuleFactory.js:342:9)
    at process._tickCallback (internal/process/next_tick.js:61:11)
```
> `babel-loader` のバージョンが8.x だったのが原因のため、`babel-loader` を 7.xにダウングレードする。

>```terminal
npm install babel-loader@7 -D
```

> ビルド成功

>```terminal
npm run build
Version: webpack 4.27.1
Time: 1397ms
Built at: 2018-12-06 20:15:36
       Asset       Size  Chunks             Chunk Names
./index.html  278 bytes          [emitted]
   server.js   5.06 KiB  server  [emitted]  server
Entrypoint server = server.js
[./server.js] 677 bytes {server} [built]
[express] external "express" 42 bytes {server} [built]
[path] external "path" 42 bytes {server} [built]
Child html-webpack-plugin for "index.html":
     1 asset
    Entrypoint undefined = ./index.html
    [./node_modules/html-webpack-plugin/lib/loader.js!./index.html] 330 bytes {0} [built]
```
> ビルド後のserver.jsが正常に実行できることを確認

>```
npm start
```
![hello world.jpg](https://qiita-image-store.s3.amazonaws.com/0/160966/5b34a7ea-ff49-7aa3-c115-19d6960eb9a5.jpeg)

## Step 3: アプリケーションにCSSとJavascriptの機能を追加する

すでにかなりの機能が実装されていますが、CSSスタイル、Javascript、画像をアプリに追加することができます。

これを行うには、Webpack設定を2つのファイルに分割する必要があります。後で3つのファイルになります。

* **webpack.server.config.js**<br>サーバーコードだけをバンドルする。
* **webpack.config.js**<br>アプリケーションコードをバンドルする。

後で、このメインの設定ファイルをDevとProdのバージョンに分け、サーバーファイルをDevとProdのバージョンに分けます。

まず、必要な依存関係をインストールしましょう。

```terminal
npm install --save-dev css-loader file-loader style-loader
```

私たちのディレクトリ構造は次のようになります

```
.babelrc
.git
.gitignore
README.md
dist
node_modules
package-lock.json
package.json
webpack.config.js
webpack.server.config.js
src
    index.js
    html
        index.html
    css
        style.css
    js
        logger.js ※原文ではindex.jsだが誤り。
    img
        bg.jpg ※原文ではawful-selfie.jpgだが誤り。原文ではbg.jpgを置く手順が抜けている。
    server
        server.js
```

package.jsonのscriptを調整します。

Unix系OSの場合

```pakage.json
"scripts": {
  "build": "rm -rf dist && webpack --mode development --config webpack.server.config.js && webpack --mode development",
  "start": "node ./dist/server.js"
},
```
> Windowsの場合

>```pakage.json
  "scripts": {
    "build": "(if exist dist rd dist /s /q) && webpack --mode development --config webpack.server.config.js && webpack --mode development",
    "start": "node ./dist/server.js"
  },
```

./src/html/index.htmlを更新してください。

```index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>Express and Webpack App</title>
    <link rel="shortcut icon" href="#">
</head>
<body>
    <h1>Expack</h1>
    <p class="description">Express and Webpack Boilerplate App</p>
    <div class="awful-selfie"></div>
</body>
</html>
```
./src/css/style.css を更新してください。

```style.css
h1, h2, h3, h4, h5, p {
  font-family: helvetica;
  color: #3e3e3e;
}
.description {
  font-size: 14px;
  color: #9e9e9e;
}
.awful-selfie{
  background: url(../img/bg.jpg);
  width: 300px;
  height: 300px;
  background-size: 100% auto;
  background-repeat: no-repeat;
}
```
./src/index.jsを更新して、インポートとスタイルが機能しているか、基本的な機能を確認します。


```index.js
import logMessage from './js/logger'
import './css/style.css'
// Log message to console
logMessage('Welcome to Expack!')
```

もちろん./src/js/logger.js

```logger.js
const logMessage = msg => console.log(msg)
export default logMessage
```
server.jsをルートから./src/serverに移動するだけです。これにより、ルートがきれいに保たれ、サーバーコードが適切な場所に保持されます。

最後に、Webpackの設定をしましょう。我々は./webpack.server.config.jsから始めます。

```webpack.server.config.js
const path = require('path')
const webpack = require('webpack')
const nodeExternals = require('webpack-node-externals')
module.exports = {
  entry: {
    server: './src/server/server.js',
  },
  output: {
    path: path.join(__dirname, 'dist'),
    publicPath: '/',
    filename: '[name].js'
  },
  target: 'node',
  node: {
    // Need this when working with express, otherwise the build fails
    __dirname: false,   // if you don't put this is, __dirname
    __filename: false,  // and __filename return blank or /
  },
  externals: [nodeExternals()], // Need this to avoid error when working with Express
  module: {
    rules: [
      {
        // Transpiles ES6-8 into ES5
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader"
        }
      }
    ]
  }
}
```

そして、./webpack.config.jsですべてを終わらせましょう。

```webpack.config.js
const path = require("path")
const webpack = require('webpack')
const HtmlWebPackPlugin = require("html-webpack-plugin")
module.exports = {
  entry: {
    main: './src/index.js'
  },
  output: {
    path: path.join(__dirname, 'dist'),
    publicPath: '/',
    filename: '[name].js'
  },
  target: 'web',
  devtool: '#source-map',
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: "babel-loader",
      },
      {
        // Loads the javacript into html template provided.
        // Entry point is set below in HtmlWebPackPlugin in Plugins 
        test: /\.html$/,
        use: [
          {
            loader: "html-loader",
            //options: { minimize: true }
          }
        ]
      },
      {
        test: /\.css$/,
        use: [ 'style-loader', 'css-loader' ]
      },
      {
       test: /\.(png|svg|jpg|gif)$/,
       use: ['file-loader']
      }
    ]
  },
  plugins: [
    new HtmlWebPackPlugin({
      template: "./src/html/index.html",
      filename: "./index.html",
      excludeChunks: [ 'server' ]
    })
  ]
}
```
appビルドにtarget： 'web'を使う方法に注目してください。これは非常に重要です。target： 'node'を使用するとエラーが発生しますので、再度確認してください。

`npm run build`を実行すると、エラーは発生しません。
> ビルド実行でエラー発生

>```
npm runbuild
ERROR in ./src/css/style.css (./node_modules/css-loader!./src/css/style.css)
Module not found: Error: Can't resolve '../img/bg.jpg' in 'e:\00_Development\src\NodeJS\express-webpack\src\css'
 @ ./src/css/style.css (./node_modules/css-loader!./src/css/style.css) 7:221-245
 @ ./src/css/style.css
 @ ./src/index.js
ERROR in ./src/js/logger.js
Module not found: Error: Can't resolve './css/style.css' in 'e:\00_Development\src\NodeJS\express-webpack\src\js'
 @ ./src/js/logger.js 7:0-26
 @ ./src/index.js
ERROR in ./src/js/logger.js
Module not found: Error: Can't resolve './js/logger' in 'e:\00_Development\src\NodeJS\express-webpack\src\js'
 @ ./src/js/logger.js 3:14-36
 @ ./src/index.js
```

> ./src/img/bg.jpg に適当な画像をおいてからビルドしたら成功

>```
npm run build
Version: webpack 4.27.1
Time: 772ms
Built at: 2018-12-06 21:06:33
    Asset      Size  Chunks             Chunk Names
server.js  5.13 KiB  server  [emitted]  server
Entrypoint server = server.js
[./src/server/server.js] 677 bytes {server} [built]
[express] external "express" 42 bytes {server} [built]
[path] external "path" 42 bytes {server} [built]
Hash: 1825c3d14b01b8828f32
Version: webpack 4.27.1
Time: 1228ms
Built at: 2018-12-06 21:06:36
                               Asset       Size  Chunks             Chunk Names
                        ./index.html  374 bytes          [emitted]
40f2415d89a70d2c43d735d7607767c0.jpg   12.7 KiB          [emitted]
                             main.js   23.6 KiB    main  [emitted]  main
                         main.js.map   26.6 KiB    main  [emitted]  main
Entrypoint main = main.js main.js.map
[./node_modules/css-loader/index.js!./src/css/style.css] ./node_modules/css-loader!./src/css/style.css 573 bytes {main} [built]
[./src/css/style.css] 1.06 KiB {main} [built]
[./src/img/bg.jpg] 82 bytes {main} [built]
[./src/index.js] 299 bytes {main} [built]
[./src/js/logger.js] 183 bytes {main} [built]
    + 4 hidden modules
Child html-webpack-plugin for "index.html":
     1 asset
    Entrypoint undefined = ./index.html
    [./node_modules/html-webpack-plugin/lib/loader.js!./src/html/index.html] 379 bytes {0} [built]
```
> `npm start` を実行してブラウザで http://localhost:8080 にアクセスする。

>```
npm start
```
![expack2.jpg](https://qiita-image-store.s3.amazonaws.com/0/160966/daaefbc9-a1a5-31fb-ba33-3aad8727cd80.jpeg)

続きはこちら [ExpressをWebpackでバンドルする その2](https://wakusan.qrunch.io/entries/ADg11L2Jw4O4HrR1) qrunch投稿なのであとでQiita用にupします。