LoginSignup
6
3

More than 3 years have passed since last update.

書いたコードを言語別に集計してシェアするサービス「Commitly」を作ってみた

Last updated at Posted at 2020-08-03

:star2: 作ったもの

次のようにGitHubにコミットしたコードについて、どの言語を何行書いたかツイートするサービスです

:thinking: なぜ作ったか

  • エンジニアとして日々コードを書くモチベーションになればと、個人的に作ったものをサービス化しました
  • もちろんコード量が多いほど良いわけではありませんが、エンジニアとして生きていくのであればコードを書き続けることになるはずで、そういったエンジニア同士で知り合えるきっかけにもなってほしいというの理由です
  • こうしてQiitaなどで記事を書くのも良いのですが、日々のコーディングの成果が自然とアウトプットになるようにしたいということで、このように自動化しました
  • また、一言にエンジニアと言っても担当領域や使用する言語によってグループ化されると思うので、どのような言語についてコミットしているのか分かるようにしました

:pencil: 技術スタック

オープンソースなので参考にしたい方はどうぞ
https://github.com/mikan3rd/commitly

React + TypeScript

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を検討してみました
  • まだそんなに本格的に使えてはいませんが、immerimmutable.jsに比べるとシンプルなAPIでイミュータブルなデータ操作ができ、アップグレードも定期的に行われているようなので今後はこっちを使ってみようかなと思っています

Next.js

  • ツイートした時にOGPの表示に対応させたかったため、SSR対応しやすいNext.jsを選択しました
    • 以前、仕事でSSR対応した時にSSRとSPAの両方の挙動に対応した作りにしないといけなくて面倒だったイメージがあり抵抗があったのですが、最新版ではgetServerSidePropsが用意されているなど問題なく実装でき、Reactに慣れていれば困るポイントはそこまでなかったです
  • 実は最初は 静的サイトジェネレーター 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と同等の機能が直感的にインラインスタイルで書けるのが普通に便利だったので今後も使いたい
  • 自分が共感した記事

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の内容を反映したホスティングも自動でしてくれるのも良い

vercel.com_mikan3rd_commitly-next-web(Laptop with HiDPI screen).png

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 }}

:sob: 困ったところ

  • GitHub APIの仕様上、GitHub Appを作らないと今回の要件(privateリポジトリを含めたコミットの行数の取得)ができなかったなど、外部のAPIに対する理解と依存する部分とサービスのすり合わせ
  • 今回の技術スタックで見本になるようなものが少なかったこと(Reactは書き慣れていたがそれ以外は新しい物を試しに使っていたりしたので)

:star2: 今後やりたいこと

  • 言語別のコミットランキング
  • QiitaのようにOGP画像を動的に生成する
  • GraphQL + Apollo or NestJS
6
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
3