Overview
JavaScript development in 2026 means navigating a sprawling ecosystem of tools for bundling, linting, testing, formatting, type-checking, and deploying code. This guide covers every major category — with comparison tables, configuration examples, and guidance for choosing the right tool for your project type.
Module Bundlers
Bundlers take your source files (JS, CSS, images) and produce optimized output for production. The 2026 landscape has four dominant players.
Comparison
| Feature | Vite | webpack | esbuild | Rollup | Turbopack |
|---|---|---|---|---|---|
| Speed (cold build) | ~50ms HMR | ~2-5s HMR | ~0.1s | ~1-2s | ~0.05s HMR |
| Tree-shaking | Yes (via Rollup) | Yes | Yes | Best-in-class | Yes |
| Configuration | Minimal | Verbose | Minimal | Moderate | Minimal |
| Plugin ecosystem | Rich (Vite/Rollup) | Largest | Growing | Mature | Early |
| SSR support | Built-in | Manual | Limited | Manual | Manual |
| Dev server | Instant | Required plugin | None | None | Instant |
Vite
Vite uses native ES modules during development for instant server startup and near-instant HMR. It delegates bundling to Rollup for production, giving you both speed and optimal output. Vite dominates new projects in 2026.
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [react(), tailwindcss()],
build: {
target: 'es2020',
minify: 'esbuild',
rollupOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-dom'],
},
},
},
},
server: {
port: 3000,
open: true,
},
})
webpack
webpack remains the most configurable bundler with the richest plugin ecosystem. Its complexity is justified for large enterprise apps that need fine-grained control over module resolution, code splitting, and asset handling.
// webpack.config.js
module.exports = {
entry: './src/index.js',
output: {
filename: '[name].[contenthash].js',
path: path.resolve(__dirname, 'dist'),
clean: true,
},
module: {
rules: [
{ test: /\.tsx?$/, use: 'ts-loader', exclude: /node_modules/ },
{ test: /\.css$/, use: ['style-loader', 'css-loader', 'postcss-loader'] },
],
},
optimization: {
splitChunks: { chunks: 'all' },
runtimeChunk: 'single',
},
}
esbuild
esbuild, written in Go, achieves 10-100x faster builds than JavaScript-based bundlers. Many tools (Vite, Vitest, TSX) use esbuild under the hood for transpilation and minification.
# Bundle and minify a single file
$ esbuild src/app.jsx --bundle --minify --outfile=dist/app.js --target=es2020
# With TypeScript and JSX
$ esbuild src/app.tsx --bundle --format=esm --outdir=dist --loader:.tsx=tsx
Rollup
Rollup produces the smallest bundles thanks to its superior tree-shaking. Library authors still prefer it for publishing npm packages.
// rollup.config.js
import resolve from '@rollup/plugin-node-resolve'
import commonjs from '@rollup/plugin-commonjs'
import terser from '@rollup/plugin-terser'
import typescript from '@rollup/plugin-typescript'
export default {
input: 'src/index.ts',
output: [
{ file: 'dist/index.cjs', format: 'cjs' },
{ file: 'dist/index.mjs', format: 'esm' },
],
plugins: [resolve(), commonjs(), typescript(), terser()],
}
Turbopack
Turbopack, built by the Vercel team in Rust, targets sub-100ms HMR for large projects. It integrates natively with Next.js but supports standalone usage.
Linters and Formatters
Comparison
| Tool | Purpose | Speed | Configurability | Language support |
|---|---|---|---|---|
| ESLint | Linting | Moderate (flat config) | Extremely high | JS, TS, JSX, Vue |
| Prettier | Formatting | Fast | Low (opinionated) | 15+ languages |
| oxc/lint | Linting | Very fast (Rust) | Growing | JS, TS |
| dprint | Formatting | Very fast (Rust) | Moderate | JS, TS, JSON, Markdown |
ESLint (Flat Config)
ESLint’s flat config system (default since v9) eliminates legacy .eslintrc confusion.
// eslint.config.js
import js from '@eslint/js'
import tseslint from 'typescript-eslint'
import reactHooks from 'eslint-plugin-react-hooks'
export default [
js.configs.recommended,
...tseslint.configs.recommended,
{
plugins: { 'react-hooks': reactHooks },
rules: {
'react-hooks/exhaustive-deps': 'warn',
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
},
},
{ ignores: ['dist/**', 'node_modules/**'] },
]
Prettier
Prettier remains the standard for automated formatting. Pair it with ESLint using eslint-config-prettier to avoid rule conflicts.
// .prettierrc
{
"semi": true,
"singleQuote": true,
"trailingComma": "all",
"printWidth": 100,
"tabWidth": 2,
"arrowParens": "always",
"endOfLine": "lf"
}
oxc (Oxidation Compiler)
oxc is an emerging Rust-based toolchain that provides a linter, parser, and minifier. Its linter (oxlint) runs 50-100x faster than ESLint for large codebases and requires zero configuration for sensible defaults.
# Run oxlint on the project
$ npx oxlint@latest
# With specific rules
$ npx oxlint@latest --deny no-console --allow no-unused-vars
Testing Frameworks
Comparison
| Framework | Type | Speed | Assertions | Mocking | Snapshot | E2E |
|---|---|---|---|---|---|---|
| Vitest | Unit/Integration | Very fast (esbuild) | Built-in | Built-in | Built-in | No |
| Jest | Unit/Integration | Moderate | Built-in | Built-in | Built-in | No |
| Playwright | E2E | Fast | Built-in | No | Screenshot | Yes |
| Cypress | E2E | Moderate | Built-in | Built-in | Screenshot | Yes |
| Testing Library | Integration | Framework-agnostic | External | External | No | No |
Vitest
Vitest shares Vite’s config and transformation pipeline, making it the fastest choice for Vite-based projects.
// vitest.config.ts
import { defineConfig } from 'vitest/config'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
test: {
environment: 'jsdom',
globals: true,
setupFiles: './src/test/setup.ts',
coverage: { provider: 'v8', reporter: ['text', 'html'] },
include: ['src/**/*.{test,spec}.{ts,tsx}'],
},
})
// src/components/Counter.test.tsx
import { describe, it, expect } from 'vitest'
import { render, screen, fireEvent } from '@testing-library/react'
import { Counter } from './Counter'
describe('Counter', () => {
it('renders initial count', () => {
render(<Counter />)
expect(screen.getByText('Count: 0')).toBeDefined()
})
it('increments on click', async () => {
render(<Counter />)
fireEvent.click(screen.getByRole('button', { name: /increment/i }))
expect(await screen.findByText('Count: 1')).toBeDefined()
})
})
Playwright
Playwright executes tests across Chromium, Firefox, and WebKit with auto-waiting, network interception, and trace viewer.
// e2e/checkout.spec.ts
import { test, expect } from '@playwright/test'
test('user completes purchase flow', async ({ page }) => {
await page.goto('/products')
await page.click('[data-testid="add-to-cart"]')
await page.click('[data-testid="checkout"]')
await page.fill('[name="email"]', '[email protected]')
await page.fill('[name="card"]', '4242424242424242')
await page.click('[data-testid="pay-now"]')
await expect(page.locator('[data-testid="confirmation"]')).toBeVisible()
})
// playwright.config.ts
import { defineConfig } from '@playwright/test'
export default defineConfig({
testDir: './e2e',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
use: {
baseURL: 'http://localhost:3000',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
},
projects: [
{ name: 'chromium', use: { browserName: 'chromium' } },
{ name: 'firefox', use: { browserName: 'firefox' } },
{ name: 'webkit', use: { browserName: 'webkit' } },
],
})
Package Managers
npm ships with Node.js and remains the default. Yarn introduced workspaces and offline caching. pnpm uses hard links to save disk space and enforces strict dependency isolation. Bun is a runtime that doubles as a package manager, claiming 10x faster installs.
# Installation speed benchmark (clean install, 50 dependencies)
npm install # ~15s
yarn install # ~12s
pnpm install # ~8s
bun install # ~2s
Package.json Scripts (Works with Any Package Manager)
{
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"lint": "eslint src/ && prettier --check src/",
"format": "prettier --write src/",
"test": "vitest run",
"test:watch": "vitest",
"test:e2e": "playwright test",
"typecheck": "tsc --noEmit",
"preview": "vite preview"
}
}
Type Checkers
TypeScript is the standard. JSDoc annotations offer a lighter alternative for projects that cannot adopt a build step.
// TypeScript: full static type checking
interface User {
id: string
email: string
role: 'admin' | 'user'
}
function formatUser(user: User): string {
return `${user.email} (${user.role})`
}
// JSDoc: type checking without a build step
/**
* @param {string} id
* @param {string} email
* @param {'admin' | 'user'} role
* @returns {string}
*/
function formatUser(id, email, role) {
return `${email} (${role})`
}
Enable JSDoc checking in tsconfig.json:
{
"compilerOptions": {
"checkJs": true,
"allowJs": true,
"noEmit": true,
"strict": true,
"target": "ESNext",
"moduleResolution": "bundler"
}
}
Runtime Alternatives
Node.js v22+ dominates server-side JavaScript. Bun and Deno offer compelling alternatives with built-in TypeScript support, modern APIs, and improved developer experience.
| Runtime | Built-in TS | Test runner | Package manager | Node compat |
|---|---|---|---|---|
| Node.js | Via tsx/ts-node | node:test | npm/pnpm/yarn | Native |
| Bun | Yes | Bun:test | Bun | ~95% |
| Deno | Yes | Deno.test | deno.land/x | ~90% |
Meta-Frameworks
Meta-frameworks combine a frontend UI library with routing, SSR, data fetching, and build tooling.
| Framework | UI Library | Rendering | Bundle Router | File-based Routing | Static Export |
|---|---|---|---|---|---|
| Next.js | React | SSR/SSG/ISR | Yes | Yes | Yes (output: export) |
| Nuxt | Vue | SSR/SSG | Yes | Yes | Yes |
| Remix | React | SSR | No (React Router) | Yes | Yes (via adapter) |
| SvelteKit | Svelte | SSR/SSG | Yes | Yes | Yes |
| Astro | Any (islands) | SSG/SSR | No | Yes | Yes (default) |
Performance Benchmarks
The following figures represent cold build times for an中等-sized application (~500 modules) in 2026:
Tool | Cold Build | HMR (single edit) | Bundle Size (gzip)
----------|------------|--------------------|---------------------
Vite | 2.1s | ~50ms | 42kB
webpack 5 | 8.4s | ~300ms | 44kB
esbuild | 0.3s | N/A (no HMR) | 46kB
Turbopack | 1.2s | ~30ms | 43kB
Which Tools to Choose
| Project Type | Bundler | Testing | Linting | Package Manager |
|---|---|---|---|---|
| Library (npm package) | Rollup + tsup | Vitest | ESLint + Prettier | pnpm |
| Single-Page App | Vite | Vitest + Testing Library | ESLint + Prettier | pnpm |
| Static Site (SSG) | Astro or Next.js | Vitest (Astro) / Jest (Next) | ESLint + Prettier | pnpm |
| SSR App | Next.js / Remix / SvelteKit | Vitest + Playwright | ESLint + Prettier | pnpm |
| Monorepo | Turborepo + Vite | Vitest (workspace mode) | ESLint + oxlint | pnpm |
| Quick Prototype | Vite (vanilla template) | Skip or Vitest | oxlint (no config) | bun |
Resources
- Vite Documentation
- esbuild Documentation
- ESLint Flat Config Guide
- Vitest Guide
- Playwright Documentation
- oxc Project (oxlint)
- pnpm Documentation
- Bun Runtime
- TypeScript Handbook
- Next.js Documentation
- Astro Documentation
- State of JS 2025 Survey
Comments