Help us understand the problem. What is going on with this article?

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

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

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に以下を追加してください。

pakage.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 にアクセスする。
実行結果
hello world.jpg

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の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 buildnpm 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'
 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にダウングレードする。

npm install babel-loader@7 -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

hello world.jpg

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の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

続きはこちら ExpressをWebpackでバンドルする その2 qrunch投稿なのであとでQiita用にupします。

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away