お知らせ

現在サイトのリニューアル作業中のため、全体的にページの表示が乱れています。

ローカル環境用の開発ドメインをhttps化する時に使えるやつ
例えばローカル環境に複数のサービスがいて、それぞれをhttps://*.example.com/のようなドメインで管理したい時に使える

確認環境

同じことをすればLinuxとかでも応用できると思う

Env Ver
nginx 1.19.8
mkcert 1.4.3
Windows 10 Pro 19043.1415

手順

  1. mkcert の導入とワイルドカード証明書の作成
    choco install mkcert
    # mkcertを認証局として登録
    mkcert -install
    # 証明書を作成するドメインを列挙
    mkcert example.test *.example.test
    mv _wildcard.example.com+1.* C:/nginx/conf/.ssl/
    
  2. nginxの設定に証明書を記載

    server {
        server_name  dev.example.com;
        listen 443 ssl;
    
        ssl_certificate     ssl/_wildcard.example.com+1.pem;
        ssl_certificate_key ssl/_wildcard.example.com+1-key.pem;
        ...
    }
    

MSYS2でzsh使いたくない?使いたいよね?そう、使いたい!
しかしその上で一つ大きな障害があります
MSYS2のzshでプロンプトにGitのブランチを表示させようとすると微妙に上手くいきません

問題

.zshrcを使ってPROMPTに現在のGitブランチの状態を表示させようとするとフリーズするケースがあります
具体的には以下のような設定を書くとターミナルのサイズ変更時にシェルがフリーズします
原因は不明ですがPROMPT='$(git)'だけでフリーズするのでGit for Windowsとの相性がなにか良くないのだと思っています

# git functions

current_git_branch() {
  (git branch 2> /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/\1/')
}

parse_git_dirty() {
  local DIRTY=$(git status -s | tail -1)
  if [[ -n $DIRTY ]]; then
    echo "%{$fg_bold[magenta]%})%{$fg_bold[red]%}✗"
  else
    echo "%{$fg_bold[magenta]%})%{$fg_bold[blue]%}✓"
  fi
}

print_git_prompt() {
  local FIND_BRANCH=$(git branch 2> /dev/null | tail -1)
  if [[ -n $FIND_BRANCH ]]; then
    echo "%{$fg_bold[magenta]%}(%{$reset_color%}%{$fg_bold[yellow]%}"$(current_git_branch)$(parse_git_dirty)" %{$reset_color%} "
  else
    echo ""
  fi
}

# setting propmt

PROMPT="
%{$fg_bold[cyan]%}%n%{$reset_color%}%{$fg_bold[blue]%}@%m%{$reset_color%}:%{${fg_bold[green]}%}%~%{$reset_color%}
%(?:%{$fg_bold[green]%}➜:%{$fg_bold[red]%}➜)  %{${reset_color}%}"'$(print_git_prompt)'

RPS1="%(?..%{$fg_bold[red]%} [ %? ]%{$reset_color%}) %D - %*"

解決策1

プロンプト表示に使うgitバイナリだけmsys2の物に差し替えると上手くいくようになります
具体的には以下のようにしてGit for Windowsと競合しないようにMSYS2のGitを構成しPROMPT='$(msys2_git)'のように呼び出してやると上手くいきます

pacman -S git
mv /usr/bin/git /usr/bin/msys2_git

課題

  • msys2/gitをアップデートするときに毎回リネームをする必要がある
  • ここが面倒で脆弱性に繋がる可能性がゼロではない

解決策2

Git for Windowsだけでやる場合の方法です
add-zsh-hook precmdでプロンプトに出したいコマンドを呼び出し、変数の値を書き換えてそれを表示するスタイルでやると上手くいきます

課題

  • プロンプトの速度が遅い
  • プロンプトする時に一瞬フォーカスが飛ぶ(別プロセルがコールされ一瞬窓ができている気がする
投稿日:
言語::TypeScriptジャンル::調査

TypeScriptをぼちぼちやってきた中で、型推論が素直に決まらないみたいな所が出てきたので書き置きとして残しておきます
具体的には何かしらの関数を通して処理を隠蔽すると上手くいかなくなるケースがあります

型が推論されないケース

検査関数的なものを通した場合に、後続処理で期待通り型が推論されなくなるケースです

その1

type HogePiyo = 'hoge' | 'piyo';

type InputProps<ValueT> = {
  value: ValueT;
  error: string;
};

const required = (val: string) => {
  return val === '';
};

const registerHogePiyo = (value: HogePiyo) => {
  console.log('register', value);
};

const input: InputProps<HogePiyo | ''> = {
  value: '',
  error: '',
};

if (required(input.value)) {
  input.error = 'ERR';
  console.error(input.error);
} else {
  // ここでは input.value は 'hoge' | 'piyo' となるはずだが
  // TSC は 'hoge' | 'piyo' | '' と解釈する
  registerHogePiyo(input.value);
}

その 2

type HogePiyo = 'hoge' | 'piyo';

type InputProps<ValueT> = {
  value: ValueT;
  error: string;
  hasError: boolean;
};

const required = <ValueT>(obj: InputProps<ValueT | ''>) => {
  if (obj.value === '') {
    return { value: '', error: '必須入力です', hasError: true };
  } else {
    return { value: obj.value, error: '', hasError: false };
  }
};

const registerHogePiyo = (value: HogePiyo) => {
  console.log('register', value);
};

const input: InputProps<HogePiyo | ''> = {
  value: '',
  error: '',
  hasError: false,
};

const result = required(input);
if (result.hasError) {
  console.error(result.error);
} else {
  // ここでは input.value は 'hoge' | 'piyo' となるはずだが
  // TSC は 'hoge' | 'piyo' | '' と解釈する
  registerHogePiyo(result.value);
}

export {};

型を推論されるようにしたケース

オブジェクトに型判定用のプロパティを生やしasで型を明示することでクリアしています

type HogePiyo = 'hoge' | 'piyo';

type InputProps<ValueT> = {
  value: ValueT;
  error: string;
  hasError: boolean;
};

const required = <ValueT>(obj: InputProps<ValueT | ''>) => {
  if (obj.value === '') {
    return { value: '', error: '必須入力です', hasError: true } as {
      value: '';
      error: string;
      hasError: true;
    };
  } else {
    return { value: obj.value, error: '', hasError: false } as {
      value: ValueT;
      error: string;
      hasError: false;
    };
  }
};

const registerHogePiyo = (value: HogePiyo) => {
  console.log('register', value);
};

const input: InputProps<HogePiyo | ''> = {
  value: '',
  error: '',
  hasError: false,
};

const result = required(input);
if (result.hasError) {
  console.error(input.error);
} else {
  registerHogePiyo(result.value);
}

export {};

あとがき

  • Assertion Functionsを使う方法もあると思いますが、何も考えずAssertion Functionsを書くと次のような弊害があり、積極的に採用しづらいと考えています
    • 何もしていなければ基本的にバンドル後のソースコードに出てくる
      • 要するにバンドルサイズが大きくなります
    • throwを書く必要性があり、バンドル後に残っている以上、このthrowcatchする必要性がある
      • 無意味な実装工数が生まれる
    • そもそも論としてAssertion Functionsを書く工数が生まれる
    • Assertion Functionsの振る舞いを見ている限り、asに限りなく近く、ぶっちゃけasで書いたほうが楽
  • 絶対にasを使わず現実的な工数で開発するというのは、割と非現実ではないかという感触があります
    • 個人的に型推論はIDEが勝手にやってくれるおかげで楽ができるシステムだと考えているので、型推論の補佐をする仕組みに工数をかけるというのは本末転倒な気がしています
  • 品質は大切なことですが、反面で少なくないビジネスには期限もあります
    • 品質に寄せすぎた過剰に冗長なコードも、納期に寄せすぎた脆弱性不具合山盛りスパゲティコードもどちらも考えものであり、その辺のバランスが上手くとれるやり方がなんかないかなーと考えることはしばしばあります
投稿日:
OS::Linux::コマンド

おなじみのアーカイブ作成コマンド。迷ったらヘルプ読んだほうが早い

圧縮編

基本

  • tar -zvcf foo.tar.gz foo
z: --gzip, gzip
v: --verbose, 詳細、別になくてもいい
c: --create, アーカイブの新規作成
f: --file, 保存するファイル名

tar -cfだとtarballになると思われる

おまけ

T: --files-from, アーカイブ対象ファイルのリストを読み込む。LF区切りのテキスト
--exclude: <pattern>, アーカイブから除外するパスのパターン

展開編

  • tar -zxvf foo.tar.gz foo
z: --gzip, gzip
x: --extract, 展開
v: --verbose, 詳細、別になくてもいい
f: --file, 展開するファイル名
投稿日:
ライブラリ::React開発::設計

別にReactに限った話でもないのですが、実務で悩ましいコードにあたって頭を抱えてる内にハゲてきたので、ハゲがこれ以上進まない事を祈り書きました。いやいっそのこと全ハゲになりたいですが、それはさておき
とりあえず個人的には以下2点を意識すれば起きないとは思うので、Next.jsを使った簡単なサンプルを交えながら3例ほどケーススタディ形式で紹介していきます

  • 密結合にしない
  • シンプルに実装する

挙げている事例については例示用にフルスクラッチで書いています(主題と関係ないコードは端折ってます
ここはこうした方がより良いのでは?などのご意見があれば是非コメントいただけると嬉しいです

コンポーネントのOptional引数のオーバーライド

コンポーネント引数をコンポーネント側で書き換えるような実装はどうかなと思います

問題点

  • 親の預かり知らぬところで値が設定されている
    • 誰かが知らずに設定値をすり替えたりしたら不具合が起きそうです
  • 型のコメントと設定値が異なる
    • 型のコメントと実装が異なるので、誰かが直すかもしれません
      • すると、コンポーネントを参照している全コンポーネントに影響が波及します
    • そもそも型と実装は本質的に関係ないので、こういった運用はNG

一例

type AccountContainerProps = {
  email: string;
  // デフォルトtrue
  isUpdate?: boolean;
  // デフォルトfalse
  gotNotify?: boolean;
};


export const AccountContainer = ({
  isUpdate = true,
  gotNotify = true,
  ...props
}: AccountContainerProps) => {
  ...
}

改善案

呼び元で明示的に指定するように変更しています
これによって親は子の実装を知る必要がなくなり、責務がそこで閉じるようになりました

改善点

  • 親の預かり知らぬところで値が設定されなくなった
    • これで子コンポーネントで値が変わることに怯える必要はなくなりました
  • 型の初期値コメントを削除できた
    • 実装と一致する保証がないコメントは削除するべきでしょう
type AccountContainerProps = {
  email: string;
  isUpdate: boolean;
  gotNotify: boolean;
};


export const AccountContainer = (props: AccountContainerProps) => {
  ...
}

コンポーネントのOptional引数の多重オーバーライド

コンポーネントのOptional引数のオーバーライドが多重化されている上に、なんか途中で更に書き換えられているとかいう地獄
どうしてそんなことをするのか…

一例

type AccountTemplateProps = {
  email: string;
  // デフォルトtrue
  isUpdate?: boolean;
  // デフォルトfalse
  gotNotify?: boolean;
  from: 'register' | 'update';
};

export const AccountTemplate = ({
  isUpdate = false,
  gotNotify = true,
  ...props
}: AccountTemplateProps) => {
  if (props.from === 'register') isUpdate = false;
  return <AccountContainer {...props} />;
};
type AccountContainerProps = {
  email: string;
  // デフォルトtrue
  isUpdate?: boolean;
  // デフォルトfalse
  gotNotify?: boolean;
};


export const AccountContainer = ({
  isUpdate = true,
  gotNotify = true,
  ...props
}: AccountContainerProps) => {
  ...
}

改善案

前項のように明示的に値を渡してあげるようにしましょう

直列に分散されたコンポーネント

コンポーネントの中にコンポーネントがネストされ続けてるパターンです
一見して何をしているのか分かりづらい上、StateやらHookやら色んな処理が各コンポーネントに分散配置されていることもあります

問題点

  • 親からみると子が何をしているのかが分かりづらい
  • コンポーネント名が意味をなしていない(責務が別れていない)
  • 子コンポーネントが状態を持っているため、親コンポーネントでハンドリングができない
  • 子コンポーネントのロジック変更が参照している全コンポーネントのロジックに波及する

一例

親コンポーネント

RegisterHeaderなる物が差し込まれていることだけがわかります
このコンポーネントが何をするのかはパッと見ではよくわかりません

const AccountUpdatePage = () => {
  return <Register header={<RegisterHeader />} />;
};
ラッピングしているコンポーネント

ラッパーなのでこのコンポーネントそのものは何をしているのかわかりません

type RegisterProps = {
  header: JSX.Element;
};

export const Register = (props: RegisterProps) => {
  return <>{props.header}</>;
};
差し込まれているコンポーネントの中身

どうやらRegisterHeaderRegisterContentを含むようです
なんでヘッダーの中にコンテンツがあるのでしょうか…

export const RegisterHeader = () => {
  return (
    <>
      <p>head</p>
      <RegisterContent />
    </>
  );
};
差し込まれているコンポーネントの子

現在のパスに応じて叩くAPIを変えるような実装がされていますが、もしパスが変わったり増えたりしたらこのコンポーネントの実装を知らない限り面倒なことになります

export const RegisterContent = () => {
  const rt = useRouter();
  const currentPath = rt.pathname;
  const url =
    currentPath === 'register'
      ? 'https://example.com/kaiin/touroku'
      : 'https://example.com/kaiin/koshin';
  const [username, setUsername] = useState<string | undefined>(undefined);

  const onSubmit = (ev: React.FormEvent<HTMLFormElement>) => {
    ev.preventDefault();
    axios.post(url, {
      username,
    });
  };

  return (
    <>
      <form onSubmit={onSubmit}>
        <input
          type="text"
          value={username}
          onChange={(ev) => setUsername(ev.target.value)}
        />
        <button>送信</button>
      </form>
      <RegisterFooter />
    </>
  );
};
export const RegisterFooter = () => {
  return <p>foot</p>;
};

改善案

改善点

  • 親から子への見通しが改善された
  • コンポーネント名が名前の通りの意味を持つようになった
    • ヘッダーはヘッダー、フォームはフォーム、フッターはフッターの責務だけに集中できます
    • Registerとか言う謎コンポーネントも姿を消しました
  • 状態をすべて親に集約した
  • 子コンポーネントのロジック変更が起きる確率が減った
    • 子コンポーネント側のロジックを減らし、親から渡されるコールバックで行うようにしたため、子コンポーネントのロジック変更が他に影響する確率が減りました
親コンポーネント
  • ひとまずヘッダーと入力フォーム、フッターがあるんだなという事が解るようにはなったと思います
  • 状態を親に集約したのでAPI叩く時のURIも態々パスから判断しなくて良くなったのでコードの複雑性が減っています
const usePageState = () => {
  const [username, setUsername] = useState('');

  return {
    username,
    setUsername,
  };
};

const onSubmit = (username: string) => {
  axios.post('https://example.com/kaiin/koshin', {
    username,
  });
};

const AccountUpdatePage = () => {
  const ps = usePageState();

  return (
    <>
      <RegisterHeader />
      <RegisterForm
        onChangeUsername={ps.setUsername}
        username={ps.username}
        onSubmit={() => onSubmit(ps.username)}
      />
      <RegisterFooter />
    </>
  );
};
ヘッダー
export const RegisterHeader = () => {
  return <p>head</p>;
};
入力フォーム
type RegisterFormProps = {
  username: string;
  onChangeUsername: (value: string) => void;
  onSubmit: () => void;
};

const onChangeUsername = (
  ev: React.ChangeEvent<HTMLInputElement>,
  setState: (value: string) => void
) => {
  const value = ev.target.value;
  setState(value);
};

const onSubmit = (
  ev: React.FormEvent<HTMLFormElement>,
  onSubmit: () => void
) => {
  ev.preventDefault();
  onSubmit();
};

export const RegisterForm = (props: RegisterFormProps) => {
  return (
    <>
      <form onSubmit={(ev) => onSubmit(ev, props.onSubmit)}>
        <input
          type="text"
          value={props.username}
          onChange={(ev) => onChangeUsername(ev, props.onChangeUsername)}
        />
        <button>送信</button>
      </form>
    </>
  );
};
フッター
export const RegisterFooter = () => {
  return <p>foot</p>;
};