2014年5月6日火曜日

超チューニング祭に参加してきた

2014/04/26(土) - 04/27(日)に開催されたニコニコ超会議3の、超チューニング祭に参加してきました。

SP版ニコニコ動画のトップページをチョッパヤにしてくれ、という課題に友人と二人で二日間かけて取り組んできました。

特に賞には引っかからなかったのですが、せっかく参加したので今回行った高速化の施策を軽く紹介しようと思います。

  1. jQueryからZepto.jsに変更
  2. JavaScriptの結合・圧縮
  3. 画像をCSSスプライトにまとめる
  4. 画像を減色
  5. HTMLの書き直しと圧縮
  6. CSSの書き直しと圧縮
  7. ファーストビューより下の画像を遅延読み込み

1. jQueryからZepto.jsに変更

共通ライブラリとして読み込まれていたjQueryをZepto.jsに変更しました。
これだけで、68KBくらい削減できます

2. JavaScriptの結合・圧縮

トップページだけで10個ものJavaScriptファイルが読み込まれていたので、uglify.jsを利用して1ファイルに結合・圧縮してしまいました。
また、アカウント設定なしでGoogle Analyticsのurchin.jsが読み込まれていたので削除してしまいました(なお本番のトップページでもアカウント設定なしで読み込まれている模様)。そもそもurchin.jsとかかなり古いコードなのでやめてほしいですね…

3. 画像をCSSスプライトにまとめる

サムネイルとか動的に切り替わる画像以外はCSSスプライトにまとめると、リクエスト数が減っていい感じです。
画像のスプライト化には自作のGruntタスクであるgrunt-spritesheet-generatorを利用しました。

4. 画像を減色

imageoptimとimagealphaを使って画像を減色します。特にpng画像はimagealphaを使うことで60-70%ほどサイズを削減できます。

5. HTMLの書き直しと圧縮

UIの改善のための書き直しと併せて、タグのネストを浅くしたりしました。
その上でgrunt-contrib-htmlminを使いコメントや改行コード、不要なスペースを削除しました。

6. CSSの書き直しと圧縮

grunt-cssoを使ってcssの圧縮をしました。cssoを利用すると、コメントや空行の削除だけでなくcssの構造的な最適化まですることができます。
また、SASSに書き直して利便性を上げるとともに未使用のCSSプロパティを消しています。

7. ファーストビューより下の画像を遅延読み込み

CSSスプライトにまとめられない画像(バナーや動画のサムネイルなど)は、ユーザーのスクロールに合わせて遅延読み込みされるようにしています。こうするとファーストビューより下の画像のリクエストを遅らせることができます。表示領域に入らなかった画像はリクエストされることもありません。この機能の実装にはlazyloadというライブラリを使っています。


以上です。

js/css/画像をすべてインラインで書いてしまいリクエストを一つにする、とか思いもよらない(でも言われてみれば合理的)な施策があったり、なかなか勉強になりました。(個人的には画像をローカルストレージにキャッシュするのが面白かったです)

他の人と同じ課題をもらって実装を競うとかしたことのない経験だったのでなかなか面白かったです。
次こんな機会があったらLTもしてみたいですね。

※一応実際のコードもおいておきます

参考(他の参加者の記事):
- 本の虫: 超会議3の超チューニング祭の感想
- レポート - 超チューニング祭で努力賞(最速賞)をとるためにやったこと - Qiita
- ニコ動 超チューニング祭で最優秀賞もらいました
- 超チューニング祭に参加した - masarakki's blog
- JavaScript - 超チューニング祭に参加&表彰した - Qiita
- kmizu/slide_cho_tuning
- 超会議の偏った感想 - mizchi's blog

2014年4月25日金曜日

Homebrewで入れたNode.jsのnpmが更新できない時にすべきたった一つのこと

一度Node.jsをアンインストールして、再インストールしましょう。

HomebrewからインストールしたNode.jsのnpmを npm update で更新しようとすると、下記のようなエラーが出る事があります。

npm ERR! error rolling back Error: Refusing to delete: /usr/local/bin/npm not in /usr/local/lib/node_modules/npm
npm ERR! error rolling back     at clobberFail (/usr/local/Cellar/node/0.10.15/lib/node_modules/npm/lib/utils/gently-rm.js:41:12)
npm ERR! error rolling back     at next (/usr/local/Cellar/node/0.10.15/lib/node_modules/npm/lib/utils/gently-rm.js:27:14)
npm ERR! error rolling back     at /usr/local/Cellar/node/0.10.15/lib/node_modules/npm/lib/utils/gently-rm.js:36:12
npm ERR! error rolling back     at Object.oncomplete (fs.js:107:15)
npm ERR! error rolling back  npm@1.3.7 { [Error: Refusing to delete: /usr/local/bin/npm not in /usr/local/lib/node_modules/npm] code: 'EEXIST', path: '/usr/local/bin/npm' }
npm ERR! Refusing to delete: /usr/local/bin/npm not in /usr/local/lib/node_modules/npm
File exists: /usr/local/bin/npm
Move it away, and try again.

参考: Error: Refusing to delete: /usr/local/bin/npm #3794

これ、8ヶ月くらい前(2014/04現在)からずっと問題になっていたんですが、3週間ほど前に解決したようです。

参考: node: install npm using tarball. #28075

解決方法は簡単。HomebrewからインストールしたNode.jsを一旦削除して、再インストールするだけ。

brew uninstall node
brew install node

これでnpmが npm update で更新できるようになります。

2013年12月25日水曜日

[FreeMarker] ftl名前空間のすゝめ

つい最近知ったのですが、ftlにも名前空間があります。
例えば、ある画面にマクロを定義しているFTLを複数読み込む時、そのFTL毎に名前空間を分ける、ということができます。

名前空間の使い方

これの何がいいかというと、名前空間を分けると各FTLで定義した変数名やマクロ名が衝突しなくなります。
library-a.ftl で foo というマクロを定義し、 library-b.ftl でも同名のマクロを定義し、この2つのFTLを同じ画面で読み込み、それぞれのマクロを同時に、別のマクロとして使うことができるようになります。

<#-- library-a.ftl -->
<#macro copyright date>  
    <p>Copyright © $(date) John Smith. All rights reserved.</p>
</#macro>  

<#assign mail = "jsmith@acme.com">

上記のライブラリFTLを lib/library-a.ftl とします。
そして、このライブラリFTLを top.ftl で利用します。

<#inclde "/lib/library-a.ftl">

このように書くと、 copyright マクロと mail 変数がグローバルな名前空間に定義されてしまいます。
これだと、仮に top.ftl ですでに copyright という名前のマクロや mail という変数を定義していると、名前が衝突してエラーになってしまいます。(変数は同じものとして扱われます)

これだと、再利用可能なコードとは言えません。
そこで、 import という、別の構文を使ってライブラリFTLを読み込みます。

<#-- top.ftl -->
<#import "/lib/library-a.ftl" as libA>
<@libA.copyright date="2012-2013"/>
${libA.mail}

<#-- 出力内容
<p>Copyright © 2012-2013 John Smith. All rights reserved.</p>
jsmith@acme.com
-->

import を使ってライブラリFTLを読み込むと、新しい名前空間が作成されます。
この新しい名前空間からは、ライブラリFTL内で定義された変数か、グローバルで定義された変数か、データモデルの変数(Javaのアクションクラスから渡された変数)しか見ることができません。

この新しい名前空間にある2つの変数(マクロ)に top.ftl からアクセスするには、 import 構文で as の後ろに指定された名前を使います。この場合、上のコードで示したように、 libA.copyright, libA.mail という形で library-a.ftl で定義された変数やマクロにアクセスできます。

このように import 構文を使ってFTLを読み込むと、たとえ top.ftl で同名の変数やマクロが定義されていたとしても衝突してエラーにはなりません。

別の名前空間内の変数を編集する

<#-- top.ftl -->
<#import "/lib/library-a.ftl" as libA>
${libA.mail}
<#assign mail="jsmith@other.com" in libA>
${libA.mail}

<#-- 出力内容
jsmith@acme.com
jsmith@other.com
-->

データモデルと名前空間

データモデルの変数はどこからでも参照できます。
例えば user という名前の変数がデータモデルにあった場合、lib/library-a.ftl はこの user 変数にアクセスできます。

<#-- library-a.ftl -->
<#macro copyright date>  
    <p>Copyright © $(date) ${user}. All rights reserved.</p>
</#macro>  

<#assign mail = "${user}@acme.com">

user が"Fred"だった場合、下記のようになります。

<#import "/lib/library-a.ftl" as libA>
<@libA.copyright date="2012-2013"/>
${libA.mail}

<#-- 出力内容
<p>Copyright © 2012-2013 Fred. All rights reserved.</p>
Fred@acme.com
-->

ただし、名前空間内でデータモデルと同名の変数が宣言されていた場合、後者が勝ちます。

名前空間のライフサイクル

同じFTLを別名で複数回 import で読み込んだ場合、名前空間は最初の読み込みに対してのみ作られ、以後の読み込みは同一の名前空間に対する別名となります。

<#import "/lib/library-a.ftl" as libA>
<#import "/lib/library-a.ftl" as foo>
<#import "/lib/library-a.ftl" as bar>
${libA.mail}, ${foo.mail}, ${bar.mail}
<#assign mail="jsmith@other.com" in my>
${libA.mail}, ${foo.mail}, ${bar.mail}

<#-- 出力内容
jsmith@acme.com, jsmith@acme.com, jsmith@acme.com,
jsmith@other.com, jsmith@other.com, jsmith@other.com,
-->

グローバル変数とローカル変数

FTLにもグローバル変数とローカル変数があります。

assign(ローカル変数)

<#assign foo="bar">

変数の定義や置き換えができます。
特定の名前空間内に変数を作成したり、置き換えることもできます。
(<#assign someValue = "foo" in someNamespace>)

assign を利用して宣言された変数は、名前空間が変わると参照できません。

global(グローバル変数)

<#global foo="bar">

global を使って定義された変数は、すべての名前空間で参照できます。 しかし、個々の名前空間内で同名の変数が定義されていると、その名前空間からはグローバル変数の方にはアクセスできません。
ただし、その場合でも globals という特別な変数を介してアクセスすることはできます。

${.globals.foo}

globals を介してグローバル変数にアクセスできるだけでなく、データモデルの変数にもアクセスすることができます。

local(ローカル変数)

<#local foo="bar">

local 構文はマクロや関数の中でのみ使用することができます マクロ/関数内で local を利用して作られた変数は、そのマクロ/関数外からは参照できません。

つまりどうしたらいい?

  • ライブラリFTLを読み込むときは import で読み込む
  • マクロから参照したい変数が親FTLにある場合、 global を使って宣言する
  • マクロや関数内では、特に意図した場合でない限り local を使って変数宣言する

参考

FreeMarker Manual - Namespaces
FreeMarker Manual - local
FreeMarker Manual - assign
FreeMarker Manual - global

2013年12月5日木曜日

HomebrewからSubversion 1.7をインストールする

普通に brew install subversion してしまうと1.8がインストールされてしまうので、下記のようにする。

brew tap hombrew/versions
brew info subversion17
brew install subversion17

これでSubversion 1.7がインストールされる。

2013年11月19日火曜日

[Android]動的にリソースを取得する

ステータスバーにバッテリー残量を表示させようとして詰まったのでメモ。
動的にリソースを変更したい時はこうすると捗る。

getResources().getIdentifier(resourceName, resourceType, getPackageName());

上記コードでリソースのIDが取得できる。
resourceNameにはリソースの名前を、
resourceTypeにはリソースのタイプ(string, id, drawable…)を
指定する。

ちなみに、

getResources().getIdentifier("@drawable/ic_launcher", null, getPackageName());

みたいにリソース名にタイプを含めちゃっても取得できる。

[Android]特定のPreferenceに依存するPreferenceをつくる

特定のスイッチがオンの時に有効になって、オフの時には使えないスイッチを作る方法です。
ガリガリコード書かなきゃいけないのかと思ったら、xmlの設定だけでいけました。

<SwitchPreference
    android:key="pref1"
    android:title="pref1_title"
    android:switchTextOff="OFF"
    android:switchTextOn="ON"
    android:defaultValue="true"/>

<SwitchPreference
    android:key="pref2"
    android:title="pref2_title"
    android:switchTextOff="OFF"
    android:switchTextOn="ON"
    android:defaultValue="true"
    android:dependency="pref2"/>

android:dependency="foo" と指定すると、指定されたkeyを持つPreferenceの状態に依存するPreferenceが作れます。

2013年10月3日木曜日

Chrome 30で「新しいタブ」を従来のものに戻す方法

最近のChromeのアップデートで「新しいタブ」のデザインが刷新された。 タブの真ん中に検索ボックスを配置するのは明らかに初心者を意識した変更だけど、オムニボックスから検索が可能なことがわかっていれば改悪である。

なによりも、右下の「最近閉じたタブ」がなくなってしまったのが非常に不便でならない。

というわけで、もとの「新しいタブ」に戻してみた。 やり方は簡単。

  1. オムニボックスから chrome:flags にアクセス

  2. 「Instant Extended API を有効にする」オプションを「無効」にする

以上。 これで見慣れた「新しいタブ」に戻った。