ninjinkun's diary

ninjinkunの日記

create-react-app 3.0にしたのでついでにTSLintからESLintに移行した

経緯

BlogFeedbackで使っているcreate-react-appを3.0に上げたらTypeScriptにもLintが効くようになっていたが、そのせいでいきなりビルドが止まるようになってしまった

  • 元々TSLintを入れてエディタとCIでTSLintを実行していた
  • create-react-app 3.0からyarn startyarn buildESLintが実行される
    • TypeScriptには@typescript-eslintで対応してるみたい
  • 新しく入ったESLintは無設定状態だとデフォルトのルール (eslint-config-react-app)で実行されるので、TSLintで設定していなかったいくつかのルールで引っかかるようになって、CIでyarn buildが止まっていた
    • eslint-config-react-appにはjsx-a11y react-hooks プラグインが入っていたので、その辺で落ちていた
  • 今後はESLintになる流れらしいので、この際なのでESLintに移行して、ルールもデフォルトに合わせることにした

ESLint対応

github.com

prettier対応だけして、あとはデフォルト。

$ yarn add eslint-config-prettier eslint-plugin-prettier

.eslintrc.json

{
  "extends": [
      "react-app", 
      "plugin:prettier/recommended"
  ]
}

package.json

scripts: {
    "eslint": "eslint './src/**/*.{ts,tsx}'",
    "eslint:fix": "eslint --fix './src/**/*.{ts,tsx}'"
}

あとはCIでtslint使ってる箇所を書き換えて終わり。

ブラクラ

const SignInPage: React.FC<Props> = props => {
  useEffect(() => {
    props.fetchUser(firebase.auth());
    return () => undefined;
  }, []);
  ...
}; 

というhooksをよく知らずに書いたコードで怒られたのでeslint --fixしたら

const SignInPage: React.FC<Props> = props => {
  useEffect(() => {
    props.fetchUser(firebase.auth());
    return () => undefined;
  }, [props]);
  ...
}; 

にされて、effectが無限に発火し続けてブラウザが固まるようになってしまった。 リリースしてから気づいたので焦って直した(fetchUser をキャプチャするようにしたら直った)。

おわりに

create-react-app 使ってると最新のベストプラクティスが勝手に突っこまれるので便利。その反面アップデート時の対応は毎回少し面倒。

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

東京の電車は混んでいて辛いという話を聞くが、自分が通勤で使っている有楽町線の東側から西側(新富町→永田町)へ向かう下り電車はかなり空いている。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