vue-router で初期表示後に、どのルートで解決されたか知りたい
初期ナビゲーションが終わった後のルーターオブジェクトにアクセスすればよい。onReady
と currentRoute
のコンボでいける。
router.onReady(() => { router.currentRoute; });
vue-router ひと通りドキュメントに目を通していたつもりだったのですが、ひさしぶりにみたら router オブジェクトのメソッドが色々増えていたり、学びがあった。
Webpack を使っていてファイルの相対パスを書くのがつらくなったとき
小ネタ。
Webpack(というよりモジュールバンドラ) を使っていて、ディレクトリの階層が深くなってくると import や require でロードするファイルのパスを ../../../../foo.js
のように ../
の数を正確に指定するゲームになってくる。
以下のように書くことで src ディレクトリをルートにしてパスを指定することができるようになるのだけど
resolve: { modules: [ path.resolve(__dirname, 'src'), "node_modules" ], },
同僚氏に npm でいれたパッケージか、src にあるファイルか分からないから、src ディレクトリの alias を定義したらどうか、と勧められた。
resolve: { alias: { '@': resolve(__dirname, 'src'), }, },
これで、どの階層にあるかに関係なく、import store from "@/store"
とか import router from "@/router"
とか書ける。Vue の Progressive Web App template や Nuxt.js でも同様のことをやっている。最悪、記号の単純な置換か、簡単なスクリプトを書けば済むので、筋は悪くないと思う。
Vue で配列を使った算出プロパティの値が変わらないとき
1回ハマったら体が覚えて、次回から気を付けるようになるのだけど、記事にしておく。
例えば、以下のように配列をスタックとして使い、トップを算出プロパティで定義したとき。1秒後にトップの id が 100 になると思いきやそうはならない。Vue は push, pop, splice といった操作のメソッドをラップして、配列の変更を検知しているので、配列の要素へ代入しても配列の変更とはみなされない。
new Vue({ data: { items: [] }, mounted() { this.items.push({id: 1}); this.items.push({id: 2}); this.items.push({id: 3}); setTimeout(() => { this.items[this.items.length - 1] = {id: 100}; }, 1000); }, computed: { top() { if (this.items.length === 0) { return null; } return this.items[this.items.length - 1]; } } });
これを意図通りに動かすには setTimeout のコールバックの処理を次のように splice メソッドを使って変更するように書き換える。
this.items.splice(this.items.length - 1, 1, {id: 100});
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 から操作するライブラリが今日発表されたので、以前、記事を書いた 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 不要論の記事も定期的にみかけますが、ちょっと込み入ったことをやる必要が出た時の「大体のブラウザで動く実装例」として捉えると、かなり有用な気がすると思ったのでした。