kitak blog

Kみたいなエンジニアになりたいブログ

vue-router でパスにマッチしたコンポーネントの Vue インスタンスにアクセスする

タイトルが長い。

お仕事で vue-router 絡みのプラグインを書いていてアクセスしたくなった。

VueRouter のインスタンスが router として、以下のようにプロパティを参照することでできる。

router.currentRoute.matched[0].instances['default']

あくまで内部がこうなっているというだけの話で、API として正式に提供されているものではないので注意。

matched が配列になっているのは、ネストしたルートを定義することができるから。 例えば、以下のような定義の場合、/user/3/profile のようなパスでアクセスしたら、/users/3 で1回、/users/3/profile でもう1回マッチすることになる。

{
  path: '/users/:id',
  component: User,
  children: [
    {
      path: 'profile',
      component: UserProfile
    },
  ],
}

instances がオブジェクトになっているのは名前付きビューがあるため。以下のようなルーティング定義をした場合、instances オブジェクトの default, a, b それぞれのプロパティでコンポーネントの Vue インスタンスにアクセスできる。名前付きビューを使っていない場合は default プロパティを参照すれば良いです。

[
  {
    path: '/',
    components: {
      default: Foo,
      a: Bar,
      b: Baz
    },
  },
]

(keepalive コンポーネントを使うと話が変わりそうですが)インスタンスの参照はいつ変更される分からないので、参照を変数に入れて、使いまわすとかはしないほうがよさそう。

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 から操作する API が今日発表されたので、以前、記事を書いた WebPagetest を使わずに Speed Index を算出する - kitak blog を puppeteer でやってみた。

puppeteer の良さ

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

なんで Speed Index の算出を puppeteer でやるの?

  • 会社の仮想環境とか実際のディスプレイがないマシンで 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 を削除したり、シャットダウンを受け付ける口を用意する等してシームレスに切り替える必要があるので、そこだけ対応が必要です。