こんにちは、フロントエンドチームです。
今週のフロントエンド定例の内容を記載します。
フロントエンド定例について、以前の記事(ランサーズのフロントエンドチームが取り組んでいること)でお伝えしたのですが、毎週金曜日に開催しており、実際の業務で取り組んでいることや気になった技術情報等をシェアしあう会になっています。
以下、今週の内容です。
Datadogによるログ収集し始めた
ログの見方として
- Logs -> Searchでログのページを開く
- Sourceでbrowser選択
- Envで対象の環境(production, development)絞り込み
- 時間帯の絞り込み
などのフィルタリングをしてログを探ることができます。
ログのカラムなど表示をカスタマイズしたのをSlackのブックマークに登録しているのでそこからみていただくのが早いと思います。
定期的にログみてますがGoogle スプレッドシートのフィルタによる検索が使い慣れているので10日分ぐらいのログをCSVに出してCONTENTにあるエラーのフィルタリングしてどういったエラーがあるのかみてるときもあります。
ログ収集の管理対象として最新のテンプレートテーマに対して
開発環境は全てのページ、本番環境は一部のアプリケーションページのみ監視対象にしてどういったログが出ているのか様子見しています。
現在のログ実装として
(function(h,o,u,n,d) {
h=h[d]=h[d]||{q:[],onReady:function(c){h.q.push(c)}}
d=o.createElement(u);d.async=1;d.src=n
n=o.getElementsByTagName(u)[0];n.parentNode.insertBefore(d,n)
})(window,document,'script','https://www.datadoghq-browser-agent.com/datadog-logs-v4.js','DD_LOGS')
DD_LOGS.onReady(function() {
DD_LOGS.init({
clientToken: 'xxxxx',
site: 'datadoghq.com',
forwardErrorsToLogs: true,
beforeSend: (log) => {
if (log.error?.origin === "network") {
// XHR errorなどのネットワークエラーはサーバ側でも検知可能なのでログ収集対象外にする
return false;
}
},
sampleRate: 100,
env: '< \Environment::getEnvironment() >',
});
< if ($userId) { >
DD_LOGS.addLoggerGlobalContext('user_id', '< $userId >');
< } >
});
のような実装をし、
ネットワークエラーのログ収集はしないようにしたり、サービスで使用しているユーザIDもログに残すようにしてエラーの原因調査をできるようにしています。
Datadogのモニターの機能より定期的にエラーの通知をSlackに流すようにしています
MPA上のReact、SASSなどビルドファイル管理案
現状一部ビルドファイルのコミット忘れ防止やコンフリクト回避でデプロイ時にビルド実行していますが、Git管理(.gitignoreから外す)するようにし、作業中の段階でコミット忘れがないか検証するようなタスクを置こうかなと思っています。 → CIの料金の都合上やっぱりやめました
Git管理にするメリットとして以下のようなものがあり
- 開発環境コンテナ上のビルドによるメモリー不足によるビルド失敗回避
- デプロイシステム上でのフロントエンドビルドタスク削除
- 上記2つに共通することとして1つのアプリケーションに複数かつ大きいフロントエンドのビルドソースが含まれているからというのがそもそもの問題であるというのはあります
デメリットとして
- 共通のCSSビルドファイルなどでコンフリクトが発生する
- 小さい粒度のCSSに分けるべきというのもある
没案になりましたがどのようにコミット忘れがないか検証方法として
- ビルドファイルをGit管理下にする
- CI上でフロントエンドのビルド実行しビルドファイルを置き換える
- git diff で差分があるときはCI失敗扱いにする
差分の取り方とかのコマンドとして下記のものをCIのタスクに登録すればいい気がします
GIT_DIFF=$(git diff --name-only)
if [ "$GIT_DIFF" != "" ];then (echo "ビルドファイルに差分が発生しています: $GIT_DIFF" && exit 1); fi
個人的にはMPAのモノリスなレポジトリから分離した際にフロントエンドのCIタスク系入れたいというのもありますし、MPAの静的ファイル管理のディレクトリにビルドファイルを置くことをそもそも辞めたいと思ってます。
@high_g_engineer
Reactコンポーネント単体のファイル分割について
最近、プロジェクトで個人的に採用しているファイルの分け方について共有します。
これは、AtomicDesignの様な全体構成の話ではなく、Reactコンポーネント単体を見た時に、ディレクトリ内でどの様にファイルを分けているかについての話になります。
前提として
- 1画面が少数フォームとボタンで構成されるシンプルなもの(下記のようなレベル)
- 対象とするReactコンポーネントは、AtomicDesignでいうOrganismsレベルのもの
- 採用している技術スタックは、CSSはemotion, グローバルデータ管理はrecoil, フォーム制御にreact-hook-formとyup
紹介したいファイル構成は以下です。
Component/
|- index.tsx
|- logic.ts
|- localConstant.ts
|- errorScheme.ts
以下にそれぞれのファイルの役割についての説明を記載しています。
index.tsx
- 各種import、jsx、cssの記述のみに集中
- メインロジックは、logic.tsに退避し、index.tsxにはロジックをゴリゴリ書かない方針
- 1画面がシンプルなことやデザインシステムや別ディレクトリに配置しているコンポーネントを使用している関係上、ほぼコンポーネントを置くだけの開発体験が実現中
import { css } from '@emotion/core';
import { Input } from '@lancers/design_guideline'; // Inputフォーム
import { FormLayout } from 'component/layout'; // 送信ボタンを含むレイアウトコンポーネント
import { TitleDescriptionHorizontal } from 'component/ui'; // タイトル、詳細を表示するコンポーネント
import React from 'react';
import { useLogic } from './logic';
export const UserPassword: React.FC = () => {
const {
handleSubmit,
onSubmit,
nicknameRegister,
passwordRegister,
purposeRegister,
errors,
} = useLogic();
return (
<FormLayout
title="ユーザー名とパスワードを設定してください"
buttonType="single"
handleSubmit={handleSubmit}
onSubmit={onSubmit}
>
<TitleDescriptionHorizontal
title="ユーザー名"
description="半角英数字記号 4文字以上(使用可能記号 -_ )"
/>
<Input
placeholder="例:user_001"
css={mtInputCss}
name={nicknameRegister.name}
inputRef={nicknameRegister.ref}
onChange={nicknameRegister.onChange}
onBlur={nicknameRegister.onBlur}
errorMessage={errors.nickname?.message}
/>
</FormLayout>
);
};
const mtCss = css`
margin-top: 32px;
`;
const mtInputCss = css`
margin-top: 8px;
`;
logic.ts
- メインのロジックを記述するファイル
- index.tsxで利用する為に必要な変数や関数をreturnするカスタムフック的な記述
- 1画面がシンプルなので、記述コードは、ほとんどフォーム制御やAPIとの接続部分のみ
localConstant.ts
- コンポーネント内で利用する定数を管理
- グローバルな単位で利用する定数は、別途グローバル用の定数ファイルで管理
export const LOCAL_CONSTANT = {
NICKNAME_KEY: 'nickname',
PASSWORD_KEY: 'password',
PURPOSE_KEY: 'purpose',
EMAIL_HASH_KEY: 'email_hash',
NICKNAME_MIN_LENGTH: 4,
NICKNAME_MAX_LENGTH: 25,
PASSWORD_MIN_LENGTH: 8,
PASSWORD_MAX_LENGTH: 32,
} as const;
errorScheme.ts
- yupで記述したバリデーションを退避したファイル
- react-hook-formを利用している為、バリデーションの記述もその範囲で出来ますが、logic.tsがごちゃごちゃしてしまう記述になる為、yupを採用
- このファイルのおかげで、logic.tsがよりシンプルに
react-hook-formでバリデーションを記述する場合
各registerにルールを直接記述しないといけないので可読性が良くありません。
// useFormのロジック
const {
register,
handleSubmit,
formState: { errors },
} = useForm<SubmitDataArgumentType>({
mode: 'onSubmit',
criteriaMode: 'all',
shouldFocusError: false,
});
// エラーバリデーションを直接記述
const nicknameRegister = register('nickname', {
required: '必須入力のエラーメッセージ',
min: {
value: 4,
message: '最小文字数入力のエラーメッセージ'
},
max: {
value: 25,
message: '最大文字数入力のエラーメッセージ'
},
pattern: {
value: /[A-Za-z]{3}/,
message: '正規表現のエラーメッセージ'
}
});
const passwordRegister = register('password', {
required: '必須エラー',
min: {
value: 4,
message: '最小文字数入力のエラーメッセージ'
},
max: {
value: 25,
message: '最大文字数入力のエラーメッセージ'
},
pattern: {
value: /[A-Za-z]{3}/,
message: '正規表現のエラーメッセージ'
}
});
const purposeRegister = register('purpose', {
required: '必須エラー'
});
バリデーションをyupで実装する場合
resolverに対してバリデーションスキーマを渡すだけでokなので、logic.tsの内容が汚れず、可読性が良い状態になります。
const {
register,
handleSubmit,
formState: { errors },
} = useForm<SubmitDataArgumentType>({
mode: 'onSubmit',
criteriaMode: 'all',
shouldFocusError: false,
resolver: yupResolver(errorScheme), // logic.ts側はresolverの記述のみでok
});
実際のerrorScheme.tsの内容は、以下の様な感じです。
最低限の必須チェック、最小・最大文字数チェック、正規表現チェックのみを行い、そこからあふれるAPIとの接続が必要になってくるエラーチェックは、logic.ts側で記述する形になります。
※実際のファイルは、定数で記述していますが、わかりやすいように数値、文字列をそのまま記述しています。
import * as yup from 'yup';
import { ERROR_MESSAGE, REGEXP } from 'constant';
import { LOCAL_CONSTANT } from './localConstant';
export const errorScheme = yup.object().shape({
nickname:
yup
.string()
.required('必須入力のエラーメッセージ')
.matches(
/^[a-zA-Z0-9_-]*$/,
'正規表現のエラーメッセージ'
)
.min(
4,
'最小文字数のエラーメッセージ'
)
.max(
25,
'最大文字数のエラーメッセージ'
),
password:
yup
.string()
.required('必須入力のエラーメッセージ')
.matches(
/^[a-zA-Z0-9!-/:-@¥[-`{-~]*$/,
'正規表現のエラーメッセージ'
)
.min(
8,
'最小文字数のエラーメッセージ'
)
.max(
32,
'最大文字数のエラーメッセージ'
),
purpose:
yup
.mixed()
.nullable()
.required('必須入力のエラーメッセージ'),
});
特に何も考えない場合だと、index.tsxになんでもまとめがちですが、それぞれのファイルで責務を分けることで、保守性が向上したり、複数人開発しやすかったりといったことが期待できるので、個人的にはファイルが肥大化してきたな。。と感じたら、どんどんファイルを分けるのがオススメです。
この辺りは、ベストなパターンが色々あると思いますので、試行錯誤しながら知識を随時アップデートしていけたらと思っています。
次回の更新予定は、6/3(金)になります!
前回の定例内容はこちらから確認可能ですのでご興味いただければ下記のリンクから閲覧いただければと思います。