Express.js + JQueryの開発案件に後からReact.jsを導入した話
背景
既存システムが、Express.js + JQueryで作成されており、既存資産を引き継ぐ形で、JQueryベースでしらばくは開発を継続していた。
契機
次期開発から、既存の画面をパネル化したいなど、動画を活用したインタフェースを利用したいなど、コンポーネント化を進めていかないと手数がかかりそうなことから、React.jsの導入に踏み切りました。
SIの受託開発案件で、アジャイル開発に取り組んでいるなど、技術選定がある程度自由に任されていましたが、要件実現には、コンポーネント化を進めるべきだと説き了解を得ました。
要点
React.js導入に当たって、抑えたポイントは以下の2点です。
- 既存画面の資産はそのまま活用する node.jsとReact.jsの共存する
- 新規画面は、React.jsで開発することでコンポーネント化を進め、開発を効率的にする
node.jsとReact.jsの共存する
狙い
既存資産と共存することを可能としました。
全てを新たに作り直すのがハードルが高かった為、共存可能を可能とすることで、React.js導入の方がスムーズに進んだように思えます。
構成
同じプロジェクト内でフォルダを切り分け、package.jsなどの構成は分割しました。
ルートの express.jsはそのままに、react.jsはサブフォルダにまとめました。
/ # express.js構成のルート (既存アプリそのまま)
├package.json # express.jsのパッケージ構成
├app.js # express.jsのメインプログラム
└react/ # react.js構成のルート
├package.json # react.jsのパッケージ構成
└src/App.js # react.jsのエントリポイント
/package.json
"scripts": {
"start": "npm-run-all --parallel dev:*",
"dev:express": "nodemon ./bin/www",
"dev:react": "npm --prefix ./react run dev",
"install": "npm-run-all --parallel install:*",
"install:react": "npm --prefix ./react install",
},
package.jsonを分けたので、ルートのpackage.jsonを起点に、npmコマンドのprefixオプションで、サブフォルダのpackage.jsonの構成のscriptsと連携させます。
デプロイスクリプトや、CIでの利用を考慮した場合に必要となります。
webpack設定
/react/webpack.js
const path = require('path');
module.exports = {
mode: 'development',
entry: [
'./src/index.js'
],
output: {
filename: 'bundle.js',
path: path.resolve('..', 'public') //expressのjs/css配置先へ出力
},
module: {
rules: [
{
test: /\.(js|jsx)?$/,
loader: 'babel-loader',
exclude: /node_modules/,
options: {
presets: ['@babel/react','@babel/env'], //Babal7の場合
}
},
]
},
resolve: {
extensions: ['.js', '.jsx']
}
}
React.jsのエンドポイントとなるbundle.js
をトランスパイルして、Express.jsが参照するjs/css配置先へ配置します。
Express.js側とReact.jsのルーティングを調整して、React.jsを共存をさせます。
ルーティング
既存機能やサーバー側のAPIはExpress.jsによるルーティングのまま残し、特定のURLのみReact.jsで動作するように切り分けを行いました。
Express.js側
Node.jsとReact.jsはパスで切り分け、Express.jsでルーティングを振り分ける
/express/app.js
var express = require('express');
var router = express.Router();
router.get('/foo', function(req, res, next) {
res.render('foo'); # 既存のexpress.js の画面やAPIのパス
});
router.get('/bar', function(req, res, next) {
res.render('react'); # 新規のreact.jsの画面のエンドポイント
});
router.get('/baz', function(req, res, next) {
res.render('react'); # 新規のreact.jsの画面のエンドポイント
});
- 既存画面は、express.jsの
Router
でルーティングされていた為、React.jsのエンドポイントもそれを経由する - Express.jsもReact.jsも複数の画面とパスがあるが、React.jsは同じエンドポイント(App.js)を呼ぶ
React.js側
/react/src/App.js
import React, { Component } from 'react';
import BarPage from './Bar';
import BazPage from './Baz';
import { BrowserRouter, Route } from "react-router-dom";
const App = () => {
console.log('App')
return (
<BrowserRouter>
<div>
<Route path="/bar" component={BarPage} />
<Route path="/baz" component={BazPage} />
</div>
</BrowserRouter>
);
}
export default App;
React.js側のルーティングには、react-router-domn のBrowserRouter
を活用できる。
express.jsと共存していても、React.js側のルーティングの仕組みを利用することは可能。
考察と発展
コンポーネント化の必要性を理由に、React.jsの導入へと踏み切ったが、それまでは普通のCommonJSによる開発を頑張っていたので、ES6の導入など開発効率はかなり高まった。
元々の画面の要件も、パネル化やダッシュボードなど参照系のユニークなUIを必要とするものだったのでコンポーネント化をしなければ厳しかっただろうと思える。過去にReact.jsで開発した知見があったため、今回はReact.jsの導入がうまくハマった。
続く開発もReact.jsで進めることができそうだ。続く開発での新規画面はマスタメンテなどの更新系の画面が中心になる予定。
次は、開発スピード重視の構成をとり、よりシンプルな作りにするため、FLUXデザインパターンの適用の必要性を説いている。
こちらには、ReduxForm導入を検討しており、更新系の画面からはReduxを採用する予定。
更新系の画面のイベントハンドリングの手数が軽減される見込み。
既存画面はExpress.jsのまま触らなかったので、特に回帰テストなどの必要性はほとんど生じなかった。
既存画面については、画面をパネル化するなどコンポーネント化が必要となった段階で、一つづつゆっくりJQueryからReact.jsに切り替えて行くことも可能となる。
改善点
一つ改善したい点としては、React部分は、Create React Appで作りたかった。
webpackを自分でメンテナンスするのは中々、手がかかるのので、楽をしたいところだった。
今回はExpress.jsと共存する必要があったため、CreateReactAppの構成に頼らず、webpack.jsで自身でエントリポイントなどを調整するようにした。
rewire-entry
ライブラリ利用すれば、ejectをしなくても、エントリポイントを変更可能だったので、Express.jsと共存の構成でもCreateReactAppは利用できたかもしれない。
参考
https://docs.npmjs.com/cli/prefix.html
https://expressjs.com/ja/4x/api.html#router
https://reacttraining.com/react-router/web/api/BrowserRouter