frontend-js-best-practices

sergiodxa/agent-skills · updated Apr 8, 2026

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

Performance optimization and code style patterns for JavaScript and TypeScript code. Contains 17 rules focused on reducing unnecessary computation, optimizing data structures, and maintaining consistent conventions.

skill.md

JavaScript Best Practices

Performance optimization and code style patterns for JavaScript and TypeScript code. Contains 17 rules focused on reducing unnecessary computation, optimizing data structures, and maintaining consistent conventions.

When to Apply

Reference these guidelines when:

  • Writing loops or array operations
  • Working with data structures (Map, Set, arrays)
  • Manipulating the DOM directly
  • Caching values or function results
  • Optimizing hot code paths
  • Declaring variables or functions

Rules Summary

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

Use const at module level, let inside functions.

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

Prefer function declarations over arrow functions for named functions.

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

Use named exports. Avoid default exports (except Remix route components).

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

Avoid as Type casts. Use type guards or Zod validation instead.

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

Only comment when adding info the code cannot express.

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

Use Set/Map for O(1) lookups instead of Array methods.

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

Build Map once for repeated lookups.

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

Use toSorted() instead of sort() to avoid mutation.

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

Combine multiple filter/map into one loop.

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

Cache object properties in loops.

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

Cache expensive function results in module-level 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

Cache localStorage/sessionStorage reads in memory.

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 early when result is determined.

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

Check array length before expensive comparison.

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

Use loop for min/max instead of 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

Hoist RegExp creation outside loops.

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

Batch DOM reads before writes to avoid layout thrashing.

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

Use an explicit Result type for success/failure.

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

Discussion

Product Hunt–style comments (not star reviews)
  • No comments yet — start the thread.
general reviews

Ratings

4.740 reviews
  • Benjamin Diallo· Dec 20, 2024

    Registry listing for frontend-js-best-practices matched our evaluation — installs cleanly and behaves as described in the markdown.

  • Dhruvi Jain· Dec 12, 2024

    Keeps context tight: frontend-js-best-practices is the kind of skill you can hand to a new teammate without a long onboarding doc.

  • Maya Thomas· Dec 4, 2024

    Solid pick for teams standardizing on skills: frontend-js-best-practices is focused, and the summary matches what you get after install.

  • Aditi Gonzalez· Nov 23, 2024

    We added frontend-js-best-practices from the explainx registry; install was straightforward and the SKILL.md answered most questions upfront.

  • Ren Reddy· Nov 11, 2024

    Keeps context tight: frontend-js-best-practices is the kind of skill you can hand to a new teammate without a long onboarding doc.

  • Oshnikdeep· Nov 3, 2024

    Registry listing for frontend-js-best-practices matched our evaluation — installs cleanly and behaves as described in the markdown.

  • Mateo Srinivasan· Nov 3, 2024

    Useful defaults in frontend-js-best-practices — fewer surprises than typical one-off scripts, and it plays nicely with `npx skills` flows.

  • Ganesh Mohane· Oct 22, 2024

    frontend-js-best-practices reduced setup friction for our internal harness; good balance of opinion and flexibility.

  • Henry Rahman· Oct 22, 2024

    I recommend frontend-js-best-practices for anyone iterating fast on agent tooling; clear intent and a small, reviewable surface area.

  • Carlos Agarwal· Oct 14, 2024

    frontend-js-best-practices fits our agent workflows well — practical, well scoped, and easy to wire into existing repos.

showing 1-10 of 40

1 / 4
frontend-js-best-practices — AI agent skill | explainx.ai | explainx.ai