Hatena::ブログ(Diary)

nazonoDiary

2015-05-18 月

angularJS と jQuery に関する誤解を解く

00:28 |  angularJS と jQuery に関する誤解を解くを含むブックマーク  angularJS と jQuery に関する誤解を解くのブックマークコメント

最近 angularJS に対する期待の低下が著しくてつらい。

なんだかんだで SPA から jQuery に戻った話 - ボクココ

Angularの問題では

はてなブックマーク - mizchi のブックマーク - 2015年5月18日

みんな使いどころを間違ってるんや。1年半くらい使ってて不満もあるけど自分のよく使う範囲では angularJS 最強だと思う。


angularJS が向いてるのは Single Page Application ではない

angularJS が向いてるのは

フォームのような細かい部品を多用 & DOMツリーとデータスコープがほぼ一致していてユーザの入力をサーバに送ったりする webアプリ。管理画面、マイページ、業務アプリなど
Single Page Application ← 簡単に作れるけどページ間の連携が必要ないならサーバ側で分けてしまった方がよい。
× SEO対策が必要なページ。SEOが大事な webサービスのフロント側とか
× ゲームのようにDOMツリーとデータスコープがあんまり関係ないもの、変化するデータが多くてfps単位でのスピードが求められるもの

とくに 管理画面にありがちな、一覧(並び替え/絞り込み/表示形式変更)・編集・追加・削除 という一連の画面の作りやすさはすごい。下手するとほとんどjavascript書かずに実装できる*1

そして「javascriptアプリケーションのサイズ」はなるべく小さい単位にとどめておくことをお薦めする。例えば「管理画面のログインからシングルページアプリケーション」というのは避けた方がよくて「ログインはjavascriptなしのフォーム」その後メニューから「商品管理画面に遷移したらそこで一つのアプリ」のように普通のページ遷移+別のmainコントローラーという構成が、扱いよい。可能なら「一覧」「詳細編集」で別にするともっと取り回しが効く。

複数のページを管理する、ということは、状態を示す変数が大きく複雑になるということだ。状態が複雑になると、実装はさらに指数関数的に複雑になり、速度も遅くなるしテストも大変になる。できるなら小さくした方が影響範囲も狭くなって改修しやすい。メモリリークなどが起こっても影響が少なくてすむし、別ユーザの操作やバッチ処理などからおこる、サーバのデータとのコンフリクトの可能性も減る。

特にパフォーマンスが求められない限り、シングルページアプリケーションよりも、ページ遷移させてそこをリッチにした方が、いろいろ初期化されるので開発も楽で、ユーザ体験も自然なことが多い。

そして angularJS で作っておくと「「一覧」「詳細編集」で別にしてたけどいろいろ仕様追加があって一つにまとめたほうが便利」的な流れでも生き残れたりする。

(追記:5/20)「angularJS はシングルページアプリケーションに向いてない」という事ではなくて「シングルページアプリケーションっ ていろいろ難しいから、避けられるなら避けたほうが開発が楽」という話です(/追記)


angularJS と jQuery は同時に使ってよい

angularJS と jQuery はまさにライブラリとフレームワークの関係で、angularJS のDOM操作部分=ディレクティブの中で、jQuery を動かすようにすればよい。いろいろな解説で「jQuery使っちゃダメ」みたいな事が書いてあるが、「コントローラーからDOMをセレクタ指定でイベント仕込むようなやり方で jQuery は使っちゃダメ」というのが正しい。なぜなら「DOMの生成破棄のタイミングがコントローラーの実行タイミングと違うから」だ。

サービスではエレメント指定しないなら使っていいし、ディレクティブでは「そのディレクティブ内に影響範囲をとどめる」という注意を持っていれば積極的に使ってもよい(scope.$apply などとの連携方法の学習が必要になるが)。

jQuery プラグインにはいろいろあって、angularJS と組み合わせたときの相性・指針は

HTML/DOM/イベントに関係ないライブラリ jQuery.md5 とか → どこでも(コントローラー内でさえ)積極的に使ってよい。まあそれjQueryである必要ないよね…
外部サービスとの通信ライブラリ $.ajax とか  → サービスにしよう。そして受信が完了したら promise でコールバックして、 $rootScope.$apply() を呼ぶ
$(elm).hoge() すると elm にエフェクトがかかるもの(text-shadow とか)  → 簡単にディレクティブ化できるのでそうやって使いましょう。
$(elm).hoge() すると elm の内部のdomを変更してなんかする系(スクロールバーをおしゃれにとか) → まあだいたいディレクティブ化できるのでそうやって使いましょう。一部 dom を壊して angularJS が設定する dom 情報とを破壊するものがあるかもしれないのでそれだけ注意
$(elm).hoge() すると elm の内部にデータに沿ったパーツが表示される系(カレンダー表示とかグラフ系とか) → 簡単にディレクティブ化できるのでそうやって使いましょう。フォームの入力パーツなら ngModel とかと連携しないといけなくてその辺がちょっと複雑だけど少し頑張ればできる
テンプレートエンジン → ディレクティブ化できるけど完全にangularJS化した方がいいよね

という感じだ*2。他の jQuery 以外のライブラリでもだいたい同じ感じで連携できる。


jQuery を angularJS のディレクティブに閉じ込めるのは難しくない

angularJS の中でもディレクティブの仕様は複雑怪奇であるが、「jQueryを閉じ込める」という目的ならそんなに難しい使い方は必要ない。

例えば

<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.js"></script>

<button class="popup" data-text="hoge1">1</button>
<button class="popup" data-text="hoge2">2</button>
<div id="alert" style="display:hidden"></div>

<script>
$(function(){
  /**
   * popup クラスのボタンはポップアップ。クリックするとアラートdiv(#alert)を表示する。表示内容は ボタンの data-text の内容。
   */
  $("button.popup").click(function(){
    $("#alert").hide().text($(this).data("text")).fadeIn();
  });
});
</script>

というのがあるとする*3。これなら

<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularJS/1.2.16/angular.js"></script>

<div ng-app="app">
<button popup data-text="hoge1">1</button>
<button popup data-text="hoge2">2</button>
<div id="alert" style="display:hidden"></div>
</div>

<script>
angular.module('app')
/**
 * popup ディレクティブはポップアップ。クリックするとアラートdiv(#alert)を表示する。表示内容は ボタンの data-text の内容。
 */
.directive('popupDirective',function(){
  return {
    link:function(scope, elm, attrs){
      elm.click(function(){
        $("#alert").hide().text(elm.data("text")).fadeIn();
      });
    }
  };
});
</script>

と、とりあえず使えるのはすぐに作れる。 #alert が、あまりに気になる(部品のスクリプトに全体の特殊事情が書かれている!)からまあ

<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularJS/1.2.16/angular.js"></script>

<div ng-app="app">
<button popup popup-target="#alert" data-text="hoge1">1</button>
<button popup popup-target="#alert" data-text="hoge2">2</button>
<div id="alert" style="display:hidden"></div>
</div>

<script>
angular.module('app',[])
/**
 * popup ディレクティブはポップアップ。クリックするとアラートを表示する。
 * 表示内容は ボタンの data-text の内容。
 * popup-target 属性にアラートボックスのセレクタを指定する(例: popup-target="#alert" )。
 */
.directive('popup',function(){
  return {
    link:function(scope, elm, attrs){
      elm.click(function(){
        $(attrs.popupTarget).hide().text(elm.data("text")).fadeIn();
      });
    }
  };
});
</script>

こうするともうちょっと汎用的になる(元のjQueryのやつより汎用的になった!)。だいたいの jQuery プログラムは、こんな感じで自分で中途半端にラップして使うと、jQuery の影響範囲を封じ込めることができる。*4

はまりがちなのは

  • 独自のタイミングで scope の値を更新したけど表示が更新されない
    • scope.hoge=huge *5 みたいに更新した後に、 scope.$apply を呼ばないと更新されません
    • 間にあるディレクティブによって変なところで子スコープが作成されてて子スコープに値を追加しているかも → scope.hoge じゃなくてscope.hoge.huge のようにオブジェクトを介して更新するんや……(バッドノウハウ)
  • jQuery でやってたアニメーションを ng-animate でどうやったらいいか分からない
    • → ディレクティブ作って内部で jQuery を使う。もちろん ng-animate 使えれば使えばいいけど書き直すコストがかかるとかなら jQuery のままでもいい
  • 出し消ししたいエレメントを動的に生成しようとして躓く
    • jQuery の show() hide() とか ng-show ng-if でええんやで
  • jQuery 非依存」を謳う angularJS専用同機能モジュールを探したけど今までと書き方がだいぶ変わってつらい
    • → 概念/使い方が全然違って学習/書き換えコストが高いことが多いしネットで見つかるのは玉石混合でバギーだったりするし、最初の内は自分でディレクティブ作って jQuery プラグインを中途半端にラップして使ったほうが楽。目当ての jQuery プラグインがなくて angularJS モジュールを知ってるなら angularJS 専用モジュールの使い方を覚える、でいいけど、すでに使い慣れた jQuery プラグインがあるなら自分の使い方に合わせてラップする。その参考に既存のモジュールを探すと、ためになる(オレオレラッパーにいろいろ機能・汎用性を足していくと最終的に「なるほど!このモジュールすごい考えられてたんだ!」となることが多い)
  • scope.$on() scope.$emit などで独自イベントを発行したりするイベント駆動
    • → イベントがグローバル変数になりがち。イベントリスナ側は scope.$watch などで変数監視、 イベント発行側は scope の変数を変更して scope.$apply。 監視する変数名を attrs で指定できるようにしたり scope.$eval や $parse などで式を実行・代入できるようになると汎用性が上がる。非同期結果取得の場合は $q ( promise ) を使ったコールバック

既存の jQuery が idやセレクタ指定つかいまくりの場合は移行が厳しいが、むしろ、この状態を制限したい(idやセレクタグローバル変数的になりがちなので回避したい)がための「ディレクティブ化」であるのでそこは積極的に変えていこう。


初期コストが増えてもメリットがある場合にフレームワークを導入する

jQuery 関係をディレクティブ・スコープに切り出すメリットとしては「angujarJS の他の機能が使える」の他に

  • view の変更とデータの変更を切り離せる
    • テストが容易になる
    • 汎用性が上がる
    • メンテナンス性が上がる
  • view の変更の影響範囲を狭めやすい
    • 再利用性が上がる

代わりに

  • 使い回ししない部分でもディレクティブ化を求められる
  • 覚えないといけない規則が多くなる

世の中のweb開発にはこれらのコストを見ない方が現実にあっている場合も多くて、例えば広告・短期イベントページなど改修・長期メンテナンスが想定されなかったり、状態が少なく「全部グローバル変数でも何とかなる」程度の複雑さのページだ。そのばあいはjQuery の方が合っているだろう。

これは「モジュール化すべきか?」「フレームワークを導入するべきか?」「マイクロサービスに分割すべきか?」みたいな問題で、PHPでも単ファイルにべた書きしたり共通includeした方がよい規模がある。rubyなどはデコードやエラー処理、cgi設定など諸々している内に「ならrails/sinatraで」となりがちだが PHP なら 3ページくらいで他と切り分けられるなら素のPHPの方が楽で早い、みたいな話だ。


まとめ

  • angularJS が向いてるのは Single Page Application ではない
  • angularJS と jQuery は同時に使ってよい
  • jQuery を angularJS のディレクティブに閉じ込めるのは難しくない
  • 初期コストが増えてもメリットがある場合にフレームワークを導入する
  • angularJS 最強なので使いましょう。

*1:まあこれについて「それjavascriptなくてもできるよ」というのは同意で、それで十分ならjavascript使わない方がいいと思います

*2:俺調べ。もうちょっと練ったらわかりやすい指針が出せるのかも。誰か……

*3:この程度だとわざわざ作らなくても既存のディレクティブで実装できるが、まあ jQuery 使う例

*4:本当はもう一段階進めて これくらい まですると、さらに angularJS っぽくて、なるほど ng-click ng-show ってそうなってるのか、みたいなのが見えてくると思う

*5:あるいは scope.$eval や $parse(exp)(scope).assign(val) など

2015-04-17 金

windos の msys git の bash で peco

| 11:19 | windos の msys git の bash で pecoを含むブックマーク windos の msys git の bash で pecoのブックマークコメント

peco は補完候補を gui で絞り込みできるコマンドラインツール。chocolatey でインストールできる。

choco install peco

インストールした後に git 用 bash から実行するとなんかエラーが出て動かないが、cmd 経由だと使えるっぽいので ~/.bashrc に

alias peco='cmd /C,peco'

としておくと、普通に peco 使える。

あとはこのへんの関数定義すると便利っぽい

http://qiita.com/sona-tar/items/fe401c597e8e51d4e243

トラックバック - http://d.hatena.ne.jp/nazoking/20150417

2015-04-16 木

[bash]githubの特定のプルリクエストをfetchできるようにしたい

18:47 |  [bash]githubの特定のプルリクエストをfetchできるようにしたいを含むブックマーク  [bash]githubの特定のプルリクエストをfetchできるようにしたいのブックマークコメント

# git で github の特定のプルリクエストをfetchできるようにする
function git-add-pull-request(){
  local PR=$1
  local REMOTE=$2
  REMOTE=${REMOTE:-origin}
  if [ -z "$PR" ]; then
    echo git-add-pull-request [remote] pull-request-id
    return
  fi
  # remote が数字なら多分remoteとプルリクエストIDが逆
  if expr "$REMOTE" : '[0-9]*' > /dev/null ; then
    PR=$2
    REMOTE=$1
  fi
  git config --add remote.${REMOTE}.fetch +refs/pull/${PR}/*:refs/remotes/${REMOTE}/pr/${PR}/*
}

消すときは .git/config を手で編集

トラックバック - http://d.hatena.ne.jp/nazoking/20150416

2014-12-11 木

jgit-chef で s3 に置いたリポジトリからデプロイする

| 00:54 |  jgit-chef で s3 に置いたリポジトリからデプロイするを含むブックマーク  jgit-chef で s3 に置いたリポジトリからデプロイするのブックマークコメント

この投稿は Chef Advent Calendar 2014 の 11日目の記事です。

社内に git サーバがあったりしてデプロイするときに困っていたりする全国の皆様こんにちは。

aws を使っているなら jgit を使えば s3 にリポジトリを置いて更新したりできます。

で、chef でリポジトリをチェックアウトする際に使える jgit-chef を作成しました。

https://github.com/team-lab/jgit-chef

基本は chef の git リソース と同じです。

コミットフックか jenkins に

source ~/.aws_env.sh # 環境変数に s3 の書き込み権限を得る
if [ $(git config --get remote.s3.url|wc -l) == 0 ];then
  git remote add s3 amazon-s3://ENV@your-s3-bucket/moromoro.git/
fi

/usr/bin/jgit push s3 "refs/heads/*:refs/heads/*"

のようにjobを設定すると s3 にリポジトリを作成、push してくれるので、 Berkshelf とかで jgit-chef を使えるようにして、

cookbook 'jgit', :git => "https://github.com/team-lab/jgit-chef.git"

metadata.rb のdepends に追加し

depends "jgit"

次のようなレシピを作ると

package "git" # ami-linuxは最初はgitがはいってなくてchefのgitリソースがこける・・
package "java-1.7.0-openjdk" # jgit-chef::install はjavaをインストールしないので自分で入れる
include_recipe "jgit::install" # github から /usr/bin/jgit にインストール

jgit "/opt/moromoro" do
  repository "amazon-s3://IAM@your-s3-bucket/moromoro.git" # こちらの認証はIAM-ROLEでインスタンからs3のリポジトリを参照できるようにしておく
  action :sync
  revision "master"
end

/opt/moromoro に社内リポジトリのmasterをチェックアウトしてくれます。

ご利用ください。

トラックバック - http://d.hatena.ne.jp/nazoking/20141211

2014-12-10 水

jgit で s3 にプライベートリポジトリ(IAM編)

| 01:01 |  jgit で s3 にプライベートリポジトリ(IAM編)を含むブックマーク  jgit で s3 にプライベートリポジトリ(IAM編)のブックマークコメント

この投稿は Git Advent Calendar 2014の 10日目の記事です。

昨日は kyanro@github さんの githubとgoogleを利用して世界征服の意図を調べる でした。

明日は @a-suenami さんです。

社内に git サーバがあったりしてデプロイするときに git pull できずに rsync を使っていたりする全国の皆様こんにちは。rsyncだとデプロイ先で緊急修正したときや試行錯誤したあとにgitリポジトリに書き戻すのが大変だったりしませんか? EC2 でリポジトリサーバたてられればいいけど EC2 のインスタンスはそれだけに使うにはちょっと高い。せめて s3 にリポジトリがおけたら……

できます。 git ではなく jgit です。git を java で実装した jgit というプロジェクトがあります*1

その中に jgit.sh という linux のシェルをくっつけて単体で実行できるようにしたファイルが有り、こいつを wget して chmod すれば*2 ./jgit.sh とコマンドラインから叩くだけで jgit を動かすことができます。

で、jgit.sh の何が嬉しいかというと AWS の S3 にリポジトリが置けることです。AWS CodeCommit を待たなくてもS3の容量価格だけでプライベートリポジトリをもてる!

詳しくは

http://www.fancybeans.com/blog/2012/08/24/how-to-use-s3-as-a-private-git-repository/

http://d.hatena.ne.jp/winebarrel/20120425/p1

jgit.sh の実装はいろいろと中途半端なのですが*3、データベースファイルは普通のgitと同じなので、 clone push fetch の時だけjgit使うようにするのもありです。

が、素の jgit だと AWS の Instance profile credential いわゆる IAM-ROLE が使えないので、使えるようにしました。副作用で STS の token も使えるようになっています。

https://github.com/team-lab/jgit/wiki

s3 の権限を ROLE で割り当てた EC2 から

wget https://github.com/team-lab/jgit/releases/download/v3.6.0.201411121045-token4-env4/jgit -O jgit
chmod oug+x jgit
git remote add s3 amazon-s3://IAM@backet-name/repo/dir.git
./jgit push s3

のようにすると s3 の backet-name バケットに push できます(jgitだと認証情報ファイル名を指定する URL のユーザ名部分に、キーワード 'IAM' を指定してください)。

ご利用ください。

とはいえ S3 はファイルロックができないので競合するときっと大変なことになると思います。s3 に push するのは管理サーバや社内 jenkins からだけにして ec2 では fetch のみがよいかと思います。EC2 側には書き込み権限をおかずに、インスタンス上で直接編集したらそれを直接社内 git サーバに push したい場合、ポートフォワードを設定しておくとできます。

# 社内http-gitサーバ git-server.syanai.local の 80 番をec2 (syagai.ec2.example.com)に持っていく 
ssh -R 10080:git-server.syanai.local:80 ec2-user@syagai.ec2.example.com
cd /opt/dir.git
# push の際のリモートを社内(=ポートフォワードのポート)に向ける
git remote set-url --push origin http://localhost:10080/git/dir.git
git commit
git push 

じゃあはじめからポートフォワードで git fetch すればよいのかというとそうでもなくてオートスケールなどしたときに困ることになるので s3 においておくのがいいんじゃないでしょうか。

OpsWorks など他のgit連携サービスから s3 においたリポジトリにアクセスさせるには、s3 の該当バケットを http website として公開して、 http プロトコルのgitリポジトリとして指定してください。

明日のこのブログでは chef から s3 のリポジトリを fetch するレシピを紹介します*4

*1:それのeclipseプラグインが EGIT

*2:あとjavaがインストールされていれば

*3:たとえば pull ができないので fetch merge する必要がある

*4:Git Advent Calendar ではなく Chef Advent Calendar になります……