アペフチ

Oreでgemのひな形を作る

Rubyのライブラリーは、RubyGemとして配布されます。自分でこのRubyGemを作るにあたり、いつも必要になるファイルを作成するのに、多くの人はBundlerというgemのbundle gemコマンドを使っていると思います。 Bundlerもいいのですが、最近(ここ三年くらい)僕はOreという別のgemを使っているので、紹介してみたいと思います。

Oreを使うには

% gem install ore

します。ツールの性質上、Gemfileに書いたりすることはなく、グローバル(やchruby、rbenvなどの環境下)にインストールします。するとmineコマンドが使えるようになるので、これでgemのひな形を作ります。

% mine --bsd \
       --homepage=https://gitlab.com/KitaitiMakoto/pathname-temporary \
       --rubygems-tasks \
       --test-unit \
       --yard \
       pathname-temporary
Generating /Users/ikeda/src/gitlab.com/KitaitiMakoto/pathname-temporary
      create  lib
      create  lib/pathname/temporary
      create  test
      create  .document
      create  .yardopts
      create  test/helper.rb
      create  test/test_pathname-temporary.rb
      create  LICENSE.txt
      create  Gemfile
      create  .gitignore
      create  ChangeLog.md
      create  README.md
      create  Rakefile
      create  pathname-temporary.gemspec
      create  lib/pathname/temporary/version.rb
      create  lib/pathname/temporary.rb
         run  git init from "."

すごいたくさんオプションを付けているけど、必須な部分だけだと次の通り。

% mine pathname-temporary

ここでのオプションは次のような意味になります。

--bsd
二条項BSDライセンスにする。gemspecファイルのライセンスの所が"BSD"となり、具体的なライセンス内容がLISENCE.txtファイルに書かれるようになる。
--homepage=...
ホームページのURI。gemspecファイルのhomepageの所がここで指定したURIになるほか、README.mdにも使われる。
--rubygems-tasks
rake buildとかrake releaseとか、gemパッケージを扱うRakeタスク用に、rubygems-tasks gemを使う。gemspecファイルの依存gemにrubygems-taskが追加され、Rakefile内でそれが使われる。--no-rubygems-tasksにすると、Rakefileではbundler/gem_tasksが読み込まれて、Bundlerが提供する各タスクが定義される。
--test-unit
テスティングフレームワークにtest-unitを使う。Rakefileでtest-unit用のタスクが定義され、testディレクトリーとヘルパーファイルが作成される。これを指定しない場合は、デフォルトでRSpecが使われる。
--yard
ドキュメンテーションツールとしてYARDを使う。依存gemに(略)、Rakefileで(略)、.yardoptsファイルも作成される。これを指定しない場合、RDocが使われる。

他にも色々あるので、--helpオプションで見てみてください。

% mine --help
Usage:
  mine PATH

Options:
          [--markup=markdown|textile|rdoc]
                                            # Default: markdown
          [--markdown], [--no-markdown]
          [--textile], [--no-textile]
  -T, [--templates=TEMPLATE [...]]
  -n, [--name=NAME]
  -n, [--name=NAME]
  -N, [--namespace=NAMESPACE]
  -V, [--version=VERSION]
                                            # Default: 0.1.0
  -s, [--summary=SUMMARY]
                                            # Default: TODO: Summary
  -D, [--description=DESCRIPTION]
                                            # Default: TODO: Description
  -A, [--author=NAME]
  -a, [--authors=NAME [...]]
  -e, [--email=EMAIL]
  -U, --website, [--homepage=HOMEPAGE]
  -B, [--bug-tracker=BUG_TRACKER]

Template options:
  [--apache], [--no-apache]
  [--bin], [--no-bin]
  [--bsd], [--no-bsd]
  [--bundler], [--no-bundler]
                                                 # Default: true
  [--code-climate], [--no-code-climate]
  [--gem-package-task], [--no-gem-package-task]
  [--gemspec-yml], [--no-gemspec-yml]
  [--git], [--no-git]
                                                 # Default: true
  [--gpl], [--no-gpl]
  [--hg], [--no-hg]
  [--lgpl], [--no-lgpl]
  [--minitest], [--no-minitest]
  [--mit], [--no-mit]
                                                 # Default: true
  [--rdoc], [--no-rdoc]
                                                 # Default: true
  [--rspec], [--no-rspec]
                                                 # Default: true
  [--rubygems-tasks], [--no-rubygems-tasks]
                                                 # Default: true
  [--test-unit], [--no-test-unit]
  [--travis], [--no-travis]
  [--yard], [--no-yard]

gemspecファイルを、RubyじゃなくてYAMLファイルで管理する--gemspec-ymlオプションとか、面白いですね。

更に、(mineではなくて)oreコマンドで各種テンプレートをインストールすることで、このオプションを増やすこともできます。

% ore install git://github.com/ruby-ore/rbenv.git

を実行すると、mineコマンドで--rbenvオプションが使えるようになります。

毎回このオプションを指定るするのは覚えられないので、~/.ore/options.ymlに、毎回使うオプションを指定して置くことができます。著者名やメールアドレスなんかもデフォルトを指定しておけます。

gemspec_yml:    true
rubygems_tasks: true
rspec:          true
yard:           true
markdown:       true
authors:
  - Alice
email: alice@example.com

Hypothes.isがEPUBへのアノテーションのためのパートナーシップを結ぶ

notecakesをやってるピースオブケイクさんと勤め先のコルクとで、毎週月曜ジャンプの発売日(僕の住んでいた札幌では火曜発売だったけどね)に#テック会議と称してみんなでテック記事を上げるようにしましょうってなって、面白そうだしnoteでなくて自分の日記でもいいってことだったので参加してみます。これまでの記事を見てみると抽象的な話が多いようで、普段書いてる「これやってみた」という記事とは違うけど頑張ります。

別に参加者を絞るつもりはないようなので、皆さんも興味があれば更新日を月曜にずらしてみてください。noteを使う場合は#テック会議でタグ付けしておくと探しやすくてありがたいです。特に報酬とかはありません。

さて長い前置きだったけど今日は、この前Hypothes.isが出してきた、とても興奮するニュースの話をします。

A partnership to bring open annotation to eBooks
(電子書籍にオープンなアノテーションをもたらすパートナーシップ)

一行で言うと、「ウェブページやPDFへのアノテーション用のウェブアプリケーション開発及びそのホスティングサービス運用を行ってきたHypothes.isが、EPUBにアノテーションを付けられるようにするべく、複数の組織とパートナーシップを結ぶ」というニュースです。これで伝わる人は殆どいないと思うので解説します。

目次

  1. アノテーションとは
  2. アノテーションサービスのHypothes.is
  3. 電子書籍フォーマットのEPUB
  4. Hypothes.isのパートナーシップ

アノテーションとは

アノテーションというのは日本語だと「注釈」です。例えばウェブページで、ある文章に線を引っ張って自分のメモ書きを残すことはアノテーションです。

「注釈」という言葉からは外れると感じますが、単に線を引っ張るだけでも、ここではアノテーションと呼びます。

先のニュースページでハイライトされている様子。ハイライトされた部分の背景が黄色になっている。

また、ページの一部でなく、ページ全体に対して何か言及することもアノテーションです。はてなブックマークなんかがいい例だと思います。

ページ全体に対するコメントを残すのもアノテーション。画像ははてなブックマークの例。

先と同じパターンで、コメントのないブックマークも、アノテーションです。

アノテーションサービスのHypothes.is

Hypothes.isは、こういうアノテーションを、ウェブページとPDFに付けられるようにするウェブアプリケーションです。ページをハイライトしたりコメントを入力したりするためのChrome拡張やJavaScriptウィジェットを作ったり、そのアノテーションを保存・参照するためのサーバー用のアプリケーションを開発しています。

と同時に、そのアプリケーションを実際に運用して、無償で提供してもいます。この日記にもJavaScriptウィジェットを埋め込んでいて、記事を(一覧ページでなく)個別ページで読んでいる場合には右側にそのためのバーが見えているはずです。

アノテーションサービスHypothes.isのためのウィジェットを、無償で自分のウェブサイトにも埋め込める。

サイトの運用者はこのウィジェットを埋め込むことで、ページ全体または一部に閲覧者がハイライトやコメントを残せるようにできます。サイト側が対応していなくても、Chrome拡張を入れることで閲覧者はどのページにもアノテーションを付けられるようになります。付けられたアノテーションは(許可すれば)誰でも見ることができます。

こうして付けたアノテーションはHypothes.isのサーバーに保管され、どの端末、どのブラウザーでも見られるようになります(ブラウザー拡張は今のところChromeだけですが。Firefoxのは開発中で、自分でビルドして入れることはできます)。

電子書籍フォーマットのEPUB

電子書籍では、こうした「アノテーションを付けて、それをどの端末でも参照できる」という体験を既に経験していると思います。Kindleのことです。

Kindleでは電子書籍にハイライトやコメント、即ちアノテーションを付けることができる。

ところで、EPUBという電子書籍フォーマットがあります。IDPFという団体が策定したオープンなフォーマットです(KindleのはKindle Formatとかmobiとか呼ばれて、Kindleを作っているAmazon社が仕様を決めて運用しています。つまりオープンではありません)。仕様は誰でも見ることができますし、従って誰でも閲覧や作成用のアプリケーションを作れます。iBooksなどで読むことができます。

ちなみにPDFやWordファイル(*.docx)、Excelファイル(*.xlsx)、MP3なんかもオープンなファイルフォーマットです。Photoshop用ファイル(*.psd)やInDesign用ファイル(*.indd)はオープンではありません。

Hypothes.isのパートナーシップ

今回のニュースは、Hypothes.isがこのEPUBにも対応するべく、幾つかの組織とパートナーシップを結ぶ、という物です。Hypothes.isとのパートナーシップが発表された組織は以下の五つ。

NYU Libraries
ニューヨーク大学の、デジタルな物を処理し、アクセスを可能にし、また保管する所。
NYU Press
ニューヨーク大学の出版社?
Evident Point
電子出版ソリューションを提供する提供する会社。Readium(下記参照)のコアコントリビューターを数人抱えているらしい。
Readium Foundation
ReadiumJSという、EPUBを扱うJavaScriptのリファレンス実装を作っている所。ReadiumJSはDRMも扱えるとのこと。
EPUBjs project
epub.jsという、Readiumとはまた別のJavaScript実装を作っているプロジェクト。既にHypothes.isと連携したプルーフオブコンセプトを作った実績がある。

イデオロギー的に僕はオープンな物やフリーな物を支持しているので、オープンなファイルフォーマットにオープンなアプリケーションでアノテーションが付けられるというこのニュースにはとても興奮しました。

いきなりイデオロギーの話が出てきて面喰らうかも知れませんが、フリーとかオープンとかは殆どイデオロギーの話だと思っています1

ま、イデオロギーは置いておいても、オープンであればロックインされない(Kindleだと、Kindleがなくなると同時に自分の本のコメントが失われてしまう)、とか無料だとかメリットがあります。例えばiBooksで付けたブックマークをGoogle Play Booksで開くといったこともできるかも知れません(Appleがブックマークデータをダウンロードさせてくれれば)。

というような感じでいいのか知ら? 皆さんも、#テック会議ぜひ参加してみてください。

  1. 川上量生『鈴木さんにも分かるネットの未来』でそんな感じのことが書かれていてはっとしました。 

Polymerでpjaxする、またはapp-locationの使い方

この日記はPolymerで作っている、つまりWebコンポーネントを使っている。そのために表示が遅い。表示するまでに

  1. Webコンポーネントに必要なpolyfillを読み込む
  2. Polymerライブラリーを読み込む
  3. 各種カスタムエレメント定義をロードする
  4. JavaScriptで各種カスタムエレメント定義を実行する
  5. HTML中の各種カスタムエレメントを有効化する

というステップがあって、これを毎ページ繰り返すからだ。前々から何とかしたいなあとは思っていて、この連休で、サイト内リンクをpjaxにすることで少し改善させた。

各ステップはpjaxによって以下のように改善される。

目次

  1. pjaxとは
  2. polyfill読み込み
  3. Polymerライブラリーの読み込み
  4. カスタムエレメント読み込み
  5. カスタムエレメント定義の実行
  6. Polymerでpjax
    1. app-location
    2. iron-ajax
  7. 終わりに

pjaxとは

有名なので不要だとは思うけど、一応pjaxを説明しておく。

pjaxは、

  • Ajaxによる画面遷移
  • locationオブジェクト(アドレスバーのURL)の書き換え

の組み合わせだ。サイト内の別ページへのリンクをタップした際に、通常のブラウザーの画面遷移をする代わりに、JavaScriptでリンク先のHTMLを取得して、現在のページと置き換える。今回は、title要素とmain要素を置き換えることで、画面遷移としている。ページ全体でなく、一部の書き換え・更新にもよく使われる。

同時にlocationを書き換えることで、ブラウザーの進む/戻る・リロード、アドレスバーからURLをコピーしての共有など、通常の画面遷移であればできていることを可能にしている。

後者のためにJavaScriptのpushState機能を使っていることからpjaxと名付けられている:
defunkt/jquery-pjax: pushState + ajax = pjax

pjaxは「現在のDOMツリー内での置き換え」が機能なので、外から飛んできて最初に表示するページでは役に立たない。

polyfill読み込み

Webコンポーネントはまだ策定中・ブラウザー実装途中の仕様なので、クロスブラウザーでは動かない。具体的には、Chromeでしか全部は動かない。そこで、他のブラウザーでも動くよう、polyfillやshimを読み込む必要がある。

これにはWebコンポーネント仕様の一部であるカスタムエレメントなど以外にも、URLコンストラクターやPromiseなどのpolyfillも含まれる。サーバー側でブラウザーの判定などはしていないので(GitHub Pagesなのでそもそもできない)、Chromeのように不要であっても読み込んでいる。

こういうのは普通、libs.jsのような一つのファイルにまとめることでリクエスト回数を減らすものだけど、面倒くさくて後回しにしている(後回しにするうちにGitHub PagesでHTTP/2が使えるようになるといいなあ、という期待もちょっとある)。

pjaxによって、mainの外にあるscriptの読み込みと実行がスキップされるので、パフォーマンスがよくなっている。あと、そもそも同じscriptを二回読み込んだりすると、イベントリスナーの登録が複数回行われたりして意図しない動作になりがちなので、基本的にscriptはpjaxでの置き換え対象に入れたくない。

Polymerライブラリーの読み込み

ページをPolymerで作っている以上、当然Polymerを読み込む必要がある。

これもJavaScriptの読み込みなので、上と同じくpjaxによってスキップし、パフォーマンスを向上させている。

カスタムエレメント読み込み

Polymerが提供していてマテリアルデザインを実現するのに便利なPaper Elementsや自作の物など、各種カスタムエレメントは通常一つのHTMLファイルになっている。その中に、HTMLタグの他CSS宣言や要素定義のJavaScriptを書くようになっているし、僕もそうしている。一つのカスタムエレメントが複数のカスタムエレメントの組み合わせであることもよくあって、依存エレメントの分HTMLを読み込む必要があるのが普通だ。

先のJavaScriptライブラリーとは違って、これはさすがにリクエストが多くなり過ぎるのでvulcanizeによって一つのファイルにまとめている。その一つにまとめたHTMLファイルはhead要素中のlink要素

<link href="components/elements.vulcanized.html" rel="import" />

によって読み込んでいる(実際にはこのタグを書き出すヘルパーがMiddleman Web Componentsにあるので、それを使っている)。

これもmain要素の外にあるので、pjaxによってスキップしている。

カスタムエレメント定義の実行

カスタムエレメントは、単にHTML中に<paper-card>などタグを書くだけでは有効にならない(知らないタグとして扱われる)。このタグがカスタムエレメントの物であることをブラウザーに知らせ、各種機能を定義するにはJavaScriptを使う必要がある(参考:Custom Elements v1: Reusable Web Components)。

この定義は、上のelements.vulcanized.htmlに書かれているので、pjaxによってやはりこのステップもスキップできる。

これまでのステップは、(HTTPヘッダーやService Workerなどで)キャッシュを上手に使うことでも飛ばせるのだけど、カスタムエレメント定義は、ファイルを読み込んだの処理なので、ページ遷移ごとに毎回実行する必要があり、キャッシュできない。なのでここが、キャッシュ機構を入れてもなおpjaxが活きるところだと思う。

本当はpolyfillやPolymer読み込みをキャッシュしても、同様にJavaScript実行はページ表示毎に発生するのだけど、カスタムエレメント定義は特に多くなりがちなので特別に節を設けた。

カスタムエレメントの有効化

カスタムエレメント定義が終わったらブラウザーは、HTML中のカスタムエレメントタグ(に相当するDOMノード)を、そのカスタムエレメントとして扱い始める。このステップはpjaxではスキップできない。

Polymerでpjax

ようやく本題だけど、今回のpjax実装では、Polymerが提供しているapp-locationiron-ajaxというカスタムエレメントを使って実現してみた(blog-router.html)。pjaxは普通、全部JavaScriptでやるものだと思うけど、半分くらいの処理はHTMLタグを書くことで実現できてしまっていて、不思議な感じがした。

app-location

app-locationを使うと、サイト内リンクが全部無効化される。その代わり、イベントリスナーでクリックイベントをハンドリングしたり、リンクに関する情報をデータバインディングを使って別の要素に渡したりできる。

今回は

<app-location url-space-regex$="[[baseRegex]]" route="{{route}}" id=location></app-location>

routeというオブジェクトに、画面遷移に関する情報を入れることにした。

{{...}}はPolymerの提供するデータバインディング用の記法で、他に{{route}}と書かれた場所と連動する(Data binding - Polymer Project)。

iron-ajax

app-locationは、アドレスバーのURLの書き換えはしてくれるけど、実際のリクエストは投げてくれない。ので、それをJavaScriptでイベントハンドラーとして書くか、今回のように別の要素と連携させないと意味がない。

iron-ajaxはその名の通りAjaxしてくれるカスタムエレメントで、画面上にレンダリングはされない。純粋にJavaScript的な実行のためにある。これが要素になっているが不思議な感じがする。

これもデータバインディングの記法を使いつつ

<iron-ajax url="{{route.path}}" handle-as=document auto on-response=transit on-error=fallback id=ajax></iron-ajax>

と書くことで、app-locationrouteオブジェクトからpathプロパティを取り出してセットしている(pathの他にhashプロパティもあって、本当はこれもちゃんとハンドリングしないといけない)。

auto属性をつけているとurl属性が変わった際に自動でAjaxが行われるので、

リンクをタップ -> app-locationのroute属性変更 -> iron-ajaxのurl変更 -> Ajaxリクエスト

という流れをJavaScriptを書かずに実現してくれる(リクエストを間引くのも、使ってないけど、HTML属性によって定義できる)。

あとはレスポンス時やエラー時の処理をそれぞれtransitfallbackという関数としてJavaScriptで書いてやって出来上がりだ。transitとしてJavaScriptで書いた処理は、殆どtitle要素とmain要素の書き換えのみ。

終わりに

Polymerによるpjaxはこのようにして実現できる。これには、公式サイトの以下のページがとても参考になった。

» Routing with <app-route> - Polymer Project

余談だけど、Turbolinksを使うと自分で実装しなくてよかったのかも知れないなと思っている。

あと、今回、ページ内に一つblog-routerを置くことによって、つまり一元的なルーターを使ってpjaxを実現している。ReactやAngularでもルーターライブラリーがあるように、この手の処理は一元的なルーターでやるのが普通なのかも知れない。でも、次へリンクなどのHTML要素に結び付く形で、それがpjaxによる遷移かどうかを管理できるようにする、引いては、そのリンクのカスタムエレメントの機能としてpjax処理を実装できた方が、コンポーネント志向としてはいいのかも知れないなあと、やった後で思った。気が向いたらやってみるかも(そして、世界中のみんながルーターを一元的に作っている理由を知るのだ、きっと)。

今この記事書いてて気付いたけど、ページ内リンクが機能しなくなってしまった……。もう遅いので、後日の対応とします。(追記。直しました。)