node-webkit を使ってみる 3 - 簡易ファイラー
node-webkit を使ってみるシリーズその 3。前回の最後に予告した簡易ファイラーのサンプル作成で得られた知見を記録しておく。サンプルに実装した機能は以下。
- フォルダのツリー表示 (ルートは USER HOME)
- ファイル・フォルダ情報の表示
- ファイルのダブル クリックで関連付いているアプリを起動
削除やリネームのようにファイル自体を変更するものは割愛。この仕様ならファイラーではなくビューアーのほうが妥当かも。
- 2015/1/12 23:22 追記
- React.js 自体の読み込みとプロジェクト構成の説明を書き忘れていたので追記
node-webkit-builder
シリーズ 1、2 で node-webkit アプリのプロジェクト作成とビルドに Nuwk! を紹介したが、その後 Web フロント エンド開発で gulp を利用するようになってから node-webkit アプリのビルドもこれで管理したくなった。
そこで node-webkit 公式の How to package and distribute your apps にも紹介されている node-webkit-builder (長いので以降は nwb と呼ぶ) を採用。これは npm で配布されており gulp タスクから利用できる。
今回のサンプルだと以下のようにビルドしている。
var gulp = require( 'gulp' );
var $ = require( 'gulp-load-plugins' )();
/**
* node-webkit イメージを生成します。
*
* @return {Object} gulp ストリーム。
*/
gulp.task( 'release', [ 'copy' ], function () {
var builder = require( 'node-webkit-builder' );
var nw = new builder( {
version: '0.11.5',
files: [ 'release/src/**' ],
buildDir: 'release/bin',
cacheDir: 'release/nw',
platforms: [ 'osx' ]
});
nw.on( 'log', function( message ) {
$.util.log( 'node-webkit-builder', message );
} );
return nw.build().catch( function( err ) {
$.util.log( 'node-webkit-builder', err );
} );
} );
nwb の version
オプション指定により対応する node-webit 本体をダウンロード & キャッシュしてくれる。version
や platforms
を変更してもビルド毎にキャッシュ済みか否かを判定。キャッシュされていなければ不足分をダウンロードしてくれるという親切設計。
Nuwk! は現時点で OS X のみ対応となっているが nwb なら node-webkit 本体のサポート環境すべてを対象にできる。一部機能 (ico ファイルの利用に Windows もしくは Wine が必要) をのぞきビルドもクロス プラットフォームで実行できる。
ただし Issue #147 で報告されている現象 (処理は正常なのにエラーが表示される) とか、私の環境だと macIcns を指定した時にファイルのコピーでエラーが起きるなど心配な点もある。とはいえ非常に便利。現時点で node-webkit アプリをビルドするなら最良の選択肢ではなかろうか。
React.js
UI 部分は React.js で実装した。フォルダ ツリーは この間の記事で実装したものを踏襲。コンポーネント関連は JSX で実装。コンパイルは reactify 任せ。
コンポーネント構成は以下のようになる。
- Explorer
- FolderTree
- FolderDetail
Explorer 内にフォルダ ツリーとフォルダ詳細コンポーネントが入れ子になっている。もしコンポーネント間でやりとりする場合は Explorer を経由する設計。現在はフォルダ ツリーのクリック時に選択フォルダの変更とファイル、フォルダ情報の再描画を実施している。実装の詳細については記事の末尾にリンクしたサンプル プロジェクトを参照のこと。
残念ながらこのサンプルにはユニット テストがない。React.js の場合 Jest がよく利用されているようなので別途調査したい。もしかすると次項に書く require
の問題がユニット テストに影響するかもしれない。
余談だが今回のサンプルではコンポーネント定義をモジュール外に公開するとき
var Component = React.createClass( {
} );
module.exports = Component;
のようにしているが、これは
module.exports = React.createClass( {
} );
にしたほうが簡潔でよいのかも。require
する側で Reacr.render
する設計なら元側で名前をつける意義は薄い。
Browserify と require
node-webkit の require
は node.js 標準ライブラリとプロジェクト内の node_modules に配置したモジュールを読み込んでくれる。
では自作スクリプトは?というと zcbenz/nw-sample-apps の file-explorer みたいに node_modules 直下に置くとか javascript - node-webkit loading module fails - Stack Overflow のようにプロセス実行時のパスと結合してフルパス指定するようだ。
var path = require( 'path' );
var mymod = require( path.join( process.cwd(),'js/mymodule.js' ) );
前者はモジュール衝突とか思わぬ事故を招きそうだし基本的に開発環境で npm install
をトリガーとして動的生成するものだから .gitignore
でリポジトリからも除外しているだろう。よってここに自作スクリプトは置きたくない。後者は記述が長くて面倒。もしこれを採用するとしてら自作スクリプト用 require
として myrequire
みたいなユーテリティ関数を用意してグローバルに読み込んでおくとかしたい。
require
を利用しない場合、HTML の script タグを利用するのだけどスクリプトの増減や順番管理が面倒である。スクリプト間に依存をスクリプト外に定義すると見通しも悪くなる。というわけで require
のために Web フロントエンド開発で利用している Browserify を採用してみた。JSX のコンパイルに reactify も利用できるしこれはよさそうだと思ったのだが、普通に使うと node-webkit の require
と競合する。
そのため node-webkitでもbrowserify使いたいしnodeのrequireも使いたい - Qiita で解説されているようにグローバルとビルトインの require 書き変えを無効化しなければならない。これで npm の require
は OK なのだが今度は node-webkit モジュールの読み込みが失敗する。例えば nw.gui
を require
したとき Error: Cannot find module 'nw.gui'
となる。
- 2015/1/21 補足 nw.js のモジュールを読み込むとき require ではなく window.require を利用すれば以下の処理が不要になる。この問題は node コンテキストから nw.js モジュールを参照できないことが原因である。次回の記事でこの辺の話を取り上げている。
結局、自作と node.js/node-webkit 系の require
を分けて呼ぶ必要がある。前述の Qiita エントリにも引用されてる Support for node-webkit Issue #481 substack/node-browserify にも解説されているとおり
- 自作スクリプトは
require
、node.js/node-webkit モジュールはnequire
で読むrequire
以外の名前ならnequire
でなくてもよいrequire
に近いスペルのほうが分かりやすく既存関数とも競合しにくいだろう
- Browserify 処理の際、
require
とnequire
を置換する- 自作スクリプト用の
require
をrequireClient
に置換 - node.js/node-webkit 用の
nequire
をrequire
に置換
- 自作スクリプト用の
- Browserify による結合実行
- Browserify による自作スクリプトの依存解決は
requireClient
で実施 - node.js/node-webkit 用の依存解決は
require
になる
- Browserify による自作スクリプトの依存解決は
これで本来の require
はそのままに自作スクリプトの依存解決を別関数によって代替できる。gulp タスクだと以下のようになる。
var gulp = require( 'gulp' );
var $ = require( 'gulp-load-plugins' )();
/**
* JavaScript と JSX ファイルをコンパイルした単一ファイルを開発フォルダに出力します。
*
* @return {Object} gulp ストリーム。
*/
gulp.task( 'js', function() {
var browserify = require( 'browserify' );
var source = require( 'vinyl-source-stream' );
var buffer = require( 'vinyl-buffer' );
return browserify(
'./src/js/main.js',
{
debug: true,
detectGlobals: false,
builtins: [],
transform: [ 'reactify' ]
}
)
.bundle()
.pipe( source( 'app.js' ) )
.pipe( buffer() )
.pipe( $.sourcemaps.init( { loadMaps: true } ) )
.pipe( $.replace( 'require', 'requireClient' ) )
.pipe( $.replace( 'nequire', 'require' ) )
.pipe( $.sourcemaps.write( './' ) )
.pipe( gulp.dest( 'src/js' ) );
} );
Source Maps と併用する場合、sourcemaps
の init
〜 write
間で置換を指定すること。
Browserify を利用しないなら process.cwd
を利用しつつ JSX コンパイルは gulp-react で実施する。この路線も検討したけど管理が面倒なので Browserify を選んだ。
Bower でインストールしたライブラリの参照
今回のサンプルでは React.js を Bower でインストールしたのだが、これは require
ではなく HTML 側の script
タグで読み込んでいる。
<!-- build:js js/lib.js -->
<script src="./bower_components/react/react.min.js"></script>
<!-- endbuild -->
Browserify を利用するのであれば debowerify もセットにして require
するところだが前述の require
競合をややこしくしそうなのでやめた。代わりに汎用ライブラリ系は script
タグでグローバルに読み gulp-useref で単一の lib.js 化するようにしている。
こうしたライブラリは多用されるので都度 require
するのが面倒というのもある。しかし依存明示や参照範囲の局所化などの観点から好ましくないので debowerify を利用しないにしても、gulp-replace による置換を導入した時点でクライアント require
化すべきだったかも。
あと今回の require
対策をすべて実行したならば debowerify を利用できたかもしれない。この辺、試行錯誤中。
プロジェクト構成
今回のプロジェクト構成 README.md などは除く) は以下のようになる。
/
├ package.json
├ gulpfile.js
├ node_modules/
│ └ ビルド用 node.js パッケージ
├ release/
│ ├ bin/
│ │ └ リリース用 node-webkit アプリ バイナリ
│ ├ nw/
│ │ └ リリース ビルド用 node-webkit 本体バイナリ
│ └ src/
│ └ リリース用 src
└ src/
├ index.html
├ css
│ └ スタイルシート
├ js
│ └ JavaScript、JSX とそのコンパイル結果の出力先
└ fonts
└ アイコン フォント
リリース用ビルドに関するものは release
に集約。これは gulp release
を実行した時に構築される。
開発リソースは /src
内へ格納。gulp js
で JavaScript & JSX コンパイルが実施され /src/js
内に app.js
が出力される。node-webkit に src
を読み込んだ状態でソース編集 & コンパイル、アプリのツール バーでリロードという流れで実装を進めていた。
node-webkit は How to run appsi の提案どおり .bash_profile
に nw
というエイリアスを設定している。よって Terminal から src を読み込ませる場合はプロジェクトのルートに cd
してから、
$ nw src
とするだけ。node-webkit にコマンドラインからリロードを送信できればもっと効率化できる (Web 開発でブラウザを更新するようなイメージ) のだけど可能なのだろうか?
サンプル
今回作成したサンプルを以下に公開する。
clone して README.md の Installation & Build に書かれた手順を実行するとアプリが生成される。