Nodeでサーバを立ててReactを使ったページでハロワをやってみる

2017.09.29

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

暑いのか寒いのか分からない時期になってまいりました。寒暖差で疲労コンパイルの齋藤です。

今回はNodeでサーバを立てて そこにReactを使ったページを表示してみます。

また、今回は開発環境の構築をメインにしているので開発環境の部分の説明が多くなります。

今回使ったのはざっと以下のライブラリ・ツールです

  • Node v 8.5.0
  • babel with JSX
  • eslint
  • webpack & webpack-dev-server
  • express

サーバーを立てる

まずはexpressというライブラリを使ってHTTPサーバーを立てます。

プロジェクトの初期化をしてexpressをインストールします。

npm init -y
npm i express -S

今回作成したサーバのソースです。src/server/index.jsに配置しておきます。

const express = require("express")
const morgan = require("morgan")
const app = express()
const path = require("path")
app.use(morgan("combined"))

app.get("/", function(req, res) {
  res.sendFile(path.resolve("./target/index.html"))
})

ホストする静的htmlは以下のようなものです。

<!DOCTYPE html>
<html>

<head>
  <title>Start Page</title>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
  <script src="/bundle.js"></script>
  <link rel="stylesheet" href="style.css" />
</head>

<body>
  <div id="root"></div>
</body>

</html>

morganはexpressのミドルウェアでロギングのライブラリです。 サーバにアクセスすると下のようなログが出力されます。

::ffff:127.0.0.1 - - [28/Sep/2017:23:34:22 +0000] "GET /users HTTP/1.1" - - "http://localhost:9000/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36"

今回はNodeを使ってサーバを立てるのはこれだけです。 本来であれば、APIとかを用意する必要があるでしょう。 今回は静的HTMLのホストのみを行います。

フロント向けに設定を追加する

babel

まずはbabelのセットアップをします。 現時点での最新です。

npm i babel-core babel-preset-env babel-preset-react babel-plugin-transform-class-properties babel-plugin-transform-object-rest-spread -D
npm i babel-polyfill -S

package.jsonはこんな感じになりました。

  ...省略
  "devDependencies": {
    "babel-core": "^6.26.0",
    "babel-plugin-transform-class-properties": "^6.24.1",
    "babel-plugin-transform-object-rest-spread": "^6.26.0",
    "babel-preset-env": "^1.6.0",
    "babel-preset-react": "^6.24.1",
  },
  "dependencies": {
    "babel-polyfill": "^6.26.0",
  }
  ...省略

また、babelの設定を .babelrc に記述します。 babel-preset-envの設定でtargetsでバージョンを指定しておくと、いい感じにbabelがトランスパイルしてくれるそうです。 今回はreactに加えて2つのプラグインを追加しています。

{
  "presets": [
    [
      "env",
      {
        "targets": {
          "node": "6.10",
          "browsers": [
            "last 2 versions"
          ]
        }
      }
    ],
    "react"
  ],
  "plugins": [
    "transform-class-properties", "transform-object-rest-spread"
  ]
}

babelのセットアップはここまでです。

eslint

次は無くてはならないeslintのセットアップです。無いと死んじゃう まずはライブラリを入れていきましょう。

npm i babel-eslint eslint-plugin-prettier eslint-plugin-react prettier

package.jsonがこんな感じのが追加されました。

  ...省略
  "devDependencies": {
    ...省略
    "eslint": "^4.7.2",
    "eslint-plugin-prettier": "^2.3.1",
    "eslint-plugin-react": "^7.4.0",
    "prettier": "^1.7.0"
  }
  ...省略

設定を .eslintrc に記述します。

{
  "extends": "eslint:recommended",
  "parser": "babel-eslint",
  "env": {
    "browser": true,
    "es6": true,
    "node": true
  },
  "parserOptions": {
    "sourceType": "module",
    "ecmaFeatures": {
      "classes": true,
      "jsx": true
    }
  },
  "plugins": [
    "prettier",
    "react"
  ],
  "ecmaFeatures": {
    "modules": true,
    "classes": true
  },
  "rules": {
    "prettier/prettier": [
      "error",
      {
        "trailingComma": true,
        "singleQuote": false,
        "semi": false
      }
    ],
    "react/jsx-uses-react": "error",
    "react/jsx-uses-vars": "error",
    "quotes": [
      "error",
      "double",
      {
        "avoidEscape": true,
        "allowTemplateLiterals": true
      }
    ],
    "semi": [
      "error",
      "never"
    ],
    "eqeqeq": "error",
    "dot-location": [
      "error",
      "property"
    ],
    "no-var": "error",
    "prefer-const": "error",
    "prefer-spread": "error",
    "prefer-template": "error",
    "no-console": "off",
    "no-undefined": "error",
    "no-extra-label": "error",
    "dot-notation": "error",
    "no-extra-bind": "error",
    "no-multi-spaces": "error",
    "no-useless-return": "error",
    "no-floating-decimal": "error",
    "no-implicit-coercion": "error",
    "wrap-iife": "error",
    "no-lonely-if": "error",
    "yoda": [
      "error",
      "never",
      {
        "exceptRange": true
      }
    ]
  }
}

eslintのセットアップはここまでです。

webpackの設定

今回は開発時はwebpack-dev-serverを使います。 まずはインストールしていきます。

npm i webpack webpack-dev-server babel-loader -D

package.jsonはこんな感じになりました。

  ...省略
  "devDependencies": {
    ...省略
    "webpack": "^3.6.0",
    "webpack-dev-server": "^2.9.0"
    ...省略
  }
  ...省略

webpackの設定をwebpack.config.jsに記述します。 内容的には下記のような設定です。 今回はシンプルにbabelだけ利用します。

const path = require("path")
module.exports = {
  devServer: {
    contentBase: "./target",
    watchContentBase: true,
    port: 9000,
    open: true,
  },
  devtool: "source-map",
  entry: "./src/browser/index.js",
  output: {
    path: path.resolve("./target/"),
    filename: "bundle.js",
  },
  resolve: {},
  module: {
    rules: [
      {
        test: /\.js$/,
        use: [
          {
            loader: "babel-loader",
          },
        ],
      },
    ],
  },
}

この設定の場合、webpack-dev-serverを起動した際に自動でブラウザが起動します。 不要な場合、devServer.openをfalseにしてください。

ここまでがwebpackの設定です。

開発サーバのスタートまでのタスクを書いてみる。

今回はタスクランナーとして、npm scriptsを使います。 それだけだと非力なのでnpm-run-allというライブラリをインストールしていきます。 これは、npm scriptsベースのタスクを並列実行できたりするライブラリです。

npm i npm-run-all -D

npm scriptsはnode_modules/.binにパスが通った状態で実行されます。 先ほどインストールしたnpm-run-allは.binにライブラリと同名の実行ファイルを配置します。 そのため、npm scriptsを記述すると、package.jsonは以下のような形になりました。

  ...省略
  "devDependencies": {
    ...省略
    "npm-run-all": "^4.1.1",
    ...省略
  }
  ...省略
  "scripts": {
    "start": "npm-run-all -p start:* watch:build",
    "start:server": "node src/server/index.js",
    "watch:build": "webpack-dev-server",
  },
  ...省略

以下のように実行するだけでwebpack-dev-serverとnodeで書いたサーバが並列で立ち上がります。

npm start

タスクの設定はここまでです。

フロント側を書く

まずは必要なライブラリをインストールします。

npm i react react-dom -S

src/browser/index.jsに以下のようなファイルを用意しました。 簡単なものです。

require("babel-polyfill")
const React = require("react")
const ReactDOM = require("react-dom")
document.addEventListener("DOMContentLoaded", () => {
    ReactDOM.render(
      <div>Hello World</div>,
      document.getElementById("root")
    )
})

フロント側のコードはここまでです。

まとめ

今回はボリュームが多くなってしまったので あまりサーバを立てた意味がなかったりするんですが (実際は開発時はwebpackでホストしたサーバの方を見ている) ざっとこんな形で動くものが一旦作成できました。

本当はcreate-react-appなどから作成されたソースを使おうかなぁと思っていたのですが 久しぶりに書いたので、ちょっとリハビリを兼ねて書いて見ました。 久しぶりにbabelを触った気がします。 babel7が出るそうなので楽しみです。

次はもう少しサーバー側とフロント側のコードを増やして記事を書けたら、と思っています。

webpackの設定やタスクの設定など、まだまだ必要なものがあるので その点についても新しい記事として上げていきたいと思っています。

ではまた。