BETA

ExpressをWebpackでバンドルする その1

投稿日:2018-12-23
最終更新:2018-12-24
※この記事は外部サイト(https://qiita.com/wakusan-6126/items/c7963...)からのクロス投稿です

記事に従ってやってみた。

Creating a Node Express-Webpack App with Dev and Prod Builds を翻訳しながら進める。(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

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

mkdir express-webpack  
cd express-webpack  

package.jsonを作成

npm init -y  

Expressをインストール

npm install --save express  

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

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

基本的なExpressサーバー・ファイルをプロジェクト・ルート・ディレクトリserver.jsに書き込んで、動作することをテストしましょう
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を作成)
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 にアクセスする。
実行結果

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

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

  • webpack -- version 4
  • webpack-cli -- cliツール
  • webpack-node-externals -- node-modules をサーバーサイドでbundleする。
npm install --save-dev webpack webpack-cli webpack-node-externals  

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

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

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

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

Webpack設定ファイル -- webpack.config.jsを作成する必要があります。
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という名前のファイルを作成し、このコードで入力してください
.babelrc

{  
  'presets': ['env']  
}  

package.jsonスクリプトを次のように変更します。
package.json
Unix系OSの場合

"scripts": {  
  "build": "rm -rf dist && webpack --mode development",  
  "start": "node ./dist/server.js"  
},  

Windowsの場合

  "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にナビゲートしてテストすることができます。
この時点で、エラーはないはずです。私はこの記事を書いているので、このステップを段階的に構築しています。

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

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'  
 [email protected] requires Babel 7.x (the package '@babel/core'). If you'd like to use Babel 6.x ('babel-core'), you should install '[email protected]'.  
    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にダウングレードする。

npm install [email protected] -D  

ビルド成功

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  

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

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

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

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

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

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

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スクリプトを調整します。
pakage.json
Unix系OSの場合

"scripts": {  
  "build": "rm -rf dist && webpack --mode development --config webpack.server.config.js && webpack --mode development",  
  "start": "node ./dist/server.js"  
},  

Windowsの場合

  "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  

続きはこちら ExpressをWebpackでバンドルする その2

技術ブログをはじめよう Qrunch(クランチ)は、プログラマの技術アプトプットに特化したブログサービスです
駆け出しエンジニアからエキスパートまで全ての方々のアウトプットを歓迎しております!
or 外部アカウントで 登録 / ログイン する
クランチについてもっと詳しく

この記事が掲載されているブログ

@wakusanの技術ブログ

よく一緒に読まれる記事

0件のコメント

ブログ開設 or ログイン してコメントを送ってみよう
目次をみる
技術ブログをはじめよう Qrunch(クランチ)は、プログラマの技術アプトプットに特化したブログサービスです
or 外部アカウントではじめる
10秒で技術ブログが作れます!