JavaScript
初心者
reactjs
webpack
babel

用語解説から丁寧に。yarnでwebpack-dev-server+Babel+React(JSX)な開発環境構築手順

More than 1 year has passed since last update.

はじめに

Reactは注目されているJavascriptライブラリの一つですが実際にReactを使ったアプリケーションを作成するためにはReactだけではなく周辺技術を理解し、開発環境に取り入れる必要があります。
今はcreate-react-appのような開発環境をよしなに作ってくれる便利ツールもあるのでそれを活用すればいいのですが、そういったツールでは自分で開発環境を色々とカスタマイズしにくいというデメリットもあります。
今回はBabelとwebpackを利用して、JSXで記述されたReactをローカルサーバーで実行・確認できる開発環境を構築します。
また、ただコマンドを記述するだけでなく、周辺技術について理解できていない方でもキャッチアップできるように説明を適宜加えていきます。

用語説明

まず手順に入る前に前提知識となる用語についてそれぞれ説明していきます。

yarn

yarnとはJavaScriptパッケージマネージャーのことをいいます。
パッケージマネージャーとはプロジェクトで利用するライブラリを管理するもので、パッケージマネージャーを利用することで使いたいライブラリの導入や削除を簡単できたり、パッケージ同士の依存関係を管理できたりすることができます。
JavaScriptパッケージマネージャーといえばnpmが有名ですが、yarnはnpmに対して互換性があります。
yarnはnpmに比べて高速に動作できたり、バージョンの厳密な管理ができたりするメリットがあります。
(今回はyarnを利用しますがnpmを利用している方は適宜読み替えてください。)

JSX

JSXとはJavaScriptの独自拡張構文のことをいい、Reactで使われるものです。
特徴としてはJavaScriptの中にHTML(DOM要素)が書かれている点が挙げられます。

具体例としてJSXを利用して書かれたReactのソースコードと利用していないソースコードの比較を載せます。

JSX
var CommentBox = React.createClass({
  render: function() {
    return (
      <div className="commentBox">
        Hello, world! I am a CommentBox.
      </div>
    );
  }
});
React.render(
  <CommentBox />,
  document.getElementById('content')
);
JavaScript
var CommentBox = React.createClass({
  displayName: "CommentBox",

  render: function render() {
    return React.createElement(
      "div",
      { className: "commentBox" },
      "Hello, world! I am a CommentBox."
    );
  }
});
React.render(React.createElement(CommentBox, null), document.getElementById('content'));

JSXで書かれているコードの方がわかりやすいのでReactもJSXで書くように推奨されていますが、ここで問題になることがあります。
JSXはJavaScriptを拡張した構文であるため、そのままではフロントエンド環境(ブラウザ)では読み込むことができないということです。そのため、JSXでフロントエンド開発を行うために準備が必要となります。

Babel

BabelはJavaScriptのトランスパイラ(トランスコンパイラなどともいう)です。
トランスパイラとは入力としてあるソースコードを受け取り、別のソースコードを出力するものです。
先ほどの例で言えば、JSXで書かれたソースコードをJavaScriptに変換するのがBabelの役割です。これにより、JSXがブラウザでも実行可能なソースコードに変換されるように可能になります。

実際にBabelを利用した入出力の変換は以下のブラウザからも確認ができます。
http://babeljs.io/

つまり、Babelを利用することでブラウザで未対応なJavaScriptの仕様を気にすることなくフロントエンド開発を進めることできます。

(補足) ES6(ES2015)

Babelの文脈ではES6という単語もよく出てくるのでES6について説明します。
先ほどBabelはJSXからjavascriptへ変換するための役割を持つと言いましたがBabelにはもう一つ大切な役割があります。それがES6からES5へ変換する役割です。
ES6は簡単に説明をすると2015年6月にリリースされたJavaScriptの仕様です。
ES6からは例えば、変数の宣言としてvarを利用していたものがES6ではさらにlet, constというシンタックスも利用できるようになったり、class, アロー関数, テンプレート構文などが追加されました。以下は異なる仕様で書いたソースコードになります。

ES5(ES6の前)
var name = "山田太郎";
var age = 20;

var Person = {
  name: name,
  age: age
}
...
ES6
let name = "山田太郎";
let age = 20;

let Person = {
  name,
  age
}
...

ES6に関してもJSXのようにブラウザで完全に対応しているわけではないため、ES6の機能を利用して開発を行う場合はBabelを利用します。

webpack

Babelを使うことによりソースコードをトランスパイルすることができ、新機能を利用したJavascriptも仕様に対応していないブラウザ上で実行が可能になりました。しかし実際のウェブアプリケーションでは複数のjsファイルで構成されておりそれぞれが依存関係を持った状態で存在しております。ブラウザで実行するためにはそれら複数のファイルを読み込まないといけないわけですが、Babelにはそういった依存関係を解決する機能は実装されていません。

そこでwebpackというものが登場してきます。webpackはモジュールバンドラの一種です。
モジュールバンドラとは名前の通りなのですがモジュールをひとまとめにする(bundle)ツールのことを言います。
webpackを利用することで複数のjsファイルの依存関係を顧慮してビルドを行い、最終的に1つのjsファイルを出力してくれます。(webpackではjsファイルだけでなくcssファイルやイメージファイルも一緒にまとめられます。)
ファイルをまとめることによりウェブページのHTTPリクエストの数を減らしたり、高度なウェブアプリケーションの開発をできたりするメリットがあります。

なお余談ですが、どのファイルを入力としてどのようにファイルを出力させるかといった内容を管理することに特化したツールをタスクランナーと呼び、有名なものではGulpやGruntというものがあります。これらはwebpackの文脈でよく見かけることがあります。
タスクの管理はタスクランナーに任せ、実際のバンドル化をwebpackで実行するという感じで組み合わせます。しかし、webpackには様々な機能があるため実際にはタスクランナーを利用しなくても事が足りる事が多いです。今回もタスクランナーは利用せずに開発環境を構築していきます。

ディレクトリ構造

今回作成する開発環境は以下のようなディレクトリ構成になっております。

├── dist
│   ├── bundle.js
│   └── index.html
├── node_modules
├── package.json
├── src
│   ├── Hello.jsx
│   └── index.jsx
├── webpack.config.js
├── yarn-error.log
└── yarn.lock

yarnの導入

今回yarnを利用するため、もしyarnをインストールしていない場合はインストールを行います。

# npmでインストール 
$ npm install -g yarn

# Homebrewでインストール 
$ brew update
$ brew install yarn

プロジェクトの作成

まずはプロジェクト用のディレクトリの作成と移動を行います。

$ mkdir react-webpack-sample
$ cd react-webpack-sample

package.jsonの作成

まずはpackage.jsonを作成します。
package.jsonとはプロジェクトの依存モジュールが記述されたファイルのことをいいます。このpackage.jsonを参照することでそのプロジェクトで必要なモジュールを揃えたり、様々なオプションを利用する事ができます。

# package.jsonの作成
$ yarn init
-> 以下のような質問が表示されるので適宜回答していきます。

question name (react-webpack-sample):
question version (1.0.0):
question description: sample application for react with webpack
question entry point (index.jsx):
question repository url:
question author: nishina555
question license (MIT):
question private:

プロジェクトの中身を調べるとpackage.jsonが作成されている事がわかります。

$ ls

package.json

動作環境構築

Reactのパッケージのインストール

Reactをプロジェクトにインストールします。
インストールにはyarn addを利用します。yarn addnpm install --saveと同意でインストールとpackage.jsonへの記録を行います。

$ yarn add react react-dom

package.jsonを確認するとdependencyに追加されていることがわかります。

package.json
{
  "name": "react-webpack-sample",
  "version": "1.0.0",
  "description": "sample application for react with webpack",
  "main": "index.jsx",
  "author": "nishina555",
  "license": "MIT",
  "dependencies": {
    "react": "^16.0.0",
    "react-dom": "^16.0.0"
  }
}

またnode_modulesとyarn.lockが新しくできていることがわかります。
yarn.lockはnpmのshrinkwrapに相当する機能で、これにより依存するモジュールのバージョンを固定することができ、バージョンの不整合によるバグなどを未然に防ぐ事ができます。
node_modulesはyarn(npm)を利用してインストールしたパッケージを格納されるディレクトリです。

$ ls
node_modules   package.json  yarn.lock

webpackとBabelのインストール

webpackをインストールします。webpackでbabelが利用できるように併せていくつかモジュールもインストールします。
インストールするモジュールの補足説明です。

  • babel-loader
    • webpackからBabelを使用できるようにするモジュールです。
  • babel-preset-es2015
    • ES6をトランスパイルできるようにするためのモジュールです。
  • babel-preset-react
    • reactをトランスパイルできるようにするためのモジュールです。

また、yarnの--devオプションは開発環境で利用するモジュールをインストール、package.jsonに記録するときのためのオプションです。

$ yarn add --dev webpack babel-loader babel-core babel-preset-react babel-preset-es2015

package.jsonを確認するとdevDependenciesに記録されている事がわかります。

package.json
{
  "name": "react-webpack-sample",
  "version": "1.0.0",
  "description": "sample application for react with webpack",
  "main": "index.jsx",
  "author": "nishina555",
  "license": "MIT",
  "dependencies": {
    "react": "^16.0.0",
    "react-dom": "^16.0.0"
  },
  "devDependencies": {
    "babel-core": "^6.26.0",
    "babel-loader": "^7.1.2",
    "babel-preset-es2015": "^6.24.1",
    "babel-preset-react": "^6.24.1",
    "webpack": "^3.7.1"
  }
}

webpackの設定

まず、webpackで出力されるファイルのディレクトリ(dist)と入力となるJSファイルのディレクトリ(src)を作成します。

$ mkdir src dist

次にwebpackの設定をwebpack.config.jsに作成します。

webpack.config.js
const path = require('path');

module.exports = {
  entry: __dirname + "/src/index.jsx", // トランスパイル対象
  output: {
    path: __dirname + '/dist', // 出力先ディレクトリ
    filename: 'bundle.js' // 入力されたファイルをまとめて出力するときのファイル名
  },
  module: {
    rules: [
      {
        test: /\.js[x]?$/,
        exclude: /node_modules/,
        loader: "babel-loader", // Babelをwebpackで利用できるようにする
        options:{
          presets: ['react', 'es2015'] // reactとes2015をトランスパイル対象とする
        }
      }
    ]
  },
  resolve: {
    extensions: ['.js', '.jsx'] // jsファイル, jsxファイルを対象とする
  }
};

サンプルの作成

サンプルファイルを作成します。まずはsrcディレクトリにreactのサンプルファイルを作成します。

index.jsx
import React from 'react'
import ReactDOM from 'react-dom'
import {Hello} from './Hello'

ReactDOM.render(
  <Hello />,
  document.getElementById('root'))

Hello.jsx
import React from 'react'
export class Hello extends React.Component {
  render (){
    return <h1>Hello!</h1>
  }
}

次にdistディレクトリにhtmlファイルを作成します。
このhtmlファイルからwebpackで作成されたbundle.jsファイルを読み込む事で上記のreactファイルをブラウザ上に表示できるようになります。

index.html
<!DOCTYPE html>
<html><head>
  <title>react-webpack-sample</title>
  <meta charset="utf-8">
</head>
  <body>
    <div id="root"></div>
    <script src="bundle.js"></script>
  </body>
</html>

実際にブラウザでHello!が見れるか確認していきます(動作を確認するために行いますが、この部分は飛ばして問題ないです)
webpackを利用してsrcディレクトリのファイルをバンドル(一つにまとめる)します。
プロジェクトのルートディレクトリに移動して以下を実行します。

$ ./node_modules/.bin/webpack

Hash: 6bea75733e774b2055c3
Version: webpack 3.7.1
Time: 1605ms
    Asset    Size  Chunks                    Chunk Names
bundle.js  836 kB       0  [emitted]  [big]  main
  [15] ./src/index.js 427 bytes {0} [built]
  [32] ./src/Hello.js 2.25 kB {0} [built]
    + 31 hidden modules

上記を実行するとdist/bundle.jsが作成されている事がわかります。

$ cd dist
$ ls
bundle.js

index.htmlを直接開くと以下のようになっている事がわかります。

スクリーンショット 2017-10-15 16.02.34.png

ローカルサーバーの作成

ここまででwebpackでコンパイルしindex.htmlを開けばReactが読み込めるところまできました。
次に直接ファイルを開くのではなくlocal:3000のようにローカルサーバーにアクセスしてファイルを閲覧できるようにします。
ローカルサーバー作成にはwebpackの機能であるwebpack-dev-serverを利用します。

$ yarn add --dev webpack-dev-server

webpack-dev-serverを起動するとデフォルトではカレントディレクトリの静的ファイルを確認するようになっているのでwebpack.config.jsの設定を変更しておきます。

webpack.config.js
const path = require('path');

module.exports = {
  entry: __dirname + "/src/index.jsx", // トランスパイル対象
  output: {
    path: __dirname + '/dist', // 出力先ディレクトリ
    filename: 'bundle.js' // 入力されたファイルをまとめて出力するときのファイル名
  },
  module: {
    rules: [
      {
        test: /\.js[x]?$/,
        exclude: /node_modules/,
        loader: "babel-loader", // Babelをwebpackで利用できるようにする
        options:{
          presets: ['react', 'es2015'] // reactとes2015をトランスパイル対象とする
        }
      }
    ]
  },
+  devServer: {
+    contentBase: path.resolve(__dirname, "dist"), // distディレクトリのファイルを確認する
+    port: 3000, // 3000ポートを利用
+  },
  resolve: {
    extensions: ['.js', '.jsx'] // jsファイル, jsxファイルを対象とする
  }
};

以下のように実行する事でローカルサーバーが立ち上がります。

$ ./node_modules/.bin/webpack-dev-server

Project is running at http://localhost:3000/
webpack output is served from /
Content not from webpack is served from /Users/nishina/workspace/react-webpack-sample/dist
Hash: f88672c6f42169dc06f7
Version: webpack 3.7.1
Time: 1891ms
    Asset     Size  Chunks                    Chunk Names
bundle.js  1.15 MB       0  [emitted]  [big]  main
   [0] ./node_modules/process/browser.js 5.42 kB {0} [built]

余談ですが、package.jsonにはscriptsというプロパティ(エイリアスのようなもの)があるので、以下のように記述する事でnpm startと打てばwebpack-dev-serverを実行できるようになります。

package.json
{
  "name": "react-webpack-sample",
  "version": "1.0.0",
  "description": "sample application for react with webpack",
  "main": "index.jsx",
  "author": "nishina555",
  "license": "MIT",
  "dependencies": {
    "react": "^16.0.0",
    "react-dom": "^16.0.0"
  },
  "scripts": {
    "start": "webpack-dev-server"
  },
  "devDependencies": {
    "babel-core": "^6.26.0",
    "babel-loader": "^7.1.2",
    "babel-preset-es2015": "^6.24.1",
    "babel-preset-react": "^6.24.1",
    "webpack": "^3.7.1",
    "webpack-dev-server": "^2.9.1"
  }
}

なお、webpack-dev-serverではファイルの変更をローカルサーバーが起動させたまま反映してくれるのでReactのソースコードを修正すると自動的にブラウザの表示が変更されます。例えば、「Hello!」を「Hello!React!」とするとブラウザも以下のように即時変更されます。

react_sample.gif

終わりに

今回はBabel, webpackを利用して、JSXで記述されたReactをローカルサーバーで実行・確認できる開発環境を構築する手順を説明しました。
周辺技術はその関係性や役割など理解するのに混乱しますが、実際に手を動かして利用することで理解が深まると思うのでまだ整理できていない方はぜひ一度チャレンジしてみてください。
また、自分も色々な記事を参考に説明などをしているのですが間違った点などあれば指摘して頂ければと思います。

参考