UZABASE Tech Blog

株式会社ユーザベースの技術チームブログです。 主に週次の持ち回りLTやセミナー・イベント情報について書きます。

NewsPicks の Chrome 拡張を作った話

f:id:uzabase:20141217181858p:plain

こんにちは。NewsPicks の開発を担当している文字(もんじ)です。本日 NewsPicks の Chrome 拡張をリリースしました。

NewsPicks の Google Chrome 拡張をリリースしました
NewsPicks での反応

幸いユーザーの皆様にもご好評頂いているようで嬉しいです。ということで今回は NewsPicks の Chrome 拡張を作った話をします。アジェンダは以下の通りです。 1.NewsPicks の Chrome 拡張が提供する機能について 2.なぜ Chrome 拡張を作ったのか? 3.どうやって Chrome 拡張を作ったのか? 4.まとめ

1. NewsPicks の Chrome 拡張が提供する機能について

Chrome 拡張が提供する機能については NewsPicks のブログをご覧下さい。主に 2 つの機能を提供しています。

現在開いているページを NewsPicks に Pick する機能

アドレスバー(Omnibox)を利用して NewsPicks 内の記事やコメントを検索する機能

2. なぜ Chrome 拡張を作ったのか?

NewsPicks は基本的にスマホファーストな方針で開発しており、Web 版は今年の夏にリリースされたばかりです。しかしヘビーユーザー ── 特に NewsPicks がターゲットとしているビジネスマン ── は、スマホ以外から NewsPicks を利用することも多いと考えられます。実際に 25 % のユーザーが Chrome から NewsPicks にアクセスしていますし、そもそも会社でスマホを使ってニュースを見ていたら印象が悪いでしょう。

また、過去記事を参照しながら長文のコメントを書いて下さるユーザーは、スマホではなく PC から書きたいと考えている方が多いとも感じていました。私自身も NewsPicks でコメントを書く場合は、スマホではなく PC を利用することが多いため、業務の隙間時間(ビルド時間)や休日の隙間時間に業務外で Chrome 拡張を開発することにしました。

3. どうやって Chrome 拡張を作ったのか?

さて本題の開発方法ですが、それほど特別なことはしていません。Chrome 拡張は必要なツールキットも揃っており、JavaScript と HTML で書けるため、実開発工数は 10-20 時間程度だと思います(気楽に作ることが出来るのが HTML + JavaScript の良いところですね)。ただ、私自身は Chrome 拡張を作ったことが無かったため、以下の順に調査をしました。

1.Google のドキュメントを読む 2.今回開発しようとしている拡張機能に似た Extension のソースコードを読む

  1. については、Google の API ドキュメントは良く整備されており、必要最低限の情報は十分に記載されていると感じました。ただ幾つか実際の使用例を見たかったので、2. のソースコードで知識を補完しました。ここでは具体的には以下を参考にしました。

pinput   ・octotree

前者は主に Pick 機能(ポップアップ)の実装について、後者はポップアップを常駐型のサイドバーにしようとしたときに読み込みました(Omnibox を操作する拡張のソースコードも幾つか読んだのですが、失念していまいました)。

ちなみにポップアップではなく常駐型のサイドバーにするのは途中で断念しました。理由は幾つかあるのですが、サイドバーを実装しようとすると content script もしくは executeScriptinsertCSS によって表示しているページにスクリプトと CSS をインジェクトする必要があるのですが、後者が表示しているページの CSS とコンフリクトするためです。今回は手間を省くために CSS Framework を利用したかったのと、すべてのウェブページに対応しようと考えていたため、コンフリクトを回避するのはコストが高くつきそうだなぁと判断して取りやめた次第です。また、その他の理由としては、後述する Yeoman の generator との相性が悪かったというのもあります。JS ファイル内で依存スクリプト / CSS をインジェクトするため、grunt-usemin を使った Yeoman のビルドフローに乗せるのが面倒でした。

さて、幾つか下準備をしたあと、具体的な開発に入りました。最初は自分でちまちまビルドスクリプトを書こうと思っていたのですが、丁度 Yeoman の generator があることに気付いたので、こちらを使うことにしました。この generator を使うと extension のパッケージングまで含めたビルドフロー全般、また livereload を使ったデバッグ環境まで一式整えてくれるため、Chrome 拡張の開発に慣れていない人にとってはなかなか便利だと思います。ディレクトリ一式も良い感じに作ってくれるので、今回の拡張ではこの generator を使ってこんな感じの構成にしています。

f:id:uzabase:20141217181939p:plain

具体的なソースコードについては特に複雑なことはしておらず、NewsPicks のサーバーが提供する REST API を叩いているだけです。あえて工夫した点を挙げると以下になるでしょうか。

まず popup については MVC で開発しました。今回はそれほど複雑な画面ではないので Backbone を利用していますが、フォームのモデルとのバインディングだけ面倒だったので backbone.stickit を利用しています。

backbone.stickit は Backbone にバインディング機能を提供してくれるライブラリです。これを Marionette と組み合わせて使う場合は、次のような Behavior を定義しておくと便利です。

class StickitBindingBehavior extends Backbone.Marionette.Behavior

  createBindings: ($root, attr="name", ignores={}) ->
    bindings = Backbone.$.extend true, {}, @options.bindings
    $root.find("[#{attr}]").each ->
      $el = $(@)
      attribute = $el.attr attr
      return if bindings[attribute]
      for ignore in ignores
        if _.isString ignore
          return if ignore is attribute
        else if _.isObject ignore
          return if ignore.test and ignore.test attribute
      selector = "[#{attr}='#{attribute}']"
      tag = $el.prop("tagName").toLowerCase()
      key = "#{tag}#{selector}"
      return if bindings[key]
      bindings[key] = "observe": attribute
    bindings

  onRender: ->
    @view.bindings = @createBindings @$el, @options.attr, @options.ignores
    @view.stickit()

  onDestroy: ->
    @view.unstickit()

Backbone.Marionette.Behaviors.behaviorsLookup = ->
  stickit: StickitBindingBehavior

これを使うと基本的には何も書かなくてもそれっぽくバインドしてくれるようになります。

class FormView extends Backbone.Marionette.ItemView

  template: "#form"
  behaviors:
    stickit: {}

ちなみに Backbone.Marionette + backbone.stickit + Browserify を使ったテンプレートを以前自分で作って github に公開しているので、良ければこちらをご参照下さい。 → backbone.marionette.example

Omnibox については Chrome の extension と API を組み合わせているだけですが、操作感については「.」を打つことでページを切り替えられるような UI にしました。また、Chrome の Omnibox は一度に表示出来る候補リストのサイズが少ないため、サーバーの負荷を減らすためにサーバー側から取得した検索結果のバッファをページングし、終端まで辿り着いた段階で API を叩いてリモートから次のページを取得するようにしています。また、当然ですがキーボード入力は適当に間引いて負荷を減らしています。

4. まとめ

以上が今回開発した内容の概要になります。
Chrome 拡張の開発についてのまとめです。

・Chrome の API ドキュメントは良く整備されており、Chrome extension のソースコードも GitHub などに沢山公開されているため、キャッチアップは比較的容易(但し GitHub に公開されている extension のコードは玉石混淆なため、安易に引用するのはオススメしません)
・Yeoman の generator を使うと開発環境は何も考えずにセットアップできる
・popup や omnibox の実装は、それほど Chrome 固有の知識を要求されるものではなく、HTML と JavaScript の知識があれば十分

「Chrome 拡張の開発」と言うとなんとなく敷居が高く感じてしまいますが、それほど難しい概念があるわけでもないので、皆さんも気楽に開発してみると楽しいのではないでしょうか。

NewsPicks では一緒に開発してくれるエンジニアを募集しています!様々なバックグラウンドを持つエンジニアや編集部の皆と世界一の経済メディアをつくりましょう!興味の在る方は是非 Wantedly などでお気軽にオフィスまでお越し下さい!