概要
Node.jsのコアモジュール(Buffer, crypto等)やnpmパッケージ利用しているJavaScriptのソースコードをbrowserifyでブラウザ上でも動くように変換し、あわせてReactも使ってWebアプリを作ってみました。ReactのJSXやES6の文法を一部使用しているため、browserifyのトランスフォームとしてbabelifyも利用しています。
アプリの内容は、ユーザの入力した文字列に対してbase64等の変換やハッシュ化をリアルタイムでパイプライン的に適用できるというもので、npmパッケージには拙作のstring-codecを利用しています。
アプリのソースはGitHubにあります。knjcode/react-string-codec-pipeline
アプリ使用イメージ
各ツールの用途
- browserify
Node.jsのコアモジュールやnpmパッケージを利用したJavaScriptのソースコードをブラウザ上でも動作するように変換する -
babelify
ECMAScript6(ES6)のコードをES5(いわゆる普通のJavaScript)のコードに変換する。また、ReactのJSXのコードを解釈してJavaScriptのコードに変換することもできる -
React
クライアントアプリのビューのライブラリとして利用 -
uglify-js
JavaScriptのソースコードを実行可能な状態のまま圧縮するツール。browserifyで変換したコードはかなり大きくなるため圧縮してサイズを減らします
動作の仕組み
string-codecを利用すると以下のように文字列を変換出来ます。
1 2 3 4 |
var codec = require('string-codec'); codec.encoder('hello', 'base64'); // => 'aGVsbG8=' |
本アプリでは、Reactを使って、使用イメージにあるような連続した文字列の変換処理をReactのコンポーネントとして管理し、入力文字列の変化に応じてすべて再計算しています。Reactを使うと、このような動作を宣言的に記述することができ、パイプラインの途中のコンポーネントを削除するといった処理も複雑な状態遷移を考慮することなく実装することができます。
メソッドを引数付きで呼び出す方法
画面上の各種変換ボタンは以下のように変換名が入った配列(ENC)に対してmap処理を使って生成しています。
ボタンのonClick属性に、this.addAlgo.bind(null,algo,’encode’)とすることでaddAlgoメソッドを引数付きで呼び出しています。
1 2 3 4 5 6 7 8 9 10 11 |
{ENC.map( algo => { return ( <button key={algo} style={style.item} onClick={this.addAlgo.bind(null,algo,'encode')}> {algo} </button> ); })} |
ボタンクリック時に渡される変換内容を保持する
ユーザが各変換処理のボタンをクリックするたびに、以下のようにルートコンポーネント(AlgoList)のstateであるalgosに変換内容を追加していきます。
1 2 3 4 5 6 7 |
addAlgo(algo,codec) { var id = Math.floor(Math.random() * 1000000000000).toString(36); this.setState({ algos: this.state.algos.concat({id:id, algo:algo, codec:codec}) }); }, |
baes64エンコードのボタンをクリックすると、AlgoListのaddAlgoメソッドに引数として、algoに’base64’、codecに’encode’を渡します。addAlgoメソッド内では、要素削除時に利用するためにランダム生成したIDを追加して、this.state.algos
の末尾に追加します。
変換処理
各変換処理の変換結果は入力文字列が変化する都度再計算します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
var value = this.state.value; var algos = this.state.algos.map( algo => { try { if(algo.codec === 'encode') { value = encoder(value,algo.algo); } else { value = decoder(value,algo.algo); } } catch(e) { value = e.toString(); } return ( <li key={algo.id}> <Algo algo={algo} result={value} onDelete={this.onDelete} /> </li> ); }); |
this.state.value
がテキストボックスに入力されている文字列です。
変換結果を格納する変数algos
に、変換処理の配列this.state.algos
からmapで取り出した内容に応じて文字列を変換し、変換結果表示用のAlgoコンポーネントを返します。
例えば、変換処理が5個ある状態では、変数algos
には5行分のAlgoコンポーネントが入ります。
最後に、AlgoListコンポーネントのrenderメソッドにて、algos
変数を渡して画面に表示します。
1 2 3 4 |
<div> <p>codec-pipeline:</p><ul style={style.list}>{algos}</ul> </div> |
コンポーネントの削除処理
1つ1つの変換処理の左側に表示される[x]ボタンをクリックするとコンポーネントを削除できますが、この部分の実装は親のコンポーネントから子のコンポーネントへ削除処理をpropsとして渡すことで実現しています。子の[x]ボタンクリック時に _onDelete が呼び出され、その中で親の onDelete がさらに呼び出されて、子が削除されるという流れです。
この程度の規模であれば管理できますが、これ以上に親子の階層が深くなったりコンポーネント間の関係が複雑になってくると管理しきれなくなるため、Flux等の発生したイベントを統一的にディスパッチするアーキテクチャを使っていくのがReactの流儀のようです。
ソースコードの変換について
GitHubのリポジトリの説明にも書いているように、browserify等の各種ツールはnpm installで導入し、グローバルではなくローカルにインストールします。この状態だと、コマンドラインでbrowserifyと打っただけではのコマンド実行できませんが、package.jsonに定義したスクリプトをnpmのrunコマンドで実行することで、ローカルのnode_moduleディレクトリ内のパッケージを実行できます。
このアプリについてはapp.jsxをbundle.jsに変換しますが、
1 2 |
npm run build |
と実行することで、node_moduleディレクトリ内も有効な状態で
1 2 |
browserify app.jsx -t babelify | uglifyjs -o bundle.js |
と実行され、グローバル環境へツールをインストールすることなく各種ツール使った変換を実行できます。
その他
画面操作のgifアニメ化にはLICEcapというソフトを使いました。特に難しい設定もなく、シンプルで使いやすくてオススメです。
参考
Helo React.js
React.jsでPropやStateを使ってComponent間のやりとりをする
Babel.jsイントロダクション