かもメモ

自分の落ちた落とし穴に何度も落ちる人のメモ帳

Git ログのツリーをいい感じに見れるエイリアスを設定したらいい感じになった!

Git で logを見る時に開発中は git log --oneline でほぼ事足りていたのですが、 色々やるようになって tree 表示 GUI だと遅いのでターミナルでいい感じに表示したくなって設定を追加しました。

Git tree をいい感じに表示するエイリアス

# 今いるブランチのツリーをシンプルに表示
$ git config --global alias.tree 'log --graph --oneline'
# 全てのブランチのツリーをいい感じに表示
$ git config --global alias.treeall 'log --graph --all --format="%x09%C(cyan bold)%an%Creset%x09%C(yellow)%h%Creset %C(magenta reverse)%d%Creset %s"'

👇

# 今いるブランチのツリーをシンプルに表示
$ git tree

git tree

# 全てのブランチのツリーをいい感じに表示
$ git treeall

git treeall

いい感じの log がいい感じ表示できるようになりました! 楽!!!
AWESOME ₍ ᐢ. ̫ .ᐢ ₎👌


[参考]

React カスタム hook で返すデフォルト引数のある関数の型をいい感じに書きたい。

React hooks では custom hooks を多用するのですが、custom hook で返す関数がデフォルト引数を取る時の型をいい感じに書きたかったのメモ。

デフォルト引数を取る関数を返す Custom Hook

こんな Hooks の型をいい感じに書きたい

export const useCount = () => {
  const [count, setCount] = useState(0); 
  
  const update = useCallback(( x = 1 ) => {
    setCount((oldCount) => oldCount + x);
  }, []);

  return {
    count,
    update,
  }
};

interface と関数とでそれぞれ関数の型を作ると、異なる型定義になってイケてない

interface に直接返される関数を定義すると微妙になってしまう

 interface IUseCount {
  count: number;
  update: (x?: number) => void;
}

export const useCount = (): IUseCount => {
  const [count, setCount] = useState<boolean>(0);
  
  const update = useCallback(( x: number | undefined = 1 ): void => {
    setCount((oldCount) => oldCount + x);
  }, []);

  return {
    count,
    update,
  }
};

update はデフォルト引数なので、関数の定義部分は (x?: number) => void となる。
実際の関数の部分はデフォルト引数を持たせるので、 (x?: number = 1): void => {...} のように書きたいがこう書くと Parameter cannot have question mark and initializer. というエラーになってしまうので、undefined を許容できるように ( x: number | undefined = 1 ): void => {...} と定義すればデフォルト引数のある関数として動作させられる。

動き上は問題がないのだけど、hook の返す型としている interface での関数の型定義と、hooks 内の関数に定義している型とが異なるのでイケてない感じです。

関数の型を別途定義すると型が揃っていい感じになる

関数の型を取り出して別途定義すると interface と hooks 内の関数の型定義を同じにすることができました!

type updateType = (x?: number) => void;

interface IUseCount {
  count: number;
  update: updateType;
}

export const useCount = (): IUseCount => {
  const [count, setCount] = useState<boolean>(0);
  
  const update = useCallback<updateType>(( x = 1 ) => {
    setCount((oldCount) => oldCount + x);
  }, []);

  return {
    count,
    update,
  }
};

関数の型を別途作成して interface, hooks 内の関数に当てはめることで、推論される型も同じになっていい感じになりました!

今まで同じ型なのにそれぞれに型書くのなんかイケてないな〜と感じつつ、interface 内に直接関数の型を書いて、関数には引数と戻り値の型を直接書いていたのですが、関数の方だけ作っておいて使い回せばよかったのですね!なっとく!


[参考]

Next で MSW (Mock Service Worker) を使うのメモ

バックエンドの API がまだできてないプロジェクトで Next のフロントを作成することになり開発中の API との通信に噂の MSW を使ってみることにしました。
Next.js 特有の問題でちょいハマりしたので導入完了したところまでのメモ。

MSW (Mock Service Worker)

MSW(Mock Service Worker)はブラウザリクエストを Service Worker がインターセプトし、任意のレスポンスを返すことが出来るライブラリです。
cf. MSW で加速するフロントエンド開発

プロダクションのコードのまま API への通信だけを Service Worker がインターセプトしてモックのレスポンスを返すことができるので、バックエンドと切り離してフロントだけを開発することができます!すごい〜

MSW の導入

$ npm i -D msw

開発時だけ使うので -D でインストール

MSW の Service Worker を生成

npx msw init <公開ディレクトリ> --save で MSW が使用する Service Worker が生成される
Next アプリの公開ディレクトリは public なので下記のようにすれば OK

$ npx msw init public --save

/publicmockServiceWorker.js が生成され、package.jsonmsw: {"workerDirectory": "public"} というコードが追加される。
これで MSW が使えるようになるので後はモック API を作成していくだけ

note.

Service Worker を生成してないと次のようなエラーが表示されます。
Error: [MSW] Failed to register a Service Worker for scope ('http://localhost:3000/') with script ('http://localhost:3000/mockServiceWorker.js'): Service Worker script does not exist at the given path.
Did you forget to run "npx msw init <PUBLIC_DIR>"?
Learn more about creating the Service Worker script: https://mswjs.io/docs/cli/init

モック API の作成

// src/mocks.js
import { setupWorker, rest } from 'msw'
const worker = setupWorker(
  rest.post('/login', (req, res, ctx) => {
    const isAuthenticated = sessionStorage.getItem('username')
    if (!isAuthenticated) {
      return res(
        ctx.status(403),
        ctx.json({
          errorMessage: 'Not authenticated',
        }),
      )
    }
    return res(
      ctx.json({
        firstName: 'John',
      }),
    )
  }),
)
// Register the Service Worker and enable the mocking
worker.start()

cf. Introduction - Mock Service Worker Docs

setupWorker の中にモックの API を定義して、worker.start() すればモック API に合致するリクエストは MSW がインターセプトしてモック API に書いている値を返すことができる。

モックの API を全部同じファイルに書くのは大変なので、下記のような構成で作成しました。

/src
  |- /mock
      |- /api
      |    |- auth.ts … モックの API の処理
      |- handler.ts … API handler 設定 (routing)
      |- worker.ts … worker.start() するファイル

モックの API の処理

/src/mock/api/auth.ts

import { MockedRequest, ResponseResolver, restContext } from 'msw';
import jwt, { SignOptions } from 'jsonwebtoken';

export const mockLogin: ResponseResolver<MockedRequest, typeof restContext> =
  async (req, res, ctx) => {
  const token = await jwt.sign({ uid: 1 }, JWT_SERCRET_KEY, {expiresIn: '1h'});
  
  return res(
    ctx.status(200),
    ctx.cookie(COOKIE_NAME, token, {
      httpOnly: true,
      path: '/'
    }),
    ctx.json({
      name: '星宮いちご',
    }
  ));
};

export const mockLogout: ResponseResolver<MockedRequest, typeof restContext> = 
  (req, res, ctx) => {
  return res(
    ctx.status(200),
    ctx.cookie(COOKIE_NAME, null, {
      expires: new Date(0),
    }),
    ctx.json('logout')
  );
};

API handler 設定 (routing)

/src/mock/handler.ts

import { rest } from 'msw';
import { mockLogin, mockLogout } from './api/auth';
import { API } from '@/config.ts';

export const handlers = [
  rest.post(`${API}/login`, mockLogin),
  rest.post(`${API}/logout`, mockLogout),
];

Mock Service Worker のメインファイル

/src/mock/worker.ts

import { setupWorker } from 'msw';
import { handlers } from './handler';

export const worker = setupWorker(...handlers);

処理を分けることで見通しが良くなった気がします!

Next アプリの開発モードの時だけ MSW を有効にする

api 呼び出しはアプリ全体で使うのでアプリのルートである /src/pages/_app.tsxprocess.env.NODE_ENV === 'development' の時だけ worker を有効にすれば OK

/src/pages/_app.tsx

import { VFC } from 'react';
import { AppProps } from 'next/app';

if (process.env.NODE_ENV === 'development') {
  // dynamic import でファイルを読み込んで MSW を有効にする
  const MockServer = () =>
    import('@/mock/browser').then((mo) => {
      mo.worker.start();
    });
  MockServer();
}

const App: VFC<AppProps> = ({ Component, pageProps }) => {
  return (<Component {...pageProps} />);
};

export default App;

これで MSW が有効に…となるつもりだったのですが、コンソールに次のようなエラーが表示されていました。 (node:12365) UnhandledPromiseRejectionWarning: Error: [MSW] Failed to execute setupWorker in a non-browser environment. Consider using setupServer for Node.js environment instead.

サーバーサイドでは Service Worker が使えないのが原因

Next は SSR できるようにサーバーサイドの動作もしているので、サーバーサイド時でもこの Service Worker を使おうとしているけどできないよ!ということのようです。

サーバーサイドでは setupWorker ではなく setupServer を使うようにする

MSW はサーバーサイドでも使える様になっており、node 環境では setupServer.listen() とすれば良いようです。
Next アプリは 1 つのコードでサーバーサイドもクライアントサイドでも実行されるので、node 環境かどうかの判定して切り替えるよう変更します。

/src/mock/browser.ts/src/mock/server.ts を作成して /src/mock/worker.ts 内で切り替えられるように変更します。

/src
  |- /mock
      |- /api
      |    |- auth.ts … モックの API の処理
      |- handler.ts … API handler 設定 (routing)
      |- browser.ts … クライアントサイド用
      |- server.ts … サーバーサイド用
      |- worker.ts … worker.start() するファイル

/src/mock/browser.ts

import { setupWorker } from 'msw';
import { handlers } from './handler';

export const worker = setupWorker(...handlers);

/src/mock/server.ts

import { setupServer } from 'msw/node';
import { handlers } from './handler';

export const server = setupServer(...handlers);

/src/mock/worker.ts

export {};

if (typeof window === 'undefined') {
  const { server } = require('./server');
  server.listen();
} else {
  const { worker } = require('./browser');
  worker.start();
}

worker.ts 内で実行させるようにしたので、読み込ませる箇所も修正する
/src/pages/_app.tsx

import { VFC } from 'react';
import { AppProps } from 'next/app';

if (process.env.NODE_ENV === 'development') {
  const MockServer = () => import('@/mock/worker');
  MockServer();
}

これで Next アプリで開発モードの時は API へのリクエストすれば MSW からモックの JSON を取得できるようになりました!
₍ ᐢ. ̫ .ᐢ ₎ ヤッタゼ!!

Auth で cookie を返す方法はコレが良いのか解ってない。


[参考]

Jungle Moc 履き心地いいよね。