Next.jsを使わないReactのSSR解説で分かりやすいやつが無かったので、書きます!
環境構築からとっても丁寧にやっていきます![:cactus: :cactus:]()
![サボテンくん]()
最初に
読んで欲しい人
- 『SSRしたいけど難しそう....
』
って人はもちろん
- 『何も分からないけどSPAが作ってみたい!
』
って人も実際に作って楽しめるように書いてます。
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で開発するなら必須です。
AppStoreからXcodeをインストールします。
以下のコマンドを実行して、コマンドライン・デベロッパツールをインストールします。
- 以下のコマンドを実行して、Homebrewをインストールします。
$ /usr/bin/ruby -e"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
- 以下のコマンドを実行して、
Example usage: ...
が表示されればOKです。
参考 : 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
の作成)しておきます。
- 作成したディレクトリをVSCodeで開きましょう。『ファイル』 >『 開く』から開けます。
![ファイルから開く]()
Git
プログラムのバージョンを管理するために、Gitも入れておきましょう。Gitはプログラムの更新履歴を管理したり、複数人で一つのプロダクトを更新するのに役立ちます。
.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
を作成して、
以下を書き込んでください。
{"extends":["airbnb","airbnb/hooks"],"env":{"browser":true},}
extends
は指定した設定を継承します。airbnb
とReact Hooksを使用するためのairbnb/hooks
を継承しています。
env
でbrowser
を指定することで、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. 必要なパッケージのインストール
インストールしたパッケージの概要は以下の通りです。
* Express.js : JavaScriptのNode.jsのWebアプリケーションフレームワークです。
* esm : node -r esm index.js
の様に指定すると、Nodeでimport
/export
の構文を利用できるようになります。
2. 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の作成
$ mkdir src &&touch$_/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
に対して実行するコマンドを設定します。
"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.json
のscript:{}
に追加したコマンドは、yarn start
のように実行することができます。
5. Expressの起動
http://localhost:3000/にアクセスして、下の画像のように表示されれば完璧です!これがSSRの基礎になります。
ページをリロードすると時間表示が更新されます。ページのリクエストごとにJavaScriptからHTMLを生成していることが確認できます。
まだハリボテのHTMLを生成しているだけなので、+
ボタンを押しても何もおきません。
![STEP1の画像]()
6. 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
インストールしたパッケージの概要は以下の通りです。
Polyfill : 最近の機能をサポートしていない古いブラウザーで、その機能を使えるようにするためにコードを変換すること。
-D
オプションをつけるかどうかの違い
yarn add
に-D
オプションをつけると、package.json
のdevDependencies
に追加されます。dependencies
に追加してもdevDependencies
に追加してもパッケージとして公開しなければ違いはありませんが、ExpressやReactは、dependencies
の追加しないとESLintに怒られます。
参考 : 【いまさらですが】package.jsonのdependenciesとdevDependencies
2. .babelrcの作成
- Babelの設定ファイル
.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の作成
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によってsrcディレクトリのssr.jsx
とCountUp.jsx
がviewsディレクトリに.js
拡張子で書き出されるはずです。
8. Expressの起動
表示される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. エントリーポイントの作成
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
オプションでproduction
かdevelopment
を指定しないと、ビルド時に警告が出ます。
5. Webpackの実行
すると、Babelによってsrcディレクトリのファイルが、assetsディレクトリにclient.js
という1ファイルにまとめて書き出されます。client.js
と関係があるファイルだけがまとめられるので、ssr.js
はバンドルに含まれません。
6. client.jsのルーティング
Webpackでバンドルしたassets/client.js
をクライアントで読み込む必要があるので、Expressでassets
ディレクトリ内のファイルを返すように設定します。
index.js
にapp.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>
が追加されるようにします。
9. Expressの起動
表示は同じですが、+
を押すことでカウントが増え、時刻表示も更新されるのが分かります。
![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
{"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の編集
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によって、srcディレクトリのgreeting.tsx
とssr.tsx
がviewsディレクトリに.js
拡張子で書き出されます。
11. Webpackの実行
Webpackによって、srcディレクトリのファイルがclient.tsx
をエントリーポイントとしてバンドルされ、viewsディレクトリにclient.js
という1ファイルにまとめて書き出されます。
12. Expressの起動
yarn start
を実行してhttp://localhost:3000/を開きます。
見た目は変わっていませんが、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: :tada:]()
![:tada: :tada:]()
以上がモダンなTypeScript & ReactでのSSR/SPA開発です。
しっかりと開発環境を作ったので、複雑なプロジェクトにも応用できると思います。
参考
React Server-Side Rendering Example : めちゃくちゃ参考にしました。SSRありとなしをエンドポイントで分けているので、違いを体感できて面白いです。
最後に
分からないところ、ご指摘等あれば、お気軽にコメントやTwitterまでご連絡ください。
本当はContextを使うところまでやりたかったですが、分かりづらくなりそうだったのでやめました。
ちなみにサボテンのIQは2らしいです![:cactus: :cactus:]()