ServiceWorker で、ネットワークから取得しようして失敗したらキャッシュにフォールバックするやつ

ここ最近、ServiceWorker を使って色々試しているのだけど、その中のひとつ。

以下の内容を、エントリーの HTML や参照系の API に適用して、アプリケーションのオフライン対応を実現する。通常はネットワークから取得した最新の内容を使うが、オフラインやネットワークが不安定な場合はキャッシュの内容を使う。

  • ネットワークから取得しようとして失敗したらキャッシュを使う
  • ネットワークから取得するとき、1秒のタイムアウトを設定する
  • ネットワークから取得しようとして成功したらキャッシュに保存する
  • ネットワークから取得しようとして失敗した場合、キャッシュが存在しなかった場合はログにその旨を出力する

ServiceWorker 内で使える API は Promise を返すものがほとんどなので、Promise を駆使するかんじ。

var CACHE_NAME = 'v1';

var fetchWithTimeout = function(request) {
    return new Promise(function(resolve, reject) {
        setTimeout(reject, 1000);
        return fetch(request).then(resolve);
    });
};

self.addEventListener('fetch', function(event) {
    // 本当はここでパスやヘッダーの内容を見て、キャッシュすべき対象か調べる
    event.respondWith(
        fetchWithTimeout(event.request)
            .then(function(response) {
                return caches.open(CACHE_NAME).then(function(cache) {
                    console.log('Put to cache', response);
                    cache.put(event.request, response.clone());
                    return response;
                });
            })
            .catch(function(e) {
                console.error('Failed to fetch', e);
                return caches.open(CACHE_NAME).then(function(cache) {
                    console.log('Fallback to cache', event.request);
                    var response = cache.match(event.request);
                    if (!response) {
                      console.error('Missing cache', event.request);
                      return;
                    }
                    return response; 
                });
            })
    );
});

puppeteer で Speed Index を算出

puppeteer という Headless Chrome を Node から操作するライブラリが今日発表されたので、以前、記事を書いた WebPagetest を使わずに Speed Index を算出する - kitak's blog を puppeteer でやってみた。

puppeteer の良さ

  • Chrome Developer Tools の開発チームのメンバーが開発・メンテナンスしているので安心
  • API が過不足なく揃っていて、かつ扱いやすい ( chrome-remote-interface 比 )

経緯

  • 会社の仮想環境とか実際のディスプレイがないマシンで Speed Index の計測を継続におこないたい。Synthetic Monitoring したい。Xvfb と格闘するのは嫌なので、Headless Browser を使いたい
  • Chrome の Headless サポートが発表された後に chrome-remote-interface で Headless Chrome を操作して Speed Index の算出を試みたのだけど、何か問題があって出来なかった(何でできなかったかは忘れた)
  • Headless Chrome を操作できる良さそうなライブラリ( puppeteer )が公開されたのでリトライ

Speed Index を算出するコード

こんなかんじ。

const fs = require('fs');
const puppeteer = require('puppeteer');
const speedline = require('speedline');

(async() => {
const browser = await puppeteer.launch();
const filename = 'trace.json';
const page = await browser.newPage();
try {
    await page.tracing.start({path: filename, screenshots: true});
    await page.goto('https://www.google.com'); // 計測したいサイトの URL に置き換えよ
    await page.tracing.stop();
    const results = await speedline(filename);
    console.log('Speed Index value:', results.speedIndex);
} catch (e) {
    console.error(e);
}
browser.close();
fs.unlinkSync(filename);
})();

色々

  • async/await を使っているので以前書いた記事のコードより読みやすい。特別、async/await で書くことにこだわりはないのだけど、puppeteer のリポジトリのサンプルコードが async/await で基本書かれているのでそれに倣った
  • 意識が低いのでファイル周りで同期 API を使っていたりするが、そこはもっと良くできそう
  • モバイル端末の viewport をエミュレートして計測したい場合は await page.emulate(devices['iPhone 6']) を足すとできる
  • Web クライアントサイドのパフォーマンスメトリクス — Speed Index、Paint Timing、TTI etc... ::ハブろぐ に書かれた Speed Index 以外のパフォーマンスメトリクスの計測もスクリプトを流し込めばできそうな気がする

その他、オートメーションの API も揃っていて、ServiceWorker のように Request/Response のインターセプトもできる。puppeteer、ページのスクレイピング、プリレンダリング、オートメーションと様々なシーンで利用できそうなおすすめのライブラリです。

DOM 要素が可視状態か調べる

小ネタ。

要素が可視状態かどうか調べたくなって、そういや jQuery に :visible ってセレクタがあったな、と思い出し、おもむろにコードを読んでみた。

結論、こんなかんじで調べることができる( jquery/hiddenVisibleSelectors.js at 2d4f53416e5f74fa98e0c1d66b6f3c285a12f0ce · jquery/jquery · GitHub )。

!!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length);

DOM の絡んだユニットテストとかで使えそうですね。

jQuery、以下の理由で新規で採用することは少なくなっていますし、jQuery 不要論の記事も定期的にみかけますが、ちょっと込み入ったことをやる必要が出た時の「大体のブラウザで動く実装例」として捉えると、かなり有用な気がすると思ったのでした。

  • ブラウザ毎のAPIの差異がなくなりつつある
  • DOM API が昔に比べて充実しており、DOM API を直接使うことに対する抵抗感も減ってきている (複雑な DOM 操作が要求される場合は React や Vue などの View ライブラリで対応できる)
  • npm のエコシステムにおいて「一つのことをうまくやる」パッケージが好まれる (モジュールバンドラを使って最終的に生成されるファイルのサイズが小さく収まるという点でもメリットがある)

Vue.js の scoped slot の理解

毎回混乱して、ドキュメントを読んでいるのでまとめ。

以下のようなスロットの機能を利用したコンポーネント child で考える。

<div>
  {{ foo }}
  <child>
     <span>{{ bar }}</span>
  </child>
  {{ baz }} 
</div>

https://jp.vuejs.org/v2/guide/components.html#コンパイルスコープ に書いてある話ですが、上記のテンプレートの foo, bar, baz のスコープは、そのテンプレートを扱っているコンポーネントになる。なんとなく、bar を展開している場所のスコープは child のような気がしてくるのだけど、そうではない。

bar を展開している場所で child のスコープを扱いたい( child のステートで class の付け替えをしたり )場合は、以前までは、child のステートを親のコンポーネントに渡し、親のコンポーネントを通してステートを展開していた。これは、親と子のコンポーネントが密になるのでよろしくない。

この問題を解決するのが scoped slot。child 内の slot コンポーネントに渡したデータが scope 属性値のオブジェクトを通して参照できるようになる。

<!-- child のテンプレート -->
<div>
  <scope :bar="..."></scope>
</div>
<div>
  {{ foo }}
  <child>
     <template scope="props">
       <span>{{ props.bar }}</span>
     </template>
  </child>
  {{ baz }} 
</div>

コンポーネントの親子間でプロパティを通してデータを渡すイメージに近く、属性値の scope の名は props と付けることが多い気がする( ドキュメントもそうなっている )。

プロフェッショナル 仕事の流儀「天職は、生涯かけて全うせよ~うなぎ職人・金本兼次郎」を見た

土用の丑の時期だからか、NHK オンデマンドのおすすめに出ていたので見た。

江戸時代からの鰻の老舗「野田岩」の五代目 金本兼次郎さんを取り上げている。

http://www.nhk-ondemand.jp/goods/G2013050750SA000/?capid=sns002

個人的に刺さった部分。

常日頃から弟子に伝えているのは 『仕事を作業にしないこと』

どうすれば日々の工程を深められるか考える姿勢がなければ、仕事ではないと金本さんは言う。

「仕事はどこまでも追求していかなきゃ。仕事への追求心がなくなると作業になっちゃう。この微妙な紙一重ね。それが職人のやり方。それがお客様を喜ばせる最大の元になる。」

このところ少し立て込みがちで、淡々とやりすぎていたな、こういった姿勢が足りていなかったな、と襟を正すきっかけになった。

now での bot の 24 時間運用

最近、bot を now で運用しているのでメモ。

PaaS の free plan というと、一定時間アクセスがないと sleep したり、24時間連続で動かすことができないものが多く、bot を 24 時間稼働させるためにあの手この手を使って、中々苦労する印象がありますが、now は we allow you to deploy infinitely (even in the OSS free plan!) ( refs: https://zeit.co/blog/scale ) ということで free で24時間連続で動かすことが出来ます( 多分、静的サイトのホスティングを意識して、こういう仕様になっているのかな )。

deploy 後に now scaleインスタンス数を 1 に設定してあげるだけです。

$ now scale my-deployment.now.sh 1

go で書いた bot を1週間程度運用していますが、今のところ、特に問題はないです。複数 deploy すると問題が生じる bot の場合は、deploy の際に以前の deploy を削除したり、シャットダウンを受け付ける口を用意する等してシームレスに切り替える必要があるので、そこだけ対応が必要です。

最近 2017年7月

一年前に書いたブログ( これからの1日の過ごし方 - kitak blog )がスターされて、「この記事を書いてから一年か。そういえば今年も半分が終わったな」と思ったので、適当に最近あったことを書いておきます。

最近の1日の過ごし方

ここ最近は これからの1日の過ごし方 - kitak blog のようなかんじ。寒いのが苦手で冬は朝早く起きることができないので、去年の冬からちょっと前までは普通に9時とか10時に出社していました。
今年の冬は会議がなければ午前中は家で好きなことをして、12時~13時出社とかがいいかな、と思っていたりします。

痩せた

去年の振り返り( 2016年のふりかえり - kitak blog )で、風邪をひくことが多いだの、体重が増えているだの書いていたと思うんですが、一念発起して、新年に半年かけて 8kg ~ 10kg 減らす目標を立てました( 自分、こういう目標をブログに書くと絶対にやらないので、新年の目標はブログに書かないようにしてました )。
以下のようなことをやって、見事、目標達成しました。

  • 腸内環境を整える。乳酸菌をオリゴ糖や食物繊維と組み合わせて摂る
  • 糖質の摂取を控えめにする。苦しくなるまでお腹いっぱい食べないようにする
  • 血糖値の急激な上昇を防ぐために極端にお腹が空いた状態を作らないようにする。お腹が空いたら、ローソンのブランパンか素焼きミックスナッツをおやつとして食べる
  • 週に3~4日、30分程度、スロージョギングする
  • お茶を飲む。毎日、特茶かヘルシアを飲む。飲み会の前後にからだすこやか茶Wを飲む

痩せた以外にも、(たまたまかもですが)風邪を半年の間に一回しかひいていないのと、健康診断の結果も去年が「大体 B で E が 2つ」だったのが、今年は「大体 A で C が 2つ」になって良かったです。 次の半年は、体重をキープ、リバウンドしないように適度に筋肉をつけようかな、と思っています。

個人事業主になった

先月、友人からベンチャー企業のお手伝いの話が来て、土日メインで副業を始めたんですが(ちゃんと会社に申請出してますよ!)、個人事業主になったほうが良いと判断したので、流れで個人事業主になりました。会社員兼です。屋号は kitak tech (キタック テック) です。

こういう話をすると、独立フラグか!?と思われそうなんですが、今、所属している会社は、今後も所属するつもりですし、当然そちらがメインです。

ありがたいことに個人事業主としての年内の仕事は埋まっているんですが、今後の関係も踏まえて話だけでもとりあえず、と思っていただけたら、Twitter の @kitak と相互フォローの関係であれば DM を送っていただくか、kei.kita2501 at gmail.com にメールをください。以下のような内容で貢献できるかもしれません(あくまで、平日の夕方・夜や、土日の空いた時間に自分がやりたいだけやっている形なので、精神・物理両面で強く制約されるお仕事はお引き受けできないかもしれません)。

  • Vue.js のハンズオン(単発のもの)
  • Single Page Application や JavaScript の関係する UI の設計/実装、あるいはそれらのレビュー
  • ウェブサイト/アプリのフロントエンドが関係するパフォーマンス上のボトルネックの特定と改善

上のようなお話をいただけるようになったのは、2, 3 年前に撒いた種が育って実がなったようなものなのかなと思っているのですが、今また次の2, 3 年を見据えて、面白いことを見つけて、種を撒きたいな、という気持ちもあります。人とダンスを踊るように目の前のことにしっかりかつ柔軟に向き合いつつ、次を見据えて種を撒く、次の半年~1年はそんなかんじになりそうです。