#この記事について
この記事では実際にReactの開発環境をセットアップする方法を説明します。ReactなどのSPAでネックになる速度を改善するための手法としてサーバーサイドレンダリングも行います。
- 出来る限り短く説明するReact.js入門 | Reactとは?を解説してくれている良記事
フロントエンド開発についてまだよく知らないという人は下の記事を読んでおきましょう。
フロントエンドとはいえ、今回は静的サイトではなく動的サイト(サーバーサイドレンダリングを行うため)を作成するのでサーバー側の話がメインとなります。
実際のところ「ブラウザを立ち上げてページが表示されるまで」には何が起きるのかではブラウザがどのようにページを描画するのかなどについて書いたのですが、ローカルでjsを実行するのはかなり時間がかかる上、jsの実行中はページの描画をストップさせてしまいます。
例えば170KBほどのjsファイルはパース・コンパイル・実行で3.5秒程かかることもあります。そこでサーバーサイドでHTMLを生成しておくことでページの描画を圧倒的に早くするという技術がサーバーサイドレンダリングです。
Reactを使うなら一緒に押さえておくべきだと思います。
ちなみにGoogleの調査ではページのロード時間が1秒から3秒に伸びると直帰率が32%増加するそうです。
#必要なソフトをインストールする
##エディタ
まずはVSCodeをインストールします。別に他のエディタでも良いのですが、特に信念がないのであればVSCodeを使いましょう。
VSCodeをインストールしたら起動して「ワークスペースフォルダー」を追加して開いておきます。ついでにターミナルで開いておくと後で便利です。
それと左の拡張機能タブから「Bracket Pair Colorizer」と「ESLint」をインストールしておきます。
**(重要)さらにメニューから設定を開きeditor.insertSpacesにチェックを入れ、editor.tabSizeを2にします。**2タブは正義です。
設定画面上の検索窓で検索すれば出てきます。
Node.js
Node.jsはローカルでjavascriptを実行できるようにしてくれるすごいやつです。一緒について来るnpmというパッケージマネージャーが超有能で、今のフロントエンド開発はこのnpmが支えています。
Git
まあ無くても良いっちゃ良いんですが。macなら最初から入っていると思います。
yarn
さっきはnpmが凄いって書いたんですがyarnの方がパッケージのインストールが早いのでyarnも使います。さっき開いたターミナルでnpmを使ってインストールします。
npm i -g yarn
ただし公式のドキュメントではhomebrewを使うように指示されているそうです。silverskyvictoさん指摘ありがとうございます。
パッケージ管理ができるようにpackage.jsonを設定しておきます。
yarn init -y
ESLint
それと構文解析ツールのESLintもインストールしておきます。サーバーにデプロイしてからエラーを発見するよりも、書いた瞬間エラーを表示してくれた方が何かと便利です。
yarn add eslint eslint-plugin-react -D
-DはdevDependenciesにインストールせよという命令で、開発中だけ使用し、本番稼働では使わないようなライブラリはこっちにインストールします。まあ間違えても大差ないですが。
.eslintrc.json
を作成してeslintの設定をします。
qiita/
├─node_modules/ ←yarnでインストールしたライブラリが保存されています。
│ └─...
├─.eslintrc.json ←new!
├─package.json
└─yarn.lock ←パッケージのバージョン情報などが保存されています。
よく分からないと思うのでreact用の設定を用意しておきました。コピーして使いましょう。
{
"env": {
"browser": true,
"es6": true,
"node": true
},
"extends": [
"eslint:recommended",
"plugin:react/recommended"
],
"parserOptions": {
"ecmaFeatures": {
"jsx": true
},
"ecmaVersion": 2018,
"sourceType": "module"
},
"plugins": [
"react"
],
"rules": {
"no-console":"warn",
"indent": [
"error",
2
],
"linebreak-style": [
"error",
"unix"
],
"quotes": [
"error",
"single"
],
"semi": [
"error",
"always"
]
}
}
#サーバーを書く
サーバーサイドはnode.jsで書いていきます。
##Expressの設定
続いてサーバーを超簡単にセットアップしてくれるライブラリ「express」をインストール。
yarn add express
serverフォルダを作りserver.jsを作成します。
qiita/
...
├─server/ ←new!
│ └─server.js ←new!
...
import express from 'express';
const app = express();
//GETリクエストでルートにアクセスが会った時の動作
app.get('/', (req, res)=>{
res.send('Hello express');
});
//3000番ポートを使ってサーバーを立ち上げ
app.listen(3000, ()=>{
console.log('app listening on 3000');
});
##babel-nodeの設定
nodeがes6を読めるようにbabelの設定をしてあげます。
yarn add babel-core babel-preset-env
qiita/
...
├─.babelrc ←new!
...
{
"presets": [
"env"
]
}
nodeに渡す前にbabelがコンパイルしてくれる便利なツールbabel-node
をインストールします。
yarn add -D babel-cli
↑ babel-cli
に babel-node
が含まれています。
...
"main": "index.js",
+ "scripts": {
+ "dev": "babel-node server/server.js"
+ },
"license": "MIT",
...
合わせてpackage.jsonを書き換えます。
+の行は追記した行で-は削除された行です。git diffで生成しています。
yarn run dev
# ↓
# app listening on 3000
これでhttp://localhost:3000/にアクセスすればHello expressと表示されるはずです。
##index.html
publicフォルダを用意してindex.htmlを作成します。
qiita/
...
├─public/ ←new!
│ └─index.html ←new!
...
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<div id="root"></div>
</body>
</html>
ちなみに今時のhtmlの書き方は普通のHTMLの書き方が参考になります。
htmlを配信できるようにserver.jsを書き換えます。
import express from 'express';
+import fs from 'fs';
const app = express();
app.get('/', (req, res)=>{
- res.send('Hello express');
+ const index = fs.readFileSync('./public/index.html', 'utf-8');
+ res.send(index);
});
...
もう一回yarn run dev
すればindex.htmlが配信されているとわかるかと思います。真っ白で分からないという場合は「⌘+⌥+i」で出現する検証モードを見ましょう。
初めてのReact
yarn add react react-dom
qiita/
...
├─src/ ←new!
│ └─index.jsx ←new!
...
import React from 'react';
import ReactDOM from 'react-dom';
ReactDOM.render(
<h1>Hello React</h1>,
document.getElementById('root')
);
ReactDOM.render()
は第一引数のjsxを第二引数のエレメントの子要素に加える関数です。
Webpackの設定
ファイルをまとめたりminifyしてくれたりする便利なやつ。
yarn add webpack -D
qiita/
...
├─webpack.config.dev.babel.js ←new!
...
webpack.config
webpackの設定ファイルである事を示しています。
dev
開発用(本番用では無い)という意味。
babel
設定ファイルを読む前にこの設定ファイル自体をbabelで変換しろという意味になります。
js
jsファイルなので。
import path from 'path';
export default {
mode: 'development',
entry:[
path.resolve(__dirname, 'src/')
],
output:{
path: path.resolve(__dirname, 'public'),
publicPath: '/',
filename: 'bundle.js'
},
resolve:{
extensions: ['.js','.json','.jsx']
},
module:{
rules:[
{
test: /\.jsx?$/,
use:{
loader: 'babel-loader'
},
include: path.resolve(__dirname, 'src')
}
]
}
};
webpackでファイルをまとめて一つのbundle.jsにするんですが、ES6で書かれたファイルとかjsxとかを読めるようにするためにbabel-loaderというローダーを使用します。
yarn add babel-loader -D
サーバーにWebpackを組み込む
サーバーでコンパイルして配信させるようにします。この設定は開発中のみ使用し、本番ではあらかじめビルドしたものを配信することになります。
yarn add webpack-dev-middleware -D
import express from 'express';
import fs from 'fs';
+import webpack from 'webpack';
+import webpackConfig from '../webpack.config.dev.babel';
+import webpackMiddleware from 'webpack-dev-middleware';
const app = express();
+const compiler = webpack(webpackConfig);
+app.use(webpackMiddleware(compiler));
+
app.get('/', (req, res)=>{
const index = fs.readFileSync('./public/index.html', 'utf-8');
...
...
<body>
<div id="root"></div>
+ <script src="/bundle.js"></script>
</body>
</html>
いざ実行!
yarn run dev
ERROR in ./src/index.jsx
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'.
🙃<ふぁっきゅ
babelを7.x系にアップグレードするかbabel-loaderを7.x系にダウングレードするか選べば良いようです。パッチノートを読むとbabel7.0.0が出たのが19日前となっていてあまり気乗りはしなかったのですが、せっかくの機会なのでbabel7.x系を使ってみます。
yarn remove babel-core babel-preset-env babel-cli
yarn add @babel/core @babel/preset-env @babel/preset-react
ついでにbabelがjsxを理解できるようにpreset-reactをインストールしておきました。
yarn add -D @babel/node
babel-node
を使用するために@babel/node
をインストールしておきます。
{
"presets": [
- "env"
+ "@babel/env",
+ "@babel/preset-react"
]
}
yarn run dev
でサーバーを実行してページを開くと"Hello React"と表示されているはずです。
#HMR(Hot Module Replacement)
HMR(Hot Module Replacement)はWebpackが提供する、ブラウザのリロードをすること無くアプリケーションのJSを更新する開発ツールです。
この機能を使用するためにサーバーを書きました。まあサーバーを書いておくと後々Server Side Rendering(SSR)出来たりもしますし。
早速実装していきます。
yarn add webpack-hot-middleware -D
+import webpack from 'webpack';
import path from 'path';
export default {
mode: 'development',
entry:[
+ 'webpack-hot-middleware/client',
path.resolve(__dirname, 'src/')
],
output:{
...
publicPath: '/',
filename: 'bundle.js'
},
+ plugins:[
+ new webpack.HotModuleReplacementPlugin()
+ ],
resolve:{
extensions: ['.js','.json','.jsx']
},
...
...
import webpackConfig from '../webpack.config.dev.babel';
import webpackMiddleware from 'webpack-dev-middleware';
+import HMR from 'webpack-hot-middleware';
const app = express();
const compiler = webpack(webpackConfig);
app.use(webpackMiddleware(compiler));
+app.use(HMR(compiler));
app.get('/', (req, res)=>{
...
qiita/
...
├─src/
│ └─App.jsx ←new!
...
import React from 'react';
const App = ()=>(
<h1>Hello React</h1>
);
export default App;
import React from 'react';
import ReactDOM from 'react-dom';
-ReactDOM.render(
- <h1>Hello React</h1>,
- document.getElementById('root')
-);
+import App from './App';
+
+const render = (_App) =>{
+ ReactDOM.render(
+ <_App />,
+ document.getElementById('root')
+ );
+};
+
+if(module.hot){
+ module.hot.accept('./App', ()=>{
+ const NextApp = require('./App').default;
+ render(NextApp);
+ });
+}
+
+render(App);
yarn run dev
あとは気の赴くままにReactを書いていきましょう。
サーバーサイドレンダリング(SSR)
Reactでサーバーサイドレンダリングを行うのは非常に簡単です。
...
</head>
<body>
- <div id="root"></div>
+ <div id="root"><%= preloadedApplication %></div>
<script src="/bundle.js"></script>
</body>
...
...
import fs from 'fs';
import webpack from 'webpack';
+import React from 'react';
+import { renderToString } from 'react-dom/server';
import webpackConfig from '../webpack.config.dev.babel';
import webpackMiddleware from 'webpack-dev-middleware';
import HMR from 'webpack-hot-middleware';
+import App from '../src/App';
+
const app = express();
...
app.use(HMR(compiler));
app.get('/', (req, res)=>{
- const index = fs.readFileSync('./public/index.html', 'utf-8');
+ let index = fs.readFileSync('./public/index.html', 'utf-8');
+
+ const appRendered = renderToString(
+ <App />
+ );
+ index = index.replace('<%= preloadedApplication %>', appRendered);
res.send(index);
});
...
...
const NextApp = require('./App').default;
render(NextApp);
});
}
-render(App);
+ReactDOM.hydrate(
+ <App />,
+ document.getElementById('root')
+);
何か分かりにくいところやエラーなどあればコメントで教えてください。
次回があれば本番用のビルド&デプロイの設定かReduxの実装とかですかね。