webアプリにてHTMLビューの作成はサーバーサイドとクライアントサイド、どちらでも可能です。迷って色々調べた所面白いフレームワークが見つかったので紹介します。
##サーバーサイド?クライアントサイド?
サーバーサイドレンダリングのサーバーはRESTful APIに専念して、極力HTMLビューの作成をさけ、クライアント側に任せるアーキテクチャーで、2010年時のツイッターがそうです。
クライアントレンダリングの問題点として、よく取り上げられるのが
- リクエストはクライアント側からサーバ側に、一往復して帰ってくる、ネット状態によっては往復する分時間がかかる。
- curl、又はJavaScriptが制限されたブラウザではアクセス不能。
- 同様、サーチエンジンと相性が悪い、SEOに不便。
で、ツイッターは2012年からレンダリングを大半サーバーサイドに戻した、とブログに書かれてます。
それに対してLinkedinの場合、クライアント側でjsonからHTMLへとレンダリングする事自体は軽い、そもそもHTMLをクライアントに転送する方が遅いと評価し、duskjsを中心にしてアーキテクチャーを築き上げた。
サーバー/クライアント両方でレンダリングしたいが、見知らぬライブラリに頼り切るのはどうだろう、また何の位まで使いこなせるかが問題。そこで今回見つけたのがOtter
##Otter
Otterはシングルページアプリに特化されたウェブサーバで、その特点は
- サーバー/クライアント、両方同じAPIを使う。
- DOM操作は馴染のあるjQueryで
- HTTPはそのままXMLHTTPRequestで
- 一回書いたコードはサーバー/クライアント両方で使う
公式サンプルではこの様なBackboneアプリで説明してます。動くサンプルはこちら。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<div class="content"></div>
</body>
<script src="/vendor/jquery-1.8.1.min.js"></script>
<script src="/vendor/underscore.js"></script>
<script src="/vendor/backbone.js"></script>
<script src="/vendor/backbone-otter.js"></script>
<script src="/app.js"></script>
</html>
var User = BackboneOtter.Model.extend({
url: function() {
return 'http://api.twitter.com/1/users/show/'+this.id+'.json?callback=?';
},
});
var BaseView = Backbone.View.extend({
hasRendered: function() {
return this.$el.children().length > 0;
},
render: function() {
this.$el.html(this.template(this.templateData()));
return this;
},
templateData: function() {
return {};
}
});
var IndexView = BaseView.extend({
template: _.template('<h1>Twitter!</h1><a href="users/14510231">bfirsh</a> <a href="users/14149919">aanand</a>')
});
var UserView = BaseView.extend({
template: _.template("<% if (name) { %><h1><%= name %></h1><p><%= description %></p><p><%= location %></p><% } else { %><p>Loading...</p> <% } %>"),
initialize: function() {
this.model.on('change', this.render, this);
},
templateData: function() {
return this.model.attributes;
}
})
var Router = Backbone.Router.extend({
routes: {
'': 'index',
'users/:id': 'user'
},
initialize: function() {
this.on('route', function() {
this.hasRouted = true;
});
},
index: function() {
var view = new IndexView({el: $('.content')});
// Render this view if we are not on first route or server has not filled content.
if (this.hasRouted || !view.hasRendered()) view.render();
},
user: function(id) {
var user = new User({id: id});
user.fetch();
var view = new UserView({el: $('.content'), model: user});
if (this.hasRouted || !view.hasRendered()) view.render();
}
});
$(function() {
var router = new Router();
Backbone.history.start({pushState: true});
$(document).on('click', 'a', function(e) {
e.preventDefault();
router.navigate($(this).attr('href'), true);
});
});
##404を出さないサーバ?
理想的なシングルページアプリケーションを実現するため、Otterは存在しないリページをリクエストされても404を返さず、ヘッドレスブラウザのzombie.jsを使ってindex.htmlを開きます。indexのJavaScript routerによりレンダリングが終わるとdocument.outerHTML
をクライアントに送ります。これによりウェブクローラなどJavaScriptが制限されてるブラウザからでもサーバーサイドレンダリングによってコンテンツがちゃんと見えます。
ココまで来たらもうお分かりでしょうがOtterの売りは、既存のアプリケーションを変更せずクライアント側のフレームワークをサーバー側で使える事です。
##それなら普通のサーバーサイドレンダリングじゃ無いのか?
いいえ、クライアント側ではBackbone.history.start
が作用してクライアントサイドレンダリングが働きます。
しかもサーバー側で一度fetch
したModelをwindow.otter.cache
にプロパティに指定すればクライアント側からもアクセスする事が出来ます。
サンプルサイトでbfirsh
をクリックするのとクライアントサイドレンダリング、実在しないusers/14510231を使ってアクセスするとサーバーサイドレンダリングになります。コンソールを使ってwindow.otter.cache
を確かめて下さい、サーバー側のキャシュ情報が残っています。