タグ別アーカイブ: Node.js

Node.js + React で SSR ~ SSR の実装 ~

Overview

前回の記事で SSR の環境を構築できたので, さっそく, React を利用した簡単な SSR の実装をしてみましょう.

1. Install packages

$ npm install --save react react-dom babel-cli babel-core babel-preset-es2015 babel-preset-react express

前回の記事でも述べましたが, 本来は –save-dev でインストールすべきパッケージも –save でインストールしていることに注意してください (Heroku のデプロイで dependencies のパッケージがインストールの対象とならないので).

2. Implement React Component

SSR で利用する, React コンポーネントを実装します. といっても, とりあえず <h1 /> で Hello SSR と表示する単純なコンポーネントです.

components/App.js

import React, { Component } from 'react';

export default class App extends Component {
    render() {
        return <h1>Hello SSR</h1>:
    }
}

また, ビルド後の App.js は, ルート直下の App.js にデプロイすることにします.

3. Implement render function

前回の記事では, express のルーターに直接レンダリング処理を記述していましたが, それだとコードの見通しがよくないので, SSR をする専用の関数を実装します.

render.js

const React          = require('react');
const ReactDOMServer = require('react-dom/server');
const App            = require('./App').default;

function render(req, res) {
    const content = ReactDOMServer.renderToString(React.createElement(App));
    const html = `<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8" />
    <title>Hello SSR</title>
</head>
<body>
    <section id="app">
        ${content}
    </section>
</body>
</html>`;

    res.status(200).send(html);
};

module.exports = render;

ポイントは, ReactDOMServer.renderToString でコンポーネントを HTML 文字列に変換している処理です. あとは, その文字列を HTML に埋め込むだけです.

render 関数が実装できたので, app-server.js を以下のように書き換えます.

app-server.js

'use strict';

const express = require('express');
const app     = express();
const path    = require('path');
const render  = require('./render');

const port = process.env.PORT || 8080;

app.use(express.static(path.join(__dirname, 'public')));

app.get('/', (req, res, next) => {
    render(req, res);
});

app.listen(port, () => {
    console.log(`Listening on port ${port} ...`);
});

4. npm scripts

package.json に以下のスクリプトを追加します.

  // ...
  "scripts": {
    "build": "babel components/App.js --out-file App.js",
    "start": "node app.js"
  },
  // ...

5. Edit Procfile

Procfile を以下のように変更します.

web: npm run build && npm start

 

以上で, SSR の実装ができました. 変更したファイルをすべてコミットして,

$ git push heroku master

を実行し, https://nodejs-ssr-sample.herokuapp.com/ にアクセスして, 「Hello SSR」が表示されていれば OK です.

ちなみに, SSR された HTML のソースは以下のようになっています.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8" />
    <title>Hello SSR</title>
</head>
<body>
    <section id="app">
        <h1 data-reactroot="" data-reactid="1" data-react-checksum="-601091822">Hello SSR</h1>
    </section>
</body>
</html>

Node.js + React で SSR ~ Heroku の利用 ~

Overview

前回までの記事で, SPA (Single Page Application) の開発環境構築に関して紹介しましたが, SPA では, 以下のような理由から, SSR (Server Side Rendering) を併用することも多いです.

  • 初期表示速度の改善
  • レガシークローラーやページ解析への対応

したがって, SSR の最低限の実装を紹介していきたいと思います.

しかしながら, SSR という名前からもわかるように, レンダリングをしてくれるサーバーが必要となります. 今回は, サーバーサイドのスクリプトには Node.js を使って, Isomorphic な実装をしたいと思います. したがって, Node.js が使えるサーバーが必要になります.

Node.js が使えて, 無料で利用できる PaaS (Platform as a Service) として Heroku があります (Node.js だけでなく, PHP, Ruby, Python, Java, Scala, Go なども使えます) . 今回の SSR の実装には, Heroku を使うことにします.

この記事では, Heroku の使い方を紹介します (すでに, Node.js を使える環境をもっているのであれば, スルーしてください).

1. Create Heroku Account

まずは, アカウントを作成します.

Heroku
Heroku
Heroku Sign up
Heroku Sign up

2. Install Heroku CLI

Heroku をターミナルから操作できるように, Heroku CLI をインストールします.

3. Log in

ターミナルで以下のコマンドを実行します (Email アドレスとパスワードを入力する必要があるので, アカウント作成したときのものをそれぞれ入力してください).

$ heroku login
Enter your Heroku credentials:
Email: rilakkuma.san.xjapan@gmail.com
Password: **************
Logged in as rilakkuma.san.xjapan@gmail.com

Heroku に ssh 公開鍵をアップロードします.

$ ssh-keygen
$ heroku keys:add ~/.ssh/id_rsa.pub

4. git init

Heroku にアプリケーションをデプロイするにも, Git でファイルを管理する必要があります

$ git init

5. Create Procfile

Procfile というファイルを作成し, アプリケーションを起動するコマンドを記述します. 今回は以下のような記述で OK です

web: node app-server.js

6. Create Application

SSR をするための, アプリケーションを作成していきます.

$ npm init -y
$ npm install --save express

1つ注意点があり, 本来は, devDependencies に入るパッケージ (–save-dev でインストールするパッケージ) は, Heroku だとデプロイの対象とならないようなので, すべて, –save オプションでパッケージをインストールします.

app.js

'use strict';                                                                                                                             

const express = require('express');
const app       = express();
const path      = require('path');

const port = process.env.PORT || 8080;

app.use(express.static(path.join(__dirname, 'public')));

app.get('/', (req, res, next) => {
    const html = `<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8" />
    <title>Hello SSR</title>
</head>
<body>
    <section id="app">
        <h1>Hello SSR</h1>
    </section>
</body>
</html>`;

    res.status(200).send(html);
});

app.listen(port, () => {↲
    console.log(`Listening on port ${port} ...`);
});

ここで注意が必要なのは,

const port = process.env.PORT || 8080;

の部分です. ローカルで動作させる場合には, 8080 のような任意のポート番号で OK なのですが, Heroku でデプロイする場合には, 任意のポート番号を利用すると, 以下のようなエラーが発生するので, Heroku の環境変数に定義されているポート番号を利用するように process.env.PORT を指定します.

Error R10 (Boot timeout) -> Web process failed to bind to $PORT within 60 seconds of launch

ここまでで, 作成した

  • package.json
  • Procfile
  • app-server.js

をコミットしておきます.

7. heroku create

以下のコマンドを実行します

$ heroku create app-ssr
Creating ⬢ app-ssr... done
https://app-ssr.herokuapp.com/ | https://git.heroku.com/app-ssr.git

app-ssr はアプリケーション名で, 省略することも可能です (その場合, 任意のアプリケーション名がつきます).

また, 以下のコマンドであとから変更することも可能です.

$ heroku rename [アプリケーション名」

8. Deployment

最後の手順です. アプリケーションを Heroku でデプロイします. といっても, 難しいことはなく, GitHub に push するのと同じ要領で,

$ git push heroku master

push すれば, 自動的にデプロイが開始されます.

デプロイが完了したら,

https://app-ssr.herokuapp.com/

にアクセスします.

SSR by Heroku
SSR by Heroku

このように表示されていれば OK です. また, ディベロッパーツールの Network をみても document としてレスポンスが返ってきているのがわかります.

もし, 何らかのエラーが原因で表示されない場合には,

$ heroku logs

でログを調べて, デバッグしてみてくだだい.

また, heroku コマンドの一覧は,

$ heroku help

で調べることが可能です.

nodebrewのインストールと利用

1. What is nodebrew ?

Node.jsのバージョンを開発マシンでバージョン管理するためのツールです.

2. Install nodebrew

既にNode.js (とnpm) がインストールされている場合はアンインストールしておきます.

そして, 以下のコマンドを実行してnodebrewをインストールします.

$ curl -L git.io/nodebrew | perl - setup

また, .bashrcなどにPATHの追加を記述しておきます.

export PATH=$HOME/.nodebrew/current/bin:$PATH

リロードします.

$ source ~/.bashrc

インストールされたか確認します.

$ nodebrew help

3. Use nodebrew

以下のコマンドを実行すると, インストール可能なバージョンが表示されます.

$ nodebrew ls-remote

インストールしたいバージョン (例えば, v4.6.1) のをインストールします.

$ nodebrew install-binary v4.6.1

インストールが完了したら, 以下のコマンドを実行してみます.

$ nodebrew ls

nodebrew ls
v4.6.1

current: none

currentに表示されるのは, 現在利用されているNode.jsのバージョンです.
まだ, インストールしただけで利用する設定になっていないので, noneと表示されます.
したがって, 利用できるように以下のコマンドを実行します.

$ nodebrew use v4.6.1
$ node -v
v4.6.1

同時に, npmも利用可能になります.

4. migrate-package

migrate-packageは, グローバルにインストールしたnode_modulesを現在のバージョンに適用してくれるものです.

$ nodebrew migrate-package v4.6.1

上記の例では, v4.6.1でグローバルにインストールされているnode_modulesを現在のバージョンにも$ npm install -gでインストールしてくれます.

Mac OS XにpkgでインストールしたNode.jsをアンインストールする手順

1. Uninstall Node.js

以下のスクリプトを実行します.

lsbom -f -l -s -pf /var/db/receipts/org.nodejs.node.pkg.bom \
| while read i; do
  sudo rm /usr/local/${i}
done
sudo rm -rf /usr/local/lib/node \
     /usr/local/lib/node_modules \
     /var/db/receipts/org.nodejs.*

2. Uninstall npm

sudo rm -rf ~/.npm

以上で完了です. あとは, nodebrewなどを利用しましょう.

gulp watch

gulpの場合, ファイルの変更監視はGruntのようにプラグインをインストールする必要はなく標準で利用可能です.

例として, js/sample.jsの変更を監視して, 変更があれば圧縮し, build以下に格納する場合のgulpfileは以下のようになります.

var gulp   = require('gulp');
var uglify = require('gulp-uglify');

gulp.task('build', function() {
    gulp.src('js/sample.js')
        .pipe(uglify())
        .pipe(gulp.dest('build/'));
});

gulp.task('watch', function() {
    gulp.watch('js/sample.js', ['build']);
});

gulp タスク順序

task1 -> task2 -> task3 の順でタスクを実行したい場合, 以下のようにgulpfileを記述しても, 必ずしもその順序性は保証されません.

var gulp = require('gulp');

gulp.task('task1', function() {
    console.log('task1 done');
});

gulp.task('task2', function() {
    console.log('task2 done');
});

gulp.task('task3', function() {
    console.log('task3 done');
});

gulp.task('default', ['task1', 'task2', 'task3']);

その理由は, gulpはタスクを並列処理されるからです. 順序性を保証するには以下のように記述します.
重要なポイントは, taskメソッドの第2引数とreturn文です.

var gulp = require('gulp');

gulp.task('task1', function() {
        return console.log('task1 done');
});

gulp.task('task2', ['task1'], function() {
        return console.log('task2 done');
});

gulp.task('task3', ['task2'], function() {
        console.log('task3 done');
});

gulp.task('default', ['task3']);

gulp

gulpとはNode.jsを利用した, フロントエンドまわり (JavaScriptやCSS) のタスク自動化ツールです.

同様のツールにGruntがありますが, Gruntよりも記述が簡単なことや高速であることから, 最近ではgulpが利用されるケースが増えています.

Gruntもそうですが, gulpもNode.jsとnpmを必要とするツールですので, Node.jsとnpm がインストールされている必要があります.

また, プロジェクトのディレクトリでpackage.jsonも作成しておきます.

まずは, gulpをグローバルにインストールしておきます.

$ sudo npm install -g gulp

以下のようにバージョンが表示されればインストール完了です.

$ gulp -v
[14:37:04] CLI version 3.9.0

gulpがインストールできたら, プロジェクトのディレクトリでgulpfile.jsを作成します.
例えば, src/sample.jsをbuild/以下にコピーするタスクは以下のように記述します.

gulpfile.js

// GruntのloadNpmTasksに相当
var gulp = require('gulp');

// GruntのregisterTaskに相当
gulp.task('copy', function() {
    gulp.src('src/sample.js')
        .pipe(gulp.dest('build/'));
});

続いて, JavaScriptのファイルを圧縮するタスクを追加します.
そのためには, gulp-uglifyをインストールしておきます.

$ npm install --save-dev gulp-uglify
// GruntのloadNpmTasksに相当
var gulp   = require('gulp');
var uglify = require('gulp-uglify');

// GruntのregisterTaskに相当
gulp.task('build', function() {
    gulp.src('src/sample.js')
        .pipe(uglify())  // GruntのinitConfigに相当
        .pipe(gulp.dest('build/'));
});

さらに, 圧縮後のファイルとわかるようにファイル名を変更するタスクも追加します.
そのためには, gulp-renameをインストールしておきます.

$ npm install --save-dev gulp-uglify
// GruntのloadNpmTasksに相当
var gulp      = require('gulp');
var uglify    = require('gulp-uglify');
var rename = require('gulp-rename'); 

// GruntのregisterTaskに相当
gulp.task('build', function() {
    gulp.src('src/sample.js')
        .pipe(uglify())  // GruntのinitConfigに相当
        .pipe(rename('sample.min.js'))  // GruntのinitConfigに相当
        .pipe(gulp.dest('build/'));
});

npm install でのエラー

npm installで以下のようなエラーが出た場合の対処法

npm WARN locking Error: EACCES, open '/ホームディレクトリ/.npm/_locks/xsound-263d68595b18b348.lock'
npm WARN locking     at Error (native)
npm WARN locking  /ホームディレクトリ/.npm/_locks/xsound-263d68595b18b348.lock failed { [Error: EACCES, open '/ホームディレクトリ/.npm/_locks/xsound-263d68595b18b348.lock']
npm WARN locking   errno: -13,
npm WARN locking   code: 'EACCES',
npm WARN locking   path: '/ホームディレクトリ/.npm/_locks/xsound-263d68595b18b348.lock' }
npm ERR! Darwin 13.4.0
npm ERR! argv "node" "/usr/local/bin/npm" "install" "xsound"
npm ERR! node v0.12.1
npm ERR! npm  v2.7.3

npm ERR! Attempt to unlock /インストールディレクトリ, which hasn't been locked
npm ERR! 
npm ERR! If you need help, you may report this error at:
npm ERR!     <https://github.com/npm/npm/issues>

npm ERR! Please include the following file with any support request:
npm ERR!     /インストールディレクトリ/npm-debug.log
$ npm cache clean

これで解決するみたいです (参考).