Quantcast
Channel: Node.jsタグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 8889

サボテンでも分かる!SSR対応SPAアプリの作り方(React/TypeScript/Express)

$
0
0

Next.jsを使わないReactのSSR解説で分かりやすいやつが無かったので、書きます!

環境構築からとっても丁寧にやっていきます:cactus:

サボテンくん

最初に

読んで欲しい人

  • SSRしたいけど難しそう....:fearful:

って人はもちろん

  • 『何も分からないけどSPAが作ってみたい!:smiley:

って人も実際に作って楽しめるように書いてます。

SPA (Single Page Application) : ネイティブアプリ(AppStoreやGooglePlayからインストールできるやつ)っぽいWebサイトのこと。ReactやVueなどのJavaScriptフレームワークを使って作るのが一般的です。

作るもの

SSRを使用した簡単なカウントアップアプリを作ってみます。

countup.gif

最終的な制作物のソースコードはこちらです。

学べること

  • Node, Yarn, Git, ESList, Babel, Webpackのフロントエンド開発環境の構築。
  • React Hooks(useState)を使って、モダンな書き方のReactを書いてみる。
  • TypeScriptを使ってみる。
  • Express.jsを使ってSSRができるようになる。

ReactやTypeScriptの詳しい記法については踏み込みません。良質な記事がQiita等にたくさんあるので、気になるところは調べながら進めてください。

進め方について

なるべくコピペはせず、何をしているか考えて進めていただきたいです。

設定ファイルを暗記するのは無駄なので、設定ファイルはコピペしてください。

SSR(Server Side Rendering)とは

Reactなどで作成されたSPAアプリは、クライアント(ユーザーが使っているブラウザ)で実行され、UI (DOM)を形成します。その性質上、以下のような問題があります。

  • Googleのクローラが正しくインデックスしてくれない。
  • OGPを含む<head/>が全てのページで同じになるので、Twitterカードなどが意図通りに表示できない。
  • 初回表示速度(クリティカルレンダリングパス)に時間がかかる。

そこで、サーバー側でJavaScriptをHTMLをして返すことをSSR(Server Side Rendering)と呼びます。やっていることはほとんどPHPなどのサーバーサイド言語と同じですが、SSRと呼ぶので難しく感じるだけです。

not-ssr.jpg

この記事ではSSRを解説しますが、小規模な(ページ数が限定されている)プロダクトでは、SSRの代わりにPrerenderingという手法を検討した方がいいです。これは、あらかじめSSG(Static Site Generator)を使用して、JavaScriptをHTMLファイルに変換して配置しておく方法です。SSRより簡潔で、リクエストごとにサーバーでHTMLを生成する処理が必要がないので、メンテナンスがしやすいことと、サーバーにかかる負荷がSSRより小さいことがメリットです。

  • クローラ: Googleが世界中にあるサイトを把握するために、いろんなサイトを巡回させているロボットのこと。クローラがサイトの情報を保存することをインデックスと呼びます。最近GoogleのクローラはJavaScriptを実行してくれるようになったらしいですが、まだ完全ではなくSEOが劣る可能性があります。

  • SEO (Search Engine Optimization) : Googleなどの検索結果の上に方に表示されるように調整すること。

  • UI (User Interface) :サイトの見た目のこと。

  • DOM (Document Object Model) :ブラウザがHTMLを元に形成するツリー状の構造のこと。よく混同されますがHTMLとは別のものです。JavaScriptから操作できます。

  • OGP (Open Graph Protocol) : Twitterカードなど、SNS上でリンク先の情報を表示するときに使われるheadタグです。OGPをクロールに来るbotはGoogleのbotと違ってJavaScriptを解釈しません。

環境構築

フロントエンド(クライアントサイド)の環境構築はとても面倒で楽しくないです。環境構築が終われば楽しいので、投げ出さずに頑張ってください!

推奨環境

環境構築はmacOSを使っている前提で進めます。

Windows等ご利用の方は別途調べながら進めてみてください。

Homebrew

Macのパッケージマネージャです。パッケージとはソフトウェアのこと。Qiitaなど多くの記事でインストールしてあることが前提になっているので、Macで開発するなら必須です。

  1. AppStoreからXcodeをインストールします。

  2. 以下のコマンドを実行して、コマンドライン・デベロッパツールをインストールします。

$ xcode-select --install
  1. 以下のコマンドを実行して、Homebrewをインストールします。
$ /usr/bin/ruby -e"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
  1. 以下のコマンドを実行して、Example usage: ...が表示されればOKです。
$ brew

参考 : macOSにHomebrewをインストール

VSCode

たぶん現時点では最も優れたコードエディタであるVSCodeをインストールします。

ダウンロードボタン

  • ダウンロードしたzipファイルを解凍してインストーラを実行し、インストールを完了してください。

Node & Yarn

Nodeは、サーバーサイドで使えるJavaScript環境です。クライアントサイドの開発環境(Webpack等)にも必須なのでよく誤解がありますが、一応サーバーで動くものです。

Yarnは、Nodeのパッケージマネージャです。同じNodeのパッケージマネージャに有名なnpmがありますが、Yarnはnpmよりインストールの速度が速く、npmの上位互換と考えていいと思います。

NodeはNodebrewでバージョン管理すると便利なので、ここではNodebrewを使用してNodeをインストールします。

homebrew.jpg

  • 以下のコードを実行してください。

$(コマンドプロンプト)が書かれているコードは、Macのターミナルで実行してください。(iTerm等でも可)

# nodeのバージョン管理のためのnodebrewのインストール$ brew install nodebrew
# nodebrewのディレクトリのセットアップ$ nodebrew setup
# パスを通しておく$ echo"export PATH=$HOME/.nodebrew/current/bin:$PATH">> ~/.bash_profile
$ source ~/.bash_profile
# nodeのダウンロードとセット$ nodebrew install stable
$ nodebrew use stable
# yarnのインストール$ brew install yarn --ignore-dependencies

Yarnは「HomebrewのNode」に依存するので、Yarnをインストール時に「HomebrewのNode」も一緒にインストールしようとしますが、今回は「NodebrewのNode」使用しているので、--ignore-dependenciesオプションを使って「HomebrewのNode」をダウンロードさせないようにしています。

参考 : install nodebrew, node and yarn

作業ディレクトリの作成

  • 適当なディレクトリを作成して移動します。
$ mkdir ssr-sample &&cd$_

$_は直前に実行したコマンドの最後の引数(上のコマンドだとssr-sample)を表します。

  • Yarnを初期化(package.jsonの作成)しておきます。
$ yarn init -y
  • 作成したディレクトリをVSCodeで開きましょう。『ファイル』 >『 開く』から開けます。

ファイルから開く

Git

プログラムのバージョンを管理するために、Gitも入れておきましょう。Gitはプログラムの更新履歴を管理したり、複数人で一つのプロダクトを更新するのに役立ちます。

  • GitをHomebrewからインストールします。

  • $ brew install git
    
  • Gitを初期化します。
$ git init
  • .gitignoreを作成します。
$ touch .gitignore
  • .gitignoreに以下を書き込みます。
  /node_modules/

.gitignoreに書いたファイルはGitで管理されません。node_modulesディレクトリ内のファイルはYarnでインストールしたパッケージなので容量が多い上に、package.jsonがあれば$ yarn installコマンドで復元できるのでGit管理はしないように設定しましょう。

この記事ではローカルリポジトリへのコミットしかしないので、詳しく知りたい人はGitをまとめた別記事を参照してください。

ESLint

ESListは、コードがちゃんとしたフォーマットになっているかチェックして、修正もしてくれるツールです。JavaScriptはセミコロン『 ; 』をつけるかどうかなど、コーディングの自由度が高いので、チームで開発する場合など特に活躍します。

設定は好みがあるのでいろいろな種類がありますが、Airbnbという企業の作った一番人気のコーディング規則を使ってみましょう。(細かくカスタマイズもできます)

ESLintと関連パッケージのインストール

# airbnbのeslint設定に必要なパッケージを一括でインストールします。# 『It seems as if you are using Yarn. Would you like to use Yarn for the installation? (y/n)』と聞かれたら、『y』と入力してEnterを押してください。$ npx install-peerdeps --dev eslint-config-airbnb

npm(Yarnと同じパッケージマネージャで、Nodeと一緒にインストールされる)のnpxコマンドを使ってinstall-peerdepsコマンドを実行し、eslint-config-airbnbが必要としているパッケージ(eslintなど)を一括でインストールしています。

参考: Github: Airbnb/JavaScript

ESLintの設定ファイルを作成

.eslintrcを作成して、

$ touch .eslintrc

以下を書き込んでください。

{"extends":["airbnb","airbnb/hooks"],"env":{"browser":true},}

extendsは指定した設定を継承します。airbnbとReact Hooksを使用するためのairbnb/hooksを継承しています。

envbrowserを指定することで、window.documentなどブラウザでのDOM操作をするためのコードでエラーが出ないようにしています。

VSCodeのESLint拡張のインストール

ESLintをYarnで入れただけでは、VSCode上で構文チェックできません。

左側にある 拡張機能をインストールするボタンのマークをクリックして、『eslint』を検索し、ESLintをインストールしてください。

VSCode-ESLintの設定

VSCode上でのESLintを設定します。

  • 以下のコマンドを実行して.vscode/settings.jsonを作成します。
$ mkdir .vscode &&touch$_/settings.json
  • 以下の内容を書き込みます。(コピペ推奨
{"eslint.run":"onSave","eslint.autoFixOnSave":true,"eslint.validate":["javascript","javascriptreact",{"language":"typescript","autoFix":true},{"language":"typescriptreact","autoFix":true}]}

設定の詳細

  • eslint.run : onSaveを指定すると、保存時に構文チェックを行います。
  • eslint.autoFixOnSave : trueにすると、保存時に構文の修正を行います。
  • eslint.validate : ESLintを有効にするファイルの種類を指定します。それぞれ、.js(JavaScript)、.jsx(JavaScript + React)、.ts(TypeScript)、.tsx(TypeScript + React)を指定しています。.ts.tsxについては、autoFixを指定しないと修正されません。

参考 : microsoft/vscode-eslint

ESLintの設定が反映されずエラーが出たままのときは、VSCodeを開きなおすと直ります。

ファイル構成

.
├── .git               ← Gitが作った。Git管理のための隠しファイル。
├── node_modules       ← Yarnが作った。Yarnでインストールしたパッケージが入ってる
├── .eslintrc      ← 新しく作った。eslintの設定ファイル。
├── .gitignore         ← 新しく作った。Gitで管理しないファイルを指定する。
├── .vscode            ← 新しく作った。vscodeの設定ファイルを入れる。
│   └── settings.json  ← 新しく作った。vscode-eslintの設定をした。
├── package-lock.json  ← Yarnが作った。パッケージの依存関係が書いてある。編集とかはしない。
├── package.json       ← Yarnが作った。インストールしたパッケージとかが書いてある。
└── yarn.lock          ← Yarnが作った。Yarnの依存関係を管理する。

STEP1 - ExpressでWebサーバを立てる

Webアプリを配信するためにはWebサーバが必要です。Webサーバとは、HTMLやJavaScript、画像などをクライアント(ブラウザ)に渡すものです。SSRでは、NodeでJavaScriptをHTMLに変換する必要があるので、NodeのフレームワークであるExpressを使いましょう。

1. 必要なパッケージのインストール

$ yarn add express esm

インストールしたパッケージの概要は以下の通りです。
* Express.js : JavaScriptのNode.jsのWebアプリケーションフレームワークです。
* esm : node -r esm index.jsの様に指定すると、Nodeでimport/exportの構文を利用できるようになります。

2. index.jsの作成

  • index.jsを作成します。
$ touch index.js
  • index.jsに以下を書き込みます。
importexpressfrom'express';importssrfrom'./src/ssr';constapp=express();// 3000番ポートでWebサーバを立てるapp.listen(3000);// https://localhost:3000 にアクセスがあったら ssr() を返すapp.get('/',(_,res)=>{res.send(ssr());});

index.jsはExpressのルーティング(どのURLが来たら、どのファイルを返すか)などを設定するファイルです。今のコードでは、http://localhost:3000/でアクセスしたら、`./src/ssr`の`ssr()`を返す設定をしました。

3. ssr.jsの作成

  • src/ssr.jsを作成します。
$ mkdir src &&touch$_/ssr.js
  • src/ssr.jsに以下を書き込みます。
constssr=()=>(`
  <html>
    <head>
      <title>CountUp</title>
      <meta charset="utf-8"/>
    </head>
    <body>
      <div id="app">
        <h1>0</h1>
        <button type="button">+</button>
        <p>${newDate().toTimeString()}</p>
      </div>
    </body>
  </html>
`);exportdefaultssr;

src/ssr.jsは、3.で設定した通り、実際に返される文字列になります。

new Date().toTimeString()のところは、現在の時刻です。

4. package.jsonの編集

Expressを起動してWebサーバを立てるために、Nodeをindex.jsに対して実行するコマンドを設定します。

  • package.jsonに以下を追加します。
"scripts":{"start":"node -r esm index.js"},

追加した後のpackage.jsonはこのような感じになります。

{"name":"ssr-sample","version":"1.0.0","main":"index.js","license":"MIT","scripts":{"start":"node -r esm index.js"},"devDependencies":{"eslint":"^6.1.0","eslint-config-airbnb":"^18.0.1","eslint-plugin-import":"^2.18.2","eslint-plugin-jsx-a11y":"^6.2.3","eslint-plugin-react":"^7.16.0","eslint-plugin-react-hooks":"^1.7.0"},"dependencies":{"esm":"^3.2.25","express":"^4.17.1"}}

package.jsonscript:{}に追加したコマンドは、yarn startのように実行することができます。

5. Expressの起動

  • Expressを起動しましょう!
$ yarn start

http://localhost:3000/にアクセスして、下の画像のように表示されれば完璧です!これがSSRの基礎になります。

ページをリロードすると時間表示が更新されます。ページのリクエストごとにJavaScriptからHTMLを生成していることが確認できます。

まだハリボテのHTMLを生成しているだけなので、+ボタンを押しても何もおきません。

STEP1の画像

6. Gitにコミットする

  • 忘れないようにgitにコミットしておきます。
$ git add .
$ git commit -m "STEP1 - ExpressでWebサーバを立てる"

ファイル構成

.
├── .git
├── node_modules
├── .eslintrc
├── .gitignore
├── .vscode
│   └── settings.json
├── index.js           ← 新しく作った。Expressの設定。
├── package-lock.json
├── package.json       ← 編集した。startコマンドを追加。
├── src                ← 新しく作った。ソースファイルを入れる。
│   └── ssr.js         ← 新しく作った。ブラウザに送る文字列を返す。
└── yarn.lock

STEP2 - Reactを使う

STEP1では実装しなかったカウントアップの仕組みをReactを使って実装してみましょう。

ReactはそのままではNodeやブラウザで実行できないので、Nodeやブラウザで実行できるJavaScriptに変換するために、Babelを使用する必要があります。

1. 必要なパッケージのインストール

$ yarn add react react-dom
$ yarn add -D @babel/cli @babel/core @babel/preset-env core-js@3 @babel/preset-react

インストールしたパッケージの概要は以下の通りです。

  • React : JavaScriptのUIフレームワーク

    • react-dom : render()などを使うために必要です。
  • Babel : ES6の構文などをブラウザで表示できるように変換します。

    • @babel/core : Babelのコア。
    • @babel/cli : Babelをコマンドラインから使用できるようにします。
    • @babel/preset-env : 最新のJavaScriptを変換するための設定をまとめたプリセット。
    • core-js : babel/preset-envがPolyfillをするために必要です。
    • @babel/preset-react : Reactを変換するためのプリセット。

Polyfill : 最近の機能をサポートしていない古いブラウザーで、その機能を使えるようにするためにコードを変換すること。

-Dオプションをつけるかどうかの違い

yarn add-Dオプションをつけると、package.jsondevDependenciesに追加されます。dependenciesに追加してもdevDependenciesに追加してもパッケージとして公開しなければ違いはありませんが、ExpressやReactは、dependenciesの追加しないとESLintに怒られます。

参考 : 【いまさらですが】package.jsonのdependenciesとdevDependencies

2. .babelrcの作成

  • Babelの設定ファイル.babelrcを作成します。
$ touch .babelrc
  • 以下の内容を書き込みます。
{"presets":[["@babel/preset-env",{"useBuiltIns":"usage","corejs":3}],["@babel/preset-react",{"useBuiltIns":"usage","corejs":3}]]}

参考 : Babel7.4で非推奨になったbabel/polyfillの代替手段と設定方法

3. index.jsの編集

  • index.jsの2行目の./src/ssr./views/ssrに変更して、以下のように書き換えます。
importexpressfrom'express';importssrfrom'./views/ssr';constapp=express();app.listen(3000);app.get('/',(_,res)=>{constresponse=ssr();res.send(response);});

import先のパスを変更したのは、./ssrディレクトリ内のReactで書かれたファイルをBabelでコンパイルして、Nodeで実行できるファイルとして./viewsに出力するからです。

4. ssr.jsxの編集

  • src/ssr.jsの拡張子を.jsxに変更しします。
$ mv src/ssr.js src/ssr.jsx

.jsx : ReactでJSX(HTMLライクな記法)を用いる場合は、拡張子を.jsxにします。

  • renderToStringを使用して、以下のように書き換えます。
importReactfrom'react';import{renderToString}from'react-dom/server';importCountUpfrom'./CountUp';// React ElementをHTMLに変換constssr=()=>(`
  <html>
    <head>
      <title>CountUp</title>
      <meta charset="utf-8"/>
    </head>
    <body>
      <div id="app">
        ${renderToString(<CountUp/>)}</div>
    </body>
  </html>
`);exportdefaultssr;

renderToString()を使用すると、Reactのコンポーネントを文字列に変換することができます。上のコードでは、<CountUp />コンポーネントを文字列に変換して、ssr()で返す文字列の中に埋め込んでいます。

5. CountUp.tsxの作成

  • src/CountUp.jsxを作成します。
$ touch src/CountUp.jsx
  • 以下の内容を書き込みます。
importReact,{useState}from'react';constCountUp=()=>{const[count,setCount]=useState(0);return(<><h1>{count}</h1><buttontype="button"onClick={()=>setCount(count+1)}>+</button><p>{newDate().toTimeString()}</p></>);};exportdefaultCountUp;

useStateは、React Hooksの機能の一つです。上の例だと、countをState(状態)、setCount()をStateを更新する関数として宣言しています。stateの初期値はuseState()の引数で決まるので0です。Stateは絶対に直接変更せず、countを1増やす場合はsetCount(count + 1)のようにします。

<></>で囲んでいるのは、Reactコンポーネントの返り値は何かのタグで囲っていないとエラーが出るためです。

6. package.jsonの編集

  • Babelを実行するためのコマンド(babel)をpackage.jsonに書き込みます。
"scripts":{"start":"node -r esm index.js","babel":"babel src -x '.js,.jsx' -d views"},

-xオプションで対象とするファイルの拡張子を指定し、-dオプションで書き出し先のディレクトリを指定しています。

上のコマンドだと、『srcディレクトリに含まれる.js.jsx拡張子のファイルをコンパイルしてviewsディレクトリに書き出す』という意味になります。

7. Babelの実行

  • Babelを実行します。
$ yarn babel

すると、Babelによってsrcディレクトリのssr.jsxCountUp.jsxがviewsディレクトリに.js拡張子で書き出されるはずです。

8. Expressの起動

  • Expressを起動します。
$ yarn start

表示されるUIはSTEP1と同じです。

9. Gitにコミットする

忘れないようにgitにコミットしておきます。

$ git add .
$ git commit -m "STEP2 - Reactを使う"

ファイル構成

.
├── .git
├── node_modules
├── .babelrc           ← 新しく作った。Babelの設定ファイル。
├── .eslintrc
├── .gitignore
├── .vscode
│   └── settings.json
├── index.js           ← 編集した。ssr.jsのパスを変更。
├── package-lock.json
├── package.json       ← 編集した。babelコマンドを追加。
├── src
│   ├── CountUp.jsx    ← 編集した。ReactでState管理してカウントする。
│   └── ssr.jsx        ← 編集した。CountUpを文字列に変換する。
├── views              ← Babelによって生成される。
│   ├── CountUp.js    ← Babelによって生成される。
│   └── ssr.js      ← Babelによって生成される。
└── yarn.lock

ポイントはssr.jsx内で、renderToString()関数を使ってJSXをHTMLとして解釈できる文字列に変換しているところです。

しかし、+を押してもまだ何も起こりません。それもそのはず、サーバーでJavaScriptをHTMLに変換してクライアントに返しているだけで、クライアントであるブラウザではJavaScriptが動いていないからです。クライアントに返されるHTMLは、STEP1と同じです。

つまり、この記事の冒頭に載せたSSRの図は正確ではなく、修正すると以下のようになります。

SSRの図2

STEP3 - Webpackを使う

Expressで配信する静的なHTMLとは別に、クライアントで動作させるためのJavaScriptファイルも生成し、クライアントに読み込ませる必要があります。Webpackを使えば、Babelでコンパイルするのと同時に、複数のJavaScriptファイルを1つにバンドルすることができます。

1. 必要なパッケージのインストール

$ yarn add -D webpack webpack-cli babel-loader

インストールしたパッケージの概要は以下の通りです。

  • Webpack : 複数のJavaScriptなどのファイルを1つのファイルにバンドルします。
    • webpack-cli : Webpackをコマンドラインから使用できるようにします。
    • babel-loader : WebpackでBabelを使用してJavaScriptをコンパイルできるようにします。

2. エントリーポイントの作成

  • src/client.jsxを作成します。
$ touch src/client.jsx
  • 以下の内容を書き込みます。
importReactfrom'react';import{hydrate}from'react-dom';importCountUpfrom'./CountUp';// idがappの部分をhydrateで描画するhydrate(<CountUp/>,document.querySelector('#app'));

SSRでないReactアプリではrender()を使用してUIを描画しますが、SSRを使用してサーバーで描画されている場合はhydrate()を使用します。サーバーで描画した部分を、ブラウザで再描画しないようにするためです。

3. webpack.config.jsの作成

Webpackの設定ファイルwebpack.config.jsを作成します。

$ touch webpack.config.js

以下の内容を書き込みます。

constpath=require('path');module.exports={resolve:{// 対象にする拡張子の指定extensions:['.js','.jsx'],},entry:{// エントリーポイントの指定client:'./src/client.jsx',},output:{// アウトプット先のディレクトリを指定(assets)path:path.resolve(__dirname,'assets'),// アウトプットするファイルの名前を指定(名前は変更しない)filename:'[name].js',},module:{rules:[{// 拡張子が.jsか.jsxだった場合に適用するルールtest:/\.js(x?)$/,// node_modulesディレクトリ(Yarnでインストールしたパッケージが入ってる)は除外exclude:/node_modules/,use:[{// babelの設定loader:'babel-loader',options:{presets:[['@babel/preset-env',{useBuiltIns:'usage',corejs:3,},],['@babel/preset-react',{useBuiltIns:'usage',corejs:3,},],],},},],},],},};

4. Webpackを実行するコマンドの追加

  • Webpackを実行するためのコマンド(build)をpackage.jsonに書き込みます。
"scripts":{"start":"node -r esm index.js","babel":"babel src -x '.js,.jsx' -d views","build":"webpack --mode development",},

--modeオプションでproductiondevelopmentを指定しないと、ビルド時に警告が出ます。

5. Webpackの実行

  • Webpackを実行します。
$ yarn build

すると、Babelによってsrcディレクトリのファイルが、assetsディレクトリにclient.jsという1ファイルにまとめて書き出されます。client.jsと関係があるファイルだけがまとめられるので、ssr.jsはバンドルに含まれません。

6. client.jsのルーティング

Webpackでバンドルしたassets/client.jsをクライアントで読み込む必要があるので、Expressでassetsディレクトリ内のファイルを返すように設定します。

  • index.jsapp.use(express.static('assets'));を追加して、以下のように編集します。
importexpressfrom'express';importssrfrom'./views/ssr';constapp=express();app.listen(3000);app.use(express.static('assets'));app.get('/',(_,res)=>{constresponse=ssr();res.send(response);});

これで、http://localhost/client.jsにアクセスすることで、assets/client.jsを取得することができるようになりました。

7. ssr.jsxの編集

  • src/ssr.jsx<script src="./client.js"></script>を追加します。
importReactfrom'react';import{renderToString}from'react-dom/server';importCountUpfrom'./CountUp';constssr=():string=>(`
  <html>
    <head>
      <title>CountUp</title>
      <meta charset="utf-8"/>
    </head>
    <body>
      <div id="app">
        ${renderToString(<CountUp/>)}</div>
      <script src="./client.js"></script>
    </body>
  </html>
`);exportdefaultssr;

これで、ブラウザでWebpackでバンドルしたclient.jsが読み込まれることになります。

8. Babelの実行

"6."ssr.jsのを編集したので、Babelを実行して、サーバーで生成されるHTMLに<script src="./client.js"></script>が追加されるようにします。

  • Babelを実行します。
$ yarn babel

9. Expressの起動

  • Expressを起動します。
$ yarn start

表示は同じですが、+を押すことでカウントが増え、時刻表示も更新されるのが分かります。

countup.gif

10. Gitにコミットする

忘れないようにgitにコミットしておきます。

$ git add .
$ git commit -m "STEP3 - Webpackを使う"

ファイル構成

.
├── .git
├── node_modules
├── .babelrc
├── .eslintrc
├── .gitignore
├── .vscode
│   └── settings.json
├── assets               ← Webpackによって生成される。
│   └── client.js        ← Webpackによって生成される。srcをバンドルしたもの。
├── index.js             ← 編集した。client.jsを配信するように設定。
├── package-lock.json
├── package.json         ← 編集した。buildコマンドを追加。
├── src
│   ├── CountUp.jsx
│   ├── client.jsx       ← 新しく作った。ブラウザで読み込む用のJavaScript。
│   └── ssr.jsx          ← 編集した。client.jsをブラウザで読み込む。
├── views
│   ├── CountUp.js
│   ├── client.js        ← Babelによって生成される。が、サーバーでは必要ないので使われない。
│   └── ssr.js
├── webpack.config.js    ← 新しく作った。Webpackの設定ファイル。
└── yarn.lock

STEP4 - TypeScriptを使う

JavaScriptは動的型付けを行います。例えば、数値と文字を足し算するとこうなります。

consta=1;constb='2';constsum=a+b;// 12

これくらい簡単なプログラムだと素晴らしいことなんですが、プロジェクトが大きくなるとバグが起きやすくなります。一方、TypeScriptを使用して静的型付けを行うと、実行前にエラーを出してくれます。

consta=1;constb='2';constsum:number=a+b;// Error

sumの型をnumber(数値)として指定しているので、数値文字列の足し算に対してエラーを出してくれています。

1. 必要なパッケージのインストール

$ yarn add -D typescript @babel/preset-typescript

インストールしたパッケージの概要は以下の通りです。

  • Typescript : JavaScriptで静的型付けをできるようにした言語です。
    • @babel/preset-typescript : TypeScriptをBabelでコンパイルできるようにするプリセットです。

2. ESLintの設定

  • TypeScriptを扱うためのパーサをインストールします。
$ yarn add -D @typescript-eslint/parser
  • .eslintrcを以下のように編集します。
{"parser":"@typescript-eslint/parser","extends":["airbnb","airbnb/hooks"],"plugins":["@typescript-eslint"],"settings":{"import/extensions":[".js",".jsx",".ts",".tsx"],"import/resolver":{"node":{"extensions":[".js",".jsx",".ts",".tsx"]}}},"rules":{"react/jsx-filename-extension":["error",{"extensions":[".js",".jsx",".ts",".tsx"]}],"import/extensions":["error","always",{"js":"never","jsx":"never","ts":"never","tsx":"never"}]}}

TypeScriptをパース(構文解析)するために、parserを指定しています。継承しているairbnbには@typescript-eslintが含まれていないので、TypeScriptのプラグインも追加しました。

.tsxファイルでもJSXを扱えるようにするためにreact/jsx-filename-extensionルールを指定しています。

参考 : @typescript-eslintでtypescriptのlintをeslintで行いつつ、airbnbの設定でいきましょう的なお話

ESLint関係の設定は難しいので、動いてるからよし!ぐらいの心持ちで行きましょう。

3. 定義ファイルのインストール

Reactの型定義ファイルもインストールしておきます。

$ yarn add -D @types/react @types/react-dom

型定義ファイル : TypeScriptで型を扱うために、Reactなどの外部モジュールの型の定義が必要です。型定義ファイルは自作することもできます。

4. .babelrcの編集

  • .babelrc@babel/preset-typescriptを追加して、以下のように編集します。
{"presets":[["@babel/preset-env",{"useBuiltIns":"usage","corejs":3}],["@babel/preset-react",{"useBuiltIns":"usage","corejs":3}],"@babel/preset-typescript"]}

5. webpack.config.jsの編集

  • webpack.config.js.tstsxの拡張子を処理するように書き換えます。

  • また、Babelでコンパイルできるように、4.で行なった.babelrcと同じく@babel/preset-typescriptを追加します。

constpath=require('path');module.exports={resolve:{// 対象にする拡張子の指定(パッケージも含まれるので、.jsは必須)extensions:['.js','.jsx','.ts','.tsx'],},entry:{// エントリーポイントの指定client:'./src/client.tsx',},output:{// 書き出し先のディレクトリを指定(assets)path:path.resolve(__dirname,'assets'),// 書き出すファイルの名前を指定(名前は変更しない)filename:'[name].js',},module:{rules:[{// 拡張子が.jsか.jsxだった場合に適用するルールtest:/\.ts(x?)$/,// node_modulesディレクトリは除外exclude:/node_modules/,use:[{// babelの設定loader:'babel-loader',options:{presets:[['@babel/preset-env',{useBuiltIns:'usage',corejs:3,},],['@babel/preset-react',{useBuiltIns:'usage',corejs:3,},],'@babel/preset-typescript',],},},],},],},};

6. ssr.jsxの編集

  • src/ssr.jsxの拡張子を.tsxに変更します。
$ mv src/ssr.jsx src/ssr.tsx
  • src/ssr.tsx`を以下の様に書き換えます。

: stringという返り値の型を追加しました。

importReactfrom'react';import{renderToString}from'react-dom/server';importCountUpfrom'./CountUp';// 返り値の型を指定しました。constssr=():string=>(`
  <html>
    <head>
      <title>CountUp</title>
      <meta charset="utf-8"/>
    </head>
    <body>
      <div id="app">
        ${renderToString(<CountUp/>)}</div>
      <script src="./client.js"></script>
    </body>
  </html>
`);exportdefaultssr;

7. CountUp.jsxの編集

  • src/CountUp.jsxの拡張子を.tsxに変更します。
$ mv src/CountUp.jsx src/CountUp.tsx
  • src/CountUp.tsxに以下の内容を書き込みます。: JSX.Elementという返り値の型を追加しました。
importReact,{useState}from'react';constCountUp=():JSX.Element=>{// 返り値の型を指定しました。const[count,setCount]=useState(0);return(<><h1>{count}</h1><buttontype="button"onClick={()=>setCount(count+1)}>+</button><p>{newDate().toTimeString()}</p><>
  );
};

export default CountUp;

8. client.jsxの編集

  • src/client.jsxの拡張子を.tsxに変更します。
mv src/client.jsx src/client.tsx

9. package.jsonの編集

  • Babelの対象とする拡張子を、.js,.jsxから.ts,.tsxに編集します。
"scripts":{"start":"node -r esm index.js","build":"babel src -x '.ts,.tsx' -d views"},

10. Babelの実行

  • Babelを実行します。
$ yarn babel

Babelによって、srcディレクトリのgreeting.tsxssr.tsxがviewsディレクトリに.js拡張子で書き出されます。

11. Webpackの実行

  • Webpackを実行します。
$ yarn build

Webpackによって、srcディレクトリのファイルがclient.tsxをエントリーポイントとしてバンドルされ、viewsディレクトリにclient.jsという1ファイルにまとめて書き出されます。

12. Expressの起動

yarn startを実行してhttp://localhost:3000/を開きます。

$ yarn start

見た目は変わっていませんが、TypeScriptを使用することで強固なプログラムになりました。

13. Gitにコミットする

忘れないようにgitにコミットしておきます。

$ git add .
$ git commit -m "STEP4 - TypeScriptを使う"

ファイル構成

.
├── .git
├── node_modules
├── .babelrc            ← 編集した。TypeScriptをコンパイルする。
├── .eslintrc           ← 編集した。TypeScriptを構文チェックする。
├── .gitignore
├── .vscode
│   └── settings.json
├── assets
│   └── client.js
├── index.js
├── package-lock.json
├── package.json        ← 編集した。buildコマンドをTypeScriptに対応。
├── src
│   ├── CountUp.tsx     ← 編集した。拡張子を変更と型の追加。
│   ├── client.tsx      ← 編集した。拡張子を変更。
│   └── ssr.tsx         ← 編集した。拡張子を変更と型の追加。
├── views
│   ├── CountUp.js
│   ├── client.js
│   └── ssr.js
├── webpack.config.js   ← 編集した。TypeScriptをバンドルする。
└── yarn.lock

お疲れ様でした!:tada::tada::tada:

以上がモダンなTypeScript & ReactでのSSR/SPA開発です。

しっかりと開発環境を作ったので、複雑なプロジェクトにも応用できると思います。

参考

React Server-Side Rendering Example : めちゃくちゃ参考にしました。SSRありとなしをエンドポイントで分けているので、違いを体感できて面白いです。

最後に

分からないところ、ご指摘等あれば、お気軽にコメントやTwitterまでご連絡ください。

本当はContextを使うところまでやりたかったですが、分かりづらくなりそうだったのでやめました。

ちなみにサボテンのIQはらしいです:cactus:


Viewing all articles
Browse latest Browse all 8889