Skip to main content

loom-javascript

Modern JavaScript (ES2022+) and Node.js expertise for idiomatic, production-quality code without a TypeScript build.

Quellinformationen

Repository
cosmix/loom
Letzte Quellaktivität
3. Oktober 2026 um 19:57
Erkannte Sprache von SKILL.md
Englisch
Sterne
61
Forks
4

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
loom-javascript
description
Modern JavaScript (ES2022+) and Node.js expertise for idiomatic, production-quality code without a TypeScript build.
triggers
["javascript","js","jsx","mjs","cjs","node","nodejs","node.js","esm","es modules","commonjs","package.json","jsdoc","ts-check","eslint","biome","vitest","jest","mocha","node:test","bun test","promise","async await","prototype pollution","event loop"]
# JavaScript Language Expertise ## Overview Idiomatic ES2022+ JavaScript on Node.js 22+ (or Bun): ES modules, async/await, JSDoc types checked by `tsc`, the built-in `node:` APIs, and a small tool set. Assumes the reader knows the language. The content is decision rules and the traps that survive review: a floating promise, `==` on mixed types, a lost `this`, `JSON.parse` output merged into an object (prototype pollution), a `catch` that swallows the error, a test runner that finds zero files and exits 0. TypeScript syntax is covered by `loom-typescript`. ## Tooling Use the package manager the repository's lockfile names. For new work prefer Bun: `bun add`, `bun remove`, `bunx`. Do not run `npm` or `npx` commands in this workflow. | Task | Command | | --- | --- | | Install | `bun install` | | Add a dependency | `bun add zod` (dev: `bun add -d vitest`) | | Run a script | `bun run build` | | Run a one-off tool | `bunx eslint .` | | Run a file | `node src/main.js` or `bun src/main.js` | | Type-check JSDoc | `bunx tsc --noEmit` | | Lint | `bunx eslint .` or `bunx biome check .` | | Format | `bunx biome format --write .` or `bunx prettier --write .` | | Test, whole suite | `bun test` / `bunx vitest run` / `node --test` | | Audit dependencies | `bun audit` | - **Lockfile:** commit `bun.lock`. Install in CI with `bun install --frozen-lockfile`, which fails when `package.json` and the lockfile disagree. - **Node version:** pin it in `package.json` (`"engines": {"node": ">=22"}`) and in `.nvmrc` or `.tool-versions`. Node 22+ ships `require(esm)`, `fetch`, `node:test`, `--watch` and `--env-file`. - **Linting:** ESLint 9 uses a flat config (`eslint.config.js`); `.eslintrc*` is the legacy format. Biome is a single binary that lints and formats (`biome.json`) and replaces ESLint plus Prettier when the rule set it ships is enough. Pick one linter per repository. Floating promises need type information. ESLint's `@typescript-eslint/no-floating-promises` works on JavaScript when `parserOptions.projectService` points at a `jsconfig.json`. Without it, review every call to an async function for an `await` or a `.catch`. ## Modules: ESM and CommonJS A package is ESM when its `package.json` has `"type": "module"`; otherwise `.js` files are CommonJS. `.mjs` is always ESM and `.cjs` always CommonJS. ```javascript // ESM import { readFile } from "node:fs/promises"; import { join } from "node:path"; import config from "./config.json" with { type: "json" }; export async function load(dir) { return readFile(join(dir, "data.txt"), "utf8"); } ``` | Concern | ESM | CommonJS | | --- | --- | --- | | Syntax | `import` / `export` | `require` / `module.exports` | | Loading | static, hoisted, async-capable (top-level `await`) | synchronous, at the call | | `__dirname`, `__filename` | absent; use `import.meta.dirname` and `import.meta.filename` (Node 20.11+) | defined | | File extensions | required in relative specifiers: `./util.js` | optional | | JSON | `with { type: "json" }` import attribute | `require("./x.json")` | | `this` at top level | `undefined` | `module.exports` | - Built-in modules take the `node:` prefix (`node:fs`, `node:crypto`): it states that the import is built in and cannot be shadowed by an installed package. - Use named exports. A default export lets every importer choose a different local name, which defeats search and rename. - Interop: ESM imports CommonJS freely (`module.exports` becomes the default export); named imports from CommonJS work only when Node can statically detect them. CommonJS loads ESM with `require()` on Node 22.12+ when the graph has no top-level `await`; otherwise use `await import("./x.mjs")`. - Circular imports resolve differently: ESM exports are live bindings and a cycle fails with a `ReferenceError` when a binding is read before its module has run; CommonJS hands back a partially filled `exports` object. Break the cycle by moving the shared piece into a third module. - A package that ships to others declares an `"exports"` map; it also blocks deep imports of files that are not listed. ```json { "name": "@acme/fmt", "type": "module", "exports": { ".": { "import": "./src/index.js", "require": "./dist/index.cjs" }, "./package.json": "./package.json" } } ``` ## Language Rules ### Values and equality - `===` and `!==` always. `==` coerces: `0 == ""`, `"1" == 1` and `null == undefined` are all true. The one defensible use is `value == null` to test for `null` or `undefined` together; ESLint's `eqeqeq` option `["error", "always", { null: "ignore" }]` allows it. - Use `Number.isNaN`, never `x === NaN` (always false) or the coercing global `isNaN`. `Object.is(a, b)` distinguishes `-0` and `+0`. - `typeof null` is `"object"`. `Array.isArray` identifies arrays. `typeof` an undeclared name is `"undefined"` and does not throw. - `||` replaces every falsy value (`0`, `""`, `false`); `??` replaces only `null` and `undefined`. Defaults for numbers and strings use `??`, and `??=` assigns when nullish. - Numbers are IEEE doubles: `0.1 + 0.2 !== 0.3`, integers are exact only up to `Number.MAX_SAFE_INTEGER`. Use `BigInt` (`10n ** 20n`) for ids and money in minor units beyond that; `BigInt` does not mix with `Number` in arithmetic and `JSON.stringify` throws on it. ### Declarations and functions - `const` by default, `let` when reassigned, never `var` (function scope, hoisted). Loop closures over `let` capture one binding per iteration. - Arrow functions capture `this` lexically and have no `arguments`; use them for callbacks. Methods that rely on `this` are written as methods or `function`, and a method passed as a callback loses its `this`: ```javascript class Counter { count = 0; inc = () => { this.count += 1; }; // field arrow: bound per instance dec() { this.count -= 1; } } const c = new Counter(); const { dec } = c; // dec(); // TypeError: this is undefined in a module const bound = c.dec.bind(c); bound(); ``` - Parameters: destructure options objects with defaults (`function open({ path, mode = "r" } = {})`) rather than long positional lists. A default value is evaluated per call, so `function f(list = [])` is safe. - Prefer `Map` and `Set` over plain objects for dynamic keys: no prototype collisions, any key type, `.size`, insertion order. `Object.create(null)` or `Object.hasOwn(obj, key)` for objects used as dictionaries. ### Collections and data | Need | Use | | --- | --- | | Copy an array without mutating | `toSorted()`, `toReversed()`, `toSpliced()`, `with(i, v)` (Node 20+) | | Last element | `arr.at(-1)` | | Group by key | `Object.groupBy(items, fn)` or `Map.groupBy` (Node 21+) | | Deep copy | `structuredClone(value)`; `JSON.parse(JSON.stringify(x))` drops `undefined`, `Date` and `Map` | | Merge objects | spread `{ ...a, ...b }` (shallow) | | Unique values | `[...new Set(arr)]` | | Iterate entries | `for (const [k, v] of Object.entries(o))` | - `Array.prototype.sort` without a comparator sorts as strings: `[10, 9, 1].sort()` gives `[1, 10, 9]`. Pass `(a, b) => a - b`. - `for...in` walks inherited enumerable keys; use `for...of` over `Object.keys`, `Object.entries` or the iterable itself. `forEach` ignores `await` inside its callback. ## Async and Errors An `async` function always returns a promise, and a throw inside it rejects that promise. A promise that rejects with no handler crashes Node (`unhandledRejection` is fatal since Node 15). ```javascript // Floating promise: the rejection has no handler and the caller never waits function save(user) { db.insert(user); // missing await } // Correct async function save(user) { await db.insert(user); } ``` - Every call to an async function is awaited, returned, or explicitly detached with a `.catch` that logs. `void fireAndForget().catch(log)` states the intent. - `await` in a loop serialises. Independent work runs with `Promise.all`; bound the fan-out for large lists (a worker pool or `p-limit`), since 10 000 simultaneous requests exhaust sockets. - `Promise.all` rejects on the first failure and leaves the others running. `Promise.allSettled` waits for all and reports each outcome; `Promise.any` resolves with the first success; `Promise.race` settles with the first result of either kind. - `return await` inside `try` is required for the `catch` to see the rejection; outside `try` it is redundant. - Wrap callback APIs once with `util.promisify`, or use the `node:fs/promises`, `node:timers/promises` and `node:stream/promises` variants. - Cancellation: pass an `AbortSignal` (`AbortSignal.timeout(5000)`, `AbortSignal.any([a, b])`) to `fetch`, `fs/promises`, `timers/promises` and your own functions, and check `signal.aborted` or `signal.throwIfAborted()` between steps. ```javascript async function getJson(url, { timeoutMs = 5000 } = {}) { const res = await fetch(url, { signal: AbortSignal.timeout(timeoutMs) }); if (!res.ok) { throw new Error(`GET ${url} failed: ${res.status}`, { cause: new HttpError(res.status) }); } return res.json(); } class HttpError extends Error { constructor(status) { super(`HTTP ${status}`); this.name = "HttpError"; this.status = status; } } ``` - Throw `Error` objects (they carry a stack), never strings. Pass the original through `{ cause }` when wrapping. Subclass `Error` for conditions a caller branches on, and test with `instanceof` or a stable `code` property. - `fetch` resolves on HTTP 4xx and 5xx: check `res.ok`. It rejects only on network failure and abort. - An empty `catch {}` is a defect. Handle the error, rethrow, or log it with the reason it is safe to ignore. - Process level: register `process.on("unhandledRejection", ...)` only to log and exit non-zero; continuing in an unknown state hides the bug. Handle `SIGTERM` to close servers and flush work before `process.exit`. ### Event loop - A synchronous loop of 200 ms blocks every request on the process. Move CPU work (hashing large input, parsing megabytes of JSON, image work) to `node:worker_threads` or a child process. - Order inside one turn: synchronous code, then `process.nextTick`, then promise microtasks, then timers and IO callbacks. `queueMicrotask` and a recursive `nextTick` can starve IO. - `*Sync` filesystem and crypto calls are acceptable at start-up and in CLIs and a defect in a request handler. - Streams: use `pipeline` from `node:stream/promises` so errors propagate and handles close; `readable.pipe()` leaks the destination on error. `for await (const chunk of stream)` respects backpressure. ## Node.js APIs | Need | API | | --- | --- | | Files | `node:fs/promises` (`readFile`, `writeFile`, `mkdir({ recursive: true })`, `rm({ recursive: true, force: true })`) | | Paths | `node:path` (`join`, `resolve`, `relative`), `node:url` (`fileURLToPath`) | | HTTP client | global `fetch`; server: `node:http` or a framework | | Hashing, random | `node:crypto` (`randomUUID`, `randomBytes`, `createHash`, `timingSafeEqual`) | | Child processes | `node:child_process` (`execFile`, `spawn`) | | CLI arguments | `node:util` `parseArgs` | | Config | `node --env-file=.env app.js`; read `process.env.NAME` once at start-up and validate | | Test | `node:test` with `node:assert/strict` | | Timing | `performance.now()`, `node:timers/promises` `setTimeout` | | Concurrency | `node:worker_threads`, `AsyncLocalStorage` for request context | ```javascript import { execFile } from "node:child_process"; import { promisify } from "node:util"; const run = promisify(execFile); // An argument array, no shell: user input cannot add commands. const { stdout } = await run("git", ["log", "-1", "--format=%H", ref], { timeout: 10_000 }); ``` ## Types Without TypeScript JSDoc plus `// @ts-check` gives the editor and `tsc` the same checking as `.ts` files with no build step. Enable it for the whole project in `jsconfig.json` or `tsconfig.json` and run `bunx tsc --noEmit` in CI. ```json { "compilerOptions": { "allowJs": true, "checkJs": true, "noEmit": true, "strict": true, "module": "NodeNext", "moduleResolution": "NodeNext", "target": "ES2022" }, "include": ["src", "test"] } ```
Auf GitHub ansehen
Diese SKILL.md ist sehr gross, daher zeigt SkillsMP hier nur den ersten Abschnitt. Auf GitHub ansehen