mithril.js v1.0 の変更点

こんにちは。DeNA Games Osaka 技術編成部のさい(@sairoutine)です。
DeNA Games OsakaはDeNAの大阪拠点です。今後ともよろしくおねがいします。

2017年01月31日に、mithril.jsのv1.0がリリースされました。 (2017年5月現在、v1.1.1までリリースされています)

軽い/高速/低学習コストというmithril.js本来の特徴はそのままに、これまでのバージョンでは制約となっていた機能が大幅に変更されています。

本記事では、v1.0のリリースに当たって、大きな変更となる箇所をご紹介したいと思います。
なお、mithril.js自体の紹介については、下記の記事をご参照ください。

最速フレームワーク Mithril 入門
http://developers.mobage.jp/blog/mithril-introduction

JSX が推奨に

仮想DOMのHTML like な独自拡張構文として React には JSXがありました。同様に、mithril.js にも MSX というのがありましたが、MSX は 1.0 から非推奨になり、公式のドキュメントでも、babel と transform-react-jsx が推奨となりました。

m.deferred が廃止され、Promise が使用される

v0.2.5 までは m.deferred という Promise like な非同期処理のための関数があり、m.request 等の一部の関数は m.deferred を使用していました。これが v1.0 からはブラウザネイティブな Promise を使用するようになりました。Promise 非対応ブラウザではpolyfill を使用してくれるので、引き続き IE9 までの古いブラウザでも、mithril.js が使用できることに変わりはありません。

m.prop が廃止され stream に

v0.2.5 までは、(主に Model)クラスのプロパティの getter/setter を作成するために m.prop という関数がありました。 v1.0 からはこれが廃止され、stream という命名で別モジュールに切り出されました。

stream では今までの getter/setter 機能に加えて、stream から新しい stream を生成して、元の stream の内容の変更を新しい stream に伝播させたり、あるいは stream 同士の合体をすることができるようになりました。

// stream から新しい stream の生成
var value = stream(1)

var doubled = value.map(function(value) {
    return value * 2
})

console.log(doubled()) // 2

// stream の合体
var firstName = stream("John")
var lastName = stream("Doe")
var fullName = stream.merge([firstName, lastName]).map(function(values) {
    return values.join(" ")
})

console.log(fullName()) // "John Doe"

firstName("Mary")

console.log(fullName()) // "Mary Doe"

streamモジュールは他にも色々と出来ることがあるので、詳しくは公式ドキュメントの stream の項を参照頂ければと思います。

vnode の概念の追加

v1.0 から vnode (Virtual DOM nodes)という概念が追加されました。vnode とは仮想DOMツリーを表すオブジェクトです。コンポーネントの view 関数や、あるいは後述するライフサイクルイベントに定義された関数が mithril から呼ばれる際に、引数として渡されます。

例えば、コンポーネントに状態を持たせて、状態を参照したり変更したりしたい場合は、vnode.state に状態を追加/変更します。

var Component = {
    oninit : function(vnode) {
        vnode.state.fooga = 1
    },
    view : function(vnode) {
        return m("p", vnode.state.fooga)
    }
}

vnode オブジェクトは他にも色々なプロパティを持つので、詳しくは公式のドキュメントの vnode の項目を参照頂ければと思います。

ライフサイクルイベント

v0.2.5 までは、仮想DOMに対する config 属性で一部のライフサイクルイベント(oninit, onupdate 等)に対する処理を実装していました。v1.0 からは config が廃止され、コンポーネントに対して、以下のライフサイクルイベントで処理される関数を定義することができるようになりました。

oninit
コンポーネントが初期化される際に呼びだされるフックです。実DOMが追加されるより前に呼び出されます。

oncreate
oninit と異なり、oncreate はコンポーネントが初期化されて、実DOMが作成した後に呼び出されます。実DOMが作成した後に呼ばれるため、vnode.dom 経由で実DOMを取得して操作を行うことが可能です。

onupdate
mithril.js による再描画によって、一度生成された DOMに更新があると呼び出されます。onupdate が呼び出された際には、既に更新された実DOMが生成されているので、vnode.dom 経由で更新後の実DOMを取得したり、操作することが可能です。

onbeforeupdate
onupdate と同様に、一度生成されたDOMに更新があると呼び出されます。onupdate が、更新された実DOMが生成された後に呼ばれるのに対して、onbeforeupdate では更新された実DOMが生成される前の、仮想DOMの差分比較のタイミングで呼び出されます。この時、onbeforeupdate で定義した関数でfalse を返すことで、差分検知をスキップすることができます。

onbeforeremove
DOMが削除される前に呼ばれます。このタイミングでは、削除される実DOMはまだ削除されていないので、vnode.dom で実DOMにアクセスすることが可能です。また、onbeforeremove で定義した関数がPromise オブジェクトを返すと、mithril.js はそのPromise が完了するまで、実DOMの削除を遅延します。

onremove
DOMが削除される際に呼ばれます。onbeforeremove に関数が定義されていると、onremove は onbeforeremove が完了した後に呼び出されます。

controller の廃止

controller という概念がなくなり、今まで controller のコンストラクタで行っていたことは、コンポーネントの oninit で行うことが推奨されました。またコントローラに紐づく関数は、コンポーネントの関数として記述することが推奨となりました。

最後に

v1.0 アップデートに当たっての大きな変更点をご紹介させていただきました。その他にも細かい変更がありますので、詳細は公式の change log を参照頂ければと思います。

コンポーネントに対するライフサイクルイベントの追加や、あるいは controller の廃止により、所感としてMVC フレームワークというより、コンポーネント指向なフレームワークに近くなった印象です。

一方で、軽い/高速/低学習コストという mithril.js 本来の特徴は失われていません。 SPAを構築する上で充分かつ必要最小限なAPIに加えて、他のライブラリやビルドツールに対して低依存であることから、JSフレームワークにおけるスイスアーミーナイフのような存在です。

v1.0 にアップデートされた mithril.js にぜひ一度皆様も触れてみてください。

続きを読む
ツイート
シェア
あとで読む
ブックマーク
送る
メールで送る

Atomパッケージを作ってみよう

この記事はDeNA Advent Calendar 2016 6日目の記事です。

こんにちは。エンジニアの加瀬です。 普段はモバイルゲームの開発をしております。

皆さんは開発をするときにどのエディタをメインに使っていますか? Vim、Emacs、それともIDEでしょうか。
昔は自分もVimを使っていたのですが、ちょっとしたプラグインを自分で作ってみようと思ったときにハードルの高さにぶつかってしまい、去年あたりからAtomをメインエディタとして使うようになりました。
AtomはCoffeeScript(後述しますが今はJSでもOKです)でパッケージ(プラグイン)を作ることができます。プラグイン作成のためにニッチな言語を習得する必要がないことからパッケージを作るハードルは比較的低いと言えます。

ですが、自分が実際に作り始めてみるとまだまだドキュメントやサンプルが少なかったことから少し苦労しました。ちょっとでもパッケージ作りに興味を持った人が最初の段階で躓いてしまわないように、基礎のところを解説したいと思います。

パッケージジェネレータ

それでは早速パッケージを作ってみましょう。 Atomが標準でパッケージのテンプレートを作るコマンドを用意してくれていますので、まずはそれを実行します。
コマンドパレット(もしくはメニューのPackagesから)からPackage Generator: Generate Packageを実行します。 保存場所とパッケージ名を尋ねられるので、ここではmy-packageとしておきます。
するといくつかのファイルとディレクトリが作られ、~/.atom/packagesと~/.atom/dev/packagesの下に今作られたmy-packageのディレクトリへのシンボリックリンクが貼られます。

packagesはAtom標準以外のサードパーティのパッケージが保存されているディレクトリで、Atom起動時にここのパッケージが読み込まれます。dev/packagesは開発モード時に読み込まれるディレクトリです。

自分が作ったパッケージを公開したり、あるいは誰かが公開しているパッケージをforkして開発する場合にはdev/packagesにシンボリックリンクを貼ってatom --devから開発モードで起動するといいでしょう。

CoffeeScriptかJSか

そもそもAtomが登場した当初はまだCoffeeScriptが流行っていた頃でしたのでCoffeeScriptで書かれたエディタとして話題を集めていたように記憶しています。なのでそのパッケージを作るにも当然CoffeeScriptと思われますが、2016年現在では素のJSを使うこともできます。それどころかES2015で書くことすらできます。

先ほどのGenerate Packageで作られたテンプレートはCoffeeScriptで書かれていました。もしCoffeeScriptではなくてJSで書きたい場合は、package-generatorというコアパッケージのSettingsでPackage Syntaxをjavascriptに切り替えてから再度Generate Packageを実行してください。

本記事ではJSでテンプレートを生成した前提で解説をしていきます。ですがCoffeeScriptの場合でも.jsを.coffee、.jsonを.csonに置き換えて見てもらえば後は文法が少し異なるだけで問題ありません。

package.jsonの解説

まずはpackage.jsonから見ていきましょう。 package.jsonにはこのパッケージのメタ情報や依存ライブラリの情報を書いていくことになります。


{
  "name": "my-package",
  "main": "./lib/my-package",
  "version": "0.0.0",
  "description": "A short description of your package",
  "keywords": [
  ],
  "activationCommands": {
    "atom-workspace": "my-package:toggle"
  },
  "repository": "https://github.com/atom/my-package",
  "license": "MIT",
  "engines": {
    "atom": ">=1.0.0 <2.0.0"
  },
  "dependencies": {
  }
}

大体のメタ情報は最初から書かれている内容を見れば分かると思うので、見慣れないactivationCommandsだけ解説します。

Atomは最初から全てのパッケージを読み込むわけではなく、遅延読み込みが可能になっています。
実は最初から書かれているこのactivationCommandsだけでそれが実現されており、atom-workspaceという場所でmy-packageのtoggleコマンドを実行しようとしたときに初めてmy-packageが読み込まれる、という意味になっています。

atom-workspaceはAtomのどこでもという意味で、toggleはGenerate Packageでテンプレートを生成したときに初めから用意されているコマンドです。 後ほど解説しますが、toggle以外のコマンドを自分で定義すればもちろんそれをactivationCommandsに割り当てることも可能です。

npmも使えるのか?

packages.jsonはパッケージのメタ情報を書くだけではなく、nodejsでの開発と同様にnpmに必要な情報を書くことも可能です。
依存ライブラリはnpm installdependenciesdevDependenciesに自動登録できますし、必要であればscriptsを作って自分でlintやwatchといったコマンドを登録しても問題ありません。

lib/my-package.js

次にパッケージのコマンドがどこで登録されているか見てみましょう。
lib/my-package.jsはパッケージの起動や終了時の処理、コマンドの登録を行う場所になっています。


// ES2015で書くために必要な記述
'use babel';

import MyPackageView from './my-package-view';
import { CompositeDisposable } from 'atom';

export default {

  myPackageView: null,
  modalPanel: null,
  subscriptions: null,

  // パッケージ起動時に実行される処理
  activate(state) {
    // toggleコマンドで表示されるモーダルのViewオブジェクト
    this.myPackageView = new MyPackageView(state.myPackageViewState);
    this.modalPanel = atom.workspace.addModalPanel({
      item: this.myPackageView.getElement(),
      visible: false
    });

    // イベントを管理するために便利なCompositeDisposable
    // Atomの各所で見かけるが今回は省略
    this.subscriptions = new CompositeDisposable();

    // コマンド登録
    this.subscriptions.add(atom.commands.add('atom-workspace', {
      'my-package:toggle': () => this.toggle()
    }));
  },

  // パッケージ終了時に実行される処理
  deactivate() {
    this.modalPanel.destroy();
    this.subscriptions.dispose();
    this.myPackageView.destroy();
  },

  // パッケージの次回起動時に状態を保存しておく処理
  // 今回は省略
  serialize() {
    return {
      myPackageViewState: this.myPackageView.serialize()
    };
  },

  // toggleコマンドの実際の処理
  toggle() {
    console.log('MyPackage was toggled!');
    // モーダルViewの表示、非表示
    return (
      this.modalPanel.isVisible() ?
      this.modalPanel.hide() :
      this.modalPanel.show()
    );
  }

};

自動的に生成されたMyPackageViewにはtoggleコマンドで表示されるモーダルのHTMLが記述されています。今回は解説しませんが、パッケージで使われるView要素はロジックとは別のモジュールに分けられていることが多いです。

my-packageに新しい機能を追加する

ここまででパッケージのコマンドの登録方法とその実装を見てきました。次はいよいよこのmy-packageに新しい機能を追加してみましょう。

今回はAtomが提供している通知機能を使ってこんな通知を出してみることにします。

atom-my-package-notify.png

Atom APIドキュメント

まずはAtomのAPIドキュメントから通知機能を使うためのAPIを調べましょう https://atom.io/docs/api/

通知機能はNotificationManagerというクラスです。ざっと見てみるとaddSuccessやaddInfo, addErrorというメソッドが並んでおり、いかにもそれっぽいですね。
このNotificationManagerはatom.notificationsからいつでも使えると書いてありますので早速試してみましょう。

コマンドの追加

今回はHello World!と表示させるだけのシンプルな通知を出すコマンドを追加します。
addInfo()を呼び出すnotify()というメソッドを作り、既にコマンド登録されているtoggleの下にnotifyを新しく追加します。


// 変更を加えたメソッドだけ抜粋

  activate(state) {
    this.myPackageView = new MyPackageView(state.myPackageViewState);
    this.modalPanel = atom.workspace.addModalPanel({
      item: this.myPackageView.getElement(),
      visible: false
    });

    this.subscriptions = new CompositeDisposable();

    this.subscriptions.add(atom.commands.add('atom-workspace', {
      'my-package:toggle': () => this.toggle(),
      // notifyを追加
      'my-package:notify': () => this.notify()
    }));
  },

  toggle() {
    console.log('MyPackage was toggled!');
    return (
      this.modalPanel.isVisible() ?
      this.modalPanel.hide() :
      this.modalPanel.show()
    );
  },

  notify() {
    atom.notifications.addInfo('Hello World!');
  }

コードを書き換えて動作確認をする前にAtomをリロードしてパッケージを再読込する必要があります。メニューのView→Developer→Reload Windowでリロードできるのですが、これから何度も使用することになるのでショートカットを覚えてしまいましょう。
Macであればctrl + option + command + Lです。

リロードできたらコマンドパレットを呼び出し、MyPackageを検索します。 toggleしか実行できるコマンドがありませんので、とりあえずtoggleを実行します。package.jsonで解説したようにmy-packageは起動コマンドとしてtoggleが設定されていますので、逆にtoggleを実行するまでは別のコマンドを実行できません。
toggle実行後に再度コマンドパレットで検索をすると今度はnotifyも選べるようになっており、実行するとHello World!という通知が出たはずです。とても簡単な機能ですが、my-packageに新しいコマンドを追加することができました!

activationCommands

新しいコマンドを追加することはできましたが、my-packageの機能を使う前に毎回toggleを実行するのはとても不便ですね。この問題はpackage.jsonのactivationCommandsを削除することで解決できます。
activationCommandsを削除してリロードしてから再度コマンドパレットでMyPackageと検索してみてください。今度は最初からnotifyを実行できるはずです。

activationCommandsが存在しない場合、Atom起動時にパッケージが自動的に読み込まれます。 便利になる反面、CPUやメモリをガンガン使う場合は何もしなくても'重い'パッケージになってしまうので、そのような場合はactivationCommandsを設定して必要になるときまでパッケージの起動を遅らせることができないか検討しましょう。

デバッグ

AtomではChromeと同じ開発者ツールを使ってデバッグができます。
メニューのView→Developer→Toggle Developer Toolsか、Chromeと同じでcommand + options + iのショートカットで起動できます(Macの場合)

JSのデバッグには欠かせないconsole.log()や、debuggerによるブレークポイントの設定ももちろん可能です。

開発者ツールのコンソールではAtomで使われているパッケージが読み込まれている状態なので、ここに直接 atom.notifications.addInfo('Hello World!') と入力しても先ほど実装したnotify()と同じように通知を出すことができます。 APIドキュメントを読みながら色々と実験をするときに重宝するでしょう。

テスト

テストはspecディレクトリに存在します。今回は割愛しますが、もしもパッケージを公開するのであればテストも追加した方がいいでしょう。 ターミナル上でapm testを実行するとspecに存在するテストが全て実行されます。

パッケージ公開

一通りの機能ができたらぜひパッケージを公開してみましょう。 今回は解説しませんが、Atomのドキュメントにパッケージを公開する方法が書いてありますのでぜひ挑戦してみてください。
http://flight-manual.atom.io/hacking-atom/sections/publishing/

終わりに

駆け足になってしまいましたが、パッケージ作成の基本を紹介しました。 Atom独自のお作法を覚えてしまえば、後はJS(CoffeeScript)とCSSで書くことができるので学習コストはそれほど高くないと思います。

ですが、パッケージについてのドキュメントは残念ながらそれほど充実している環境ではないというのが自分の感想です。
Atomのパッケージは公式、サードパーティのどちらもGitHub上で公開されていますので、他の人が作成したパッケージの構成やソースコードを読むことが理解への近道になるはずです。 普段自分が使用しているパッケージや、これから自分が作ろうとしているものに近い機能を提供しているパッケージを参考にするといいでしょう。

プライベートや業務でAtomにこんな機能あったらいいな、と思ったらぜひパッケージ作成に挑戦してみてください。

最後に、拙作ではありますがAtom上でVimのタブ機能を再現するパッケージを公開していますので興味を持った方は使ってみて頂けると嬉しいです。
ソースコードもGitHubに公開してありますので、こちらもぜひ見てみて下さい。
https://atom.io/packages/atom-vim-like-tab
https://github.com/Kesin11/atom-vim-like-tab

続きを読む
ツイート
シェア
あとで読む
ブックマーク
送る
メールで送る

JavaScript MVC フレームワーク Mithril をプロダクト導入した時の話

この記事はDeNA Advent Calendar 2016 5日目の記事です。

はじめに

初めまして、DeNA Games Osaka 技術編成部のさい(@sairoutine)です。DeNA Games Osaka は DeNA の大阪拠点です。今後ともよろしくおねがいしますね。

本記事では、JavaScript MVC フレームワーク Mithril を導入した際のお話をさせていただこうかと思います。

Mithril とは

Mithril とはクライアントサイドのMVCフレームワークです。2014年にLeo Horie氏によって公開され、現在も絶賛開発が進められています。(2016/12 現在、バージョンは0.2.5 です)

特徴として React と同様にレンダリングに仮想DOMを採用していること、またファイルサイズが軽く、処理速度/描画共に他フレームワークより速いことが挙げられます。

Mithril フレームワークについての詳細は手前味噌ですが、 Mobage Developers Blog にも記事がございますので、そちらも確認頂ければと思います。

最速フレームワーク Mithril 入門

採用経緯

ブラウザゲームにおけるレイドボスバトル(1つのボスを複数プレイヤーで攻撃する形式のバトル)において使用しました。このようなバトル画面では、下記の項目のように、変動する数値(一般に状態と呼ばれる)が多く、またバトルの攻撃演出を行う上で、CSSアニメーションを多用する特徴があります。

レイドボスバトルにおける状態の例一覧
1. ボスのHP
2. ボスの各種パラメータ(攻撃力/防御力etc...)
3. プレイヤーのデッキ
4. デッキの各ユニットのHP
5. 各ユニットの各種パラメータ
6. ユニットのスキル使用状況
etc...

担当プロジェクトでは、これまで tt.js という jQuery like な DOM 操作 JavaScript ライブラリを使って、ブラウザ上でのリッチなUXを提供してきましたが、今回の要件のように、多様な状態を管理するには、 DOM操作ライブラリ及び素の JavaScript では、状態を管理するコードを書くのは大変なため、JavaScript フレームワークを導入する運びになりました。

JavaScript フレームワークと言っても、BackboneAngular, React 等、多種多用なフレームワークが存在します。 その中でも Mithril は、処理速度が速くて CSS アニメーションを多用しても問題になりにくいこと、ブラウザ依存が少ないこと、そしてMithril フレームワーク自体のコード行数が2000行程度と少ないため、何らかのバグを踏んでも調査及びモンキーパッチを当てることが容易であることから、採用をしました。

Mithrilを採用して良かったこと

msx という HTML と親和性の高い View コンパイラ

React における jsx と同様に、Mithril においても msx という HTML like に仮想DOMを記述するためのコンパイラが存在します。 msx のおかげで、普段から HTML に慣れ親しんでいるマークアップエンジニアが View 周りを触ることが容易にできました。

MVC + 仮想 DOM がサーバーサイドエンジニアにとって親しみやすい

チーム体制として、専業のフロントエンドエンジニアがおらず、サーバーサイドエンジニアと マークアップエンジニア、そしてデザイナーによる開発でした。

元々 MVC というアーキテクチャに精通しているサーバーサイドエンジニアにとって、Mithril の MVC を習得することは容易でした。 また、仮想 DOM のおかげで、DOM の追加/変更/削除がロジック内に散らばらずに、View に DOM のあるべき姿を書くことができ、 これは、View 周りを PHP における Smarty テンプレートや、Node.js における Jade と同じような概念で書けるため、これも サーバーサイドエンジニアにとって習得が容易でした。

学習コスト低め

Mithril 自身が ES5 で記述されていることもあり、アプリケーションコードを ES5 or ES6 どちらでも記述することができます。 View については、msx という仮想DOM 記述のための独自構文を使用しましたが、それ以外については、 素の JavaScript で書けるため、例えば Angular のように独自の構文を覚えなくて済む点で、 本職のフロントエンドエンジニアでなくとも、学習が容易でした。

CSS アニメーションとの相性の良さ

バトル周りのアニメーションを実装するに当たって、HTML5 Canvas を使用する方法と、DOM を CSS3 Animation を使用する方法があります。 今回は、CSS を使ってアニメーションする方法を採用しました。CSS を使ったアニメーションは、基本的に DOM に css を適用することで アニメーションの実行が発生します。DOM の操作でアニメーションが行えるため、仮想 DOM の恩恵を最大限に受けることができました。

ブラウザ互換性の高さ

Mithril はブラウザ互換性が高く、es5-shim を併用することで、モバイル端末では、 iOS 4.x 系、Android 2.x 系などの古い端末でも動くことが確認できています。

課題点

msx の独自構文を覚えなくてはいけない点

良かった点として、msx が HTML like な点を挙げましたが、逆に言うと、あくまで HTML like であり、 HTML とまったく同じように記述できるわけではありません。

例えば、HTML の class 属性については、javaScript の予約語と重複するため、className 属性で記述する必要があります。また例えば br タグのような閉じタグのないタグについては、<br /> のように XHTML と同様に最後に / をつけないと msx のコンパイルに失敗する等といった HTML との差異があります。

仮想DOM という概念の難しさ

仮想DOMから実DOM へのレンダリングは全て Mithril が担当してくれます。このおかげで、 我々は仮想DOMを記述するだけで、実DOMをレンダリングすることができます。 しかしCSS アニメーションを多用する関係上、仮想DOMから実DOMへいつどのタイミングでレンダリングされるのか、 どのDOMが更新され、どのDOMが更新されないのか、というのをきちんと把握しながら実装する必要があるのですが、 このあたりは Mithril フレームワークのコードを読み解いていく必要がありました。

終わりに

以上、Mithril を導入した際のお話をさせていただきました。 ブラウザゲームにおける採用であったり、本業のフロントエンドエンジニアがいない中での実装であったり、 となかなか特殊な要件での採用でしたが、Mithril は要件とチームの現状に即した採用だったと思います。

仮想DOM といえば、React がある程度の立ち位置を確保してきた感じがありますが、 もし機会ございましたら、ぜひ Mithril も検討してみてください!

続きを読む
ツイート
シェア
あとで読む
ブックマーク
送る
メールで送る

レガシーなwebページをAngularで書き換えてみて良かったこと

この記事はDeNA Advent Calendar 2016 3日目の記事です。

はじめまして。DeNAでエンジニアをしています。平野です。 かつては大規模プラットフォーム決済システムのサーバーサイドを、 今は"マンガボックス"のサーバー/クライアントサイドを担当しています。

この記事では、 マンガボックスのアプリ内webviewで展開している機能をブラウザにも展開するために、 クライアントサイドをAngularで、サーバーサイドもそれに即すように作り変えた時の基本的な構成の紹介とちょっとしたTipsを記載しています。

自分と同じAngular初学者の方や、既存のサービスをAngularで書き換えるとなんかいいことあるんか?的な方の参考になれば幸いです。

背景

結論に行く前に軽く背景の説明です。レガシーなwebページという若干煽り気味な名前ですが、自サービスの構成のことを指しました。 マンガボックスのiOS/Androidアプリ上で提供している機能のうち、一部はアプリ内webviewで動いており、 とあるURLを叩けばHTMLが返ってくる、たまにAJAXで動的にHTMLを組み換え最終的にwebviewがレンダリングするといういわば普通のwebサイトな構成になっています。

今回その部分をアプリ外のSafariやChromeなどのブラウザでも使えるようにしたいということで、 既存のwebviewが叩いてるURLをブラウザからも叩けるように改修するという方針ではなく、このご時世だしクライアント側にMVCの概念を持たせる構成で再実装してみようというワリとTRY的な位置づけでプロジェクトがはじまりました。

結果的に良くなったこと

  • サーバーとの通信量が減ったことで体感速度が向上した
    • 旧構成では表示する分のHTMLを常にレスポンスとして要していた
    • angularでは表示に必要なデータ部分のみをレスポンスとして受け取り、事前に取得したHTMLにバインドする
  • アプリのwebview部分のネイティブ化への足がかりができた
    • アプリ側が今回用意したAPIを叩くように改修をすれば理屈上ネイティブ化が可能

以上の理由を今回のAngularの構成を紹介しつつ以下で説明していきます。

システムの構成

まず旧構成です。 以下の概念図のように、1画面1エンドポイントになっており、サーバー内で必要な情報をかき集めて最終的にHTMLを返しています。 not_angular.png

angularではこれが以下のようになります。 angular.png まず / へのアクセス時にangularのコードが返ってきますので、これをクライアント側が実行することになります。 その後 /#/top/ にアクセスすると対応したangularのcontrollerが必要な情報毎にサーバーに対してリクエストを送ります。 そして返ってきたJSONをすでに取得済みのHTMLにバインドしてレンダリングを行います。

またこのような構成にすることで、旧構成ではHTMLが返ってくるために困難だったサーバー側のend to endのテストもイメージしやすくなるはずです。

逆に改善したいこと

  • リリース時の考慮ポイントが増える
    • クライアント側またはサーバー側の挙動が変わるリリースの場合、クライアント側の状態を保証できないため
    • 後方互換性を意識した実装が必要
  • seo対策
    • クローラーがjavascriptを認識できない
    • 仮のHTMLを返す等の対応が必要

Tips

ブラウザバックの検知

静的なデータを扱う場合はブラウザバックで戻ってきたときに再度サーバーにデータを問い合わせる事は不要です。 AngularではAPIのレスポンスをキャッシュすることができるのですが、通常の設定では任意のAPIのキャッシュを常にするorしないのどちらかしか設定することはできません。 なのでブラウザバック等で戻ってきた時とそれ以外の時を以下のコードで判別して、その都度APIのキャッシュをするしないを設定しています。


myApp.run(function($rootScope, $state, $location, $document){
    $rootScope.$on('$locationChangeStart', function() {
        $rootScope.use_cache = ($rootScope.newLocation == $location.path()) ? 0 : 1;
    });

    $rootScope.$watch(function () { return $location.path(); }, function (newLocation, oldLocation) {
        $rootScope.oldLocation = oldLocation;
        $rootScope.newLocation = newLocation;
    });
});

おわりに

Angular化における良かった事悩ましい事をいくつか紹介させていただきました。 クライアント側にMVCモデルの概念を持ち込むことで実装上の役割が明確になり、非常にスッキリとコードを書けたことが印象的でした。 Javascript界隈は次から次へとトレンドが移り変わるイメージがありますが、このタイミングに乗れてよかったと思いました。と同時にこれからはもっと早めに乗っかろうとも思いました。

明日は4日目、key-amb さんです。 お楽しみに!

続きを読む
ツイート
シェア
あとで読む
ブックマーク
送る
メールで送る

CreateJS 互換ライブラリ wahid の開発

このブログは「mobage developers blog」2016.10.3の記事を転載させていただきました。


こんにちは、システム本部の坊野です。

多くの方々のご協力をもちまして 9 月末にCreateJS 互換ライブラリ wahidをオープンソースで公開することができました。

本日はこの wahid の開発の話をしようと思います。

名前の由来

開発の話に入る前にこの wahid という名前の由来についてお話します。

この wahid という単語は、アラビア語で「(数字の) 1」を意味する単語 واحد をアルファベット表記にしたもので、 CreateJS 互換ライブラリ CreateJS-Lite (仮) の開発コード名でした。

しかし、この CreateJS-Lite (仮) をオープンソースで公開することになったときに CreateJS-Lite (仮) のままだとCreateJS と混同される恐れが出てきたため、区別しやすくするためこの wahid という名前を用いて公開することになりました。

開発の背景

そもそも CreateJS とは gskinner という会社が開発した JavaScript ライブラリです。

CreateJS を用いることによって、 Adobe Flash Professional (現 Adobe Animate CC) で作成された Flash アニメーションを JavaScript (HTML5) 形式で出力できます。

このため、 CreateJS は Flash アニメーションを用いたゲームでもよく用いられています。
しかし、当時
 CreateJS はあまりゲームに特化した設計でなかったため、 CreateJS を用いたゲームをスマートフォンで実行した場合十分なパフォーマンスが発揮できないという問題がありました。

このため、目的を「ゲームのパフォーマンス向上」に特化して CreateJS を再実装することになりました。

開発の方針

このような経緯で CreateJS を再実装することになったのですが、開発に関していくつかの懸念点がありました。その中でも一番大きいものが「CreateJS との互換性をどうするのか」というものでした。

結論からいいますと、私たちは「CreateJS との互換性に注力しない」ことにしました。なぜなら、私たちは「CreateJS との互換性を重視してパフォーマンスを低下させるよりも互換性を犠牲にしてでもゲームのパフォーマンスを向上させるべきだ」と思ったからです。

実際のところ、互換性テストを見てみると、現在においても wahid の CreateJS との互換性はあまり良くありません...というかかなり悪いです。とはいえ、実際の CreateJS を用いたゲームを見てみますと (ゲーム開発者のみなさんのご協力のおかげで) 互換性テストで見るほどの違いはないと思います。

また、パフォーマンスに関しても残念ながら「すべてのブラウザで向上した」とまでは言えませんが、多くのスマートフォンのブラウザにおいては向上していると思います。

補足ですが、この「CreateJS との互換性に注力しない」は、決して「wahid は CreateJS との互換性を軽視している」ということではありませんし、私たちは常に互換性向上のための努力を行っています。しかし、残念ながら私たちの勉強不足のため、互換性問題を修正する方法としてパフォーマンスを犠牲にする方法しか思いつかない場合があり、パフォーマンスを犠牲にしなくても解決できる方法を思いつくまでその互換性問題を修正しないという選択をしました。

実装の過程

この wahid の実装にあたり、私たちはゲーム開発者のみなさんのご協力のもと実際のゲームにおいて CreateJS がどのように利用されているのか詳しく解析しました。その結果としてたくさんの有益な情報が得られたのですが、その中でも「ゲームは CreateJS のすべての機能を利用している訳ではない」ということは大変貴重でした。
なぜなら 
CreateJS は非常に大きなライブラリなのでその全てを再実装することは現実的ではなかったからです。

また、この解析結果をうけて、私たちは WebGL を用いて「ゲームで利用されている CreateJS の機能をエミュレートする」という方針で実装することにしました。もっと率直に言えば「WebGL を用いて高速実行可能な CreateJS の機能のみを実装する」ということです。

WebGL はスマートフォンのネイティブゲームなどで用いられている OpenGL ES 2.0 という API をブラウザ上で実装したもので、非常に高速に動作します。他方 WebGL は CreateJS が用いている Canvas 2D という API とは非常に異なる API のため、WebGL を用いて実装すると CreateJS との互換性を保障するのがより困難になります。このように WebGL を利用することには利点と欠点が両方存在していたため、私たちは実際にゲームの解析を行うまでWebGL を利用するべきかどうか決めかねていました。

しかし、実際のゲームを解析した結果、ゲームにおいて利用されている CreateJS の機能に限れば、それらを WebGLでエミュレートすることは十分可能と思われましたし、何よりも「ゲームのパフォーマンス向上のためなら少々の互換性問題は私たちの方でカバーする」というゲーム開発者のみなさんのサポートのおかげで、私たちはあえて WebGL を用いて wahid を実装することにしました。

ゲームへの組み込み

実装後も CreateJS の代わりに、 wahid をゲームに組み込んでリリースされるまでには、さまざまな問題が発生しました。もちろん、これらの問題の大半は wahid で対処すべき問題だったのですが、中にはゲーム側で対応したほうが良いと思われる問題もありました。

たとえば CreateJS で色変換を行うと非常に遅いため、色違いの複数の画像を用意しているゲームがあり、それらの画像が大量のメモリを消費しているという問題がありました。 (補足ですが、一般的に wahid は CreateJS よりも多くのメモリを消費します。) 他方 wahid では、色変換は非常に高速なのでそれらの色違いの画像を使わないほうが画像の読み込みが高速になりますし、メモリも節約できます。

ですから、この問題に対してはゲーム側で対処したほうが良いと思われたので、ゲーム開発者のみなさんに事情を説明してゲーム側で対処していただくことにしました。

また、問題の中にはあえて CreateJS との互換性を犠牲にしたほうがよいと思われるものもありました。たとえばCreateJS は画面をタップしたとき、画像のピクセルデータの読み込みをおこなってタップした位置にあるオブジェクト (画像) を取得します。
この方法は非常に高い精度で判定できるのですが、画像のピクセルデータの読み込みはブラウザでは非常に遅いため、ゲームのようなアプリケーションで行うとパフォーマンスが低下します。

このため、ゲーム開発者に目的を尋ねたところそこまで高い精度を必要としていなかったため、wahid では画像のピクセルデータの読み込む代わりに画像の表示位置と大きさを用いることによって、精度は犠牲にするかわりに高速に判定できる方法を提供することにしました。 (もちろん CreateJS と同様に画像のピクセルデータの読み込みをおこなって判定することもできます。)

他にも、ゲームに wahid を組み込む際には、本来の CreateJS の機能ではないのですが、追加したほうがよいと思われる機能も見つかりました。

たとえば、多くのスマートフォンのブラウザはタップをしないと音を鳴らないようになっているためスマートフォン用ゲームでは最初のタップ時に音を鳴らすという処理をするのが一般的です。

とはいえ、この処理の実装方法はスマートフォンの OS によって微妙に異なるためすべてのスマートフォンに対応するのは大変な労力が必要です。このようなゲームにおいて一般的な (共通の) 処理はライブラリ側で行ったほうがゲーム開発者が楽になると思われたため、私たちは最初のタップ時に音を鳴らす機能を wahid に追加しました。

このように CreateJS の代わりに wahid を利用すると、さまざまな問題が発生しましたし、これからも発生すると思われます。しかし、先に書いたとおりそれらの中はゲーム開発者のみなさんで対処していただいたり CreateJS との互換性を犠牲にしたほうが結果としてゲームのために良いと思われるものもあります。

ですから、問題を発見した場合は私たちがみなさんのゲームにとって最適な方法を見つけられるようにご協力いただければと思います。

さいごに

多くの方々のご協力によって、ここに wahid をオープンソースで提供できるようになりました。
この場をもちまして、改めて今までご協力いただいたすべてのみなさんに感謝いたします。

また、先に書いたとおり、私たちの目的はあくまでも「ゲームのパフォーマンス向上」です。
とはいえ、実際にゲーム開発をしているみなさんの協力なくしてこの目的を達成することは困難ですし、そもそもゲームによって最適な方法自体異なると思われます。

つまり「ゲーム開発者のみなさんのご協力あってこそ私たちがゲームにとって最適な方法を提供できる」ということです。

ですから、私たちはゲームをより良くしていくためにこれからも wahid だけでなくゲーム開発者のみなさんと協力して様々な方法を提供していければと思います。

続きを読む
ツイート
シェア
あとで読む
ブックマーク
送る
メールで送る

nodeconf.eu 探訪記

こんにちは、Engineer Blog 初投稿です。 @yosuke_furukawaです。 この度、国際学会派遣制度を使ってnodeconf.euへ行ってきたのでそれの紹介です。

nodeconf.eu とは?

スクリーンショット 2015-01-13 11.45.44.png

node.jsのカンファレンスの一つです。ヨーロッパ(EU)のアイルランドで行われるnode.jsカンファレンスなので、nodeconf.euと呼ばれています。

nodeconf.eu特徴

nodeconf.euはWaterford Castle Islandという離島で行われます。この離島がこの一日だけは名前が変わって "NodeLand" になるんです。市長にも認められた公的な島の名前です。

nodeconf.euで語られてること

主に3点です。もちろんこれら以外にもnode.jsの次のバージョンについて、とかnpmの話とかあるんですが、現在進行形でnode.jsの次のバージョンがどうなるかは未定の状態なので一旦置いておきます。

  • microservices
  • next web application
  • Internet of things

microservices

モノリシックなアプリを作るんじゃなくて、小さいサービスをたくさん作って結合するというアプローチ。基本的にはデータストアは一個である必要はないし、言語も一つである必要はない。サービスに合わせて適したデータストアや言語を使うという考え方ですね。

サービス間のメッセージングにはMQTTやAMQPといったメッセージキューを使ってやりとりする、といった方式が話されていました。

Node.jsを使っている会社には、IBM、PayPal、Netflixなんかの大手企業が使っており、そこでの使い方の話でした。

Node and Micro-Services at IBM from Dejan Glozic

またmicroserviceのフレームワークとしてGraft と呼ばれるサービスが紹介されていました。

日本でも流行りつつあるbuzzwordとしてのmicroservicesが向こうでは実例とツールを伴って紹介されていたのが特徴的でした。

Next Web Application

WebRTC, WebSocket, WebGLといった次世代Web規格の話と最近話題のIsomorphicの話が語られていました。

WebRTC

動画も一緒に見ていただけると分かりやすいと思いますが、WebRTCを使うことで、音声や動画といったストリーミングデータを多人数で共有することができます。動画はホワイトボード共有 + video 共有をWebで実現する例です。

発表している @feross はNode.jsでは有名な人物で、彼が作っているwebtorrentはweb上でWebRTCの技術を使ってP2P通信でファイル共有する仕組みです。bittorrentのトランスレータを現在作成中らしく、それが完成すればbittorrentがweb上でも動くし、シームレスにbittorrentとwebtorrentのファイル共有ができるようになるでしょう。

Isomorphic

strongloopのloopbackやhapiといったウェブアプリケーションフレームワークが次のフェーズとして見据えているのがこのIsomorphicで、クライアントでのレンダリングとサーバーでのレンダリングを同じコードで共通化して出来るように試みていました。これが完成すると、サーバー・クライアントでのviewのロジックが共通化でき、validationもロジックがサーバーとクライアントで共通化できるようになります。

Internet of things

所謂ハードウェアやセンサーデータがhttpを解す事でウェブ上から色んな物を操作しようとする試みです。これは非常に面白くて、ロボット作ってhttpリクエストで動作させたり、クアッドコプターをwebrtcを使って動かしたりといった事を行っていました。これも動画を貼っておきます。

まとめ

nodeconf.euに行くことで海外のエンジニアがどういう未来を描いているのかがわかりました。また、海外のカンファレンスへ行くと自分がpull requestしたプロダクトを作っている人に生でお礼を言われたり、rejectした時の理由を聞けたりするのですごくためになりました。

こういう人脈を作れることが海外カンファレンスに行く一番の魅力だと思います。

続きを読む
ツイート
シェア
あとで読む
ブックマーク
送る
メールで送る

JSDoc Toolkit→JSDoc 3移行ガイド

こんにちわ、DeNA San Francisco の渋川と申します。エンジニアブログには初登場です。

JavaScriptのソースコードからAPIドキュメントを生成するツールには何種類かありますが、日本語の書籍やウェブサイトでも情報が得やすいこともあって、JSDocの系統が幅広く使われています。Google Closure Compilerも、JSDocを拡張したドックコメントをアノテーションとして読み込んで最適化します。

JSDocは長い期間メンテナンスされているオープンソースプロダクトです。初代はPerlで書かれたJSDoc 1です。これはすでにリポジトリも削除されています。現在最も使われているのがJSDoc Toolkit (JSDoc 2)です。弊社のngCoreのドキュメントでも使用しています。

ただ、これも現在は機能追加を停止していて、後継プロダクトのJSDoc 3が開発されています。今年の3月に入ってJSDoc 3自体のドキュメントもほぼ整備されたため、今後は実践で使われることが多くなりそうです。

JSDoc Toolkitと比較すると、JSDoc 3はタグが整理されていたり、JavaScriptで多様されるイディオムを表現するための新しいタグが追加されていたりします。以前も拡張機能を使ってタグを増やすこともできましたが(ngCoreのドキュメントのコールバックの説明が独自拡張のタグ)、はじめからコールバックの記述もできるようになっていたり、コーディングの意図を伝える表現力は以前よりも増しています。

JSDoc 3は、タグが整理されたことによって、JSDoc Toolkitとは後方互換性がなくなっています。タグによって名前や意味が変わったり、パラメータが変わったりしています。JSDoc 3自体のドキュメントには互換性に関するまとまった説明がなかったため、業務で開発しているJavaScriptのコードのドキュメントをJSDoc ToolkitからJSDoc 3に切り替えた時は、新旧のドキュメントを見比べて手探りで移行しました。このエントリーではその時の経験を元に、JSDoc ToolkitのコメントをJSDoc 3にどのように対応させればいいのかを紹介します。

続きを読む
ツイート
シェア
あとで読む
ブックマーク
送る
メールで送る

Android Bazzar and Conference 2011 Winter のスライドを公開します

と言う訳で新春のスライド公開の最後です。2011/1/9 に行われました Android Bazzar and Conference 2011 Winter にて私の方で講演致しました ngCore engine for mobage platform のスライドを公開致します。

続きを読む
ツイート
シェア
あとで読む
ブックマーク
送る
メールで送る

DeNA Technology Seminar #3 のスライドを公開致します

新年早々、スライド公開祭りの第2弾です。

こちらは2010/11/15に行われました DeNA Technology Seminar #3 のスライドです。今回はJavaScript と UI 特集と銘打って行われました HTML5 や SmartPhone 向けの開発についての話です。

続きを読む
ツイート
シェア
あとで読む
ブックマーク
送る
メールで送る