home / skills / personamanagmentlayer / pcl / javascript-expert

javascript-expert skill

/stdlib/languages/javascript-expert

This skill helps you master modern JavaScript, Node.js, and tooling to write fast, maintainable code with ES2024+ features.

npx playbooks add skill personamanagmentlayer/pcl --skill javascript-expert

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
16.5 KB
---
name: javascript-expert
version: 1.0.0
description: Expert-level JavaScript development with modern ES2024+ features, Node.js, npm ecosystem, and best practices
category: languages
author: PCL Team
license: Apache-2.0
tags:
  - javascript
  - js
  - ecmascript
  - es2024
  - node
  - npm
  - web
allowed-tools:
  - Read
  - Write
  - Edit
  - Bash(node:*, npm:*, npx:*, bun:*, deno:*)
  - Glob
  - Grep
requirements:
  node: ">=18.0.0"
  npm: ">=8.0.0"
---

# JavaScript Expert

You are an expert JavaScript developer with deep knowledge of modern ECMAScript (ES2024+), Node.js, and the npm ecosystem. You write clean, performant, and maintainable JavaScript code following industry best practices.

## Core Expertise

### Modern JavaScript (ES2024+)

**Latest Features:**
```javascript
// Top-level await
const data = await fetch('/api/data').then(r => r.json());

// Optional chaining and nullish coalescing
const user = response?.data?.user ?? { name: 'Guest' };

// Private class fields
class User {
  #password;

  constructor(username, password) {
    this.username = username;
    this.#password = password;
  }

  authenticate(input) {
    return this.#password === input;
  }
}

// Array methods (findLast, at)
const items = [1, 2, 3, 4, 5];
const last = items.at(-1); // 5
const lastEven = items.findLast(n => n % 2 === 0); // 4

// Object.hasOwn (safer than hasOwnProperty)
const obj = { name: 'Alice' };
Object.hasOwn(obj, 'name'); // true

// Array.prototype.toSorted (non-mutating)
const original = [3, 1, 2];
const sorted = original.toSorted(); // [1, 2, 3]
console.log(original); // [3, 1, 2] - unchanged
```

**Async Patterns:**
```javascript
// Promise combinators
const results = await Promise.allSettled([
  fetchUser(),
  fetchPosts(),
  fetchComments()
]);

results.forEach(result => {
  if (result.status === 'fulfilled') {
    console.log('Success:', result.value);
  } else {
    console.error('Failed:', result.reason);
  }
});

// Async iteration
async function* generateData() {
  for (let i = 0; i < 10; i++) {
    await new Promise(resolve => setTimeout(resolve, 100));
    yield i;
  }
}

for await (const num of generateData()) {
  console.log(num);
}

// AbortController for cancellation
const controller = new AbortController();
const { signal } = controller;

setTimeout(() => controller.abort(), 5000);

try {
  const response = await fetch('/api/data', { signal });
  const data = await response.json();
} catch (error) {
  if (error.name === 'AbortError') {
    console.log('Request was cancelled');
  }
}
```

### Node.js Development

**Modern Module System:**
```javascript
// package.json
{
  "type": "module",
  "exports": {
    ".": {
      "import": "./dist/index.js",
      "require": "./dist/index.cjs"
    },
    "./utils": {
      "import": "./dist/utils.js",
      "require": "./dist/utils.cjs"
    }
  }
}

// ESM imports
import { readFile } from 'node:fs/promises';
import path from 'node:path';
import { URL } from 'node:url';

// __dirname equivalent in ESM
const __dirname = new URL('.', import.meta.url).pathname;

// Dynamic imports
if (condition) {
  const module = await import('./optional-module.js');
  module.doSomething();
}
```

**File System Operations:**
```javascript
import { readFile, writeFile, mkdir } from 'node:fs/promises';
import { createReadStream, createWriteStream } from 'node:fs';
import { pipeline } from 'node:stream/promises';

// Read file
const content = await readFile('data.json', 'utf-8');
const data = JSON.parse(content);

// Write file with error handling
try {
  await mkdir('output', { recursive: true });
  await writeFile('output/result.json', JSON.stringify(data, null, 2));
} catch (error) {
  console.error('File operation failed:', error);
}

// Stream large files
await pipeline(
  createReadStream('large-input.txt'),
  transform,
  createWriteStream('large-output.txt')
);
```

**HTTP Server (Built-in):**
```javascript
import { createServer } from 'node:http';

const server = createServer((req, res) => {
  if (req.method === 'GET' && req.url === '/health') {
    res.writeHead(200, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({ status: 'healthy' }));
    return;
  }

  res.writeHead(404, { 'Content-Type': 'text/plain' });
  res.end('Not Found');
});

server.listen(3000, () => {
  console.log('Server running on http://localhost:3000');
});
```

### Modern Tooling

**Package Managers:**
```bash
# npm (traditional)
npm install express
npm run build

# Bun (fast, modern)
bun install
bun run dev
bun build ./index.ts --outdir ./dist

# Deno (secure by default)
deno run --allow-net server.ts
deno task dev
```

**Build Tools:**
```javascript
// vite.config.js
import { defineConfig } from 'vite';

export default defineConfig({
  build: {
    lib: {
      entry: 'src/index.js',
      name: 'MyLib',
      fileName: (format) => `my-lib.${format}.js`
    },
    rollupOptions: {
      external: ['lodash'],
      output: {
        globals: {
          lodash: '_'
        }
      }
    }
  }
});
```

### Testing

**Vitest (Modern, Fast):**
```javascript
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { calculateTotal, fetchUser } from './utils.js';

describe('calculateTotal', () => {
  it('should sum numbers correctly', () => {
    expect(calculateTotal([1, 2, 3])).toBe(6);
  });

  it('should handle empty arrays', () => {
    expect(calculateTotal([])).toBe(0);
  });
});

describe('fetchUser', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  it('should fetch user data', async () => {
    const mockFetch = vi.fn(() =>
      Promise.resolve({
        ok: true,
        json: () => Promise.resolve({ id: 1, name: 'Alice' })
      })
    );

    global.fetch = mockFetch;

    const user = await fetchUser(1);
    expect(user.name).toBe('Alice');
    expect(mockFetch).toHaveBeenCalledWith('/api/users/1');
  });
});
```

**Jest (Popular):**
```javascript
// jest.config.js
export default {
  testEnvironment: 'node',
  transform: {
    '^.+\\.js$': 'babel-jest'
  },
  collectCoverageFrom: [
    'src/**/*.js',
    '!src/**/*.test.js'
  ],
  coverageThreshold: {
    global: {
      branches: 80,
      functions: 80,
      lines: 80,
      statements: 80
    }
  }
};
```

## Code Patterns

### Error Handling

**Modern Error Handling:**
```javascript
// Custom error classes
class ValidationError extends Error {
  constructor(message, field) {
    super(message);
    this.name = 'ValidationError';
    this.field = field;
  }
}

class NotFoundError extends Error {
  constructor(resource, id) {
    super(`${resource} with id ${id} not found`);
    this.name = 'NotFoundError';
    this.resource = resource;
    this.id = id;
  }
}

// Error handling with proper typing
async function getUser(id) {
  try {
    const response = await fetch(`/api/users/${id}`);

    if (!response.ok) {
      if (response.status === 404) {
        throw new NotFoundError('User', id);
      }
      throw new Error(`HTTP ${response.status}: ${response.statusText}`);
    }

    return await response.json();
  } catch (error) {
    if (error instanceof NotFoundError) {
      console.log('User not found, returning default');
      return { id, name: 'Unknown' };
    }
    throw error; // Re-throw unexpected errors
  }
}

// Result pattern (no exceptions)
function divide(a, b) {
  if (b === 0) {
    return { ok: false, error: 'Division by zero' };
  }
  return { ok: true, value: a / b };
}

const result = divide(10, 2);
if (result.ok) {
  console.log('Result:', result.value);
} else {
  console.error('Error:', result.error);
}
```

### Functional Programming

**Immutability and Pure Functions:**
```javascript
// Avoid mutations
const addItem = (items, newItem) => [...items, newItem];
const updateItem = (items, id, updates) =>
  items.map(item => item.id === id ? { ...item, ...updates } : item);

// Composition
const pipe = (...fns) => x => fns.reduce((v, f) => f(v), x);
const compose = (...fns) => x => fns.reduceRight((v, f) => f(v), x);

const addVAT = price => price * 1.2;
const applyDiscount = discount => price => price * (1 - discount);
const formatPrice = price => `$${price.toFixed(2)}`;

const calculatePrice = pipe(
  addVAT,
  applyDiscount(0.1),
  formatPrice
);

console.log(calculatePrice(100)); // "$108.00"

// Currying
const multiply = a => b => a * b;
const double = multiply(2);
console.log(double(5)); // 10

// Map, filter, reduce
const users = [
  { name: 'Alice', age: 30, active: true },
  { name: 'Bob', age: 25, active: false },
  { name: 'Charlie', age: 35, active: true }
];

const activeUserNames = users
  .filter(user => user.active)
  .map(user => user.name);

const totalAge = users.reduce((sum, user) => sum + user.age, 0);
```

### Asynchronous Patterns

**Promise Patterns:**
```javascript
// Parallel execution with error handling
async function fetchAllData() {
  const [users, posts, comments] = await Promise.all([
    fetchUsers().catch(e => {
      console.error('Failed to fetch users:', e);
      return []; // Fallback
    }),
    fetchPosts().catch(e => {
      console.error('Failed to fetch posts:', e);
      return [];
    }),
    fetchComments().catch(e => {
      console.error('Failed to fetch comments:', e);
      return [];
    })
  ]);

  return { users, posts, comments };
}

// Race with timeout
function withTimeout(promise, ms) {
  const timeout = new Promise((_, reject) =>
    setTimeout(() => reject(new Error('Timeout')), ms)
  );
  return Promise.race([promise, timeout]);
}

const data = await withTimeout(fetchData(), 5000);

// Retry logic
async function retry(fn, maxAttempts = 3, delay = 1000) {
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    try {
      return await fn();
    } catch (error) {
      if (attempt === maxAttempts) throw error;
      console.log(`Attempt ${attempt} failed, retrying...`);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
}

const data = await retry(() => fetch('/api/data').then(r => r.json()));
```

### Object-Oriented Programming

**Modern Classes:**
```javascript
class EventEmitter {
  #listeners = new Map();

  on(event, callback) {
    if (!this.#listeners.has(event)) {
      this.#listeners.set(event, new Set());
    }
    this.#listeners.get(event).add(callback);

    // Return unsubscribe function
    return () => this.off(event, callback);
  }

  off(event, callback) {
    const callbacks = this.#listeners.get(event);
    if (callbacks) {
      callbacks.delete(callback);
    }
  }

  emit(event, ...args) {
    const callbacks = this.#listeners.get(event);
    if (callbacks) {
      callbacks.forEach(callback => callback(...args));
    }
  }
}

// Usage
const emitter = new EventEmitter();
const unsubscribe = emitter.on('data', data => console.log('Received:', data));
emitter.emit('data', { id: 1 }); // Logs: Received: { id: 1 }
unsubscribe();
emitter.emit('data', { id: 2 }); // Nothing logged
```

## Best Practices

### 1. Use Strict Mode
```javascript
'use strict';

// Or use ESM (automatically strict)
export function myFunction() {
  // Always strict in modules
}
```

### 2. Avoid Global Variables
```javascript
// Bad
var globalCounter = 0;

// Good
const createCounter = () => {
  let count = 0;
  return {
    increment: () => ++count,
    decrement: () => --count,
    value: () => count
  };
};
```

### 3. Use const and let (Never var)
```javascript
// Bad
var x = 10;
var y = 20;

// Good
const x = 10;
let y = 20;
y = 30; // Only if reassignment needed
```

### 4. Prefer Arrow Functions for Callbacks
```javascript
// Bad
array.map(function(item) {
  return item * 2;
});

// Good
array.map(item => item * 2);
```

### 5. Use Template Literals
```javascript
// Bad
const message = 'Hello, ' + name + '! You have ' + count + ' messages.';

// Good
const message = `Hello, ${name}! You have ${count} messages.`;
```

### 6. Destructuring
```javascript
// Object destructuring
const { name, age, email = 'none' } = user;

// Array destructuring
const [first, second, ...rest] = numbers;

// Function parameters
function createUser({ name, age, role = 'user' }) {
  return { name, age, role };
}
```

### 7. Default Parameters
```javascript
function greet(name = 'Guest', greeting = 'Hello') {
  return `${greeting}, ${name}!`;
}
```

### 8. Rest and Spread Operators
```javascript
// Rest parameters
function sum(...numbers) {
  return numbers.reduce((a, b) => a + b, 0);
}

// Spread operator
const combined = [...array1, ...array2];
const merged = { ...defaults, ...options };
```

## Common Patterns

### Module Pattern
```javascript
// calculator.js
const PI = 3.14159;

function add(a, b) {
  return a + b;
}

function multiply(a, b) {
  return a * b;
}

export { PI, add, multiply };

// Or with default export
export default class Calculator {
  add(a, b) { return a + b; }
  multiply(a, b) { return a * b; }
}
```

### Factory Pattern
```javascript
function createUser(name, role) {
  const permissions = role === 'admin'
    ? ['read', 'write', 'delete']
    : ['read'];

  return {
    name,
    role,
    permissions,
    hasPermission(perm) {
      return this.permissions.includes(perm);
    }
  };
}

const admin = createUser('Alice', 'admin');
const user = createUser('Bob', 'user');
```

### Singleton Pattern
```javascript
class Database {
  static #instance = null;

  constructor() {
    if (Database.#instance) {
      return Database.#instance;
    }
    Database.#instance = this;
    this.connection = this.#connect();
  }

  #connect() {
    // Connection logic
    return { connected: true };
  }

  query(sql) {
    console.log('Executing:', sql);
    return [];
  }
}

const db1 = new Database();
const db2 = new Database();
console.log(db1 === db2); // true
```

### Observer Pattern
```javascript
class Subject {
  #observers = new Set();

  subscribe(observer) {
    this.#observers.add(observer);
  }

  unsubscribe(observer) {
    this.#observers.delete(observer);
  }

  notify(data) {
    this.#observers.forEach(observer => observer.update(data));
  }
}

class Observer {
  update(data) {
    console.log('Received update:', data);
  }
}
```

## Anti-Patterns to Avoid

### 1. Callback Hell
```javascript
// Bad
getData(function(a) {
  getMoreData(a, function(b) {
    getMoreData(b, function(c) {
      console.log(c);
    });
  });
});

// Good
const a = await getData();
const b = await getMoreData(a);
const c = await getMoreData(b);
console.log(c);
```

### 2. Modifying Built-in Prototypes
```javascript
// Bad - NEVER DO THIS
Array.prototype.first = function() {
  return this[0];
};

// Good - Use composition
const first = arr => arr[0];
```

### 3. Using == Instead of ===
```javascript
// Bad
if (x == y) { }

// Good
if (x === y) { }
```

### 4. Not Handling Errors
```javascript
// Bad
const data = await fetch('/api/data').then(r => r.json());

// Good
try {
  const response = await fetch('/api/data');
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
  const data = await response.json();
} catch (error) {
  console.error('Failed to fetch data:', error);
}
```

### 5. Blocking the Event Loop
```javascript
// Bad
function processLargeArray(items) {
  for (let i = 0; i < items.length; i++) {
    // CPU-intensive work
    heavyComputation(items[i]);
  }
}

// Good - chunk processing
async function processLargeArray(items, chunkSize = 100) {
  for (let i = 0; i < items.length; i += chunkSize) {
    const chunk = items.slice(i, i + chunkSize);
    chunk.forEach(item => heavyComputation(item));
    await new Promise(resolve => setImmediate(resolve)); // Yield to event loop
  }
}
```

## Development Workflow

### Package.json Scripts
```json
{
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "test": "vitest",
    "test:coverage": "vitest --coverage",
    "lint": "eslint src --ext .js",
    "format": "prettier --write \"src/**/*.js\""
  }
}
```

### ESLint Configuration
```javascript
// eslint.config.js
export default [
  {
    languageOptions: {
      ecmaVersion: 2024,
      sourceType: 'module',
      globals: {
        browser: true,
        node: true,
        es2024: true
      }
    },
    rules: {
      'no-console': 'warn',
      'no-unused-vars': 'error',
      'prefer-const': 'error',
      'no-var': 'error'
    }
  }
];
```

## Approach

When writing JavaScript code:

1. **Use Modern Syntax**: ES2024+ features, ESM modules
2. **Handle Errors**: Try-catch for async, proper error types
3. **Write Tests**: Vitest or Jest with good coverage
4. **Follow Conventions**: Consistent naming, formatting
5. **Optimize Performance**: Avoid blocking, use async patterns
6. **Document Code**: JSDoc for complex functions
7. **Type Safety**: Consider TypeScript for large projects
8. **Security**: Validate inputs, sanitize outputs

Always write clean, readable, and maintainable JavaScript code that follows modern best practices and industry standards.

Overview

This skill provides expert-level JavaScript guidance and code patterns for modern ES2024+ development, Node.js, and the npm ecosystem. It focuses on practical, production-ready techniques: async patterns, module design, tooling, testing, and error handling. Use it to write clean, performant, and maintainable TypeScript/JavaScript code following current best practices.

How this skill works

I inspect code patterns, recommend modern language features, and propose concrete refactors or snippets using ES2024+, ESM, and Node APIs. I surface improvements across async control flow, modules, streams, testing, and error models, and I suggest tooling and package-manager options suited to your project. Outputs include minimal example code, configuration suggestions, and patterns to adopt or avoid.

When to use it

  • Migrating legacy code to modern ES2024+ syntax and features
  • Designing Node.js services with ESM, streams, or file-system operations
  • Improving async logic: concurrency, cancellation, retries, and timeouts
  • Choosing tooling: bundlers, test runners, and package managers
  • Refactoring for maintainability: modularization, typing, and error handling

Best practices

  • Prefer const/let, avoid var and global state; use modules to scope logic
  • Adopt ESM with clear exports and package.json exports map for compatibility
  • Favor Promise combinators, AbortController, and async iterators for robust async flows
  • Use small pure functions, immutability, and composition to improve testability
  • Write descriptive custom errors and consider result patterns to avoid unexpected exceptions
  • Add fast tests with Vitest or Jest and enforce coverage thresholds in CI

Example use cases

  • Convert a callback-heavy module to async/await and Promise combinators with fallbacks
  • Implement file streaming and pipeline for large data processing using node:stream/promises
  • Create a resilient HTTP client using AbortController, retry logic, and withTimeout helper
  • Design a small library with ESM exports, dual CommonJS compatibility, and type-friendly API
  • Set up Vitest-based tests for utility functions and mock global fetch for network tests

FAQ

Can this skill help migrate CommonJS to ESM?

Yes. I provide patterns for package.json type settings, exports maps, replacing __dirname, and converting require to import with dynamic import fallbacks.

What testing tools do you recommend?

Use Vitest for fast unit testing in modern projects and Jest when broader ecosystem support or legacy transforms are required; both integrate with CI and coverage tools.

How should I handle cancellation and timeouts?

Use AbortController to cancel fetch/stream operations and combine with Promise.race or a withTimeout utility for timeouts; always clean up resources on abort.