mcpField
F
frontend-js-best-practices

frontend-js-best-practices

github.com/sergiodxa開発92333 回インストール38 週間前に更新
リポジトリ

frontend-js-best-practices について

JavaScript と TypeScript の開発者向けに、パフォーマンス最適化とコードスタイルを示すガイドです。データ構造、キャッシュ、反復処理、DOM 操作などを扱う 17 のルールで構成されています。

主な機能

  1. 0117 のコーディングルール
  2. 02Map、Set、配列の検索を最適化
  3. 03反復処理と計算の重複を削減
  4. 04キャッシュによる再アクセスの抑制
  5. 05DOM 操作とコード構成の標準化

活用シーン

  1. 01JavaScript または TypeScript の実装
  2. 02ループやホットコードパスの最適化
  3. 03データ構造とキャッシュ利用のレビュー
  4. 04プロジェクト内のスタイル統一

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/sergiodxa/agent-skills --skill frontend-js-best-practices

インストール後、Claude Code で/frontend-js-best-practicesを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

rules/
batch-dom-css.md
cache-function-results.md
cache-property-access.md
cache-storage.md
combine-iterations.md
comments-meaningful-only.md
const-let-usage.md
early-exit.md
function-declarations.md
hoist-regexp.md
index-maps.md
length-check-first.md
min-max-loop.md
no-as-type-casts.md
no-default-exports.md
result-type.md
set-map-lookups.md
tosorted-immutable.md

詳細

JavaScript のベストプラクティス

JavaScript および TypeScript のコードにおけるパフォーマンス最適化とコードスタイルのパターンです。不要な計算の削減、データ構造の最適化、一貫した規約の維持に重点を置いた 17 のルールが含まれています。

適用する場面

次の作業を行う際に、これらのガイドラインを参照してください。

  • ループや配列操作を記述するとき
  • データ構造(Map、Set、配列)を扱うとき
  • DOM を直接操作するとき
  • 値や関数の結果をキャッシュするとき
  • ホットコードパスを最適化するとき
  • 変数や関数を宣言するとき

ルールの概要

const-let-usage (MEDIUM) — @rules/const-let-usage.md

モジュールレベルではconstを、関数内ではletを使用します。

// Module level: const with UPPER_SNAKE_CASE for primitives
const MAX_RETRIES = 3;
const userCache = new Map<string, User>();

// Inside functions: always let
function process(items: Item[]) {
  let total = 0;
  let result = [];
  for (let item of items) {
    total += item.price;
  }
  return { total, result };
}
function-declarations (MEDIUM) — @rules/function-declarations.md

名前付き関数には、アロー関数より関数宣言を使用します。

// Good: function declaration
function calculateTotal(items: Item[]): number {
  let total = 0;
  for (let item of items) {
    total += item.price;
  }
  return total;
}

// Good: arrow for inline callbacks
let active = users.filter((u) => u.isActive);

// Good: arrow when type requires it
const handler: ActionFunction = async ({ request }) => {
  // ...
};
no-default-exports (MEDIUM) — @rules/no-default-exports.md

名前付きエクスポートを使用します。デフォルトエクスポートは避けてください(Remix のルートコンポーネントを除く)。

// Bad: default export
export default function formatCurrency(amount: number) { ... }

// Good: named export
export function formatCurrency(amount: number) { ... }

// Exception: Remix routes use default export named "Component"
export default function Component() { ... }
no-as-type-casts (HIGH) — @rules/no-as-type-casts.md

as Typeによるキャストは避けてください。代わりに型ガードまたは Zod による検証を使用します。

// Bad: type assertion
let user = response.data as User;

// Good: Zod validation
let user = UserSchema.parse(response.data);

// Good: type guard
if (isUser(response.data)) {
  let user = response.data;
}
comments-meaningful-only (MEDIUM) — @rules/comments-meaningful-only.md

コードで表現できない情報を追加する場合にのみ、コメントを記述します。

// Bad: restates the code
// Set the user's name
let userName = user.name;

// Good: explains business rule
// Transactions under $250 don't require written acknowledgment per policy
if (transaction.amount < 250) {
  return { requiresAcknowledgment: false };
}
set-map-lookups (LOW-MEDIUM) — @rules/set-map-lookups.md

Array のメソッドではなく、O(1)で検索できる Set/Map を使用します。

// Bad: O(n) per check
const allowedIds = ["a", "b", "c"];
items.filter((item) => allowedIds.includes(item.id));

// Good: O(1) per check
const allowedIds = new Set(["a", "b", "c"]);
items.filter((item) => allowedIds.has(item.id));
index-maps (LOW-MEDIUM) — @rules/index-maps.md

繰り返し検索する場合は、Map を一度だけ作成します。

// Bad: O(n) per lookup = O(n*m) total
orders.map((order) => ({
  ...order,
  user: users.find((u) => u.id === order.userId),
}));

// Good: O(1) per lookup = O(n+m) total
const userById = new Map(users.map((u) => [u.id, u]));
orders.map((order) => ({
  ...order,
  user: userById.get(order.userId),
}));
tosorted-immutable (MEDIUM-HIGH) — @rules/tosorted-immutable.md

変更を避けるため、sort()ではなくtoSorted()を使用します。

// Bad: mutates original array
const sorted = users.sort((a, b) => a.name.localeCompare(b.name));

// Good: creates new sorted array
const sorted = users.toSorted((a, b) => a.name.localeCompare(b.name));
combine-iterations (LOW-MEDIUM) — @rules/combine-iterations.md

複数の filter/map を 1 つのループにまとめます。

// Bad: 3 iterations
const admins = users.filter((u) => u.isAdmin);
const testers = users.filter((u) => u.isTester);
const inactive = users.filter((u) => !u.isActive);

// Good: 1 iteration
const admins: User[] = [],
  testers: User[] = [],
  inactive: User[] = [];
for (const user of users) {
  if (user.isAdmin) admins.push(user);
  if (user.isTester) testers.push(user);
  if (!user.isActive) inactive.push(user);
}
cache-property-access (LOW-MEDIUM) — @rules/cache-property-access.md

ループ内ではオブジェクトのプロパティをキャッシュします。

// Bad: repeated lookups
for (let i = 0; i < arr.length; i++) {
  process(obj.config.settings.value);
}

// Good: cached lookup
const value = obj.config.settings.value;
const len = arr.length;
for (let i = 0; i < len; i++) {
  process(value);
}
cache-function-results (MEDIUM) — @rules/cache-function-results.md

コストの高い関数の結果を、モジュールレベルの Map にキャッシュします。

const slugifyCache = new Map<string, string>();

function cachedSlugify(text: string): string {
  if (!slugifyCache.has(text)) {
    slugifyCache.set(text, slugify(text));
  }
  return slugifyCache.get(text)!;
}
cache-storage (LOW-MEDIUM) — @rules/cache-storage.md

localStorage/sessionStorage の読み取り結果をメモリにキャッシュします。

const storageCache = new Map<string, string | null>();

function getLocalStorage(key: string) {
  if (!storageCache.has(key)) {
    storageCache.set(key, localStorage.getItem(key));
  }
  return storageCache.get(key);
}
early-exit (LOW-MEDIUM) — @rules/early-exit.md

結果が確定したら、早期に return します。

// Bad: continues after finding error
function validate(users: User[]) {
  let error = "";
  for (const user of users) {
    if (!user.email) error = "Email required";
  }
  return error ? { error } : { valid: true };
}

// Good: returns immediately
function validate(users: User[]) {
  for (const user of users) {
    if (!user.email) return { error: "Email required" };
  }
  return { valid: true };
}
length-check-first (MEDIUM-HIGH) — @rules/length-check-first.md

コストの高い比較を行う前に、配列の長さを確認します。

// Bad: always sorts even when lengths differ
function hasChanges(a: string[], b: string[]) {
  return a.sort().join() !== b.sort().join();
}

// Good: early return if lengths differ
function hasChanges(a: string[], b: string[]) {
  if (a.length !== b.length) return true;
  let aSorted = a.toSorted();
  let bSorted = b.toSorted();
  return aSorted.some((v, i) => v !== bSorted[i]);
}
min-max-loop (LOW) — @rules/min-max-loop.md

最小値/最大値を求めるには、sort ではなくループを使用します。

// Bad: O(n log n)
const latest = [...projects].sort((a, b) => b.updatedAt - a.updatedAt)[0];

// Good: O(n)
let latest = projects[0];
for (const p of projects) {
  if (p.updatedAt > latest.updatedAt) latest = p;
}
hoist-regexp (LOW-MEDIUM) — @rules/hoist-regexp.md

ループの外で RegExp を作成します。

// Bad: creates regex every iteration
items.forEach(item => {
  if (/pattern/.test(item.text)) { ... }
})

// Good: create once
const PATTERN = /pattern/
items.forEach(item => {
  if (PATTERN.test(item.text)) { ... }
})
batch-dom-css (MEDIUM) — @rules/batch-dom-css.md

レイアウトのスラッシングを避けるため、DOM の読み取りをまとめてから書き込みます。

// Bad: interleaved reads/writes force reflows
element.style.width = "100px";
const width = element.offsetWidth; // forces reflow
element.style.height = "200px";

// Good: batch writes, then read
element.style.width = "100px";
element.style.height = "200px";
const { width, height } = element.getBoundingClientRect();
result-type (MEDIUM) — @rules/result-type.md

成功/失敗を表すには、明示的なResult型を使用します。

let result = success(data);
if (isFailure(result)) return handleError(result.error);

コメント