Imported from practicalswan/agent-skills (
javascript-development/SKILL.md). Install upstream withnpx skills add practicalswan/agent-skills --skill javascript-development. Copyright stays with the author.
JavaScript Development
Optimized for ECMAScript 2024+, Node.js 22+, TypeScript 5.5+, and modern browser or server-first JavaScript runtimes.
Expert guidance for writing modern JavaScript code with ES2024+ features, async programming patterns, DOM manipulation, API integration, and best practices following official JavaScript resources at https://developer.mozilla.org/en-US/docs/Web/JavaScript.
- Leverage native parallel subagent dispatch and 200k+ context windows where available.
Anti-Patterns
- Copying outdated browser or framework patterns: Deprecated APIs and old workarounds add complexity immediately.
- Skipping abort, timeout, or response checks in async code: Network paths fail at the edges first, not on the happy path.
- Treating accessibility as a final polish pass: Markup and state shape are harder to fix after the component contract is set.
Verification Protocol
Before claiming "skill applied successfully":
- Pass/fail: The Javascript Development implementation names the target runtime, framework version, and affected files.
- Pass/fail: Build, lint, test, or equivalent local validation is run for the changed surface.
- Pass/fail: Edge cases for errors, dependency drift, and environment differences are addressed or explicitly out of scope.
- Pressure-test scenario: Apply the workflow to a change that passes happy-path tests but fails one boundary condition.
- Success metric: Zero untested success claims; every implementation claim maps to a command or artifact.
Before and After Example
// Before
async function loadProfile() {
const response = await fetch('/api/profile');
return response.json();
}
// After
export async function loadProfile(signal) {
const response = await fetch('/api/profile', { signal });
if (!response.ok) {
throw new Error(`Profile request failed: ${response.status}`);
}
return response.json();
}
Adds cancellation and explicit response validation so network failures do not masquerade as parsing bugs.
Activation Conditions
Use symptom -> action triggers: when one matches, apply this skill and verify with the protocol below.
Core JavaScript Development:
- Writing modern JavaScript with ES2024+ features
- Creating React components and hooks
- Working with DOM manipulation and events
- Implementing forms and user interactions
- Managing state in frontend applications
Asynchronous Programming:
- Using Promises and async/await patterns
- Fetching data from APIs
- Handling loading and error states
- Implementing retry mechanisms
- Working with concurrent operations
Data Handling:
- Manipulating arrays and objects
- Using modern array methods (map, filter, reduce, find)
- Working with JSON data
- LocalStorage and session management
- Data transformation and formatting
API Integration:
- Fetch API for HTTP requests
- Axios for advanced HTTP client features
- RESTful API design and consumption
- Authentication with JWT tokens
- CORS and error handling
Part 1: Modern JavaScript (ES2024+)
New Features & Syntax
// Logical Assignment Operators (ES2021)
const obj = { a: 1 };
obj.a ??= 10; // Only assign if obj.a is null/undefined
console.log(obj.a); // 1 (no change)
obj.b ??= 20; // Assign if missing
console.log(obj.b); // 20
// Numeric Separators (ES2021)
const billion = 1_000_000_000;
const bytes = 0xff_13_ff; // Hex
// String methods (ES2022)
const str = "Hello World";
console.log(str.replaceAll('l', 'L')); // "HeLLo WorLd"
console.log(str.at(-1)); // "d" (last character)
// Array methods (ES2023)
const array = [1, 2, 3, 4, 5];
console.log(array.toReversed()); // [5, 4, 3, 2, 1]
console.log(array.toSorted()); // [1, 2, 3, 4, 5]
console.log(array.with(2, 99)); // [1, 2, 99, 4, 5] (non-mutating)
// Hashbang (for scripts)
#!/usr/bin/env node
console.log("Executable script!");
// Object.groupBy (ES2024)
const people = [
{ name: "Alice", age: 25, role: "admin" },
{ name: "Bob", age: 30, role: "user" },
{ name: "Charlie", age: 25, role: "user" },
];
const groupedByAge = Object.groupBy(people, ({ age }) => age);
console.log(groupedByAge);
// { 25: [{name: "Alice", age: 25, role: "admin"}, ...], 30: [...] }
const groupedByRole = Map.groupBy(people, ({ role }) => role);
console.log(groupedByRole);
// Map { "admin" => [...], "user" => [...] }
Template Literals & Tagged Templates
// Template literals with expressions
const firstName = "John";
const lastName = "Doe";
const greeting = `Hello, ${firstName} ${lastName}!`;
// Tagged template for custom formatting
function highlight(strings, ...values) {
return strings.reduce((result, str, i) => {
return result + str + (values[i] ? `<strong>${values[i]}</strong>` : '');
}, '');
}
const message = highlight`User ${firstName} is online.`;
// "User <strong>John</strong> is online."
Destructuring & Spread
// Object destructuring
const user = { id: 1, name: "Alice", email: "alice@example.com", role: "admin" };
const { name, email, role: userRole } = user;
console.log(name, email, userRole); // "Alice", "alice@example.com", "admin"
// Array destructuring
const numbers = [1, 2, 3, 4, 5];
const [first, second, ...rest] = numbers;
console.log(first, second, rest); // 1, 2, [3, 4, 5]
// Destructuring in function parameters
function processRecipe({ title, difficulty, ingredients = [] }) {
return `${title} (${difficulty}) - ${ingredients.length} ingredients`;
}
const recipe = { title: "Pasta", difficulty: "Medium", ingredients: ["Pasta", "Sauce"] };
processRecipe(recipe); // "Pasta (Medium) - 2 ingredients"
Part 2: Async Programming
Async/Await Patterns
// Basic async/await
async function fetchUser(userId) {
try {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const user = await response.json();
return user;
} catch (error) {
console.error("Failed to fetch user:", error);
throw error;
}
}
// Parallel async operations with Promise.all
async function fetchRecipeData(recipeId) {
try {
const [recipe, reviews, ingredients] = await Promise.all([
fetch(`/api/recipes/${recipeId}`).then(r => r.json()),
fetch(`/api/recipes/${recipeId}/reviews`).then(r => r.json()),
fetch(`/api/recipes/${recipeId}/ingredients`).then(r => r.json()),
]);
return { recipe, reviews, ingredients };
} catch (error) {
console.error("Failed to fetch recipe data:", error);
throw error;
}
}
// Race with Promise.any (ES2021)
async function fetchFromMultipleEndpoints() {
const endpoints = [
'/api/v1/users',
'/api/v2/users',
'/api/v3/users',
];
try {
const response = await Promise.any(
endpoints.map(url => fetch(url).then(r => r.json()))
);
return response;
} catch (error) {
// All promises rejected
console.error("All endpoints failed:", error);
throw error;
}
}
// All Settled (ES2020)
async function fetchWithStatus() {
const requests = [
fetch('/api/users'),
fetch('/api/recipes'),
fetch('/api/stats'),
];
const results = await Promise.allSettled(requests);
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
console.log(`Request ${index} successful`);
} else {
console.error(`Request ${index} failed:`, result.reason);
}
});
}
Async Iterator (ES2018)
// Async generator function
async function* fetchPaginatedUsers(pageSize = 10) {
let page = 1;
let hasMore = true;
while (hasMore) {
const response = await fetch(`/api/users?page=${page}&limit=${pageSize}`);
const { users, totalPages } = await response.json();
yield* users;
page++;
hasMore = page <= totalPages;
// Add delay to avoid rate limiting
await new Promise(resolve => setTimeout(resolve, 500));
}
}
// Using async iterator
async function getAllUsers() {
const userIterator = fetchPaginatedUsers();
const allUsers = [];
for await (const user of userIterator) {
allUsers.push(user);
console.log(`Fetched: ${user.name}`);
}
return allUsers;
}
Top-level Await (ES2022)
// In ES modules, you can use await at top level
const config = await fetch('/api/config').then(r => r.json());
console.log('App config loaded:', config);
// This is useful for modules that need to load data before export
export const recipes = await fetch('/api/recipes').then(r => r.json());
export const users = await fetch('/api/users').then(r => r.json());
Part 3: API Integration
Fetch API Patterns
// Basic GET request
async function getRecipes(filters = {}) {
const queryParams = new URLSearchParams(filters);
const url = `/api/recipes?${queryParams}`;
const response = await fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
});
if (!response.ok) {
throw new Error(`Request failed: ${response.status}`);
}
return await response.json();
}
// POST request with JSON body
async function createRecipe(recipeData) {
const response = await fetch('/api/recipes', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: JSON.stringify(recipeData),
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.message || 'Failed to create recipe');
}
return await response.json();
}
// PUT request with authentication
async function updateRecipe(recipeId, recipeData, token) {
const response = await fetch(`/api/recipes/${recipeId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': `Bearer ${token}`,
},
body: JSON.stringify(recipeData),
});
if (!response.status === 200 && response.status !== 204) {
throw new Error(`Update failed: ${response.status}`);
}
return await response.json();
}
// DELETE request
async function deleteRecipe(recipeId, token) {
const response = await fetch(`/api/recipes/${recipeId}`, {
method: 'DELETE',
headers: {
'Authorization': `Bearer ${token}`,
},
});
if (!response.ok) {
throw new Error(`Delete failed: ${response.status}`);
}
return true;
}
Axios Integration
import axios from 'axios';
// Create axios instance with default config
const api = axios.create({
baseURL: '/api',
timeout: 10000,
headers: {
'Content-Type': 'application/json',
},
});
// Request interceptor for adding authentication
api.interceptors.request.use(
(config) => {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => Promise.reject(error)
);
// Response interceptor for error handling
api.interceptors.response.use(
(response) => response,
(error) => {
if (error.response?.status === 401) {
// Unauthorized - redirect to login
localStorage.removeItem('token');
window.location.href = '/login';
}
return Promise.reject(error);
}
);
// API methods
export const recipesApi = {
async getAll(filters = {}) {
const response = await api.get('/recipes', { params: filters });
return response.data;
},
async getById(recipeId) {
const response = await api.get(`/recipes/${recipeId}`);
return response.data;
},
async create(recipeData) {
const response = await api.post('/recipes', recipeData);
return response.data;
},
async update(recipeId, recipeData) {
const response = await api.put(`/recipes/${recipeId}`, recipeData);
return response.data;
},
async delete(recipeId) {
const response = await api.delete(`/recipes/${recipeId}`);
return response.data;
},
};
Error Handling & Retry
// Retry utility with exponential backoff
async function fetchWithRetry(url, options = {}, maxRetries = 3) {
let lastError;
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const response = await fetch(url, options);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return response.json();
} catch (error) {
lastError = error;
console.log(`Attempt ${attempt + 1} failed, retrying...`);
// Exponential backoff: 1s, 2s, 4s
const delay = Math.pow(2, attempt) * 1000;
await new Promise(resolve => setTimeout(resolve, delay));
}
}
throw new Error(`Failed after ${maxRetries} attempts: ${lastError.message}`);
}
// Usage with error handling
async function getRecipeWithRetry(recipeId) {
try {
const recipe = await fetchWithRetry(`/api/recipes/${recipeId}`);
console.log('Recipe loaded:', recipe);
return recipe;
} catch (error) {
console.error('Failed to load recipe:', error);
// Show user-friendly error message
return { error: true, message: 'Failed to load recipe. Please try again.' };
}
}
Part 4: DOM Manipulation & Events
Element Selection & Manipulation
// Modern element selection
const button = document.querySelector('#submit-btn');
const items = document.querySelectorAll('.list-item');
const container = document.getElementById('container');
const firstChild = document.querySelector('.item:first-child');
// Using closest for event delegation
document.addEventListener('click', (event) => {
const button = event.target.closest('.action-button');
if (button) {
console.log('Button clicked:', button.dataset.id);
}
});
// Element creation and manipulation
function addRecipeToList(recipe) {
const li = document.createElement('li');
li.className = 'recipe-item';
li.dataset.recipeId = recipe.id;
li.innerHTML = `
<h3>${recipe.title}</h3>
<p>${recipe.description}</p>
<button class="delete-btn">Delete</button>
`;
// Add event listener to delete button
const deleteBtn = li.querySelector('.delete-btn');
deleteBtn.addEventListener('click', () => deleteRecipe(recipe.id));
// Append to container
document.getElementById('recipe-list').appendChild(li);
}
// Element removal
function removeRecipeElement(recipeId) {
const element = document.querySelector(`[data-recipe-id="${recipeId}"]`);
if (element) {
element.remove();
}
}
Event Handling
// Form submission with validation
const form = document.getElementById('recipe-form');
form.addEventListener('submit', async (event) => {
event.preventDefault(); // Prevent default form submission
const formData = new FormData(form);
const recipeData = {
title: formData.get('title'),
description: formData.get('description'),
category: formData.get('category'),
difficulty: formData.get('difficulty'),
};
// Validation
if (!recipeData.title || recipeData.title.length < 3) {
showError('Title is required and must be at least 3 characters');
return;
}
try {
// Submit to API
const response = await fetch('/api/recipes', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(recipeData),
});
if (response.ok) {
showSuccess('Recipe created successfully!');
form.reset();
}
} catch (error) {
showError('Failed to create recipe: ' + error.message);
}
});
// Debounce for search input
let searchTimeout;
const searchInput = document.getElementById('search');
searchInput.addEventListener('input', (event) => {
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => {
const query = event.target.value;
if (query.length >= 2) {
performSearch(query);
}
}, 300); // Wait 300ms after user stops typing
});
// Intersection Observer for infinite scroll
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
loadMoreRecipes();
}
});
}, {
root: null,
threshold: 0.1,
});
// Observe sentinel element
const sentinel = document.getElementById('load-more-sentinel');
observer.observe(sentinel);
Part 5: Data Structures & Algorithms
Array Methods
// map - Transform array
const recipes = [
{ title: 'Pasta', difficulty: 'Medium' },
{ title: 'Salad', difficulty: 'Easy' },
];
const titles = recipes.map(recipe => recipe.title);
// ['Pasta', 'Salad']
// filter - Select elements
const easyRecipes = recipes.filter(recipe => recipe.difficulty === 'Easy');
// [{ title: 'Salad', difficulty: 'Easy' }]
// reduce - Aggregate array
const wordCount = recipes.reduce((count, recipe) => {
return count + recipe.title.split(' ').length;
}, 0);
// find - Find first matching element
const pasta = recipes.find(recipe => recipe.title.includes('Pasta'));
// some - Check if any matches
const hasMediumDifficulty = recipes.some(recipe => recipe.difficulty === 'Medium'); // true
// every - Check if all match
const allHaveTitles = recipes.every(recipe => recipe.title.length > 0); // true
// sort - Sort array
const sortedRecipes = [...recipes].sort((a, b) =>
a.title.localeCompare(b.title)
);
// flatMap - Map and flatten
const nested = [[1, 2], [3, 4]];
const flattened = nested.flatMap(arr => arr); // [1, 2, 3, 4]
Object Methods
// Object.keys, Object.values, Object.entries
const user = {
id: 1,
name: 'Alice',
email: 'alice@example.com',
role: 'admin',
};
const keys = Object.keys(user); // ['id', 'name', 'email', 'role']
const values = Object.values(user); // [1, 'Alice', 'alice@example.com', 'admin']
const entries = Object.entries(user);
// [['id', 1], ['name', 'Alice'], ['email', 'alice@example.com'], ['role', 'admin']]
// Object.fromEntries - Convert back to object
const filtered = Object.fromEntries(
Object.entries(user).filter(([key]) => key !== 'role')
);
// { id: 1, name: 'Alice', email: 'alice@example.com' }
// Object.freeze - Make immutable
const config = Object.freeze({ apiUrl: '/api/v1' });
config.apiUrl = '/api/v2'; // Error in strict mode
Set and Map
// Set - Unique values
const tags = new Set(['Easy', 'Medium', 'Medium', 'Hard']);
console.log(tags.size); // 3
tags.add('Easy');
console.log(tags.has('Medium')); // true
tags.delete('Hard');
const uniqueTags = Array.from(tags); // ['Easy', 'Medium']
// Map - Key-value pairs with any type
const userRoles = new Map();
userRoles.set(1, 'admin');
userRoles.set(2, 'user');
userRoles.set('alice', 'editor');
console.log(userRoles.get(1)); // 'admin'
console.log(userRoles.has('alice')); // true
const roles = Array.from(userRoles.entries());
// [[1, 'admin'], [2, 'user'], ['alice', 'editor']]
Part 6: Date & Time
Date Operations
// Creating dates
const now = new Date();
const specificDate = new Date('2024-02-01');
const fromTimestamp = new Date(17068896000000);
// Date formatting
function formatDate(date) {
const options = {
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
};
return new Intl.DateTimeFormat('en-US', options).format(date);
}
// "February 1, 2024 at 12:00 PM"
// Relative time (e.g., "2 hours ago")
function getRelativeTime(date) {
const now = new Date();
const diff = now - date;
const seconds = Math.floor(diff / 1000);
const minutes = Math.floor(seconds / 60);
const hours = Math.floor(minutes / 60);
const days = Math.floor(hours / 24);
if (seconds < 60) return 'just now';
if (minutes < 60) return `${minutes} minute${minutes > 1 ? 's' : ''} ago`;
if (hours < 24) return `${hours} hour${hours > 1 ? 's' : ''} ago`;
return `${days} day${days > 1 ? 's' : ''} ago`;
}
// Date manipulation
function addDays(date, days) {
const result = new Date(date);
result.setDate(result.getDate() + days);
return result;
}
function isSameDay(date1, date2) {
return date1.toDateString() === date2.toDateString();
}
Part 7: LocalStorage & State Management
LocalStorage Wrapper
class StorageManager {
static set(key, value) {
try {
localStorage.setItem(key, JSON.stringify(value));
} catch (error) {
console.error('Failed to save to localStorage:', error);
}
}
static get(key, defaultValue = null) {
try {
const item = localStorage.getItem(key);
return item ? JSON.parse(item) : defaultValue;
} catch (error) {
console.error('Failed to read from localStorage:', error);
return defaultValue;
}
}
static remove(key) {
try {
localStorage.removeItem(key);
} catch (error) {
console.error('Failed to remove from localStorage:', error);
}
}
static clear() {
try {
localStorage.clear();
} catch (error) {
console.error('Failed to clear localStorage:', error);
}
}
static has(key) {
return localStorage.getItem(key) !== null;
}
}
State Management (Simple)
class StateManager {
constructor(initialState = {}) {
this.state = initialState;
this.listeners = [];
}
setState(newState) {
this.state = { ...this.state, ...newState };
this.notify();
}
subscribe(listener) {
this.listeners.push(listener);
listener(this.state);
}
notify() {
this.listeners.forEach(listener => listener(this.state));
}
}
// Usage
const stateManager = new StateManager({
user: null,
recipes: [],
loading: false,
});
stateManager.subscribe((state) => {
console.log('State updated:', state);
// Update UI
});
stateManager.setState({ recipes: [...] });
Part 8: Utility Functions
Common Utilities
// Debounce
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// Throttle
function throttle(func, limit) {
let inThrottle;
return function(...args) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
// Random ID
function generateId() {
return Math.random().toString(36).substr(2, 9);
}
// Slugify text
function slugify(text) {
return text
.toString()
.toLowerCase()
.trim()
.replace(/\s+/g, '-')
.replace(/[^\w\-]+/g, '');
}
// Format number with commas
function formatNumber(num) {
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
// Truncate text
function truncate(text, maxLength) {
return text.length > maxLength
? text.substring(0, maxLength - 3) + '...'
: text;
}
JavaScript Development Best Practices
Code Quality
- Use
constandletinstead ofvar - Use strict mode (
'use strict') - Add JSDoc comments for functions
- Use modern ES2024+ features
- Avoid global variables
- Use meaningful variable and function names
Asynchronous Code
- Prefer async/await over .then() chains
- Handle errors with try-catch
- Use Promise.all for parallel operations
- Implement retry logic for network requests
- Provide loading states for async operations
DOM & Events
- Use event delegation for dynamic content
- Clean up event listeners to prevent memory leaks
- Use dataset for custom data attributes
- Validate form input before submission
- Use debouncing/throttling for rapid events
Performance
- Minimize DOM manipulation
- Use document fragments for bulk inserts
- Implement lazy loading for images/lists
- Cache expensive computations
- Use requestAnimationFrame for animations
Security
- Sanitize user input to prevent XSS
- Use HTTPS for API calls
- Store tokens securely (HttpOnly cookies)
- Validate data from localStorage
- Implement CSRF protection
Modern Component and Testing Examples
Server Components
export default async function ProfileCard({ userId }) {
const user = await getUser(userId);
return <section>{user.name}</section>;
}
Error Boundaries
import { ErrorBoundary } from 'react-error-boundary';
<ErrorBoundary fallbackRender={() => <p>Something went wrong.</p>}>
<ProfileDashboard />
</ErrorBoundary>
Accessibility Testing Tools
import { axe } from 'jest-axe';
test('search form has no obvious accessibility violations', async () => {
const { container } = render(<SearchForm />);
expect(await axe(container)).toHaveNoViolations();
});
Common Pitfalls
- Copying outdated browser or framework patterns: Deprecated APIs and unnecessary workarounds add complexity immediately.
- Skipping response and abort handling: Network code fails in edge cases first, so the happy path alone is misleading.
- Treating accessibility as post-processing: Component structure is harder to fix later if semantics were not built in from the start.
References & Resources
Official Documentation
- MDN Web Docs — Complete JavaScript reference
- ECMAScript 2024 Spec — Latest language specification
- Fetch API — HTTP requests with Fetch
- Web Storage API — LocalStorage and SessionStorage
Libraries & Tools
- Axios Documentation — Popular HTTP client library
- Vite Documentation — Build tool for modern web apps
- ESLint — Code linting for JavaScript
- Prettier — Code formatting tool
Learning Resources
- JavaScript.info — Modern JavaScript tutorial
- JavaScript 30 — 30-day JavaScript challenge
- You Don't Know JS — Deep dive into JavaScript
Cross-Client Portability
This skill is written to stay usable across GitHub Copilot, Claude Code, and Codex.
- GitHub Copilot: keep the folder in a Copilot-visible skill path or wrap the workflow in project instructions when folder discovery is unavailable.
- Claude Code: keep the folder in a local skills directory or a compatible plugin source.
- Codex: install or sync the folder into
$CODEX_HOME/skills/javascript-developmentand restart Codex after major changes.
MCP Availability And Fallback
Preferred MCP Server: None required
- Fallback prompt: "Use the JavaScript Development skill without MCP. Rely on the local
SKILL.md, bundled references or scripts, and manual verification. Show the exact commands, evidence, and final checks you used before concluding." - If the current host does not expose a matching server, use the bundled references, scripts, native toolchain, and manual workflow already described in this skill.
- Treat direct local verification, rendered output, logs, tests, or screenshots as the fallback evidence path before completion.
Related Skills
- react-development: Use it when the workflow also needs React component architecture and client or server boundaries.
- nextjs-development: Use it when the workflow also needs Next.js App Router and server-first React patterns.
- vite-development: Use it when the workflow also needs Vite build and development-server configuration.
- web-testing: Use it when the workflow also needs browser and end-to-end testing evidence.