最高のツイッタークライアントを求めて

最高のツイッター

クライアントを求めて
石井遼司
@airtoxin
2015年2月3日 ピコもん株式会社 社内勉強会
@airtoxin
• 石井遼司
•  
• 大学ではプログラムを書いて遺伝子の探索をしていた
• ボードゲームとかよくする
• 曲もほんのちょっと作ったり
最高のツイッター

クライアントを求めて
経緯
• 夜フクロウが最高だったけどもうずっとサポートされていない
• webは意外と使いやすいけど、結局アプリじゃないのが面倒
• 公式クライアントはヌルヌル動いていい感じと見せかけて、

よく引っかかるしめっちゃ落ちる
• echofon (見た目が
• mikutter (名前が
• tweetbot (値段が
じゃあ作れば良いのでは?
ツイッタークライアント
最高のツイッタークライアントを求めて
を作った
ツイッタークライアント(仮)
今できること
• アイコン、相対時間付きでTLが流れる
• ツイートの投稿
• ツイートのお気に入り登録
• エラーがあったらなんか出てくる
• マルチメディアツイートの表示
• 画像クリックでプレビュー
ツイッタークライアント(仮)
今できないこと
• TL以外のタブ機能(Reply、DM、List…)
• リプライ、リツイートボタン
• ✨👮👊
• 画像投稿
• ユーザーホームの観覧
• Lorem Ipsum
最高のツイッタークライアントを求めて
技術的なところ
アプリケーション

ビルド
nw.js
nw.js
• node.js + HTML + CSS + JSで

デスクトップアプリが作れる
• 旧 node-webkit
• node.jsのフォーク、io.jsに移行した際に名称を
変更
• Windows / Mac / Linuxいずれもサポート
nw.js
• node.jsのapiもブラウザコンテキストのライブ
ラリもどっちも使える
• 例えばnodeのfsモジュールでディレクトリ構成
を取得し、グラフィカルに表示するなども
• つい最近、ウィンドウの透過をサポート
nw.jsでビルド
• package.jsonに main としてエントリーポイン
トのHTMLパスを書く
• package.jsonがあるディレクトリを指定してビ
ルドするとアプリ化される
• 手動ならnuwk!が手軽
最高のツイッタークライアントを求めて
こんなnw.jsは嫌だ
こんなnw.jsは嫌だ
• browserコンテキストとnodeコンテキストの混在で混乱が起きる
• ショートカットにESCとかEnterが登録できない
• Atom-Shellと覇権争い
• windowオブジェクトの管理が大変
• アプリを作っているとviewのイベントに応じてトリガー引いて
処理を…という形になり、結局RESTfullなSPAっぽくなる
JSフレームワーク
(ライブラリ)
React.js
React.js
• ブラウザーサイドのjsライブラリ(フレームワーク)
• Facebook謹製
• 最近なんか流行ってるっぽい
• ViewModelなのでデータバインドが楽ちん
• 仮想DOMの差分レンダリングで超高速レンダリング
• コンポーネント指向で再利用可能なパーツ
var	
  Tweet	
  =	
  React.createClass(	
  {	
  
	
  	
  	
  	
  propTypes:	
  {	
  
	
  	
  	
  	
  	
  	
  	
  	
  tweetPayload:	
  React.PropTypes.object	
  //	
  tweet	
  object	
  https://dev.twitter.com/overview/api/tweets	
  
	
  	
  	
  	
  },	
  
	
  	
  	
  	
  getInitialState:	
  function	
  ()	
  {	
  
	
  	
  	
  	
  	
  	
  	
  	
  return	
  {	
  relativeTime:	
  ‘1s'	
  };	
  
	
  	
  	
  	
  },	
  
	
  	
  	
  	
  componentDidMount:	
  function	
  ()	
  {	
  
	
  	
  	
  	
  	
  	
  	
  	
  var	
  self	
  =	
  this;	
  
	
  	
  	
  	
  	
  	
  	
  	
  setInterval(	
  function	
  ()	
  {	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  self.setState(	
  {	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  relativeTime:	
  self.getRelativeTime(	
  self.props.tweetPayload.created_at	
  )	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  }	
  );	
  
	
  	
  	
  	
  	
  	
  	
  	
  },	
  3000	
  );	
  
	
  	
  	
  	
  },	
  
	
  	
  	
  	
  render:	
  function	
  ()	
  {	
  
	
  	
  	
  	
  	
  	
  	
  	
  var	
  payload	
  =	
  this.props.tweetPayload;	
  
	
  	
  	
  	
  	
  	
  	
  	
  var	
  medias	
  =	
  [];	
  
	
  	
  	
  	
  	
  	
  	
  	
  if	
  (	
  this.props.tweetPayload.extended_entities	
  &&	
  this.props.tweetPayload.extended_entities.media	
  &&	
  
this.props.tweetPayload.extended_entities.media.length	
  >	
  0	
  )	
  {	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  medias	
  =	
  this.props.tweetPayload.extended_entities.media.map(	
  function	
  (	
  media	
  )	
  {	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  return	
  <Media	
  mediaPayload={	
  media	
  }	
  key={	
  media.id_str	
  }	
  />	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  }	
  );	
  
	
  	
  	
  	
  	
  	
  	
  	
  }	
  
	
  	
  	
  	
  	
  	
  	
  	
  return	
  (	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  <div	
  className="tweet	
  row">	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  <div	
  className="col-­‐sm-­‐2	
  text-­‐center">	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  <img	
  className="profile-­‐icon"	
  src={	
  payload.user.profile_image_url_https.replace(	
  '_normal',	
  ''	
  )	
  }	
  />	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  </div>	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  <div	
  className="col-­‐sm-­‐9">	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  <p>	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  <span	
  className="user-­‐name">{	
  payload.user.name	
  }</span>	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  <span	
  className="user-­‐id">@{	
  payload.user.screen_name	
  }</span>	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  </p>	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  <div	
  className="text"><p>{	
  payload.text	
  }</p></div>	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  <div	
  className="row">{	
  medias	
  }</div>	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  </div>	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  <div	
  className="col-­‐sm-­‐1">	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  <div	
  className="post-­‐time	
  btn	
  disabled">{	
  this.state.relativeTime	
  }</div>	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  <div	
  className="action-­‐icons	
  btn-­‐group-­‐vertical">	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  <button	
  className="action-­‐icon	
  btn	
  btn-­‐default"	
  type="button"><i	
  className="fa	
  fa-­‐reply"></i></button>	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  <button	
  className="action-­‐icon	
  btn	
  btn-­‐default"	
  type="button"><i	
  className="fa	
  fa-­‐retweet"></i></button>	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  <Favorite	
  id={	
  payload.id_str	
  }	
  favorited={	
  payload.favorited	
  }	
  />	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  </div>	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  </div>	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  </div>	
  
	
  	
  	
  	
  	
  	
  	
  	
  );	
  
	
  	
  	
  	
  },	
  
	
  	
  	
  	
  getRelativeTime:	
  function	
  (	
  targetDate	
  )	
  {	
  /*	
  do	
  something	
  */	
  }	
  
}	
  );
StateとProps
• Stateはコンポーネントの状態を表す変数

(読み書き可)

Model的
• Propsはコンポーネントの外部から受け取った変数

(読み取りのみ可)

Interface的
• stateはsetState()で書き込みを行う
• stateが変更されるとrenderが自動的に走る
よく使うメソッド等
• propTypes

コンポーネントが公開しているPropsのインターフェースを記述

開発者に向けたバリデーション
• getInitialState()

Stateの初期値を記述
• componentDidMount()

コンポーネントがDOMに追加された後に呼ばれる

DOMに関する初期化処理

ajaxでデータを取ってきてsetState()など
• render()

単一の仮想DOMを返す

{}で囲むと評価された値が挿入される

同じコンポーネントが複数ある場合はkeyを指定しなければならない
シンプルで分かりやすい
こんなReact.jsは嫌だ
こんなReact.jsは嫌だ
• jsxのトランスパイル
• jsにテンプレートが内包されている
• className ?
• CSS当てにくい
nw.js + React.js
ビルド自動化
• Grunt / gulpなどのタスクランナーで自動化
• jsxの変換や必要モジュールのロードなどを行ったものを
compileディレクトリに出力
• compileディレクトリを対象にnw.jsのアプリをbuild
!
!
var	
  gulp	
  =	
  require(	
  'gulp'	
  );	
  
var	
  NwBuilder	
  =	
  require(	
  'node-­‐webkit-­‐builder'	
  );	
  
!
gulp.task(	
  'nw',	
  function	
  ()	
  {	
  
	
  	
  	
  	
  var	
  nw	
  =	
  new	
  NwBuilder(	
  {	
  
	
  	
  	
  	
  	
  	
  	
  	
  files:	
  './compile/**/*',	
  
	
  	
  	
  	
  	
  	
  	
  	
  platforms:	
  [	
  'osx64'	
  ],	
  
	
  	
  	
  	
  	
  	
  	
  	
  version:	
  (	
  process.env.NODE_ENV	
  ===	
  'production'	
  )	
  ?	
  'latest'	
  :	
  'v0.10.5'	
  
	
  	
  	
  	
  }	
  );	
  
	
  	
  	
  	
  return	
  nw.build();	
  
}	
  );
ディレクトリ構成
browserifyとrequire
• 一連のjsコードをbrowserifyしてapp.jsとして出力
• ルートのindex.htmlでscriptタグで読み込む事で

全てnodeコンテキストで書ける様になる
• browserifyがrequireを書き換える事に注意

browserifyしても使えないfsなどのモジュールは
requireの代わりにwindow.requireを使う必要がある
アプリケーションの公開
• ビルドされたアプリはソースコードが丸見え
• oauthのコンシューマーキーなどを隠す必要がある
• nwsnapshotを使ってsnapshot.binを出力
• package.jsonで snapshot : snapshot.bin
• htmlの読み込み後にsnapshotが評価される
絶対皆で最高の

クライアントを作ろうな!
https://github.com/airtoxin/
twitter-client
最高のツイッタークライアントを求めて
1 of 37

More Related Content

Similar to 最高のツイッタークライアントを求めて

TwitterToDayOneTwitterToDayOne
TwitterToDayOneAlex Onsager
489 views28 slides
Road to UI LibraryRoad to UI Library
Road to UI LibraryManato KAMEYA
2.6K views50 slides
Jqm20120210Jqm20120210
Jqm20120210cmtomoda
1.2K views55 slides
jQuery勉強会#2jQuery勉強会#2
jQuery勉強会#2Ryo Maruyama
426 views42 slides

Similar to 最高のツイッタークライアントを求めて(20)

TwitterToDayOneTwitterToDayOne
TwitterToDayOne
Alex Onsager489 views
Road to UI LibraryRoad to UI Library
Road to UI Library
Manato KAMEYA2.6K views
Jqm20120210Jqm20120210
Jqm20120210
cmtomoda1.2K views
jQuery勉強会#2jQuery勉強会#2
jQuery勉強会#2
Ryo Maruyama426 views
Teclab3Teclab3
Teclab3
Eikichi Yamaguchi1.4K views
jQuery Mobileの基礎jQuery Mobileの基礎
jQuery Mobileの基礎
Takashi Okamoto3.1K views
2006112520061125
20061125
小野 修司977 views
Twitter連携chrome extension作り方Twitter連携chrome extension作り方
Twitter連携chrome extension作り方
Hiroshi Oyamada2.9K views
20120118 titanium20120118 titanium
20120118 titanium
Hiroshi Oyamada1.9K views
Sails workshop1Sails workshop1
Sails workshop1
Tomokatsu Iguchi1.4K views
jQuery勉強会#3jQuery勉強会#3
jQuery勉強会#3
Ryo Maruyama608 views
Reflexの紹介Reflexの紹介
Reflexの紹介
Rie Nakau1.3K views

Recently uploaded(8)

最高のツイッタークライアントを求めて