作ったもの
次のようにGitHubにコミットしたコードについて、どの言語を何行書いたかツイートするサービスです
2020年8月1日(土)@mikan_the_third は12438行のコードを書きました!
— みかん三世 (@mikan_the_third) August 2, 2020
TypeScript: 633
YAML: 344
JSON: 242
Markdown: 24
JavaScript: 2
.lock: 11176
.gitignore: 17#commitlyhttps://t.co/nPgFirPd14
なぜ作ったか
- エンジニアとして日々コードを書くモチベーションになればと、個人的に作ったものをサービス化しました
- もちろんコード量が多いほど良いわけではありませんが、エンジニアとして生きていくのであればコードを書き続けることになるはずで、そういったエンジニア同士で知り合えるきっかけにもなってほしいというの理由です
- こうしてQiitaなどで記事を書くのも良いのですが、日々のコーディングの成果が自然とアウトプットになるようにしたいということで、このように自動化しました
- また、一言にエンジニアと言っても担当領域や使用する言語によってグループ化されると思うので、どのような言語についてコミットしているのか分かるようにしました
技術スタック
オープンソースなので参考にしたい方はどうぞ
https://github.com/mikan3rd/commitly
React + TypeScript
- 普段からReact + TypeScriptのSPAを作っており、自分としては(今は)最善手だと思っているのでいつもこの構成で作っています
- VueやAngularもやってみようかなと調べてみたのですが、やはりテンプレート構文というところに型定義の限界と状態管理の限界を感じたため、引き続きJSXで書けるReactを選択しました
- 自分が共感した記事
No Redux
- 逆に使ってないのはReduxです
- これまでログインユーザー情報などグローバルな状態管理のためになんだかんだReduxを使ってきましたが、
React.createContext
React.useContext
によりhooksで同等の実装ができるようになったので試してみました - また、これまで非同期処理についてredux-thunkやredux-sagaを使ってきましたが、これも不要になりました
- 自分も「結局Reduxでやることと一緒ならRedux使えばいいかな?」と思ってましたが、コードもスッキリしてredux関連のライブラリも不要になったので、試しに使ってみるのがおすすめです(サービスの性質にもよるとは思いますが)
example.tsx
import React from "react";
type UserContextType = {
user: firebase.User | null;
loadingUser: boolean;
login: () => Promise<void>;
logout: () => Promise<void>;
export const UserContext = React.createContext<UserContextType>(undefined);
export default function UserContextComp({ children }) {
const [user, setUser] = React.useState<firebase.User | null>(null);
const [loadingUser, setLoadingUser] = React.useState(true);
const login = async () => {
// pass
};
const logout = async () => {
// pass
};
return (
<UserContext.Provider
value={{
user,
loadingUser,
login,
logout,
}}
>
{children}
</UserContext.Provider>
);
}
export const useUser = () => React.useContext(UserContext);
immer
- これまでイミュータブルなデータの取り扱いに
immutable.js
を使っていたのですが2018年10月にv4.0.0-rc.12
がリリースされて以降ほとんど音沙汰がなくなってしまいました -
immutable.js
のRecordでモデルクラスを作ってデータを操作するのが個人的には使いやすかったので残念ですが、固有メソッドが多く使い方に慣れる必要があるなどとっつきにくい点もあったため、代替としてimmer
を検討してみました - まだそんなに本格的に使えてはいませんが、
immer
はimmutable.js
に比べるとシンプルなAPIでイミュータブルなデータ操作ができ、アップグレードも定期的に行われているようなので今後はこっちを使ってみようかなと思っています
Next.js
- ツイートした時にOGPの表示に対応させたかったため、SSR対応しやすいNext.jsを選択しました
- 以前、仕事でSSR対応した時にSSRとSPAの両方の挙動に対応した作りにしないといけなくて面倒だったイメージがあり抵抗があったのですが、最新版では
getServerSideProps
が用意されているなど問題なく実装でき、Reactに慣れていれば困るポイントはそこまでなかったです
- 以前、仕事でSSR対応した時にSSRとSPAの両方の挙動に対応した作りにしないといけなくて面倒だったイメージがあり抵抗があったのですが、最新版では
- 実は最初は 静的サイトジェネレーター Gatsby で作っていました
- 前々から興味があったのと動的ページも作れそうという見込みで使ったのですが、あくまでbuild時に動的ページを全てgenerateする(Static Generation)というだけで、アクセス時に静的ページが用意される(Server-side Rendering)のは難しそうだったのでNext.jsに移行しました
- ブログやLPページなどをReactで作りたいのであればGatsbyで良かったと思うのですが、webアプリを作る場合はやはりNext.jsの方が良さげです
- 残骸: https://github.com/mikan3rd/commitly-gatsby-web
emotion
- 以前は自分もscssをBEM記法で書いてましたが、DOMとstyleを別々に記述することや、別々のファイルにあるものをclass名でつなぎ合わせる作業が面倒なので、自分は CSS in JS が好きです
- スタイルのスコープがグローバルな場合はclass名の競合に気をつけないといけなかったり、そもそもclass名の設計を考えること自体が面倒だったりチーム開発の場合は命名規則がズレがち
- styled-componentsの場合はコンポーネントごとにファイルを分けている場合は名前の競合は基本発生しない
- 最近はstyled-componentsで記述していたのですが、Gatsbyがemotionを採用していたこともあり使ってみました
- styled-componentsで個人的に気になる点としてはちょっとスタイルを当てたいだけでも
styled.XXX
を書かないといけないのと、その記述がJSXの記述の前後にあると長くなってしまうことでした - emotionの場合はstyled-componentと同等の機能が直感的にインラインスタイルで書けるのが普通に便利だったので今後も使いたい
- styled-componentsで個人的に気になる点としてはちょっとスタイルを当てたいだけでも
- 自分が共感した記事
Semantic UI React
- 自分でコンポーネントのスタイルのデザインができないのと、さっと作りたいやっぱりこういうのを使っちゃってます
- BootstrapやMaterial UIよりかっこい気がする(主観的な意見です)
- 問題点はCSSの当て方が
.parent > .child
みたいな親子関係で指定されているとoverrideし辛い(!important
を使うハメになる)ことですね
Firebase
- イメージ的にはGCPのweb/mobileアプリ向け簡易セットみたいな感じなので、スモールスタートで何か作るにはちょうどいいと思います
- DBはFirestoreを使っていますが、NoSQLなのとドキュメント呼び出しごとの従量課金となるので、設計や使い方は考えておかないとサービスに合わなくなってしまいます(無料枠はありますが)
- バックエンドはCloud Functionで実装しており、フロントと同じくTypeScriptでサッと書けるのが自分としては嬉しいです
- Cloud FunctionではCloud Schedulerを使った定期処理やPubSubをトリガーにした処理も書けるのでやりたいことは問題なくできた
-
firebase.analytics()
firebase.performance()
でアナリティクスやパフォーマンスチェックも手軽に有効にできる - SSRの場合、client用の
firebase
とnode用のfirebase-admin
を使い分ける必要があるのが微妙
Vercel
- Firebaseを使っていたので最初はFirebase Hosting + Cloud Function でSSRのホスティングをしたのですが、体感でも結構遅かったのでやめました(たぶんregionが
us-central1
しか対応してないせい) - Next.js公式からおすすめされている通りVercelを使ってホスティングしたらGitHubと連携するだけで簡単にホスティングができました
- また、PRを作るとPreviewとしてPRの内容を反映したホスティングも自動でしてくれるのも良い
GitHub Actions
- Next.jsのデプロイのみVercelのGitHubインテグレーションで行っていますが、型チェック・lintチェック・buildチェック・build結果の保存・buildしたファイルのデプロイなどについてはGitHub Actionsで動かしています
example.yaml
name: Node.js CI
on: [push]
jobs:
functions-build:
runs-on: macos-latest
strategy:
matrix:
node-version: [12.x]
steps:
- uses: actions/checkout@main
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- uses: actions/cache@v2
id: yarn-cache
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: ${{ runner.os }}-yarn-
- run: yarn install
working-directory: ./functions
- run: yarn lint
working-directory: ./functions
- run: yarn build
working-directory: ./functions
- name: Archive Production Artifact
uses: actions/upload-artifact@main
with:
name: functions-build-files
path: functions/lib
functions-deploy:
if: github.ref == 'refs/heads/master'
needs: functions-build
runs-on: macos-latest
strategy:
matrix:
node-version: [12.x]
steps:
- uses: actions/checkout@main
- name: Download Artifact
uses: actions/download-artifact@main
with:
name: functions-build-files
path: functions/lib
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- uses: actions/cache@v2
id: yarn-cache
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: ${{ runner.os }}-yarn-
- run: yarn install
working-directory: ./functions
- name: deploy to Firebase Functions
working-directory: ./functions
run: yarn deploy:prod --token=${{ secrets.FIREBASE_TOKEN }}
env:
FIREBASE_TOKEN: ${{ secrets.FIREBASE_TOKEN }}
困ったところ
- GitHub APIの仕様上、GitHub Appを作らないと今回の要件(privateリポジトリを含めたコミットの行数の取得)ができなかったなど、外部のAPIに対する理解と依存する部分とサービスのすり合わせ
- 今回の技術スタックで見本になるようなものが少なかったこと(Reactは書き慣れていたがそれ以外は新しい物を試しに使っていたりしたので)
今後やりたいこと
- 言語別のコミットランキング
- QiitaのようにOGP画像を動的に生成する
- GraphQL + Apollo or NestJS