Tooling — Cheat Sheet
JavaScript & TypeScript · 5 topics. Download the PDF or the Instagram carousel and share it.
npm, package.json and Dependencies
package.json is the manifest and the lockfile is the truth. Understanding semver ranges and why the lockfile is committed prevents the "works on my machine" class of bug.
- ✓^1.2.3 allows minor and patch updates, ~1.2.3 allows patch only, and below 1.0.0 the caret behaves like a tilde
- ✓The lockfile records the exact resolved tree — commit it, and use npm ci in CI so installs are reproducible
- ✓dependencies ship at runtime; devDependencies are build and test only
- ✓Every dependency is unreviewed code you ship, plus its transitive tree — check size, maintenance and alternatives first
- ✓fetch, structuredClone, crypto.randomUUID and Intl have replaced several once-standard packages
{
"name": "toolhub",
"type": "module",
"scripts": {
"dev": "next dev",
"build": "next build",
"typecheck": "tsc --noEmit"
},
"dependencies": { // shipped to the browser / needed at runtime
"next": "14.2.5", // exact
"zod": "^3.23.0" // >=3.23.0 <4.0.0
},
"devDependencies": { // build and test only, not shipped
"typescript": "~5.5.0", // >=5.5.0 <5.6.0
"vitest": "^2.0.0"
},
"engines": { "node": ">=20" }
}
// Ranges
// ^1.2.3 -> minor and patch updates (the default npm adds)
// ~1.2.3 -> patch updates only
// 1.2.3 -> exactly this
// Below 1.0.0, ^ behaves like ~ — 0.x is treated as unstable.
// Run scripts
npm run build
npx tsc --noEmit // run a binary without installing it globallyBundlers — What They Actually Do
A bundler resolves your import graph, transforms each file, and emits chunks the browser can load efficiently. Tree shaking and code splitting are the two outputs that matter for users.
- ✓A bundler resolves the import graph, transforms files and emits a few hashed chunks instead of hundreds of requests
- ✓Tree shaking requires ES modules, no top-level side effects, and a sideEffects declaration — CommonJS defeats it
- ✓Barrel files can pull far more into the graph than the one component you imported
- ✓A dynamic import creates a separate chunk loaded on demand — split editors, charts, modals and admin routes
- ✓Measure with a bundle analyzer before optimising; the cause is usually one oversized dependency
// This ships one function, not the whole library
import { debounce } from 'lodash-es'
// This ships all of lodash — CommonJS cannot be statically analysed
const _ = require('lodash')
import _ from 'lodash'
// Requirements for tree shaking to work:
// 1. ES modules (static import/export), not CommonJS
// 2. No side effects at module top level
// 3. "sideEffects": false in the package's package.json
// A top-level side effect defeats it:
// utils.js
export function unused() { }
window.analytics = init() // runs on import, so the module is kept
// Barrel files are the usual culprit in application code:
// components/index.ts re-exporting 80 components means importing
// one can pull the graph of all 80 into the analysis.
import { Button } from '@/components' // risky
import { Button } from '@/components/Button' // predictableBuild Configuration — Targets, Polyfills and Env Vars
Which browsers you compile for, what gets polyfilled, and how configuration reaches the client without leaking secrets.
- ✓Transpiling rewrites syntax; polyfills add missing runtime APIs — a low target inflates the bundle for everyone
- ✓browserslist drives the target, and ES2022 is a reasonable modern baseline
- ✓NEXT_PUBLIC_ and VITE_ variables are inlined at build time and are permanently public
- ✓Secrets belong only in unprefixed server-side variables — a prefixed one is visible in DevTools
- ✓Upload source maps to your error tracker rather than serving them, and test the production build locally
// browserslist drives the target "browserslist": ["> 0.5%", "last 2 versions", "not dead"] // Transpiling handles SYNTAX const x = a?.b ?? c // becomes, for an old target: var x = (a === null || a === void 0 ? void 0 : a.b) ?? c // Polyfills handle MISSING APIs — rewriting syntax cannot invent // Array.prototype.at or structuredClone; those need runtime code. import 'core-js/actual/array/at' // A lower target means more rewriting, which means a bigger, slower // bundle for everybody. In 2026, ES2022 is a safe baseline unless // you have a documented legacy requirement. "target": "ES2022" // Check support before using a feature — caniuse.com, or the // compatibility table at the bottom of every MDN page.
DevTools — Debugging Beyond console.log
Breakpoints, the network panel, and the performance profiler. Being fluent here is the difference between fixing a bug in ten minutes and in two hours.
- ✓Conditional breakpoints and logpoints stop or log exactly where you need without editing and refreshing
- ✓"Break on exceptions" pauses at the moment of failure, giving you the live stack and scope
- ✓The network panel settles frontend-versus-backend questions: did it send, what status, what body, what timing
- ✓Copy as cURL reproduces a request exactly and is the fastest way to hand over a bug report
- ✓A memory leak appears as heap growth with detached DOM nodes — nearly always an uncleaned listener or interval
// Conditional breakpoint — right-click a line number
// condition: problem.slug === 'two-sum'
// Stops on the one iteration you care about out of 500.
// Logpoint — logs without pausing and without editing the file
// Right-click -> Add logpoint -> problem.slug, state.status
// Break on a DOM change: Elements -> right-click -> Break on ->
// attribute modifications / subtree modifications
// This is how you find what code is adding that class.
// Break on any exception: Sources -> pause icon -> caught + uncaught
// Then the stack at the moment of failure, not after it.
// In code
debugger // pauses when DevTools is open, ignored otherwise
// Useful console methods people forget
console.table(problems) // arrays of objects as a grid
console.time('render'); console.timeEnd('render')
console.trace() // how did we get here
console.assert(items.length > 0, 'empty')Workspaces, Monorepos and Shipping a Package
How a frontend, a backend and a shared library live in one repository — and what changes when you publish one of them.
- ✓Workspaces install once and link local packages, so a shared schema change breaks both apps at compile time
- ✓The exports field defines the package's public surface and replaces main — unlisted paths cannot be imported
- ✓Publish the .d.ts files or TypeScript consumers see any; set sideEffects: false so they can tree-shake
- ✓Semver is a promise to consumers: patch fixes, minor adds, major breaks
- ✓npm link can produce duplicate React copies and "Invalid hook call" — npm pack tests the real published artifact
// root package.json
{ "private": true, "workspaces": ["apps/*", "packages/*"] }
// apps/web/package.json
{ "dependencies": { "@aicancode/shared": "workspace:*" } }
// Layout
// apps/web Next frontend
// apps/api Node backend
// packages/shared types + zod schemas used by both
// packages/ui component library
npm install // installs everything once
npm run build -w apps/web // run a script in one workspace
// The payoff: change a schema in packages/shared and both apps
// fail to typecheck immediately — no publish step, no version drift.
// The cost: slower CI unless you cache and only build what changed.
// Turborepo or Nx exist for exactly that.