読者です 読者をやめる 読者になる 読者になる

MastodonはP2Pではないという話、もしくはMastodonの脱中央集権の仕組みについて

Fukuoka.php vol22にてMastodonについて話してきました。

Mastodonは最近盛り上がってる分散型SNSですが、その仕組みに興味をもったので調べてみたのが今回の話になります。スライドの中で主に説明しているのは、Mastodonが実装している分散型SNSを実現するためのプロトコルOStatusについてです。

OStatusはAtomフィードを核とした仕様です。ユーザーのつぶやきをAtomフィードで表現し、さらにそれをコンタクト情報を形式化するPortable ContactsとSocial Network上の活動を形式化するActivity Streamsで拡張しています。フィードだけだと、リモートのサーバはポーリングする必要があるため、それを補うためにPubSubHubbubでAtomフィードの更新をほぼリアルタイムに受け取ることができるようにしています。また、FollowやFavやReplyやRetweetを相手先に通知するためにはSalmonを使っています。リモートフォローする際に入力する(ID)@(Mastodonのドメイン)という文字列からそのユーザーの情報を取得するためには、WebFingerを使っています。

日本国内ではP2Pと結び付けられて語られることのあるこのMastodonですが、こうやって見てみるとP2Pとはあまり関係がなく、フィード関係のプロトコルを使って分散型SNSを構築できる仕組みであることがわかると思います。

HTML5とか勉強会にて「大量の要素を高速に表示するバーチャルレンダリング入門」という題で話をした

第68回HTML5とか勉強会にて「大量の要素を高速に表示するバーチャルレンダリング入門」という題で話をしてきました。

ブラウザのレンダリングは、基本的にそのドキュメントに含まれるDOM要素の数が多ければ多いほどレンダリングが重たくなります。バーチャルレンダリングというのは、JavaScriptでDOM要素の数を調整して見えない部分の描画をうまくサボる仕組みです。

バーチャルレンダリング自体は結構いろんなところで利用されているにもかかわらずその仕組みやアルゴリズムの流れについて日本語での解説をあまり見かけなかったので、HTML5とか勉強会ではこれについて話すことにしました。

このテクニックは結構昔からあるテクニックで、知ってる人は知っているけれども知らない人は知らないままという感じになりがちなんですが、今回はHTML5とか勉強会という参加者が比較的大きな勉強会(180人ぐらい参加してる)で話せて良かったなと思いました。

JavaScriptのUIライブラリはどうあるべきかという話とOnsen UIのアーキテクチャ

Onsen UI Advent Calendar の12/9の記事です。

Onsen UIは、モバイルアプリのネイティブライクなUIをHTML + CSS + JavaScriptで簡単に構築することを目的としたUIライブラリです(UIフレームワークともたまに呼ばれます)。 ↓みたいなネイティブなモバイルアプリっぽい画面をサクッと作ることができます。

f:id:anatoo:20161209200818p:plain

私は数年前から開発メンバーとしてOnsen UIの設計開発を行っています。この記事では、Onsen UIに求められるUIライブラリとしての要件とそれを解決するためにどのようなアーキテクチャを取っているのかについて解説します。

特定のフレームワークに依存しない

jQuery UIやReactの上に乗っかっているUIライブラリなどのように特定のフレームワークの仕組みを使って実装されたUIライブラリというのはたくさんありますが、ある特定のフレームワークに依存することは避けるべきだという考えのもとにOnsen UIは開発されています。

ある特定のフレームワークに依存したUIライブラリを作ると、その他のフレームワークから使ったり素のJavaScriptから使うことが困難になります。例えばAngularJSに依存したUIコンポーネントは他のフレームワークから利用することは基本的にできなくなります。技術的にはできなくも無いでしょうが、それをわざわざやりたいと思う開発者はおそらくいないでしょう。

UIライブラリの提供者側にとっても特定のフレームワークに依存するのはリスクがあります。もしその依存しているフレームワークが使われなくなった場合に、UIライブラリを書き直すことになる可能性があります。

これは実際に起こったことですが、Onsen UIの1系はAngularJSのdirectiveとして実装されていたので、AngularJSと互換性の無いAngular2が登場した時にdirectiveとして実装されていたすべてのUIコンポーネントを書き直したことがありました。

フレームワーク非依存にするためにしていること

Onsen UIでは、特定のフレームワーク依存せず、かつどのフレームワークからでもある程度利用できるように次のような構成を取っています。

f:id:anatoo:20161211015934p:plain

CSS Components層は、Onsen UIが提供する最もレベルの低いコンポーネント層です。これは主にすべてのUIコンポーネントの見た目を提供します。CSS Componentsという名前のとおり、CSSファイルとして提供されます。

Web Components層では、Custom ElementsのAPIを使ってCSS ComponentsにJavaScriptで振る舞いを与えます。Custom Elementsを使っているので、素のJavaScriptからでも、フレームワークからでも同じように扱えるように設計されています。

Framework Bindings層では、各種フレームワーク用のバインディングを記述します。現在のところAngular1, Angular2, React.js,とVue2用のバインディングが記述されています。

CSS Components層

CSS Components層では各コンポーネントがCSSだけで完結するCSS Componentsとして実装されています。CSSだけで実装できるもの、すなわちコンポーネントの見た目はここで実装されています。

このCSS ComponentsはAdobe製の高速CSSフレームワークであるTopcoatをフォークして開発されたものです。CSSメタ言語としてStylus、CSSコンポーネントのドキュメントの記述にはtopdoc、設計規約としては高速なCSSセレクタを記述することができるBEM+MindBEMdingを採用しています。

開発者はCSS Components層が提供するCSSのみを使うこともできます。例えば、Onsen UIのリポジトリのCSSファイルを読み込んで、swtichコンポーネントのタグを記述するとiOSでよく見るSwitchのUIが表示されます。

<link href="https://unpkg.com/onsenui@2.0.4/css/onsen-css-components.css" rel="stylesheet" /> 
<label class="switch">
  <input type="checkbox" class="switch__input" checked>
  <div class="switch__toggle">
    <div class="switch__handle"></div>
  </div>
</label>

f:id:anatoo:20161209195916p:plain

リポジトリ的には次の場所に全て記述されています。

Web Components層

先ほどのCSS Components層の上に位置するのがWeb Components層です。CSSで記述された見た目に対してJavaScriptで振る舞いを追加します。

ここでは独自のHTML要素を定義することが出来るCustom ElementsのAPIを使ってCSSコンポーネントに振る舞いを追加するカスタム要素を定義しています。ドキュメントを見ると現在は約40程度のカスタム要素が定義されています。

例えば<ons-button>という要素があるのですがこれはCSSコンポーネントして実装したbuttonにCustom Elementsを被せたものです。

<button class="button">...</button> <!-- CSSコンポーネント -->
<ons-button>...</ons-button> <!-- ons-buttonカスタム要素 -->

Custom Elementsとして実装すると、そのDOM要素のプロパティやメソッドや属性などの振る舞いを定義することができます。これを使って素のJavaScriptやjQueryなどからでも扱える、かつAngular2やReact.jsやVue.jsなどのフレームワークからでも扱えるコンポーネントを定義することができます。

リポジトリ的には次の場所に全て記述されています。Custom Elements以外にも各種JavaScriptのAPIも提供しているのでこの部分をひっくるめてcoreとも呼ばれます。Onsen UIのonsenui.jsonsenui.cssはこのcoreから生成されます。

Framework Bindings層

この層では、各フレームワークごとのラッパーを定義しています。このラッパーはOnsen UIではバインディングと呼ばれています。現在対応しているフレームワークにはReact.js, AngularJS, Angular2, Vue.jsなどがあります。jQueryや素のJSから利用する場合には、このバインディングは利用しません。

開発者は、利用するフレームワークに合わせてこのバインディングも利用します。

なんでこのバインディングがあるかというと、Web Componentsを提供していたとしても、そのカスタム要素のプロパティやメソッドにアクセスできなければ意味がありません。各フレームワークごとにコンポーネントの操作をどのように行うかについても流儀が異なります。

例えば、React.jsではそもそもコンポーネントのメソッドを叩くといった操作はしないのが普通なので、Custom Elementsが持つメソッドに依る操作をReact.jsのコンポーネントのpropsやstateによって管理する必要があります。AngularJSやAnglar2の場合にはDOM要素が持つメソッドをDirectiveから叩けるようにする必要があります。

Framework Bindings層ではこのフレームワークごとに異なる流儀を吸収しながら、カスタム要素に対するインターフェイスを提供しています。

フレームワークによってどういうふうに書き方が変わるかというのは次の公式ブログの記事にも書かれています。

リポジトリ的には次の場所に全て記述されています。bindingsディレクトリ以下にフレームワークごとのnpmパッケージが提供されています。

まとめ

Onsen UIは様々なフレームワークに対応するために、Custom Elementsを使っています。その際のUIライブラリとしての大まかなアーキテクチャについてこの記事では説明しました。

「PHPに型推論を実装する〜入門編〜」という題でPHPカンファレンス福岡2016で話してきた

PHPカンファレンス福岡2016で型推論器ってどんな感じなのという話をしました。PHPカンファレンス福岡は去年も登壇したんですが今年は弊社もスポンサードしつつの登壇です。

参加者や運営スタッフの皆さんの対応含めて心地良い雰囲気だったので、ああ参加して良かったなと自然と思えるような素晴らしいイベントでした。参加者や運営のスタッフの皆さんお疲れ様でした!

JavaScript(ES2015)でvarやletを使う必要はほぼ無い

ES2015でvarやletを使う場面はほとんど無いので、まずconstを使う。constだとダメな場合にはletを使う。

背景

ES2015では、変数を宣言するための文法としてconstとletが導入された。

const foo = 'foo';
let bar = 'bar';

constは再代入できない変数を宣言できる。letは再代入できる変数を宣言できる。

const foo = 'foo';
foo = 'hoge'; // ERROR

let bar = 'bar';
bar = 'hoge'; // OK

あれ、じゃあvarとletは同じなの?っていうとそうではなく、letやconstはvarとは違って、関数スコープよりも細かなブロック単位のスコープを提供する。例えばconstやletを使うと、if文やfor文などのブロック中でのみ有効な変数を宣言できる。

で、プロジェクトにES2015を導入すると、「お、varじゃなくてlet使えばええんやな!」と言わんばかりにletだけを使う人を見かけるんだけど、実際にはletもほとんど使う必要なくて、多くの場合はconstで問題ない。また、他人が書いたソースコードの中でletを見ると、「これ後で再代入されるん?」って一瞬身構えるので、再代入の必要がないならconstを使う方が可読性も良くなる。

ES2015になって変数の文法が増えたわけだけど、開発者はまずconstを使って、再代入が必要ならletを使って、letも使えない特殊な事情がある場合のみvarを使う、というようにするといい。

varは別に非推奨になったわけではないので普通に使えるんだけど、プロジェクトで機械的にvarを使わせたくない場合には、ESLintでno-varを有効にする。

Airbnbが公開しているES6に対応したJavaScriptコーディング規約においても、まずconstを使うこと、varは避けることが書かれている。

追記: よくよく調べてみると、ESLintには再代入しないletに警告を出すprefer-constがあった。より厳格にしたい場合はこのprefer-constを有効にする。

まとめ

ES2015では、varやletを使う必要のある場面はそれほどない。変数の宣言にはconstを使う。再代入が必要な場合にのみlet使う。

factorでズンドコキヨシを書いてみた

連鎖性言語のfactorで今流行り(?)のズンドコキヨシ書いてみた。連鎖性言語って何?っていう方はここの説明を見てほしい。

IN: .
USING: random io kernel sequences math ;

"" [ dup "ズンズンズンズンドコ" tail? not ]
[ { "ズン" "ドコ" } random append ] while
"キ・ヨ・シ!" append write

実行する。

$ factor zundoko.factor
ズンドコドコズンズンズンズンドコキ・ヨ・シ!

昔触ってたfactorのこと完全に忘れててこれ書くのになんやかんやで1時間ぐらいかかったけど、たまにfactor書くとやっぱ面白い。最近の連鎖性言語をちょろっと調べてみたら、今はkittenというイケてる感じの関数型連鎖性言語も登場してるみたい。暇があったらまたこういうの触っていきたい。

JavaScriptでのDOM操作は重いのかという話とForced Synchronous Layoutについて

2015年にもなるのにJavaScriptでのDOM操作のパフォーマンスについて書く。ウェブページにインタラクションを持たせたい時に、JavaScriptでDOM操作を行うことがよくある。このDOM操作のパフォーマンスについて、よく聞く意見を大別すると次の2つがある。

  • JavaScriptによるDOM操作は重たい
  • レンダリングが重いだけで、DOM操作そのものはそれほど重たくない

JavaScriptでオブジェクトのプロパティを操作したりする単体の処理は通常1ミリ秒もかからないが、DOM操作をするとレンダリングが完了するまでに数十ミリ秒程度かかったりする場合がある。1番目のDOM操作が重たいと言っている人は経験則的にそう言っていることが多い。

レンダリングの仕組みを知っている人は2番目の意見を言うが、重箱の隅をつつくような話をするとこれも必ずしも正しいわけではない。DOM操作するコードによっては、JavaScriptの実行そのものをブロックするケースもあるからだ。この記事ではその辺りの事情についておさらい的に説明する。

DOM操作するとそもそも何が起こるのか

現在見ているウェブページのドキュメントのDOMツリーに属するDOM要素を操作をすると、JavaScriptの実行が終わってからブラウザのレンダリング処理が行われる。具体的にはWebkit系のレンダリングエンジンだと次の図のようになる。

f:id:anatoo:20151013210539p:plain

ScriptingではJavaScriptの実行が行われる。DOM操作の処理もここに含まれる。

Calculate Styleでは、更新されたDOM要素ごとのスタイルが再計算される。例えば、DOM要素を生成してDOMツリーに追加すると、その影響で当たるスタイルを更新しないといけない要素が出てくるので、それらのDOM要素に当たるスタイルがCSSセレクタのマッチング処理とともに再計算される。

Layoutでは、計算されたDOM要素のスタイルを元に要素のレイアウトの計算を行う。いわゆるリフローと呼ばれるものはこれ。Firefoxだとリフローと呼ばれる。

Paintでは要素をレイヤごとにラスタライズする。Composite Layersではラスタライズした各レイヤを1枚に合成して最終的なレンダリング結果を生成する。

HTML5アプリでのインタラクションやアニメーションのレンダリングのパフォーマンス改善というのは、このDOM操作した後のこの一連の実行パスをどのようにして最適化するかという話である。

例えば、iOSやAndroidなどのモバイル端末で60FPSのアニメーションを達成しようとすると、LayoutやPaintなどの処理をやってたら速度的にちょっときつくなるのでなるべく最後のComposite Layersだけが実行されるようなコードを記述したりする。

DOM操作に伴うレンダリングの処理は、JavaScriptの実行そのものを遅くするわけではないので、そういう意味では後続するレンダリングが重いだけでDOM操作そのものは大して重くない、というのはおおむね正解である。実際ドキュメントのDOMツリーに属していないDOM要素を操作してもこれらのレンダリング処理は行われない。

ただし、JavaScriptのコードの書き方によってはForced Synchronous Layoutを引き起こして、JavaScriptの実行そのものをブロックする場合がある。

Forced Synchronous Layout is 何

Forced Synchronous Layoutとは、JavaScriptと同期する形で実行されるレイアウト計算処理のことである。通常このレイアウト計算は、JavaScriptの実行が終わってから初めて行われるので、JavaScriptの実行を遅くしたりするわけではない。しかしForced Synchronous LayoutではJavaScriptの実行中に同期的に処理されるので、JavaScriptの実行パフォーマンスを劣化させることがある。

DOM APIにはレイアウト情報を参照するメソッドやプロパティがいくつかある。具体的には、offsetHeightプロパティやoffsetWidthプロパティやgetBoundingClientRect()メソッド等がある。Forced Synchronous Layoutは、ドキュメントのDOMツリー内でDOM操作を行った後、そのDOM要素のレイアウト情報を参照するコードを書くと起こる。

例えば、DOM要素を生成してbody要素に追加する次のようなコードを見てみよう。

var div = document.createElement('div');
div.innerHTML = 'hogehoge';

document.body.appendChild(div);

このコードでは、JavaScriptの実行が終わってから初めてレイアウト計算が行われる。対して、次のコードではForced Synchronous Layoutが発生する。

var div = document.createElement('div');
div.innerHTML = 'hogehoge';

document.body.appendChild(div);

// ここでForced Synchronous Layoutが起こる 
console.log(div.offsetHeight);

Webkitなどのレンダリングエンジンでは、JavaScriptによるDOM操作が行われてもすぐにレイアウト計算を行うのではなくJavaScriptの実行が一旦終わってから行うが、DOM操作を行った後にgetBoundingClientRect()やoffsetHeightなどのプロパティを参照すると、正しいレイアウト情報を返すためにレンダリングエンジンはその場でレイアウト計算を行う。

Forced Synchronous Layoutが起こっているかどうかは、ChromeのウェブインスペクタのTimelineでプロファイルを取ればわかる。

f:id:anatoo:20151013222902p:plain
https://developers.google.com/web/tools/profile-performance/rendering-tools/forced-synchronous-layouts

Forced Synchronous Layoutが発生したからと言って即パフォーマンス上の問題が出るわけではないが、ループ内などでForced Synchronous Layoutを引き起こすと、JavaScript実行中にレイアウト計算が何度も繰り返し起こるのでJavaScriptのパフォーマンスが大幅に劣化する事がある。

まとめ

JavaScriptでのDOM操作自体は大して重くない。重く見えるのはDOM操作に伴うレンダリング処理がJavaScriptの実行後に行われるからである。ただし、DOM操作するコードがForced Synchronous Layoutを引き起こす場合にはJavaScriptの実行パフォーマンスを劣化させる原因になる。