- 投稿日:2019-03-02T21:19:05+09:00
NERM (Node.js + Express + React + Material-UI) ベースなWebアプリ開発用のシンプルなビルド環境 2019年3月版
Node.js + Express で Web アプリを開発するのはもう一般的だと思いますが、ちょっと凝ったアプリを作成しようとすると、フロントエンドの UI で何を使おうか悩みますね。
個人的には React + Material-UI あたりが使いやすいと思います。(勝手に)名付けて NERM (Node.js + Express + React + Material-UI) 環境!で、この環境用にシンプルなビルド環境を用意してみました。
2017年に投稿した browserify+babelifyでReactアプリをトランスコンパイルできるシンプルなビルド環境を用意する が進化した2019年版です。Node.js ベースのWebアプリを気軽に作成したい場合に、スケルトンとして使っていただければ、嬉しいです。
準備
node と npm は利用できるよう準備しておいてください。私はWindows環境なので nodist を使っています。
まずは作業用のディレクトリ(フォルダ)を作成します。今回は "rtk-nerm" としました。そして src, public のサブディレクトリを作成しておきます。
シンプルな React + Material-UI サンプル
今回も非常にシンプルなサンプルを用意しましょう。
src ディレクトリに App.jsx ファイルを用意します。内容は以下をコピペしてください。
src/App.jsximport React from 'react'; import ReactDOM from 'react-dom'; import Button from '@material-ui/core/Button'; function App() { return ( <Button variant="contained" color="primary"> Hello World </Button> ); } ReactDOM.render( <App />, document.getElementById('react-root') );また public ディレクトリに index.html ファイルを用意します。内容は以下をコピペしてください。
public/index.html<!doctype html> <html> <head> <meta charset="UTF-8"> <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500"> <title>rtk-nerm</title> </head> <body> <div id="react-root"></div> <script type="text/javascript" src="./App.js"></script> </body> </html>サンプルはこれで準備できましたので、いよいよ環境の構築を始めましょう。
トランスコンパイル環境の構築
作業ディレクトリに package.json ファイルを用意します。内容は以下をコピペしてください。
package.json{ "name": "rtk-nerm", "version": "1.0.0", "description": "Simple transcode environment for NERM (Node.js + Express + React + Material-UI) application.", "scripts": { "build": "browserify -t [ babelify --presets [ @babel/preset-env @babel/preset-react ] ] src/App.jsx -o public/App.js", "start": "node Server.js" }, "engines": { "node": ">= 8" }, "author": "yamachan / Toshio Yamashita", "license": "MIT" }※ scripts 要素以外は自由に変更してok
そして以下のコマンドで必要なパッケージを導入します。
npm install --save express npm install --save react react-dom @material-ui/core @material-ui/icons npm install --save-dev browserify babelify npm install --save-dev @babel/core @babel/preset-env @babel/preset-reactちなみに私は以下のようなバージョンのモジュールが導入されました。
package.json"dependencies": { "@material-ui/core": "^3.9.2", "@material-ui/icons": "^3.0.2", "express": "^4.16.4", "react": "^16.8.3", "react-dom": "^16.8.3" }, "devDependencies": { "@babel/core": "^7.3.4", "@babel/preset-env": "^7.3.4", "@babel/preset-react": "^7.0.0", "babelify": "^10.0.0", "browserify": "^16.2.3" }以下のコマンドを実行して public/App.js ファイルが生成されれば ok です。
npm run buildNode.js + Express サーバーを起動
プロジェクトのルートディレクトリに Server.js ファイルを用意し、内容は以下をコピペしてください。
Server.jsconst express = require("express"); const app = express(); app.use(express.static('public')); let port = process.env.PORT || 3000; app.listen(port);以下のコマンドで Server.js コードを実行し、ローカルで Web サーバーを起動します。
npm startブラウザで http://localhost:3000/ にアクセスすれば、Material-UI を使った React ページが表示されます。
さあアプリ開発を開始しましょう
Server.js にはバックエンドのロジックを実装しましょう。こちらを更新した時には npm start で起動するWeb サーバーを再起動(終了して再実行)することを忘れずに。
src/App.jsx にはフロントエンドの UI を実装し、npm run build で public/App.js を更新しましょう。
それぞれ拡張し、自分なりの Web アプリケーションを作成してみてください。
IBM Cloud の Node.js 環境で動かしてみる
ついでにプロジェクトをIBM Cloud (Cloud Foundry) の Node.js 環境で動かしてみましょう。
IBM Cloudアカウントをお持ちでない方は、こちらからすぐに使える無料アカウント登録をしてください。
プロジェクトのルートディレクトリに .cfignore ファイルを用意し、内容は以下をコピペしてください。このファイルが無くても動作しますが、用意したほうが push 動作が軽くなります。
.cfignorenode_modules src README.md .gitignoreさあ、IBM Cloud にログインして、アプリを push しましょう。(アプリ名 rtk-nerm はユニークなものに変更する必要があります)
アプリ名はWebサイトを公開した時のURLの一部になります。IBM Cloudの場合、https://<アプリ名>.mybluemix.netとなります。
cf login cf push -m 128M rtk-nerm【追記】package.json に start コマンドを記載したので -c "node Server.js" オプションは不要になりました
IBM Cloud ダッシュボードでアプリの開始を確認します。
「アプリURLにアクセス」リンクから、実際の表示を確認します。
関連ファイルのダウンロード
rtk-nerm というGitHubリポジトリに今回のファイルを置いておきましたので、面倒な方はそちらからダウンロード/clone してお使いください。
Enjoy!
Node.js でロジックを実装し、Express + React + Material-UI ベースで開発したWebアプリをビルドするための、できるだけシンプルな環境を構築してみました。これを出発点として自分なりのアプリやビルド環境を構築する助けになれば嬉しいです。
ではまた!
- 投稿日:2019-03-02T18:05:05+09:00
【備忘録】react-templatesについてまとめた
react-templates
- wixが提供しているライブラリ(http://wix.github.io/react-templates/)
- reactのテンプレート箇所を".rt" 拡張子ファイルとして切り分け、コンパイルしテンプレートとして出力する。
- HTML感覚で書ける。
- お試し: http://wix.github.io/react-templates/fiddle.html
install
https://www.npmjs.com/package/react-templates
npm i -g react-templatesgulp: https://www.npmjs.com/package/gulp-react-templates
npm i gulp-react-templatesgulp task code
サンプル
const gulp = require('gulp'); const rt = require('gulp-react-templates'); const rename = require("gulp-rename"); // 出力ファイル名を変更するため const rt_path = './src/rt/*.rt'; gulp.task('rt', () => { return gulp.src(rt_path) .pipe(rt({ modules: 'es6' })) // 吐き出す際の出力フォーマットを選択 .pipe(rename(name_info => { const new_basename = name_info.basename.replace('.rt', ''); // '.rt'を''に置換 name_info.basename = new_basename; })) .pipe(gulp.dest('./src/templates')); }); gulp.task("watch", () => { return gulp.watch(rt_path, gulp.series(['rt'])); });Command line Interface
source: https://github.com/wix/react-templates/blob/gh-pages/docs/cli.md
先ほどの出力する際のoptionの例を以下に記載
// temp.rt <h2>Hello React</h2>// es6 // rt [file.rt|glob]* -m es6 import * as React from 'react'; import * as _ from 'lodash'; export default function () { return React.createElement('h2', {}, 'Hello React'); }// commonJS // rt [file.rt|glob]* -m commonJS 'use strict'; var React = require('react'); var _ = require('lodash'); module.exports = function () { return React.createElement('h2', {}, 'Hello React'); };// none (default) // rt [file.rt|glob]* var tempRT = function () { return React.createElement('h2', {}, 'Hello React'); };reactに組み込む
▼ こんなrtファイルを書いて
// temp.rt <h2>Hello react-templates</h2>▼ コンパイル(es6)してこんな感じにして
// temp.js import * as React from 'react'; import * as _ from 'lodash'; export default function () { return React.createElement('h2', {}, 'Hello react-templates'); }▼ importしてrenderのなかでreturnする
// App.js import React, { Component } from 'react'; import Temp from './templates/temp.js'; class App extends Component { render() { return Temp(); } } ReactDOM.render(<App />, document.getElementById('root'));ブラウザに Hello react-templates が出力されればok
参考
- 投稿日:2019-03-02T12:16:54+09:00
takeWhile はメモリリーク (無駄なメモリ消費) の原因になり得る
takeWhile
に参照透過でない式を渡すのは良くない。上記が分かっていれば下記を読む必要はありません。
経緯
React開発ノウハウメモ(随時更新)
上記記事を読んでunsubscribe
をcomponentWillUnmount
に書かない方法を知ったので調べてみた。1
takeWhile
はメモリリークの原因になり得る。
takeWhile
は値が来たときのみ実行されるので値が来なければunsubscribe
されない。
無駄なネットワーク通信やDBへのアクセスを続けてしまう。
takeWhile
は値に応じて処理を続けるべきか決まる場合で使うべき。
Observable
を流れる値と関係なく処理を止めたいならunsubscribe
を呼ぶかtakeUntil
を使う。テストしてみる
実際に試してみるためのコードを書いた。
それらで下記の 3 つの関数を使っている。utils.ts// s 秒待つ const sleep = (s: number) => new Promise(r => setTimeout(r, s * 1000)) // タイムスタンプをつけてログを出す const log = (...v: any[]) => { const ts = (performance.now() / 1000) | 0 console.log(...v, `(${ts})`) } // キャンセルできる Observable を生成 const make = (name: string) => { return new Observable<number>(s => { let canceled = false const push = (v: number) => { log(name, 'sub1', v, '- canceled', canceled) s.next(v) } Promise.resolve().then(async () => { push(12) // すぐに 12 を渡す await sleep(10) // 10 秒待ってから push(13) // 13 を渡す push(14) // 14 を渡す }) return () => { log(name, 'unsub1') canceled = true } }) }テスト内容
上記
make
でObservable
を生成したのち 1 秒後に処理を止める。
unsubscribe
する一般的なやりかたであると思われる。
問題なく動いている。test1.tsconst test1 = async () => { const read = make('test1').pipe( tap(v => log('test1', 'sub2', v)), ).subscribe() await sleep(1) read.unsubscribe() } test1() /* test1 sub1 12 - canceled false (0) test1 sub2 12 (0) test1 unsub1 (1) test1 sub1 13 - canceled true (10) test1 sub1 14 - canceled true (10) */
takeWhile
を使う1 秒後に止めようとしたが、その後 9 秒経って次の値が来るまで止まっていない。
test2.tsconst test2 = async () => { let alive = true make('test2').pipe( takeWhile(() => alive), tap(v => log('test2', 'sub2', v)), ).subscribe() await sleep(1) alive = false } test2() /* test2 sub1 12 - canceled false (0) test2 sub2 12 (0) test2 sub1 13 - canceled false (10) test2 unsub1 (10) test2 sub1 14 - canceled true (10) */
takeUntil
を使う問題なく動く。
test3.tsconst test3 = async () => { const unsub = new Subject<void>() make('test3').pipe( takeUntil(unsub), tap(v => log('test3', 'sub2', v)), ).subscribe() await sleep(1) unsub.next() unsub.complete() } test3() /* test3 sub1 12 - canceled false (0) test3 sub2 12 (0) test3 unsub1 (1) test3 sub1 13 - canceled true (10) test3 sub1 14 - canceled true (10) */
unsubscribe
とtakeUntil
、どちらを使うべきか?こちらの記事 (英語)では
takeUntil
がオススメされている。手続き的な
unsubscribe
よりも宣言的なtakeUntil
が良いという判断だろうか?補足
React
とRxJS
を組み合わせる場合大規模なアプリなら
redux-observable
を、小規模ならrxjs-hooks
を使うことを勧める。
古いReact
を使っているならrecompose
も良い。
useEffect
のある今のReact
でcomponentWillUnmount
のような古い方法を使う必要はないと思う。 ↩
- 投稿日:2019-03-02T10:16:36+09:00
react-typescript-wijmoでサンプル
注意書き
C●deZineで見かけたサンプルを参考に実装しています。
(そっくりそのままtsに移植しただけですごめんなさい)作成したコードは以下にあります。
https://github.com/ishibashi-futos/react-wijmo当方環境
package.json{ "name": "wijmo-ts", "version": "0.1.0", "private": true, "dependencies": { "react": "^16.8.3", "react-dom": "^16.8.3", "react-scripts-ts": "3.1.0", "wijmo": "^5.20183.568" }, "scripts": { "start": "react-scripts-ts start", "build": "react-scripts-ts build", "test": "react-scripts-ts test --env=jsdom", "eject": "react-scripts-ts eject" }, "devDependencies": { "@types/jest": "^24.0.6", "@types/node": "^11.9.5", "@types/react": "^16.8.4", "@types/react-dom": "^16.8.2", "typescript": "^3.3.3333" } }create-react-appしてサーバを立ち上げる
create-react-appコマンドを利用して、reactアプリの雛形を出します。
今回はtypescript
を利用するので、--scripts-version
オプションをつけます。$ create-react-app wijmo-ts --scripts-version=react-scripts-ts $ cd wijmo-ts/ && yarn start # or npm startよく見るReactの画面が出てきたかと思います(スクショ略)
このあと、npm install --save wijmo
とすると、↑のようなpackage.json
ができるかと思います。guageサンプルを実行してみる。
完成品はこちら(いきなり)
Guage.tsximport * as React from 'react'; import 'wijmo/styles/wijmo.css'; import * as wjGauge from 'wijmo/wijmo.react.gauge'; interface IState { // point 1 gaugeValue: number; } class Guage extends React.Component<{}, IState> { constructor(props: any) { super(props); this.state = { gaugeValue: 30 } this.gaugeChanged = this.gaugeChanged.bind(this); // point 2 this.textboxChanged = this.textboxChanged.bind(this); } public render() { return ( <div className="App"> <h1>Wijmo + React(ゲージ)</h1> <wjGauge.LinearGauge className="wijmo-control" value={this.state.gaugeValue} valueChanged={this.textboxChanged}/> gaugeValue:<input type="text" className="textBox" value={this.state.gaugeValue} onChange={this.textboxChanged}/> </div> ); } private gaugeChanged(newValue: number) { this.setState({gaugeValue: newValue}); } private textboxChanged(newValue: React.ChangeEvent<HTMLInputElement>) { const input = newValue.target as HTMLInputElement; if (input != null) { // point 3 if (!isNaN(Number(input.value))) { this.setState({gaugeValue: Number(input.value)}); } } } } export default Guage;react自体触るのがほとんど初めてだったこともあり、wijmoと関係ないところではまりました。
point 1. interface name must start with a capitalized I
TSLintのデフォルト設定なのか、InterfaceのprefixにIを指定してないと、npm startしたときにエラーになります・・・。
tslint.jsonの rulesに以下のように設定すると消えます。tslint.json{ "rules" : { "interface-name": [false] } }point 2. 'gaugeChanged' is declared but its value is never read.
リンク先でも触れている通り、React.Componentを使用する場合は、
イベントハンドラが自動的にbindされないらしいので、コンストラクタ内でbindする。point3. TypeError: Cannot read property 'value' of undefined
イベントが発生した時に、HTMLInputElementが取得できていない時がある。
そのため上記のようなエラーが出るので、HTMLInputElementの型で取れなかった(nullだった?)時は
処理しないように直してみた。終了
長くなってきたので、Gridのサンプルは別に書きます。