Claude-skill-registry javascript-modern
Implements modern JavaScript features from ES2020-ES2024 including optional chaining, nullish coalescing, private fields, Promise methods, and array transformations. Use when modernizing JavaScript code, using ES2024 features, or when user asks about latest ECMAScript standards.
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/javascript-modern" ~/.claude/skills/majiayu000-claude-skill-registry-javascript-modern && rm -rf "$T"
manifest:
skills/data/javascript-modern/SKILL.mdsource content
Modern JavaScript (ES2020-ES2024)
Latest ECMAScript features for cleaner, more expressive code.
ES2024 Features
Object.groupBy / Map.groupBy
const products = [ { name: 'Apple', category: 'fruit', price: 1.5 }, { name: 'Banana', category: 'fruit', price: 0.75 }, { name: 'Carrot', category: 'vegetable', price: 0.5 }, { name: 'Broccoli', category: 'vegetable', price: 2.0 }, ]; // Group into object const byCategory = Object.groupBy(products, (item) => item.category); // { // fruit: [{ name: 'Apple', ... }, { name: 'Banana', ... }], // vegetable: [{ name: 'Carrot', ... }, { name: 'Broccoli', ... }] // } // Group into Map (preserves non-string keys) const byPriceRange = Map.groupBy(products, (item) => item.price >= 1 ? 'expensive' : 'cheap' ); // Map { 'expensive' => [...], 'cheap' => [...] } // Complex grouping const users = [ { name: 'Alice', role: 'admin', active: true }, { name: 'Bob', role: 'user', active: false }, { name: 'Charlie', role: 'admin', active: false }, ]; const groupedUsers = Object.groupBy(users, (user) => `${user.role}-${user.active ? 'active' : 'inactive'}` );
Promise.withResolvers()
// Before: Awkward pattern let resolve, reject; const promise = new Promise((res, rej) => { resolve = res; reject = rej; }); // After: Clean extraction const { promise, resolve, reject } = Promise.withResolvers(); // Practical example: Timeout wrapper function timeout(ms) { const { promise, resolve } = Promise.withResolvers(); setTimeout(resolve, ms); return promise; } // Event-based promise function waitForClick(element) { const { promise, resolve } = Promise.withResolvers(); element.addEventListener('click', resolve, { once: true }); return promise; } // Cancellable fetch function cancellableFetch(url) { const { promise, resolve, reject } = Promise.withResolvers(); const controller = new AbortController(); fetch(url, { signal: controller.signal }) .then(resolve) .catch(reject); return { promise, cancel: () => controller.abort(), }; }
Immutable Array Methods
const numbers = [3, 1, 4, 1, 5, 9]; // toSorted() - Returns new sorted array const sorted = numbers.toSorted((a, b) => a - b); // sorted: [1, 1, 3, 4, 5, 9] // numbers: [3, 1, 4, 1, 5, 9] (unchanged) // toReversed() - Returns new reversed array const reversed = numbers.toReversed(); // reversed: [9, 5, 1, 4, 1, 3] // toSpliced() - Returns new spliced array const spliced = numbers.toSpliced(2, 1, 100, 200); // spliced: [3, 1, 100, 200, 1, 5, 9] // with() - Returns new array with element replaced const replaced = numbers.with(0, 999); // replaced: [999, 1, 4, 1, 5, 9] // Chaining immutable operations const result = numbers .toSorted((a, b) => b - a) .toSpliced(0, 2) .with(0, 0);
Set Methods
const setA = new Set([1, 2, 3, 4]); const setB = new Set([3, 4, 5, 6]); // Union - All elements from both sets const union = setA.union(setB); // Set { 1, 2, 3, 4, 5, 6 } // Intersection - Elements in both sets const intersection = setA.intersection(setB); // Set { 3, 4 } // Difference - Elements in A but not in B const difference = setA.difference(setB); // Set { 1, 2 } // Symmetric Difference - Elements in either but not both const symmetricDiff = setA.symmetricDifference(setB); // Set { 1, 2, 5, 6 } // Subset/Superset checks const subset = new Set([2, 3]); subset.isSubsetOf(setA); // true setA.isSupersetOf(subset); // true setA.isDisjointFrom(new Set([7, 8])); // true
RegExp /v Flag (Unicode Sets)
// Match any emoji const emojiPattern = /\p{Emoji}/v; emojiPattern.test('Hello 👋'); // true // Set operations in regex const greekOrCyrillic = /[\p{Script=Greek}--\p{Letter}]/v; // String properties const pattern = /^\p{RGI_Emoji}$/v; pattern.test('👨👩👧👦'); // true (family emoji)
ES2023 Features
Array findLast / findLastIndex
const numbers = [1, 2, 3, 4, 5, 4, 3]; // Find from end const lastEven = numbers.findLast(n => n % 2 === 0); // 4 (the second occurrence) const lastEvenIndex = numbers.findLastIndex(n => n % 2 === 0); // 5 // Practical: Find most recent matching item const logs = [ { level: 'info', message: 'Started' }, { level: 'error', message: 'Failed' }, { level: 'info', message: 'Retry' }, { level: 'error', message: 'Failed again' }, ]; const lastError = logs.findLast(log => log.level === 'error'); // { level: 'error', message: 'Failed again' }
Hashbang Grammar
#!/usr/bin/env node // Valid at start of file console.log('Script executed directly');
WeakMap Symbols as Keys
const weakMap = new WeakMap(); const key = Symbol('unique'); weakMap.set(key, 'value'); weakMap.get(key); // 'value'
ES2022 Features
Top-Level Await
// In ES modules (.mjs or type: "module") const response = await fetch('https://api.example.com/data'); const data = await response.json(); export { data }; // Dynamic imports with await const { default: lodash } = await import('lodash');
Private Class Fields & Methods
class BankAccount { // Private fields (prefixed with #) #balance = 0; #transactions = []; constructor(initialBalance) { this.#balance = initialBalance; } // Private method #logTransaction(type, amount) { this.#transactions.push({ type, amount, date: new Date() }); } deposit(amount) { if (amount <= 0) throw new Error('Invalid amount'); this.#balance += amount; this.#logTransaction('deposit', amount); } withdraw(amount) { if (amount > this.#balance) throw new Error('Insufficient funds'); this.#balance -= amount; this.#logTransaction('withdraw', amount); } get balance() { return this.#balance; } // Private static static #instanceCount = 0; static getInstanceCount() { return BankAccount.#instanceCount; } } const account = new BankAccount(1000); // account.#balance; // SyntaxError: Private field
Static Class Blocks
class Config { static settings; static { // Complex initialization logic try { const stored = localStorage.getItem('settings'); this.settings = stored ? JSON.parse(stored) : {}; } catch { this.settings = { theme: 'light', lang: 'en' }; } } }
at() Method
const arr = [1, 2, 3, 4, 5]; // Positive index (same as bracket notation) arr.at(0); // 1 arr.at(2); // 3 // Negative index (counts from end) arr.at(-1); // 5 (last element) arr.at(-2); // 4 (second to last) // Works on strings too const str = 'Hello'; str.at(-1); // 'o'
Object.hasOwn()
const obj = { name: 'Alice' }; // Before: Verbose and potentially unsafe Object.prototype.hasOwnProperty.call(obj, 'name'); // true // After: Clean and safe Object.hasOwn(obj, 'name'); // true Object.hasOwn(obj, 'toString'); // false (inherited) // Works with objects that don't inherit from Object.prototype const nullProto = Object.create(null); nullProto.key = 'value'; Object.hasOwn(nullProto, 'key'); // true // nullProto.hasOwnProperty('key'); // TypeError
Error Cause
async function fetchUser(id) { try { const response = await fetch(`/api/users/${id}`); return await response.json(); } catch (error) { throw new Error('Failed to fetch user', { cause: error }); } } try { await fetchUser(123); } catch (error) { console.log(error.message); // 'Failed to fetch user' console.log(error.cause); // Original fetch error console.log(error.cause.stack); // Original stack trace }
ES2021 Features
String replaceAll()
const text = 'foo bar foo baz foo'; // Before: regex with global flag text.replace(/foo/g, 'qux'); // 'qux bar qux baz qux' // After: Simple and clear text.replaceAll('foo', 'qux'); // 'qux bar qux baz qux' // Works with regex too (must have global flag) text.replaceAll(/foo/g, 'qux');
Numeric Separators
const billion = 1_000_000_000; const bytes = 0xFF_FF_FF_FF; const binary = 0b1010_0001_1000; const fraction = 0.000_001; // Readable large numbers const price = 999_99; // $999.99 in cents
Promise.any()
const promises = [ fetch('https://api1.example.com/data'), fetch('https://api2.example.com/data'), fetch('https://api3.example.com/data'), ]; // Returns first fulfilled promise try { const fastest = await Promise.any(promises); console.log('Got response from:', fastest.url); } catch (error) { // AggregateError if all reject console.log('All failed:', error.errors); }
Logical Assignment Operators
let a = null; let b = 'hello'; let c = 0; // Nullish coalescing assignment a ??= 'default'; // a = 'default' (a was null) b ??= 'default'; // b = 'hello' (b was truthy) // Logical OR assignment c ||= 10; // c = 10 (c was falsy) // Logical AND assignment let user = { name: 'Alice' }; user &&= { ...user, updated: true }; // user = { name: 'Alice', updated: true } // Practical: Initialize if missing const config = {}; config.debug ??= false; config.timeout ??= 5000;
WeakRef & FinalizationRegistry
// Weak reference to object let obj = { data: 'important' }; const weakRef = new WeakRef(obj); // May return undefined if object was garbage collected const value = weakRef.deref(); if (value) { console.log(value.data); } // Cleanup callback when object is GC'd const registry = new FinalizationRegistry((heldValue) => { console.log(`Object with id ${heldValue} was garbage collected`); }); registry.register(obj, 'myObjectId');
ES2020 Features
Optional Chaining (?.)
const user = { name: 'Alice', address: { city: 'NYC' } }; // Safe property access user?.address?.city; // 'NYC' user?.contact?.email; // undefined (no error) // Safe method calls user.getName?.(); // undefined if method doesn't exist // Safe array access const arr = [1, 2, 3]; arr?.[0]; // 1 arr?.[10]; // undefined // Combined with nullish coalescing const email = user?.contact?.email ?? 'no-email@example.com';
Nullish Coalescing (??)
// Only triggers on null/undefined, not falsy values const count = 0; const text = ''; count ?? 10; // 0 (count is not nullish) text ?? 'default'; // '' (text is not nullish) count || 10; // 10 (different behavior with ||) text || 'default'; // 'default' // Perfect for optional parameters function greet(name) { const displayName = name ?? 'Guest'; return `Hello, ${displayName}!`; } greet(''); // 'Hello, !' (empty string is valid) greet(null); // 'Hello, Guest!' greet(undefined); // 'Hello, Guest!'
BigInt
const big = 9007199254740991n; const bigger = BigInt('123456789012345678901234567890'); // Arithmetic big + 1n; // 9007199254740992n bigger * 2n; // 246913578024691357802469135780n // Cannot mix with regular numbers // big + 1; // TypeError // Comparison works big > 1000; // true big === 9007199254740991n; // true // Useful for IDs, timestamps, etc. const snowflakeId = 1234567890123456789n;
Promise.allSettled()
const promises = [ Promise.resolve('success'), Promise.reject('error'), Promise.resolve('another success'), ]; const results = await Promise.allSettled(promises); // [ // { status: 'fulfilled', value: 'success' }, // { status: 'rejected', reason: 'error' }, // { status: 'fulfilled', value: 'another success' } // ] // Filter by status const fulfilled = results .filter(r => r.status === 'fulfilled') .map(r => r.value);
Dynamic Import
// Conditional loading if (needsChart) { const { Chart } = await import('chart.js'); new Chart(canvas, config); } // Route-based code splitting const routes = { '/dashboard': () => import('./pages/Dashboard.js'), '/settings': () => import('./pages/Settings.js'), }; async function loadPage(path) { const loader = routes[path]; if (loader) { const module = await loader(); return module.default; } }
globalThis
// Works in browser, Node.js, Web Workers, etc. globalThis.setTimeout === window.setTimeout; // true in browser globalThis.setTimeout === global.setTimeout; // true in Node.js // Safe global access globalThis.myGlobal = 'accessible everywhere';
Browser Support
| Feature | Chrome | Firefox | Safari | Node.js |
|---|---|---|---|---|
| ES2024 (groupBy, etc.) | 117+ | 119+ | 17.4+ | 21+ |
| ES2023 (findLast, etc.) | 97+ | 104+ | 15.4+ | 18+ |
| ES2022 (private fields) | 84+ | 90+ | 14.1+ | 16+ |
| ES2021 (replaceAll) | 85+ | 77+ | 13.1+ | 15+ |
| ES2020 (?., ??) | 80+ | 72+ | 13.1+ | 14+ |
Reference Files
- async-patterns.md - Advanced async/await patterns
- iterators-generators.md - Iterator and generator patterns