ninjinkun's diary

ninjinkunの日記

毎朝空いている有楽町線下りの考察

東京の電車は混んでいて辛いという話を聞くが、自分が通勤で使っている有楽町線の東側から西側(新富町→永田町)へ向かう下り電車はかなり空いている。9:30くらいの電車ではほぼ座ることができ、早めに出勤しても1、2駅立っていればだいたい座れる。

なぜこんなに空いている電車が走っているのか考えてみた。

  • 有楽町線は西側で西部、東武と相互乗り入れしており、郊外からの乗客が多い
    • このため、朝の時間は10両編成車両が4, 5分おきにやってくる
  • 一方で東側は新木場でJRに乗り換えられるだけ
    • かつ接続する京葉線の乗客はほぼ乗り換えずに東京駅に向かうと思われる
    • 沿線の住宅地も月島、豊洲くらい
  • 鉄道はやってきた車両を車庫に戻す運用が必要なので、どちらかの列車本数を減らすことはしづらい

つまり人数が多い西側の乗客を運ぶためのキャパシティで人数が少ない東側の乗客を運んでいるので、ガラガラになっているわけだ(とドヤ顔するほどのことでもない)。

もちろんこの状態では経営効率が良くないことは明らかなので、有楽町線豊洲-住吉間に支線を作って延伸する計画が進行中らしい。

他にも片側だけ相互直通がない路線がないか調べてみたが、あったのは都営新宿線の東側くらいで、こちらは郊外まで地下鉄自体が伸びていた。

Mini Metroというゲームをやっていると、こういうことを延々考えてしまうのでおすすめです。

ミニメトロ

ミニメトロ

  • Dinosaur Polo Club
  • ゲーム
  • ¥480

play.google.com

Suica入りスマホ2台持ち生活

  • Suica契約済みのiPhoneSuica未契約のPixel 3を持ち歩いていた
  • 通勤で改札の前に来る度にPixelを取り出しまい、ああ違ったiPhoneじゃないとなっていた
  • PixelもSuica契約すればダブルSuicaで最強!と思ってPixelもSuica契約した
  • 今度は改札を出る際に、あれどっちのSuicaで入ったっけ?となって問題が余計面倒になった

ブログのSNSシェア数を毎朝メールでお知らせする機能をリリースしました

BlogFeedbackにシェア数の増加をメールでお知らせしてくれる機能を追加しました。

シェア数が昨日より増加していると、以下のようなメールが配信されます。

f:id:ninjinkun:20190120124338j:plain:w320

設定

設定はブログの追加時

f:id:ninjinkun:20190120125642p:plain:w320

または以下の設定画面から行うことができます。

f:id:ninjinkun:20190120125944p:plain:w320

配信する時間はとりあえず朝の9時にしてありますが、予告なく変更する場合があります。自分で時間や配信メールアドレスを選ぶ機能は今のところありません。

実装

  • クロールからメール送信まで全てFirebase Cloud Functionsで実装
  • Firebaseにはcronがなかったので、GCPにappengineをデプロイする方法で実装(これが一般的な方法らしい)
  • 動きは以下の通り
    • GCP cron→AppEngine→Cloud Pub/Sub→Cloud Functionsの対象者抽出Function→Cloud Pub/Subで対象ブログごとにFunctionsを起動
  • Firebase Cloud Functionsの環境にはGCP用の環境変数が設定されているらしく、Cloud Pub/SubのSDKが設定なしで動いてびっくり
    • Firebase Cloud Functionsの中身はGoogle Cloud Functionsなのでよく考えたら当たり前ではあるが
  • Cloud Pub/Subは1回以上実行保証で、2回実行されることが普通にあるのでロック処理を入れた
    • Firestoreにロック用テーブルを作ってjobごとにUUIDを入れる実装
  • フロント側のクローラとコードが共有できそうな気もしたが、細かい部分で差異が出てくるのでとりあえずはコピペで対応
  • HTMLメールのマークアップ辛すぎる
    • Gmailがflexboxタグを抜いてくるので、古き良きtableとfloatでマークアップした(やったことなかったけど)
  • 今はメールのSMTPサーバが自分のGmailなので、500通/dayしか送れない
    • ユーザーが増えたらSESなりSendGridなり検討する
  • 対象ブログ抽出の実装が1000ユーザーで上限決め打ち
    • 拡張する対応は簡単なので、とりあえずこれも今は放置

スマホ2台生活

右ポケットにiPhone X、左ポケットにPixel 3 XL。どう考えても無駄な気がするが、この生活を始めて2ヶ月くらい経ってしまった。

元々iPhone Xを持っていたところにPixel 3 XLを買ったのだが、iOSからAndroidへの環境移行がとても面倒で、特に支払い周りのApple Pay(iD)とMobile Suicaが直接移行できなかった(新規でセットアップ & 登録が必要。Mobile SuicaはなぜかAndroidだけ年会費もかかる)のが原因で、二台持ちが始まってしまった。

日常的なブラウジングやSlackのチェックはどちらか出てきた端末でやる。Netflixなどの動画は画面が大きいPixel、支払いはiPhoneとなんとなく使い分けがされてきている。

開発者としてはMobile SafariAndroid Chromeがすぐに試せるのが最大のメリット。両OSの進んでいる部分がちょっとずつ試せるのも悪くない。

ランニング、水泳、睡眠トラッキングApple Watchが手放せないのもあってiOSも捨てられない。

なんとなくこのまま二台持ちで行きそうな気がする。

create-react-appで作ったアプリのFetch as Google対応

結論から言うと以下のエントリを参考に

import 'babel-polyfill';

したらできた。

scrapbox.io

追記

yarn add babel-polyfill して埋め込んでいたが、その後 yarn start が遅くなったような気がしたので、CDNからの読み込みに修正した。

<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-polyfill/7.2.5/polyfill.min.js"></script>

経緯

  • 先日作ったPWAである BlogFeedbackGoogleにインデックスして欲しかった
    • 一応トップページのtitleとdescriptionは登録されている

f:id:ninjinkun:20190112210513p:plain

  • しかしbodyはインデックスされていない
    • create-react-app でSPAを作っただけは普通こうなると思われる
    • 大半のページはユーザー毎のprivateページなので、そんなにSEOをがんばってもうまみはない
    • でも規約とかキャンポリとか、もうちょっとは登録するページもあるし…
  • 職場ではServer Side Renderingが導入されているので、それなりに茨の道であることは知っていた
  • この辺りの話は以下のスライドが参考になります
  • Dynamic Rendering…するほどページ数もない(正直に言うとよくわからんのでやりたくない)のでとりあえずクローラが読めるJSにするぞ

挫折の歴史

とりあえずChrome 41対応したらええんやろ

  • "browserslist": ["chrome >= 41"] したらできるのでは?
  • pollyfill.io使ったらできるのでは?
    • なぜかfetch as googleで読めず…
  • react-app-polyfillIE対応すればChrome 41でも動くのでは?
    • なぜかfetch as googleで読めず…
  • 俺が悪かった import 'babel-polyfill' するわ
    • 読めた!
    • JSの容量は増えるが、まあ個人サービスなので目をつぶろう(11KB+だった)

f:id:ninjinkun:20190113101206p:plain

iOSアプリ開発者がWebアプリ(PWA)をリリースするまでの流れ

先日リリースした個人アプリBlogFeedbackを開発した動機と、開発の時系列、開発してみての感想(ネイティブ開発者から見たPWAとか)を書いていきます。リリースエントリにも書きましたが、このアプリはiOSネイティブアプリからWebアプリへの移植です。

TL;DR

  • BlogFeedbackのケースではWebでもネイティブアプリとほぼ同等の体験を作ることができた
  • ネイティブ歴が長くHTML/CSSに明るくなかったので、まずReact Native for WebでUIを組んでいって、自力でHTML/CSSが書けるようになってから脱React Native for Webした
  • React / TypeScript / create-react-app / styled-components / storybook おすすめ
  • ブログを書いている人はBlogFeedbackを使ってみて欲しい!
続きを読む

create-react-appのService Workerサポートで手軽にオフラインキャッシュを使う

この記事はPWA Advent Calendar 21日目の記事です。

create-react-appService Workerサポートを使ってお手軽にオフラインキャッシュを組み込んでみたので、調べたことをメモします。とりあえずすぐにオフラインでPWAが動かせるようになって便利です。

使い方

create-react-app でアプリを生成していればindex.js|ts

import { register } from './serviceWorker';

register();

するだけ。

内部

  • create-react-app(2.0以上の場合、現在は2.1.4)のService Worker supportは内部でworkbox-webpack-pluginを使っている
    • 使われているのはgenerateSW モード
    • プロジェクト内のhtml/js/imageが一通りキャッシュされる設定になっている
    • カスタマイズはreact-app-rewireとか使わないとできない?かも。未検証

挙動

以下はcreate-react-appというよりはworkboxの挙動だと思われる

  • 新しくページを開くと前回pre-cacheされたindex.htmlやjsがロードされる
  • デプロイして更新があった場合はバックグラウンドでpre-cacheされる(tempに入る)
    • 該当ページを開いているタブを全部閉じてから開き直すと、tempに入っていた更新が有効になる
    • ページを開きっぱなしでスーパーリロードしても更新が反映されないので注意(はまる)
      • これサーバーのAPIと合わせて更新する必要がある時とかどうするんだ?
      • アップデートは検知できるから、「タブを全部閉じて開き直してください」みたいなプロンプトを出すのか
      • ドキュメントによれば、これは遅延ロードによるレースコンディションを防ぐためらしい
      • (推測)恐らくServiceWorkerのキャッシュは全てのタブプロセスで共通なので、同じアプリの古いアセットをロードしたのタブが残っている状態で新しいキャッシュを有効にしてしまうと、古いアセットで動いているページが新しいキャッシュを読み込んで壊れるから、ということだと思う

temp領域に入っているアセットはApplicationタブから確認できます。 f:id:ninjinkun:20181221175931p:plain

参考

使いだすと、たぶん以下を熟読することになると思います。

こちらも参考になりました。 qiita.com

Cloud FirestoreのSecurity RulesをCircleCIで自動テストする

この記事はFirebase Adventcalendar #2の13日目の記事です(もう12/17ですが、丁度書けそうなネタがあったので、空いてる日を見つけて埋めることにしました)。

Firabase Cloud Firestoreを使う場合、Security Rulesがアクセス制御全てのかなめと言えます。ここをミスるとデータが漏れて終わる、しかしその割に簡単に変更できてしまう。というわけで自動テストできると安心でしょう。

今回は先日公開した拙作のBlogFeedback(repo https://github.com/ninjinkun/blog-feedback-app/ )で用いているCircleCIによる自動テスト事例を紹介します。

なお、Security Rulesをテストするというアイデアは以下のエントリから頂きました。

エミュレーターの準備

Cloud Firestoreのエミュレーターとテスト用のライブラリが提供されているので、これを利用することでテストを記述・実行できます。エミュレーターの利用はこちらの記事を参考にしました。

firebase-toolsが入っていれば以下でインストールして

firebase setup:emulators:firestore

以下で立ち上げられます。

firebase serve --only firestore

テストの実装

公式のサンプルを参考にfirestore.rulesをテストしていきます。

サンプルではテストはmochaで記述されていますが、今回はcreate-react-appに同梱のJestで記述しました。

アプリ内で使っているRepositoryの関数を呼んで書き込みのテストをしているのと、他人のデータや本来書き込めない領域への書き込みが失敗するテストも記述しました。

src/tests/firebase-rulest.test.tsより一部抜粋

describe('/users/:user_id/blogs', () => { 
    it('save blog', async () => {
    const db = authedApp({ uid: 'ninjinkun' });
    // repositoryが内部で使っているdbを差し替える
    firebaseDB.mockReturnValue(db);

    await firebase.assertSucceeds(
    // repositoryに定義した関数で書き込めるか
        saveBlog(
        'ninjinkun',
        'https://ninjinkun.hatenablog.com/',
        "ninjinkun's diary",
        'https://ninjinkun.hatenablog.com/feed',
        'atom'
        )
    );
    });

    it('save undefined blog field', async () => {
    const db = authedApp({ uid: 'ninjinkun' });
    firebaseDB.mockReturnValue(db);
    const user = userRef('ninjinkun');
    // 存在しないフィールドに値をセット
    // rulesでちゃんとフィールドを制御しないと書き込めてしまうので注意
    await firebase.assertFails(user.set({ hoge: 'fuga' }));
    });
});

ローカルでテストが成功するようになったら、CircleCIの設定へ進みます。

CircleCIでの実行

CircleCIで実行する際のポイントとしては、エミュレータの実行にJavaが必要なため、OpenJDKとNode.js両方が入ったDocker Imageを選ぶことです。今回はとりあえず適当にcircleci/openjdk:11-jdk-sid-nodeを選択しました。

余談ですがCloud FunctionsのビルドにはNode.js v6 or v8が必要なため、デプロイ用のjobの方では circleci/node:8.14 を指定してあります。Cloud Functionsのテストも同時に行う場合は、OpenJDK + Node.js v8の組み合わせが入っているDocker Imageを用意する必要があるでしょう。

テストの実行前にエミュレーターのインストールと実行を追記する必要があります。

.circleci/config.ymlから一部抜粋(インストールしたエミュレーターをキャッシュできるとは思うのですが、そんなに時間がかかっているわけでもないのでさぼっています)

- run: yarn firebase setup:emulators:firestore
- run:
    command: yarn firebase serve --only firestore
    background: true

CIを実行すると以下の様にローカルと同じようにテストが実行されました。これで一安心。

CI実行結果から抜粋

#!/bin/bash -eo pipefail
yarn test:ci
yarn run v1.12.3
$ CI=true react-scripts test --env=jsdom
    PASS  src/__tests__/save-count-response.test.ts
    PASS  src/__tests__/firebase-rules.test.ts
    PASS  src/__tests__/App.test.tsx

Test Suites: 3 passed, 3 total
Tests:       10 passed, 10 total
Snapshots:   0 total
Time:        3.208s
Ran all test suites.
Done in 4.68s.

テストの速度

テストケース10個で以下の通りでした。エミュレーターの起動に18秒かかっているので、ここが一番遅いですね。とりあえず許容範囲内としていますが、他のテストでも時間がかかっているとストレスになるかもしれません。

f:id:ninjinkun:20181217172804p:plain

2019/2/18 追記

firebase-tools > 6.1.1 ではCircleCIでのエミュレータ起動が失敗するので、.jar の直接起動が必要になりました。そのうち解消されるとは思いますが 具体的な作業は以下に。

Upgrade firebase tools by ninjinkun · Pull Request #54 · ninjinkun/blog-feedback-app · GitHub

2019/6/14 追記