DEV Community

Cover image for 30 Years of JavaScript: The Complete Evolution Guide
Nayden Gochev
Nayden Gochev

Posted on

30 Years of JavaScript: The Complete Evolution Guide

30 Years of JavaScript: The Complete Evolution Guide

December 4, 2025 - A comprehensive guide for developers returning to JavaScript or learning its complete history.

Table of Contents

  1. The Beginning: JavaScript 1.0 - 1.5 (1995-2000)
  2. The Dark Ages: Browser Wars & Stagnation (2000-2005)
  3. The AJAX Revolution (2005-2009)
  4. The Modern Era Begins: ES5 & Node.js (2009-2015)
  5. The Renaissance: ES6/ES2015 (2015)
  6. Annual Releases: ES2016-ES2025
  7. TypeScript Evolution (2012-2025)
  8. Server-Side JavaScript: Node.js & Beyond
  9. Frameworks & Libraries: Rise and Fall
  10. Testing Ecosystem Evolution
  11. Build Tools & Module Systems
  12. Package Management
  13. Current Landscape (2025)

The Beginning: JavaScript 1.0 - 1.5 (1995-2000)

1995: The Birth

Brendan Eich created JavaScript in 10 days at Netscape in May 1995. Originally named "Mocha," then "LiveScript," finally "JavaScript."

JavaScript 1.0 (December 1995) - Netscape Navigator 2.0

Basic features:

// Variables (no let/const, only var)
var name = "John";
var age = 25;

// Functions
function greet(name) {
    return "Hello, " + name;
}

// Objects (prototype-based)
var person = {
    name: "John",
    age: 25,
    greet: function() {
        return "Hello, " + this.name;
    }
};

// Arrays
var numbers = [1, 2, 3, 4, 5];

// Loops
for (var i = 0; i < numbers.length; i++) {
    console.log(numbers[i]);
}

// Conditionals
if (age >= 18) {
    console.log("Adult");
} else {
    console.log("Minor");
}
Enter fullscreen mode Exit fullscreen mode

1996-1997: Standardization

ECMAScript 1 (June 1997) - First standardized version

Microsoft created JScript for Internet Explorer 3.0, leading to fragmentation.

1998-1999: ECMAScript 2 & 3

ECMAScript 3 (December 1999) - Major update

Added:

  • Regular expressions
  • Better string handling
  • try/catch exception handling
  • switch statements
  • do-while loops
// Regular expressions
var pattern = /hello/i;
var text = "Hello World";
console.log(pattern.test(text)); // true

// try/catch
try {
    throw new Error("Something went wrong");
} catch (e) {
    console.log(e.message);
} finally {
    console.log("Cleanup");
}

// switch
var day = "Monday";
switch (day) {
    case "Monday":
        console.log("Start of week");
        break;
    case "Friday":
        console.log("End of week");
        break;
    default:
        console.log("Midweek");
}
Enter fullscreen mode Exit fullscreen mode

The Dark Ages: Browser Wars & Stagnation (2000-2005)

The Problem

  • ECMAScript 4 was abandoned due to disagreements
  • Browser incompatibilities were rampant
  • JavaScript was considered a "toy language"
  • No standard library management
  • Security issues (XSS attacks)

Browser Detection Hell

// Typical code from this era
if (document.all) {
    // Internet Explorer
    document.all.myElement.style.color = "red";
} else if (document.getElementById) {
    // Standards-compliant browsers
    document.getElementById("myElement").style.color = "red";
}

// Or worse
if (navigator.userAgent.indexOf("MSIE") > -1) {
    // IE-specific code
}
Enter fullscreen mode Exit fullscreen mode

Early Libraries (Pre-jQuery)

Prototype.js (2005)

// Prototype.js extended native objects
$('myElement').hide();

// Array extensions
[1, 2, 3, 4, 5].each(function(n) {
    console.log(n);
});
Enter fullscreen mode Exit fullscreen mode

The AJAX Revolution (2005-2009)

2005: AJAX Changes Everything

AJAX (Asynchronous JavaScript and XML) - Term coined by Jesse James Garrett

// XMLHttpRequest - The old way
var xhr = new XMLHttpRequest();
xhr.open('GET', '/api/data', true);
xhr.onreadystatechange = function() {
    if (xhr.readyState === 4 && xhr.status === 200) {
        var data = JSON.parse(xhr.responseText);
        console.log(data);
    }
};
xhr.send();
Enter fullscreen mode Exit fullscreen mode

2006: jQuery Revolution

jQuery 1.0 by John Resig - Changed everything

Before jQuery:

// Cross-browser event handling nightmare
var element = document.getElementById('myButton');
if (element.addEventListener) {
    element.addEventListener('click', handleClick, false);
} else if (element.attachEvent) {
    element.attachEvent('onclick', handleClick);
}

// DOM manipulation
var elements = document.getElementsByTagName('p');
for (var i = 0; i < elements.length; i++) {
    elements[i].style.color = 'red';
}

// AJAX
var xhr = new XMLHttpRequest();
xhr.open('GET', '/api/users', true);
xhr.onreadystatechange = function() {
    if (xhr.readyState === 4 && xhr.status === 200) {
        document.getElementById('result').innerHTML = xhr.responseText;
    }
};
xhr.send();
Enter fullscreen mode Exit fullscreen mode

With jQuery:

// Simple, elegant, cross-browser
$('#myButton').click(handleClick);

// DOM manipulation
$('p').css('color', 'red');

// AJAX
$.get('/api/users', function(data) {
    $('#result').html(data);
});

// Chaining
$('#myDiv')
    .addClass('active')
    .fadeIn()
    .css('color', 'blue')
    .html('Hello World');

// Powerful selectors
$('div.container > p:first-child').hide();
Enter fullscreen mode Exit fullscreen mode

Why jQuery dominated:

  • Cross-browser compatibility
  • Simple, intuitive API
  • Powerful DOM manipulation
  • Easy AJAX
  • Plugin ecosystem
  • "Write less, do more"

2008-2009: Other Libraries

MooTools (2006)

// Object-oriented approach
var MyClass = new Class({
    initialize: function(name) {
        this.name = name;
    },
    greet: function() {
        alert('Hello ' + this.name);
    }
});
Enter fullscreen mode Exit fullscreen mode

Dojo Toolkit (2004-2009)

// Enterprise-focused
dojo.require("dijit.form.Button");
dojo.ready(function() {
    new dijit.form.Button({
        label: "Click me!",
        onClick: function() { alert("Clicked!"); }
    }, "myButton");
});
Enter fullscreen mode Exit fullscreen mode

The Modern Era Begins: ES5 & Node.js (2009-2015)

2009: ECMAScript 5 (ES5)

After 10 years of stagnation, ES5 brought crucial improvements.

Strict Mode:

// Without strict mode (sloppy mode)
function oldWay() {
    x = 10; // Creates global variable accidentally
    var arguments = 5; // Can override arguments
    delete Object.prototype; // Can delete built-ins
}

// With strict mode
"use strict";
function newWay() {
    x = 10; // ReferenceError: x is not defined
    var arguments = 5; // SyntaxError
    delete Object.prototype; // TypeError
}
Enter fullscreen mode Exit fullscreen mode

Array Methods:

var numbers = [1, 2, 3, 4, 5];

// forEach - Before: for loop
numbers.forEach(function(num) {
    console.log(num);
});

// map - Transform arrays
var doubled = numbers.map(function(num) {
    return num * 2;
}); // [2, 4, 6, 8, 10]

// filter - Filter arrays
var evens = numbers.filter(function(num) {
    return num % 2 === 0;
}); // [2, 4]

// reduce - Aggregate
var sum = numbers.reduce(function(acc, num) {
    return acc + num;
}, 0); // 15

// some & every
var hasEven = numbers.some(function(num) {
    return num % 2 === 0;
}); // true

var allPositive = numbers.every(function(num) {
    return num > 0;
}); // true
Enter fullscreen mode Exit fullscreen mode

Object Methods:

// Object.create - Proper prototypal inheritance
var parent = {
    greet: function() {
        return "Hello";
    }
};
var child = Object.create(parent);
child.greet(); // "Hello"

// Object.keys
var obj = { a: 1, b: 2, c: 3 };
Object.keys(obj); // ['a', 'b', 'c']

// Object.defineProperty - Property descriptors
var person = {};
Object.defineProperty(person, 'name', {
    value: 'John',
    writable: false,
    enumerable: true,
    configurable: false
});
person.name = 'Jane'; // Silently fails (throws in strict mode)

// Getters and setters
var account = {
    _balance: 0,
    get balance() {
        return this._balance;
    },
    set balance(value) {
        if (value < 0) throw new Error("Balance cannot be negative");
        this._balance = value;
    }
};
account.balance = 100;
console.log(account.balance); // 100
Enter fullscreen mode Exit fullscreen mode

JSON Native Support:

// Before: eval() or libraries
var jsonString = '{"name":"John","age":30}';
var obj = eval('(' + jsonString + ')'); // Dangerous!

// ES5: Native JSON
var obj = JSON.parse('{"name":"John","age":30}');
var str = JSON.stringify({ name: "John", age: 30 });
Enter fullscreen mode Exit fullscreen mode

Function.prototype.bind:

// Before: Manual context binding
var obj = {
    name: "John",
    greet: function() {
        console.log("Hello, " + this.name);
    }
};

var greet = obj.greet;
greet(); // "Hello, undefined" - lost context

// Workaround
var self = obj;
setTimeout(function() {
    self.greet();
}, 1000);

// ES5: bind
var boundGreet = obj.greet.bind(obj);
boundGreet(); // "Hello, John"
setTimeout(obj.greet.bind(obj), 1000);
Enter fullscreen mode Exit fullscreen mode

2009: Node.js - JavaScript on the Server

Ryan Dahl created Node.js, bringing JavaScript to the server.

Before Node.js:

  • JavaScript only in browsers
  • Server-side: PHP, Ruby, Python, Java, .NET

With Node.js:

// Simple HTTP server
var http = require('http');

var server = http.createServer(function(req, res) {
    res.writeHead(200, { 'Content-Type': 'text/plain' });
    res.end('Hello World\n');
});

server.listen(3000);
console.log('Server running at http://localhost:3000/');
Enter fullscreen mode Exit fullscreen mode

Event-driven, non-blocking I/O:

// Blocking (traditional server-side)
var data = readFileSync('file.txt'); // Waits
processData(data);

// Non-blocking (Node.js)
readFile('file.txt', function(err, data) {
    if (err) throw err;
    processData(data);
});
// Continues immediately
Enter fullscreen mode Exit fullscreen mode

CommonJS Modules:

// math.js
function add(a, b) {
    return a + b;
}

function subtract(a, b) {
    return a - b;
}

module.exports = {
    add: add,
    subtract: subtract
};

// app.js
var math = require('./math');
console.log(math.add(5, 3)); // 8
Enter fullscreen mode Exit fullscreen mode

2010: npm - Node Package Manager

npm revolutionized package management.

# Install packages
npm install express

# package.json
{
  "name": "my-app",
  "version": "1.0.0",
  "dependencies": {
    "express": "^4.17.1"
  }
}
Enter fullscreen mode Exit fullscreen mode

Express.js (2010) - Web framework for Node.js

var express = require('express');
var app = express();

app.get('/', function(req, res) {
    res.send('Hello World!');
});

app.get('/users/:id', function(req, res) {
    res.json({ id: req.params.id, name: 'John' });
});

app.listen(3000);
Enter fullscreen mode Exit fullscreen mode

2010-2012: The Framework Wars Begin

Backbone.js (2010) - First major MV* framework

// Model
var Todo = Backbone.Model.extend({
    defaults: {
        title: '',
        completed: false
    }
});

// Collection
var TodoList = Backbone.Collection.extend({
    model: Todo
});

// View
var TodoView = Backbone.View.extend({
    tagName: 'li',
    template: _.template('<%= title %>'),

    render: function() {
        this.$el.html(this.template(this.model.toJSON()));
        return this;
    }
});

// Usage
var todo = new Todo({ title: 'Learn Backbone' });
var view = new TodoView({ model: todo });
$('#todos').append(view.render().el);
Enter fullscreen mode Exit fullscreen mode

Knockout.js (2010) - MVVM with observables

function AppViewModel() {
    this.firstName = ko.observable("John");
    this.lastName = ko.observable("Doe");

    this.fullName = ko.computed(function() {
        return this.firstName() + " " + this.lastName();
    }, this);
}

ko.applyBindings(new AppViewModel());
Enter fullscreen mode Exit fullscreen mode
<p>First name: <input data-bind="value: firstName" /></p>
<p>Last name: <input data-bind="value: lastName" /></p>
<h2>Hello, <span data-bind="text: fullName"></span>!</h2>
Enter fullscreen mode Exit fullscreen mode

AngularJS 1.0 (2010, released 2012) - Google's framework

// Controller
angular.module('myApp', [])
    .controller('TodoController', function($scope) {
        $scope.todos = [];

        $scope.addTodo = function() {
            $scope.todos.push({
                text: $scope.todoText,
                done: false
            });
            $scope.todoText = '';
        };

        $scope.remaining = function() {
            return $scope.todos.filter(function(todo) {
                return !todo.done;
            }).length;
        };
    });
Enter fullscreen mode Exit fullscreen mode
<div ng-app="myApp" ng-controller="TodoController">
    <input ng-model="todoText" placeholder="Add new todo">
    <button ng-click="addTodo()">Add</button>

    <ul>
        <li ng-repeat="todo in todos">
            <input type="checkbox" ng-model="todo.done">
            <span ng-class="{done: todo.done}">{{todo.text}}</span>
        </li>
    </ul>

    <p>{{remaining()}} of {{todos.length}} remaining</p>
</div>
Enter fullscreen mode Exit fullscreen mode

Ember.js (2011) - Convention over configuration

// Router
App.Router.map(function() {
    this.route('about');
    this.route('posts', function() {
        this.route('post', { path: '/:post_id' });
    });
});

// Route
App.PostsRoute = Ember.Route.extend({
    model: function() {
        return this.store.findAll('post');
    }
});

// Controller
App.PostsController = Ember.ArrayController.extend({
    sortProperties: ['date'],
    sortAscending: false
});

// Template (Handlebars)
Enter fullscreen mode Exit fullscreen mode
{{#each post in controller}}
    <article>
        <h2>{{post.title}}</h2>
        <p>{{post.body}}</p>
    </article>
{{/each}}
Enter fullscreen mode Exit fullscreen mode

2012: TypeScript Arrives

Microsoft releases TypeScript 0.8 - JavaScript with types

JavaScript:

function greet(name) {
    return "Hello, " + name.toUpperCase();
}

greet("John"); // "Hello, JOHN"
greet(42); // Runtime error: name.toUpperCase is not a function
Enter fullscreen mode Exit fullscreen mode

TypeScript:

function greet(name: string): string {
    return "Hello, " + name.toUpperCase();
}

greet("John"); // "Hello, JOHN"
greet(42); // Compile-time error: Argument of type 'number' is not assignable to parameter of type 'string'
Enter fullscreen mode Exit fullscreen mode

Interfaces:

interface User {
    id: number;
    name: string;
    email: string;
    age?: number; // Optional
}

function createUser(user: User): void {
    console.log(`Creating user: ${user.name}`);
}

createUser({ id: 1, name: "John", email: "john@example.com" }); // OK
createUser({ id: 1, name: "John" }); // Error: Property 'email' is missing
Enter fullscreen mode Exit fullscreen mode

Classes (before ES6):

class Animal {
    private name: string;

    constructor(name: string) {
        this.name = name;
    }

    public move(distance: number): void {
        console.log(`${this.name} moved ${distance}m.`);
    }
}

class Dog extends Animal {
    bark(): void {
        console.log("Woof! Woof!");
    }
}

const dog = new Dog("Buddy");
dog.bark();
dog.move(10);
Enter fullscreen mode Exit fullscreen mode

2013-2014: Build Tools & Module Bundlers

Grunt (2012) - Task runner

// Gruntfile.js
module.exports = function(grunt) {
    grunt.initConfig({
        uglify: {
            build: {
                src: 'src/*.js',
                dest: 'build/app.min.js'
            }
        },
        watch: {
            files: ['src/*.js'],
            tasks: ['uglify']
        }
    });

    grunt.loadNpmTasks('grunt-contrib-uglify');
    grunt.loadNpmTasks('grunt-contrib-watch');
    grunt.registerTask('default', ['uglify']);
};
Enter fullscreen mode Exit fullscreen mode

Gulp (2013) - Streaming build system

// gulpfile.js
var gulp = require('gulp');
var uglify = require('gulp-uglify');
var concat = require('gulp-concat');

gulp.task('scripts', function() {
    return gulp.src('src/*.js')
        .pipe(concat('app.js'))
        .pipe(uglify())
        .pipe(gulp.dest('build'));
});

gulp.task('watch', function() {
    gulp.watch('src/*.js', ['scripts']);
});

gulp.task('default', ['scripts', 'watch']);
Enter fullscreen mode Exit fullscreen mode

Browserify (2011) - Bring Node.js modules to browser

// Before: Multiple script tags
<script src="jquery.js"></script>
<script src="lodash.js"></script>
<script src="app.js"></script>

// With Browserify
// app.js
var $ = require('jquery');
var _ = require('lodash');

// Build
browserify app.js -o bundle.js

// HTML
<script src="bundle.js"></script>
Enter fullscreen mode Exit fullscreen mode

Webpack (2012, popular 2014+) - Module bundler

// webpack.config.js
module.exports = {
    entry: './src/index.js',
    output: {
        filename: 'bundle.js',
        path: __dirname + '/dist'
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: /node_modules/,
                use: {
                    loader: 'babel-loader'
                }
            },
            {
                test: /\.css$/,
                use: ['style-loader', 'css-loader']
            }
        ]
    }
};
Enter fullscreen mode Exit fullscreen mode

The Renaissance: ES6/ES2015 (2015)

The biggest update to JavaScript ever. Changed the language fundamentally.

let and const

Before (var):

// Function scope, hoisting issues
function example() {
    console.log(x); // undefined (hoisted)
    var x = 10;

    if (true) {
        var x = 20; // Same variable!
        console.log(x); // 20
    }
    console.log(x); // 20
}

// Loop problem
for (var i = 0; i < 3; i++) {
    setTimeout(function() {
        console.log(i); // 3, 3, 3 (all reference same i)
    }, 100);
}
Enter fullscreen mode Exit fullscreen mode

After (let/const):

// Block scope, no hoisting
function example() {
    console.log(x); // ReferenceError: Cannot access 'x' before initialization
    let x = 10;

    if (true) {
        let x = 20; // Different variable!
        console.log(x); // 20
    }
    console.log(x); // 10
}

// Loop fixed
for (let i = 0; i < 3; i++) {
    setTimeout(function() {
        console.log(i); // 0, 1, 2 (each has own i)
    }, 100);
}

// const - immutable binding
const PI = 3.14159;
PI = 3; // TypeError: Assignment to constant variable

const obj = { name: "John" };
obj.name = "Jane"; // OK - object is mutable
obj = {}; // TypeError: Assignment to constant variable
Enter fullscreen mode Exit fullscreen mode

Arrow Functions

Before:

// Function expressions
var add = function(a, b) {
    return a + b;
};

// this binding issues
var obj = {
    name: "John",
    friends: ["Jane", "Bob"],
    printFriends: function() {
        var self = this; // Workaround
        this.friends.forEach(function(friend) {
            console.log(self.name + " knows " + friend);
        });
    }
};
Enter fullscreen mode Exit fullscreen mode

After:

// Arrow functions
const add = (a, b) => a + b;

// Implicit return
const square = x => x * x;

// Multiple statements
const greet = name => {
    const message = `Hello, ${name}`;
    return message.toUpperCase();
};

// Lexical this
const obj = {
    name: "John",
    friends: ["Jane", "Bob"],
    printFriends() {
        this.friends.forEach(friend => {
            console.log(`${this.name} knows ${friend}`); // this works!
        });
    }
};

// Array methods become elegant
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(n => n * 2);
const evens = numbers.filter(n => n % 2 === 0);
const sum = numbers.reduce((acc, n) => acc + n, 0);
Enter fullscreen mode Exit fullscreen mode

Template Literals

Before:

var name = "John";
var age = 30;
var message = "Hello, my name is " + name + " and I am " + age + " years old.";

// Multiline strings
var html = "<div>\n" +
           "  <h1>" + title + "</h1>\n" +
           "  <p>" + content + "</p>\n" +
           "</div>";
Enter fullscreen mode Exit fullscreen mode

After:

const name = "John";
const age = 30;
const message = `Hello, my name is ${name} and I am ${age} years old.`;

// Multiline strings
const html = `
    <div>
        <h1>${title}</h1>
        <p>${content}</p>
    </div>
`;

// Expressions
const price = 19.99;
const tax = 0.08;
const total = `Total: $${(price * (1 + tax)).toFixed(2)}`;

// Tagged templates
function highlight(strings, ...values) {
    return strings.reduce((result, str, i) => {
        return result + str + (values[i] ? `<mark>${values[i]}</mark>` : '');
    }, '');
}

const name = "John";
const html = highlight`Hello, ${name}!`; // "Hello, <mark>John</mark>!"
Enter fullscreen mode Exit fullscreen mode

Destructuring

Before:

var user = { name: "John", age: 30, email: "john@example.com" };
var name = user.name;
var age = user.age;

var numbers = [1, 2, 3, 4, 5];
var first = numbers[0];
var second = numbers[1];

function getUser() {
    return { name: "John", age: 30 };
}
var user = getUser();
var name = user.name;
Enter fullscreen mode Exit fullscreen mode

After:

// Object destructuring
const user = { name: "John", age: 30, email: "john@example.com" };
const { name, age } = user;

// Renaming
const { name: userName, age: userAge } = user;

// Default values
const { name, age, country = "USA" } = user;

// Nested destructuring
const user = {
    name: "John",
    address: {
        city: "New York",
        country: "USA"
    }
};
const { address: { city, country } } = user;

// Array destructuring
const numbers = [1, 2, 3, 4, 5];
const [first, second] = numbers; // 1, 2
const [first, , third] = numbers; // 1, 3
const [first, ...rest] = numbers; // 1, [2, 3, 4, 5]

// Function parameters
function greet({ name, age }) {
    console.log(`Hello, ${name}. You are ${age} years old.`);
}
greet({ name: "John", age: 30 });

// Swapping variables
let a = 1, b = 2;
[a, b] = [b, a]; // a = 2, b = 1
Enter fullscreen mode Exit fullscreen mode

Default Parameters

Before:

function greet(name, greeting) {
    name = name || "Guest";
    greeting = greeting || "Hello";
    return greeting + ", " + name;
}

// Problem with falsy values
greet("", "Hi"); // "Hi, Guest" - empty string is falsy!
Enter fullscreen mode Exit fullscreen mode

After:

function greet(name = "Guest", greeting = "Hello") {
    return `${greeting}, ${name}`;
}

greet(); // "Hello, Guest"
greet("John"); // "Hello, John"
greet("John", "Hi"); // "Hi, John"
greet("", "Hi"); // "Hi, " - empty string is preserved

// Default can be expressions
function createUser(name, id = Date.now()) {
    return { name, id };
}

// Can reference earlier parameters
function greet(name, greeting = `Hello, ${name}`) {
    return greeting;
}
Enter fullscreen mode Exit fullscreen mode

Rest and Spread Operators

Before:

// Rest parameters
function sum() {
    var args = Array.prototype.slice.call(arguments);
    return args.reduce(function(acc, n) {
        return acc + n;
    }, 0);
}

// Combining arrays
var arr1 = [1, 2, 3];
var arr2 = [4, 5, 6];
var combined = arr1.concat(arr2);

// Copying arrays
var original = [1, 2, 3];
var copy = original.slice();
Enter fullscreen mode Exit fullscreen mode

After:

// Rest parameters
function sum(...numbers) {
    return numbers.reduce((acc, n) => acc + n, 0);
}
sum(1, 2, 3, 4, 5); // 15

function greet(greeting, ...names) {
    return `${greeting}, ${names.join(" and ")}!`;
}
greet("Hello", "John", "Jane", "Bob"); // "Hello, John and Jane and Bob!"

// Spread operator - arrays
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const combined = [...arr1, ...arr2]; // [1, 2, 3, 4, 5, 6]
const copy = [...original];

// Spread operator - objects (ES2018)
const obj1 = { a: 1, b: 2 };
const obj2 = { c: 3, d: 4 };
const combined = { ...obj1, ...obj2 }; // { a: 1, b: 2, c: 3, d: 4 }

// Overriding properties
const defaults = { theme: "light", lang: "en" };
const userPrefs = { theme: "dark" };
const settings = { ...defaults, ...userPrefs }; // { theme: "dark", lang: "en" }

// Function calls
const numbers = [1, 5, 3, 9, 2];
Math.max(...numbers); // 9
Enter fullscreen mode Exit fullscreen mode

Classes

Before (Prototypes):

// Constructor function
function Person(name, age) {
    this.name = name;
    this.age = age;
}

Person.prototype.greet = function() {
    return "Hello, I'm " + this.name;
};

Person.prototype.haveBirthday = function() {
    this.age++;
};

// Inheritance
function Employee(name, age, title) {
    Person.call(this, name, age);
    this.title = title;
}

Employee.prototype = Object.create(Person.prototype);
Employee.prototype.constructor = Employee;

Employee.prototype.work = function() {
    return this.name + " is working as " + this.title;
};

var emp = new Employee("John", 30, "Developer");
emp.greet(); // "Hello, I'm John"
emp.work(); // "John is working as Developer"
Enter fullscreen mode Exit fullscreen mode

After (Classes):

class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }

    greet() {
        return `Hello, I'm ${this.name}`;
    }

    haveBirthday() {
        this.age++;
    }

    // Static methods
    static species() {
        return "Homo sapiens";
    }
}

class Employee extends Person {
    constructor(name, age, title) {
        super(name, age); // Call parent constructor
        this.title = title;
    }

    work() {
        return `${this.name} is working as ${this.title}`;
    }

    // Override parent method
    greet() {
        return `${super.greet()}, I'm a ${this.title}`;
    }
}

const emp = new Employee("John", 30, "Developer");
emp.greet(); // "Hello, I'm John, I'm a Developer"
emp.work(); // "John is working as Developer"
Person.species(); // "Homo sapiens"

// Getters and setters
class Rectangle {
    constructor(width, height) {
        this.width = width;
        this.height = height;
    }

    get area() {
        return this.width * this.height;
    }

    set area(value) {
        this.width = Math.sqrt(value);
        this.height = Math.sqrt(value);
    }
}

const rect = new Rectangle(10, 5);
console.log(rect.area); // 50
rect.area = 100;
console.log(rect.width); // 10
Enter fullscreen mode Exit fullscreen mode

Promises

Before (Callback Hell):

// Pyramid of doom
getUserData(userId, function(err, user) {
    if (err) {
        handleError(err);
    } else {
        getOrders(user.id, function(err, orders) {
            if (err) {
                handleError(err);
            } else {
                getOrderDetails(orders[0].id, function(err, details) {
                    if (err) {
                        handleError(err);
                    } else {
                        displayDetails(details);
                    }
                });
            }
        });
    }
});
Enter fullscreen mode Exit fullscreen mode

After (Promises):

// Promise chain
getUserData(userId)
    .then(user => getOrders(user.id))
    .then(orders => getOrderDetails(orders[0].id))
    .then(details => displayDetails(details))
    .catch(err => handleError(err));

// Creating promises
function delay(ms) {
    return new Promise((resolve, reject) => {
        setTimeout(resolve, ms);
    });
}

delay(1000).then(() => console.log("1 second passed"));

// Promise.all - parallel execution
Promise.all([
    fetch('/api/users'),
    fetch('/api/posts'),
    fetch('/api/comments')
])
.then(([users, posts, comments]) => {
    console.log("All data loaded");
})
.catch(err => console.error("One failed:", err));

// Promise.race - first to complete
Promise.race([
    fetch('/api/fast'),
    fetch('/api/slow')
])
.then(result => console.log("First one won:", result));

// Error handling
fetch('/api/data')
    .then(response => {
        if (!response.ok) {
            throw new Error('HTTP error ' + response.status);
        }
        return response.json();
    })
    .then(data => processData(data))
    .catch(err => console.error('Failed:', err))
    .finally(() => console.log('Cleanup'));
Enter fullscreen mode Exit fullscreen mode

Modules (ES6 Modules)

Before (CommonJS - Node.js):

// math.js
function add(a, b) {
    return a + b;
}

function subtract(a, b) {
    return a - b;
}

module.exports = { add, subtract };

// app.js
var math = require('./math');
console.log(math.add(5, 3));
Enter fullscreen mode Exit fullscreen mode

Before (Browser - Script tags):

<script src="jquery.js"></script>
<script src="lodash.js"></script>
<script src="utils.js"></script>
<script src="app.js"></script>
<!-- Global namespace pollution, order matters -->
Enter fullscreen mode Exit fullscreen mode

After (ES6 Modules):

// math.js
export function add(a, b) {
    return a + b;
}

export function subtract(a, b) {
    return a - b;
}

// or
export { add, subtract };

// Default export
export default function multiply(a, b) {
    return a * b;
}

// app.js
import multiply, { add, subtract } from './math.js';
console.log(add(5, 3)); // 8
console.log(multiply(5, 3)); // 15

// Import everything
import * as math from './math.js';
console.log(math.add(5, 3));

// Rename imports
import { add as sum } from './math.js';

// Re-export
export { add, subtract } from './math.js';
export * from './math.js';
Enter fullscreen mode Exit fullscreen mode

Other ES6 Features

Map and Set:

// Before: Objects as maps (problems with keys)
var map = {};
map['key1'] = 'value1';
map[{}] = 'value2'; // [object Object] as key - not what you want!

// ES6 Map
const map = new Map();
map.set('key1', 'value1');
map.set({}, 'value2'); // Objects as keys work!
map.set(function() {}, 'value3'); // Functions as keys!

map.get('key1'); // 'value1'
map.has('key1'); // true
map.delete('key1');
map.size; // 2

// Iteration
for (const [key, value] of map) {
    console.log(key, value);
}

// Set - unique values
const set = new Set([1, 2, 3, 3, 4, 4, 5]);
console.log(set); // Set { 1, 2, 3, 4, 5 }

set.add(6);
set.has(3); // true
set.delete(3);
set.size; // 5

// Array deduplication
const numbers = [1, 2, 3, 3, 4, 4, 5];
const unique = [...new Set(numbers)]; // [1, 2, 3, 4, 5]
Enter fullscreen mode Exit fullscreen mode

Symbols:

// Unique identifiers
const sym1 = Symbol('description');
const sym2 = Symbol('description');
sym1 === sym2; // false - always unique

// Private-ish object properties
const _private = Symbol('private');
class MyClass {
    constructor() {
        this[_private] = 'secret';
        this.public = 'visible';
    }

    getPrivate() {
        return this[_private];
    }
}

const obj = new MyClass();
console.log(obj.public); // 'visible'
console.log(obj[_private]); // undefined (need the symbol reference)
Object.keys(obj); // ['public'] - symbol not enumerated
Enter fullscreen mode Exit fullscreen mode

Iterators and Generators:

// Iterator
const iterable = {
    data: [1, 2, 3, 4, 5],
    [Symbol.iterator]() {
        let index = 0;
        return {
            next: () => {
                if (index < this.data.length) {
                    return { value: this.data[index++], done: false };
                } else {
                    return { done: true };
                }
            }
        };
    }
};

for (const value of iterable) {
    console.log(value); // 1, 2, 3, 4, 5
}

// Generator - easier way
function* numberGenerator() {
    yield 1;
    yield 2;
    yield 3;
}

const gen = numberGenerator();
gen.next(); // { value: 1, done: false }
gen.next(); // { value: 2, done: false }
gen.next(); // { value: 3, done: false }
gen.next(); // { done: true }

// Infinite sequences
function* fibonacci() {
    let [a, b] = [0, 1];
    while (true) {
        yield a;
        [a, b] = [b, a + b];
    }
}

const fib = fibonacci();
fib.next().value; // 0
fib.next().value; // 1
fib.next().value; // 1
fib.next().value; // 2
fib.next().value; // 3

// Practical use - lazy evaluation
function* range(start, end) {
    for (let i = start; i <= end; i++) {
        yield i;
    }
}

for (const num of range(1, 5)) {
    console.log(num); // 1, 2, 3, 4, 5
}
Enter fullscreen mode Exit fullscreen mode

2013: React.js

Facebook releases React - Component-based UI library

Before React (jQuery):

// Imperative, manual DOM manipulation
var todos = [];

function addTodo(text) {
    todos.push({ id: Date.now(), text: text, done: false });
    renderTodos();
}

function toggleTodo(id) {
    var todo = todos.find(function(t) { return t.id === id; });
    todo.done = !todo.done;
    renderTodos();
}

function renderTodos() {
    var html = todos.map(function(todo) {
        return '<li>' +
               '<input type="checkbox" ' + (todo.done ? 'checked' : '') + ' data-id="' + todo.id + '">' +
               '<span>' + todo.text + '</span>' +
               '</li>';
    }).join('');
    $('#todos').html(html);
}

$(document).on('change', 'input[type=checkbox]', function() {
    toggleTodo($(this).data('id'));
});
Enter fullscreen mode Exit fullscreen mode

With React:

// Declarative, component-based
class TodoApp extends React.Component {
    state = {
        todos: [],
        input: ''
    };

    addTodo = () => {
        this.setState({
            todos: [...this.state.todos, {
                id: Date.now(),
                text: this.state.input,
                done: false
            }],
            input: ''
        });
    };

    toggleTodo = (id) => {
        this.setState({
            todos: this.state.todos.map(todo =>
                todo.id === id ? { ...todo, done: !todo.done } : todo
            )
        });
    };

    render() {
        return (
            <div>
                <input
                    value={this.state.input}
                    onChange={e => this.setState({ input: e.target.value })}
                />
                <button onClick={this.addTodo}>Add</button>

                <ul>
                    {this.state.todos.map(todo => (
                        <li key={todo.id}>
                            <input
                                type="checkbox"
                                checked={todo.done}
                                onChange={() => this.toggleTodo(todo.id)}
                            />
                            <span>{todo.text}</span>
                        </li>
                    ))}
                </ul>
            </div>
        );
    }
}
Enter fullscreen mode Exit fullscreen mode

JSX - HTML in JavaScript:

// JSX (transpiled by Babel)
const element = <h1>Hello, {name}!</h1>;

// Compiles to
const element = React.createElement('h1', null, 'Hello, ', name, '!');

// Components
function Welcome(props) {
    return <h1>Hello, {props.name}</h1>;
}

// Usage
<Welcome name="John" />
Enter fullscreen mode Exit fullscreen mode

2014: Vue.js

Evan You releases Vue.js - Progressive framework

// Vue instance
new Vue({
    el: '#app',
    data: {
        message: 'Hello Vue!',
        todos: [],
        newTodo: ''
    },
    methods: {
        addTodo() {
            this.todos.push({
                id: Date.now(),
                text: this.newTodo,
                done: false
            });
            this.newTodo = '';
        },
        toggleTodo(todo) {
            todo.done = !todo.done;
        }
    }
});
Enter fullscreen mode Exit fullscreen mode
<div id="app">
    <input v-model="newTodo" @keyup.enter="addTodo">
    <button @click="addTodo">Add</button>

    <ul>
        <li v-for="todo in todos" :key="todo.id">
            <input type="checkbox" v-model="todo.done">
            <span :class="{ done: todo.done }">{{ todo.text }}</span>
        </li>
    </ul>
</div>
Enter fullscreen mode Exit fullscreen mode

Annual Releases: ES2016-ES2025

ES2016 (ES7)

Array.prototype.includes:

// Before
const numbers = [1, 2, 3, 4, 5];
numbers.indexOf(3) !== -1; // true
numbers.indexOf(6) !== -1; // false

// Problem with NaN
[1, 2, NaN].indexOf(NaN); // -1 (doesn't work!)

// After
numbers.includes(3); // true
numbers.includes(6); // false
[1, 2, NaN].includes(NaN); // true (works!)
Enter fullscreen mode Exit fullscreen mode

Exponentiation Operator:

// Before
Math.pow(2, 3); // 8

// After
2 ** 3; // 8
2 ** 3 ** 2; // 512 (right-associative: 2 ** (3 ** 2))

// With assignment
let num = 2;
num **= 3; // 8
Enter fullscreen mode Exit fullscreen mode

ES2017 (ES8)

async/await - Game changer for asynchronous code

Before (Promises):

function fetchUserData(userId) {
    return fetch(`/api/users/${userId}`)
        .then(response => response.json())
        .then(user => {
            return fetch(`/api/orders/${user.id}`)
                .then(response => response.json())
                .then(orders => {
                    return fetch(`/api/details/${orders[0].id}`)
                        .then(response => response.json())
                        .then(details => {
                            return { user, orders, details };
                        });
                });
        })
        .catch(err => console.error(err));
}
Enter fullscreen mode Exit fullscreen mode

After (async/await):

async function fetchUserData(userId) {
    try {
        const userResponse = await fetch(`/api/users/${userId}`);
        const user = await userResponse.json();

        const ordersResponse = await fetch(`/api/orders/${user.id}`);
        const orders = await ordersResponse.json();

        const detailsResponse = await fetch(`/api/details/${orders[0].id}`);
        const details = await detailsResponse.json();

        return { user, orders, details };
    } catch (err) {
        console.error(err);
    }
}

// Parallel execution
async function fetchAllData() {
    const [users, posts, comments] = await Promise.all([
        fetch('/api/users').then(r => r.json()),
        fetch('/api/posts').then(r => r.json()),
        fetch('/api/comments').then(r => r.json())
    ]);

    return { users, posts, comments };
}

// Error handling
async function getData() {
    try {
        const data = await fetch('/api/data');
        return await data.json();
    } catch (err) {
        console.error('Failed:', err);
        throw err;
    } finally {
        console.log('Cleanup');
    }
}

// Top-level await (ES2022)
const data = await fetch('/api/data').then(r => r.json());
Enter fullscreen mode Exit fullscreen mode

Object.entries() and Object.values():

const obj = { a: 1, b: 2, c: 3 };

// Before
Object.keys(obj); // ['a', 'b', 'c']
Object.keys(obj).map(key => obj[key]); // [1, 2, 3]

// After
Object.values(obj); // [1, 2, 3]
Object.entries(obj); // [['a', 1], ['b', 2], ['c', 3]]

// Practical use
for (const [key, value] of Object.entries(obj)) {
    console.log(`${key}: ${value}`);
}

// Convert to Map
const map = new Map(Object.entries(obj));

// Convert from Map
const obj = Object.fromEntries(map);
Enter fullscreen mode Exit fullscreen mode

String padding:

'5'.padStart(3, '0'); // '005'
'5'.padEnd(3, '0'); // '500'

// Practical use
const numbers = [1, 10, 100];
numbers.forEach(n => {
    console.log(n.toString().padStart(3, '0'));
});
// 001
// 010
// 100
Enter fullscreen mode Exit fullscreen mode

Object.getOwnPropertyDescriptors():

const obj = {
    get foo() { return 'bar'; }
};

// Shallow clone with getters/setters
const clone = Object.defineProperties(
    {},
    Object.getOwnPropertyDescriptors(obj)
);
Enter fullscreen mode Exit fullscreen mode

ES2018 (ES9)

Rest/Spread for Objects:

// Rest
const { a, b, ...rest } = { a: 1, b: 2, c: 3, d: 4 };
console.log(rest); // { c: 3, d: 4 }

// Spread
const obj1 = { a: 1, b: 2 };
const obj2 = { c: 3, d: 4 };
const merged = { ...obj1, ...obj2 }; // { a: 1, b: 2, c: 3, d: 4 }

// Overriding
const defaults = { theme: 'light', lang: 'en' };
const userPrefs = { theme: 'dark' };
const settings = { ...defaults, ...userPrefs }; // { theme: 'dark', lang: 'en' }

// Shallow clone
const clone = { ...original };

// Remove property
const { password, ...userWithoutPassword } = user;
Enter fullscreen mode Exit fullscreen mode

Async Iteration:

// Async iterator
const asyncIterable = {
    async *[Symbol.asyncIterator]() {
        yield await Promise.resolve(1);
        yield await Promise.resolve(2);
        yield await Promise.resolve(3);
    }
};

for await (const num of asyncIterable) {
    console.log(num); // 1, 2, 3
}

// Practical use - streaming data
async function* fetchPages(url) {
    let page = 1;
    while (true) {
        const response = await fetch(`${url}?page=${page}`);
        const data = await response.json();
        if (data.length === 0) break;
        yield data;
        page++;
    }
}

for await (const pageData of fetchPages('/api/items')) {
    console.log(pageData);
}
Enter fullscreen mode Exit fullscreen mode

Promise.finally():

fetch('/api/data')
    .then(response => response.json())
    .then(data => processData(data))
    .catch(err => handleError(err))
    .finally(() => {
        hideLoadingSpinner(); // Always runs
    });
Enter fullscreen mode Exit fullscreen mode

RegExp improvements:

// Named capture groups
const pattern = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const match = pattern.exec('2025-12-04');
console.log(match.groups.year); // '2025'
console.log(match.groups.month); // '12'
console.log(match.groups.day); // '04'

// Lookbehind assertions
/(?<=\$)\d+/.test('$100'); // true
/(?<!\$)\d+/.test('100'); // true
Enter fullscreen mode Exit fullscreen mode

ES2019 (ES10)

Array.prototype.flat() and flatMap():

// Before - flatten nested arrays
const nested = [1, [2, 3], [4, [5, 6]]];
const flat = nested.reduce((acc, val) =>
    acc.concat(Array.isArray(val) ? val : [val]), []
); // [1, 2, 3, 4, [5, 6]]

// After
nested.flat(); // [1, 2, 3, 4, [5, 6]]
nested.flat(2); // [1, 2, 3, 4, 5, 6] - depth 2
nested.flat(Infinity); // Flatten all levels

// flatMap - map then flatten
const arr = [1, 2, 3];
arr.map(x => [x, x * 2]); // [[1, 2], [2, 4], [3, 6]]
arr.flatMap(x => [x, x * 2]); // [1, 2, 2, 4, 3, 6]

// Practical use
const sentences = ['Hello world', 'How are you'];
sentences.flatMap(s => s.split(' ')); // ['Hello', 'world', 'How', 'are', 'you']
Enter fullscreen mode Exit fullscreen mode

Object.fromEntries():

// Convert array of entries to object
const entries = [['a', 1], ['b', 2], ['c', 3]];
const obj = Object.fromEntries(entries); // { a: 1, b: 2, c: 3 }

// Convert Map to object
const map = new Map([['a', 1], ['b', 2]]);
const obj = Object.fromEntries(map);

// Transform object
const obj = { a: 1, b: 2, c: 3 };
const doubled = Object.fromEntries(
    Object.entries(obj).map(([key, value]) => [key, value * 2])
); // { a: 2, b: 4, c: 6 }

// Filter object
const filtered = Object.fromEntries(
    Object.entries(obj).filter(([key, value]) => value > 1)
); // { b: 2, c: 3 }
Enter fullscreen mode Exit fullscreen mode

String.prototype.trimStart() and trimEnd():

'  hello  '.trimStart(); // 'hello  '
'  hello  '.trimEnd(); // '  hello'
'  hello  '.trim(); // 'hello'
Enter fullscreen mode Exit fullscreen mode

Optional catch binding:

// Before
try {
    JSON.parse(data);
} catch (err) { // Had to specify err even if not used
    console.log('Invalid JSON');
}

// After
try {
    JSON.parse(data);
} catch {
    console.log('Invalid JSON');
}
Enter fullscreen mode Exit fullscreen mode

ES2020 (ES11)

Optional Chaining (?.):

// Before
const city = user && user.address && user.address.city;

if (user && user.address && user.address.city) {
    console.log(user.address.city);
}

// After
const city = user?.address?.city;

if (user?.address?.city) {
    console.log(user.address.city);
}

// With arrays
const firstItem = arr?.[0];

// With functions
const result = obj.method?.();

// Practical examples
const userName = response?.data?.user?.name ?? 'Guest';
const onClick = props?.onClick?.();
Enter fullscreen mode Exit fullscreen mode

Nullish Coalescing (??):

// Before - problem with falsy values
const value = input || 'default';
// Problem: 0, '', false are all replaced with 'default'

const count = 0;
const display = count || 'No items'; // 'No items' - wrong!

// After - only null/undefined
const value = input ?? 'default';

const count = 0;
const display = count ?? 'No items'; // 0 - correct!

const name = '';
const displayName = name ?? 'Anonymous'; // '' - empty string is kept

// Combined with optional chaining
const userName = user?.name ?? 'Guest';
Enter fullscreen mode Exit fullscreen mode

BigInt - Arbitrary precision integers:

// Before - limited to Number.MAX_SAFE_INTEGER
const big = 9007199254740991; // MAX_SAFE_INTEGER
big + 1; // 9007199254740992
big + 2; // 9007199254740992 - wrong!

// After
const bigInt = 9007199254740991n;
bigInt + 1n; // 9007199254740992n
bigInt + 2n; // 9007199254740993n - correct!

// Or
const bigInt = BigInt('9007199254740991');

// Operations
10n + 20n; // 30n
10n * 20n; // 200n
10n / 3n; // 3n (truncates)

// Cannot mix with Number
10n + 5; // TypeError
10n + BigInt(5); // 15n
Enter fullscreen mode Exit fullscreen mode

Promise.allSettled():

// Promise.all - fails if any fails
Promise.all([
    fetch('/api/users'),
    fetch('/api/posts'),
    fetch('/api/comments')
])
.then(results => console.log('All succeeded'))
.catch(err => console.log('One failed')); // Stops on first failure

// Promise.allSettled - waits for all
Promise.allSettled([
    fetch('/api/users'),
    fetch('/api/posts'),
    fetch('/api/comments')
])
.then(results => {
    results.forEach(result => {
        if (result.status === 'fulfilled') {
            console.log('Success:', result.value);
        } else {
            console.log('Failed:', result.reason);
        }
    });
});

// Result format
[
    { status: 'fulfilled', value: response1 },
    { status: 'rejected', reason: error },
    { status: 'fulfilled', value: response3 }
]
Enter fullscreen mode Exit fullscreen mode

globalThis:

// Before - different in different environments
// Browser: window
// Node.js: global
// Web Workers: self

// After - works everywhere
globalThis.setTimeout(() => {}, 1000);
Enter fullscreen mode Exit fullscreen mode

Dynamic import():

// Before - static imports only
import { heavy } from './heavy-module.js'; // Loaded immediately

// After - dynamic imports
button.addEventListener('click', async () => {
    const module = await import('./heavy-module.js');
    module.doSomething();
});

// Conditional loading
if (condition) {
    const module = await import('./module-a.js');
} else {
    const module = await import('./module-b.js');
}

// Practical use - code splitting
async function loadChart() {
    const { Chart } = await import('chart.js');
    return new Chart(ctx, config);
}
Enter fullscreen mode Exit fullscreen mode

ES2021 (ES12)

String.prototype.replaceAll():

// Before
const str = 'hello world, hello universe';
str.replace(/hello/g, 'hi'); // 'hi world, hi universe'
str.split('hello').join('hi'); // Workaround

// After
str.replaceAll('hello', 'hi'); // 'hi world, hi universe'
Enter fullscreen mode Exit fullscreen mode

Logical Assignment Operators:

// Before
if (!obj.prop) {
    obj.prop = 'default';
}

// After
obj.prop ||= 'default';

// Nullish coalescing assignment
obj.prop ??= 'default'; // Only if null/undefined

// AND assignment
obj.prop &&= newValue; // Only if truthy

// Practical examples
let count = 0;
count ||= 10; // count = 10
count ||= 20; // count = 10 (already truthy)

let value = null;
value ??= 'default'; // value = 'default'
value ??= 'other'; // value = 'default' (not null)
Enter fullscreen mode Exit fullscreen mode

Numeric Separators:

// Before
const billion = 1000000000;
const bytes = 0b11111111;

// After - more readable
const billion = 1_000_000_000;
const bytes = 0b1111_1111;
const hex = 0xFF_FF_FF_FF;
const price = 1_234_567.89;
Enter fullscreen mode Exit fullscreen mode

Promise.any():

// Returns first fulfilled promise
Promise.any([
    fetch('/api/server1'),
    fetch('/api/server2'),
    fetch('/api/server3')
])
.then(response => console.log('First success:', response))
.catch(err => console.log('All failed'));

// vs Promise.race (returns first settled, even if rejected)
Promise.race([...]) // First to complete (success or failure)
Promise.any([...])  // First to succeed
Enter fullscreen mode Exit fullscreen mode

WeakRef and FinalizationRegistry:

// WeakRef - weak reference to object
const obj = { data: 'important' };
const weakRef = new WeakRef(obj);

// Later
const obj = weakRef.deref();
if (obj) {
    console.log(obj.data);
} else {
    console.log('Object was garbage collected');
}

// FinalizationRegistry - cleanup callback
const registry = new FinalizationRegistry((value) => {
    console.log(`Cleanup: ${value}`);
});

let obj = { data: 'test' };
registry.register(obj, 'my-object');
obj = null; // Eventually triggers cleanup callback
Enter fullscreen mode Exit fullscreen mode

ES2022 (ES13)

Top-level await:

// Before - had to wrap in async function
(async () => {
    const data = await fetch('/api/data');
    console.log(data);
})();

// After - in modules
const data = await fetch('/api/data');
console.log(data);

// Practical use
const [users, posts] = await Promise.all([
    fetch('/api/users').then(r => r.json()),
    fetch('/api/posts').then(r => r.json())
]);

export { users, posts };
Enter fullscreen mode Exit fullscreen mode

Class Fields and Private Methods:

// Before
class Counter {
    constructor() {
        this.count = 0;
        this._private = 'secret'; // Convention, not enforced
    }
}

// After - public fields
class Counter {
    count = 0; // Public field
    #private = 'secret'; // Private field

    increment() {
        this.count++;
    }

    #privateMethod() { // Private method
        return this.#private;
    }

    getPrivate() {
        return this.#privateMethod();
    }
}

const counter = new Counter();
counter.count; // 0
counter.#private; // SyntaxError
counter.#privateMethod(); // SyntaxError

// Static fields and methods
class MathUtils {
    static PI = 3.14159;
    static #secret = 'private static';

    static double(n) {
        return n * 2;
    }

    static #privateStatic() {
        return this.#secret;
    }
}

MathUtils.PI; // 3.14159
MathUtils.double(5); // 10

// Static initialization blocks
class Database {
    static #connection;

    static {
        // Runs once when class is defined
        this.#connection = createConnection();
    }
}
Enter fullscreen mode Exit fullscreen mode

Array.prototype.at():

const arr = [1, 2, 3, 4, 5];

// Before - negative indexing
arr[arr.length - 1]; // 5
arr[arr.length - 2]; // 4

// After
arr.at(-1); // 5
arr.at(-2); // 4
arr.at(0); // 1

// Works with strings too
'hello'.at(-1); // 'o'
Enter fullscreen mode Exit fullscreen mode

Object.hasOwn():

const obj = { prop: 'value' };

// Before
obj.hasOwnProperty('prop'); // true
// Problem: can be overridden
obj.hasOwnProperty = () => false;
obj.hasOwnProperty('prop'); // false - wrong!

// After
Object.hasOwn(obj, 'prop'); // true - always works
Enter fullscreen mode Exit fullscreen mode

Error.cause:

// Before
try {
    await fetch('/api/data');
} catch (err) {
    throw new Error('Failed to fetch data'); // Lost original error
}

// After
try {
    await fetch('/api/data');
} catch (err) {
    throw new Error('Failed to fetch data', { cause: err });
}

// Access original error
catch (err) {
    console.log(err.message); // 'Failed to fetch data'
    console.log(err.cause); // Original fetch error
}
Enter fullscreen mode Exit fullscreen mode

RegExp Match Indices:

const pattern = /test(\d+)/d; // Note the 'd' flag
const match = pattern.exec('test123');

match.indices; // [[0, 7], [4, 7]]
// [0, 7] - full match position
// [4, 7] - first capture group position
Enter fullscreen mode Exit fullscreen mode

ES2023 (ES14)

Array methods: findLast(), findLastIndex():

const arr = [1, 2, 3, 4, 5, 4, 3, 2, 1];

// Before - find from start
arr.find(x => x > 3); // 4 (first occurrence)
arr.findIndex(x => x > 3); // 3

// After - find from end
arr.findLast(x => x > 3); // 4 (last occurrence)
arr.findLastIndex(x => x > 3); // 5

// Practical use
const logs = [
    { level: 'info', msg: 'Started' },
    { level: 'error', msg: 'Failed' },
    { level: 'info', msg: 'Retry' },
    { level: 'error', msg: 'Failed again' }
];

const lastError = logs.findLast(log => log.level === 'error');
// { level: 'error', msg: 'Failed again' }
Enter fullscreen mode Exit fullscreen mode

Array.prototype.toSorted(), toReversed(), toSpliced(), with():

const arr = [3, 1, 4, 1, 5];

// Before - mutating methods
arr.sort(); // Modifies arr
arr.reverse(); // Modifies arr
arr.splice(1, 2); // Modifies arr

// After - non-mutating methods
const sorted = arr.toSorted(); // Returns new array
const reversed = arr.toReversed(); // Returns new array
const spliced = arr.toSpliced(1, 2); // Returns new array
const modified = arr.with(2, 99); // Returns new array with arr[2] = 99

// Original array unchanged
console.log(arr); // [3, 1, 4, 1, 5]

// Practical use - immutable updates
const state = [1, 2, 3, 4, 5];
const newState = state.with(2, 99); // [1, 2, 99, 4, 5]
Enter fullscreen mode Exit fullscreen mode

Hashbang Grammar:

#!/usr/bin/env node
// Now valid JavaScript syntax

console.log('Hello from script');
Enter fullscreen mode Exit fullscreen mode

Symbols as WeakMap keys:

const weak = new WeakMap();
const sym = Symbol('key');

weak.set(sym, 'value'); // Now allowed
Enter fullscreen mode Exit fullscreen mode

ES2024 (ES15)

Array Grouping:

const items = [
    { type: 'fruit', name: 'apple' },
    { type: 'vegetable', name: 'carrot' },
    { type: 'fruit', name: 'banana' },
    { type: 'vegetable', name: 'broccoli' }
];

// Before
const grouped = items.reduce((acc, item) => {
    if (!acc[item.type]) acc[item.type] = [];
    acc[item.type].push(item);
    return acc;
}, {});

// After
const grouped = Object.groupBy(items, item => item.type);
// {
//   fruit: [{ type: 'fruit', name: 'apple' }, { type: 'fruit', name: 'banana' }],
//   vegetable: [{ type: 'vegetable', name: 'carrot' }, { type: 'vegetable', name: 'broccoli' }]
// }

// Map.groupBy for Map result
const groupedMap = Map.groupBy(items, item => item.type);
Enter fullscreen mode Exit fullscreen mode

Promise.withResolvers():

// Before
let resolve, reject;
const promise = new Promise((res, rej) => {
    resolve = res;
    reject = rej;
});

// After
const { promise, resolve, reject } = Promise.withResolvers();

// Practical use
class AsyncQueue {
    #queue = [];

    async dequeue() {
        if (this.#queue.length > 0) {
            return this.#queue.shift();
        }

        const { promise, resolve } = Promise.withResolvers();
        this.#resolvers.push(resolve);
        return promise;
    }
}
Enter fullscreen mode Exit fullscreen mode

Atomics.waitAsync():

// Non-blocking wait on SharedArrayBuffer
const buffer = new SharedArrayBuffer(4);
const view = new Int32Array(buffer);

// Before - blocking
Atomics.wait(view, 0, 0); // Blocks thread

// After - non-blocking
const result = Atomics.waitAsync(view, 0, 0);
result.value.then(() => {
    console.log('Value changed');
});
Enter fullscreen mode Exit fullscreen mode

ES2025 (Current)

Duplicate named capture groups in RegExp:

// Now allowed
const pattern = /(?<year>\d{4})-\d{2}|\d{2}-(?<year>\d{4})/;
const match1 = pattern.exec('2025-12');
const match2 = pattern.exec('12-2025');

match1.groups.year; // '2025'
match2.groups.year; // '2025'
Enter fullscreen mode Exit fullscreen mode

Set methods:

const set1 = new Set([1, 2, 3]);
const set2 = new Set([3, 4, 5]);

// Union
set1.union(set2); // Set {1, 2, 3, 4, 5}

// Intersection
set1.intersection(set2); // Set {3}

// Difference
set1.difference(set2); // Set {1, 2}

// Symmetric difference
set1.symmetricDifference(set2); // Set {1, 2, 4, 5}

// Subset/superset
set1.isSubsetOf(set2); // false
set1.isDisjointFrom(set2); // false
Enter fullscreen mode Exit fullscreen mode

TypeScript Evolution (2012-2025)

Created: 2012 by Microsoft (Anders Hejlsberg - creator of C#)

Problem it solved:

  • 🎯 JavaScript had no type safety (runtime errors everywhere)
  • 🎯 Large codebases were unmaintainable (refactoring was dangerous)
  • 🎯 No IDE autocomplete/IntelliSense (guessing API)
  • 🎯 Bugs only found at runtime (no compile-time checks)
  • 🎯 Documentation was comments (no enforced contracts)

The breakthrough:

  • Static typing - Catch errors before running code
  • IntelliSense - IDE knows what properties/methods exist
  • Refactoring - Rename safely across entire codebase
  • Compiles to JavaScript - Works everywhere JS works
  • Gradual adoption - Can add types incrementally
// JavaScript - Runtime errors
function getUser(id) {
    return fetch(`/api/users/${id}`)
        .then(res => res.json());
}

const user = getUser(123);
console.log(user.name); // Error! user is a Promise, not an object
// Only discover this when running the code

// TypeScript - Compile-time errors
async function getUser(id: number): Promise<User> {
    const res = await fetch(`/api/users/${id}`);
    return res.json();
}

const user = getUser(123);
console.log(user.name); // Error! Type 'Promise<User>' has no property 'name'
// IDE shows error immediately, before running

const user = await getUser(123);
console.log(user.name); // ✅ Works! IDE autocompletes 'name'
Enter fullscreen mode Exit fullscreen mode

TypeScript 1.0 (2014)

Basic Types:

// Primitive types
let name: string = "John";
let age: number = 30;
let isActive: boolean = true;
let nothing: null = null;
let notDefined: undefined = undefined;

// Arrays
let numbers: number[] = [1, 2, 3];
let strings: Array<string> = ["a", "b", "c"];

// Tuples
let tuple: [string, number] = ["John", 30];

// Enums
enum Color {
    Red,
    Green,
    Blue
}
let color: Color = Color.Red;

// Any - escape hatch
let anything: any = 4;
anything = "string";
anything = true;

// Functions
function add(a: number, b: number): number {
    return a + b;
}

const multiply = (a: number, b: number): number => a * b;
Enter fullscreen mode Exit fullscreen mode

Interfaces:

interface User {
    id: number;
    name: string;
    email: string;
    age?: number; // Optional
    readonly created: Date; // Readonly
}

function createUser(user: User): void {
    console.log(user.name);
}

// Extending interfaces
interface Admin extends User {
    role: string;
    permissions: string[];
}
Enter fullscreen mode Exit fullscreen mode

TypeScript 1.4 (2015) - Union Types

// Union types
let value: string | number;
value = "hello";
value = 42;

// Type guards
function process(value: string | number) {
    if (typeof value === "string") {
        return value.toUpperCase(); // TypeScript knows it's string
    } else {
        return value.toFixed(2); // TypeScript knows it's number
    }
}

// Literal types
let direction: "north" | "south" | "east" | "west";
direction = "north"; // OK
direction = "up"; // Error
Enter fullscreen mode Exit fullscreen mode

TypeScript 1.6 (2015) - Intersection Types

// Intersection types
interface Loggable {
    log(): void;
}

interface Serializable {
    serialize(): string;
}

type LoggableSerializable = Loggable & Serializable;

class MyClass implements LoggableSerializable {
    log() { console.log("logging"); }
    serialize() { return "{}"; }
}
Enter fullscreen mode Exit fullscreen mode

TypeScript 2.0 (2016) - Major Update

Non-nullable types:

// Before - everything could be null/undefined
let name: string = null; // OK

// After - strictNullChecks
let name: string = null; // Error
let name: string | null = null; // OK

function greet(name: string) {
    console.log(name.toUpperCase()); // Safe - name cannot be null
}

function greet(name: string | null) {
    if (name) {
        console.log(name.toUpperCase()); // Must check first
    }
}
Enter fullscreen mode Exit fullscreen mode

Readonly properties:

interface Point {
    readonly x: number;
    readonly y: number;
}

let point: Point = { x: 10, y: 20 };
point.x = 5; // Error

// Readonly arrays
let arr: ReadonlyArray<number> = [1, 2, 3];
arr.push(4); // Error
arr[0] = 5; // Error
Enter fullscreen mode Exit fullscreen mode

Tagged union types:

interface Square {
    kind: "square";
    size: number;
}

interface Rectangle {
    kind: "rectangle";
    width: number;
    height: number;
}

interface Circle {
    kind: "circle";
    radius: number;
}

type Shape = Square | Rectangle | Circle;

function area(shape: Shape): number {
    switch (shape.kind) {
        case "square":
            return shape.size ** 2; // TypeScript knows it's Square
        case "rectangle":
            return shape.width * shape.height; // Rectangle
        case "circle":
            return Math.PI * shape.radius ** 2; // Circle
    }
}
Enter fullscreen mode Exit fullscreen mode

TypeScript 2.1 (2016) - Advanced Features

Mapped types:

// Make all properties optional
type Partial<T> = {
    [P in keyof T]?: T[P];
};

// Make all properties readonly
type Readonly<T> = {
    readonly [P in keyof T]: T[P];
};

interface User {
    name: string;
    age: number;
}

type PartialUser = Partial<User>; // { name?: string; age?: number; }
type ReadonlyUser = Readonly<User>; // { readonly name: string; readonly age: number; }
Enter fullscreen mode Exit fullscreen mode

keyof and lookup types:

interface Person {
    name: string;
    age: number;
}

type PersonKeys = keyof Person; // "name" | "age"

function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
    return obj[key];
}

const person = { name: "John", age: 30 };
const name = getProperty(person, "name"); // string
const age = getProperty(person, "age"); // number
Enter fullscreen mode Exit fullscreen mode

TypeScript 2.8 (2018) - Conditional Types

// Conditional types
type IsString<T> = T extends string ? true : false;

type A = IsString<string>; // true
type B = IsString<number>; // false

// Practical use - extract return type
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

function getUser() {
    return { name: "John", age: 30 };
}

type User = ReturnType<typeof getUser>; // { name: string; age: number; }

// Exclude/Extract
type Exclude<T, U> = T extends U ? never : T;
type Extract<T, U> = T extends U ? T : never;

type T1 = Exclude<"a" | "b" | "c", "a">; // "b" | "c"
type T2 = Extract<"a" | "b" | "c", "a" | "f">; // "a"
Enter fullscreen mode Exit fullscreen mode

TypeScript 3.0 (2018) - Tuples and Rest

Tuple improvements:

// Rest elements in tuples
type Strings = [string, string];
type Numbers = [number, number];
type StrStrNumNumBool = [...Strings, ...Numbers, boolean];
// [string, string, number, number, boolean]

// Optional elements
type OptionalTuple = [string, number?];
let t1: OptionalTuple = ["hello"];
let t2: OptionalTuple = ["hello", 42];
Enter fullscreen mode Exit fullscreen mode

TypeScript 3.7 (2019) - Optional Chaining

// Optional chaining (same as JavaScript)
const city = user?.address?.city;

// Nullish coalescing
const name = user?.name ?? "Guest";

// Assertion functions
function assert(condition: any, msg?: string): asserts condition {
    if (!condition) {
        throw new Error(msg);
    }
}

function processValue(value: string | null) {
    assert(value !== null, "Value must not be null");
    // TypeScript knows value is string here
    console.log(value.toUpperCase());
}
Enter fullscreen mode Exit fullscreen mode

TypeScript 4.0 (2020) - Variadic Tuples

// Variadic tuple types
function concat<T extends unknown[], U extends unknown[]>(
    arr1: T,
    arr2: U
): [...T, ...U] {
    return [...arr1, ...arr2];
}

const result = concat([1, 2], ["a", "b"]);
// type: [number, number, string, string]

// Labeled tuple elements
type Range = [start: number, end: number];

function createRange(...[start, end]: Range) {
    // ...
}
Enter fullscreen mode Exit fullscreen mode

TypeScript 4.1 (2020) - Template Literal Types

// Template literal types
type World = "world";
type Greeting = `hello ${World}`; // "hello world"

type Color = "red" | "blue";
type Quantity = "one" | "two";
type SeussFish = `${Quantity | Color} fish`;
// "one fish" | "two fish" | "red fish" | "blue fish"

// Practical use - API routes
type HTTPMethod = "GET" | "POST" | "PUT" | "DELETE";
type Route = "/users" | "/posts" | "/comments";
type APIRoute = `${HTTPMethod} ${Route}`;
// "GET /users" | "POST /users" | ... (12 combinations)

// Intrinsic string manipulation
type Uppercase<S extends string> = intrinsic;
type Lowercase<S extends string> = intrinsic;
type Capitalize<S extends string> = intrinsic;
type Uncapitalize<S extends string> = intrinsic;

type Loud = Uppercase<"hello">; // "HELLO"
type Quiet = Lowercase<"HELLO">; // "hello"
Enter fullscreen mode Exit fullscreen mode

Recursive conditional types:

// Deep readonly
type DeepReadonly<T> = {
    readonly [P in keyof T]: T[P] extends object
        ? DeepReadonly<T[P]>
        : T[P];
};

interface Nested {
    a: {
        b: {
            c: number;
        };
    };
}

type ReadonlyNested = DeepReadonly<Nested>;
// All levels are readonly
Enter fullscreen mode Exit fullscreen mode

TypeScript 4.5 (2021) - Awaited Type

// Awaited - unwrap Promise types
type A = Awaited<Promise<string>>; // string
type B = Awaited<Promise<Promise<number>>>; // number

// Practical use
async function fetchUser() {
    return { name: "John", age: 30 };
}

type User = Awaited<ReturnType<typeof fetchUser>>;
// { name: string; age: number; }
Enter fullscreen mode Exit fullscreen mode

TypeScript 4.7 (2022) - Module Detection

ES Module support improvements:

// package.json
{
    "type": "module"
}

// Now .ts files are treated as ES modules by default
Enter fullscreen mode Exit fullscreen mode

TypeScript 4.9 (2022) - satisfies Operator

// Before - lose type information
const config: Record<string, string | number> = {
    host: "localhost",
    port: 3000
};

config.host.toUpperCase(); // Error - might be number

// After - keep specific types
const config = {
    host: "localhost",
    port: 3000
} satisfies Record<string, string | number>;

config.host.toUpperCase(); // OK - TypeScript knows it's string
config.port.toFixed(2); // OK - TypeScript knows it's number
Enter fullscreen mode Exit fullscreen mode

TypeScript 5.0 (2023) - Decorators

Standard decorators:

// Class decorator
function sealed(constructor: Function) {
    Object.seal(constructor);
    Object.seal(constructor.prototype);
}

@sealed
class MyClass {
    // ...
}

// Method decorator
function log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    const original = descriptor.value;
    descriptor.value = function(...args: any[]) {
        console.log(`Calling ${propertyKey} with`, args);
        return original.apply(this, args);
    };
}

class Calculator {
    @log
    add(a: number, b: number) {
        return a + b;
    }
}
Enter fullscreen mode Exit fullscreen mode

const type parameters:

// Infer literal types
function makeArray<const T>(arr: T[]): T[] {
    return arr;
}

const arr1 = makeArray([1, 2, 3]); // number[]
const arr2 = makeArray([1, 2, 3] as const); // readonly [1, 2, 3]
Enter fullscreen mode Exit fullscreen mode

TypeScript 5.2 (2023) - using Declarations

// Explicit resource management
{
    using file = openFile("data.txt");
    // file is automatically closed at end of scope
}

// Async disposal
{
    await using connection = await connectToDatabase();
    // connection is automatically closed
}
Enter fullscreen mode Exit fullscreen mode

TypeScript 5.4 (2024) - NoInfer

// Prevent type inference in specific places
function createStore<T>(
    initial: T,
    updater: (current: NoInfer<T>) => T
) {
    // ...
}

// Forces updater parameter to match initial type exactly
Enter fullscreen mode Exit fullscreen mode

TypeScript 5.5 (2024) - Inferred Type Predicates

// Before - manual type predicate
function isString(value: unknown): value is string {
    return typeof value === "string";
}

// After - inferred
function isString(value: unknown) {
    return typeof value === "string";
}
// TypeScript infers the type predicate automatically
Enter fullscreen mode Exit fullscreen mode

Server-Side JavaScript: Node.js & Beyond

Created: 2009 by Ryan Dahl

Problem it solved:

  • 🎯 JavaScript was only for browsers (couldn't build servers)
  • 🎯 Apache/PHP blocked on I/O (one thread per request = slow)
  • 🎯 Couldn't use same language for frontend and backend
  • 🎯 Needed non-blocking I/O for real-time apps (chat, streaming)
  • 🎯 C10K problem (handling 10,000 concurrent connections)

The breakthrough:

  • V8 engine - Fast JavaScript execution (from Chrome)
  • Event loop - Non-blocking I/O (handle thousands of connections)
  • Single language - JavaScript everywhere (frontend + backend)
  • npm - Largest package ecosystem ever created
  • Async by default - Perfect for I/O-heavy applications
// Before Node.js - PHP (blocking)
<?php
// Each request blocks a thread
$data = file_get_contents('file.txt'); // Blocks!
$result = mysql_query("SELECT * FROM users"); // Blocks!
echo $data;
?>
// Apache: 1 thread per request = max ~1000 concurrent users

// With Node.js - Non-blocking
const fs = require('fs');
const mysql = require('mysql');

// Handles thousands of concurrent requests
fs.readFile('file.txt', (err, data) => {
    // Doesn't block! Event loop continues
    console.log(data);
});

mysql.query("SELECT * FROM users", (err, results) => {
    // Doesn't block! Event loop continues
    console.log(results);
});
// Node.js: Event loop = handle 10,000+ concurrent users
Enter fullscreen mode Exit fullscreen mode

Node.js Evolution

Node.js 0.x (2009-2015):

// Callback-based APIs
const fs = require('fs');

fs.readFile('file.txt', 'utf8', function(err, data) {
    if (err) throw err;
    console.log(data);
});

// Event emitters
const EventEmitter = require('events');

class MyEmitter extends EventEmitter {}
const emitter = new MyEmitter();

emitter.on('event', () => {
    console.log('Event fired');
});

emitter.emit('event');
Enter fullscreen mode Exit fullscreen mode

Node.js 4.x (2015) - ES6 Support:

// Arrow functions, classes, etc.
const fs = require('fs');

class FileReader {
    read(path) {
        return new Promise((resolve, reject) => {
            fs.readFile(path, 'utf8', (err, data) => {
                if (err) reject(err);
                else resolve(data);
            });
        });
    }
}
Enter fullscreen mode Exit fullscreen mode

Node.js 8.x (2017) - async/await:

const fs = require('fs').promises;

async function readFile(path) {
    try {
        const data = await fs.readFile(path, 'utf8');
        return data;
    } catch (err) {
        console.error(err);
    }
}
Enter fullscreen mode Exit fullscreen mode

Node.js 12.x (2019) - ES Modules:

// package.json
{
    "type": "module"
}

// Now can use ES modules
import fs from 'fs/promises';

const data = await fs.readFile('file.txt', 'utf8');
Enter fullscreen mode Exit fullscreen mode

Node.js 14.x (2020) - Top-level await:

// No need for async wrapper
import { readFile } from 'fs/promises';

const data = await readFile('config.json', 'utf8');
const config = JSON.parse(data);

export default config;
Enter fullscreen mode Exit fullscreen mode

Node.js 16.x (2021) - Timers Promises:

import { setTimeout } from 'timers/promises';

await setTimeout(1000);
console.log('1 second passed');

// AbortController support
const controller = new AbortController();
setTimeout(1000, 'result', { signal: controller.signal });
controller.abort();
Enter fullscreen mode Exit fullscreen mode

Node.js 18.x (2022) - Fetch API:

// Native fetch (no need for node-fetch)
const response = await fetch('https://api.example.com/data');
const data = await response.json();

// Test runner
import { test } from 'node:test';
import assert from 'node:assert';

test('addition', () => {
    assert.strictEqual(1 + 1, 2);
});
Enter fullscreen mode Exit fullscreen mode

Node.js 20.x (2023) - Stable Features:

// Permission model
node --experimental-permission --allow-fs-read=/path script.js

// Single executable applications
node --experimental-sea-config sea-config.json
Enter fullscreen mode Exit fullscreen mode

Alternative Runtimes

Deno (2020):

// Secure by default, TypeScript native
// No package.json, no node_modules

// Permissions required
deno run --allow-net --allow-read script.ts

// URL imports
import { serve } from "https://deno.land/std@0.140.0/http/server.ts";

serve((req) => new Response("Hello World"));

// Top-level await
const data = await fetch("https://api.example.com/data");
const json = await data.json();
console.log(json);

// Built-in tools
deno fmt // Format code
deno lint // Lint code
deno test // Run tests
deno bundle // Bundle code
Enter fullscreen mode Exit fullscreen mode

Bun (2022):

// Fast all-in-one runtime
// Built-in bundler, transpiler, package manager

// Fast startup
console.log("Hello from Bun!");

// Native TypeScript
import type { User } from "./types";

// Built-in SQLite
import { Database } from "bun:sqlite";
const db = new Database("mydb.sqlite");

// Fast package manager
bun install // Much faster than npm

// Built-in test runner
import { expect, test } from "bun:test";

test("addition", () => {
    expect(1 + 1).toBe(2);
});

// Web APIs
const server = Bun.serve({
    port: 3000,
    fetch(req) {
        return new Response("Hello from Bun!");
    }
});
Enter fullscreen mode Exit fullscreen mode

Frameworks & Libraries: Rise and Fall

The jQuery Era (2006-2015)

jQuery - Dominated web development

Peak usage: 2010-2015 (used on 70%+ of websites)

Problem it solved:

  • 🎯 Browser incompatibility nightmare (IE6, IE7, IE8, Firefox, Chrome all different)
  • 🎯 DOM manipulation was verbose and inconsistent
  • 🎯 AJAX was different in every browser
  • 🎯 Event handling was inconsistent
  • 🎯 CSS selectors weren't supported in old browsers
// Before jQuery - Browser hell
// Different code for different browsers
if (document.getElementById) {
    var elem = document.getElementById('myDiv');
} else if (document.all) {
    var elem = document.all['myDiv'];
}

// AJAX - Different in IE vs others
var xhr;
if (window.XMLHttpRequest) {
    xhr = new XMLHttpRequest();
} else {
    xhr = new ActiveXObject("Microsoft.XMLHTTP");
}

// Event handling - Different everywhere
if (elem.addEventListener) {
    elem.addEventListener('click', handler, false);
} else if (elem.attachEvent) {
    elem.attachEvent('onclick', handler);
}

// With jQuery - Works everywhere!
$('#myDiv').click(function() {
    $.ajax({
        url: '/api/data',
        success: function(data) {
            $('#result').html(data);
        }
    });
});
Enter fullscreen mode Exit fullscreen mode

Why it succeeded:

  • Cross-browser compatibility (one API for all browsers)
  • Simple API ($() for everything)
  • Plugin ecosystem (10,000+ plugins)
  • "Write less, do more"

Why it declined:

  • Modern browsers standardized (querySelector, fetch, classList)
  • Native APIs improved (no more IE6-8)
  • Vanilla JS became easier
  • SPAs needed more structure (React, Vue, Angular)
  • Performance concerns (manipulating DOM directly)

jQuery plugins ecosystem:

// Thousands of plugins
$('#slider').slick(); // Carousel
$('#datepicker').datepicker(); // Date picker
$('#modal').modal(); // Modals
$('.tooltip').tooltip(); // Tooltips

// jQuery UI
$('#accordion').accordion();
$('#tabs').tabs();
$('#draggable').draggable();
Enter fullscreen mode Exit fullscreen mode

What replaced it:

  • Native DOM APIs (querySelector, fetch)
  • Modern frameworks (React, Vue, Angular)
  • Utility libraries (Lodash)

When it became legacy: ~2017-2018

What replaced it:

  • Native DOM APIs (querySelector, fetch, classList)
  • Modern frameworks (React, Vue, Angular)
  • Utility libraries (Lodash for utilities only)

Pros of jQuery (why it dominated):

  • ✅ Cross-browser compatibility (IE6-11)
  • ✅ Simple, intuitive API
  • ✅ Huge plugin ecosystem (10,000+ plugins)
  • ✅ Easy to learn
  • ✅ Chainable methods
  • ✅ AJAX made simple
  • ✅ Animation built-in

Cons of jQuery (why it died):

  • ❌ Performance overhead (entire library loaded)
  • ❌ DOM manipulation slow for complex UIs
  • ❌ No component model
  • ❌ No state management
  • ❌ Imperative (not declarative)
  • ❌ Large bundle size (30KB+ minified)
  • ❌ Modern browsers don't need it
  • ❌ Not suitable for SPAs

Migration path:

// jQuery → Vanilla JS
$('#id')  document.getElementById('id')
$('.class')  document.querySelectorAll('.class')
$.ajax()  fetch()
$(el).addClass()  el.classList.add()
$(el).on('click', fn)  el.addEventListener('click', fn)

// jQuery → React
$('#app').html(content)  ReactDOM.render(<App />, root)
Enter fullscreen mode Exit fullscreen mode

Current status (2025):

  • Still used in legacy projects (~30% of websites)
  • WordPress still includes it (but optional)
  • Usage declining rapidly (-10% per year)
  • Not recommended for new projects
  • Maintenance mode only

The MV* Framework Wars (2010-2015)

Backbone.js (2010)

Status: Dead

When it became legacy: ~2015-2016

Peak usage: 2012-2014

What replaced it: React, Vue, Angular

Pros of Backbone:

  • ✅ Lightweight (7KB)
  • ✅ Flexible, unopinionated
  • ✅ RESTful by default
  • ✅ Simple event system
  • ✅ Works with any template engine
  • ✅ Good for small apps

Cons of Backbone:

  • ❌ Too minimal (needed many plugins)
  • ❌ Boilerplate-heavy
  • ❌ Manual DOM updates
  • ❌ No two-way binding
  • ❌ No component model
  • ❌ Required Underscore.js dependency
  • ❌ Memory leaks common (zombie views)
  • ❌ Routing limited

Migration path:

  • Backbone → React (most common)
  • Backbone → Vue (easier migration)
  • Some stayed on Backbone (legacy apps)

Legacy: Influenced modern frameworks (models, collections, events)


Knockout.js (2010)

Status: Legacy/Maintenance mode

When it became legacy: ~2016-2017

Peak usage: 2011-2014

What replaced it:

  • Vue.js (similar reactivity, better DX)
  • React
  • Angular

Pros of Knockout:

  • ✅ MVVM pattern (clean separation)
  • ✅ Automatic UI updates (observables)
  • ✅ Declarative bindings
  • ✅ No dependencies
  • ✅ Works with any web framework
  • ✅ Good documentation
  • ✅ Two-way binding

Cons of Knockout:

  • ❌ Observable syntax verbose (ko.observable())
  • ❌ Must call observables as functions
  • ❌ No component model (initially)
  • ❌ No virtual DOM (slower updates)
  • ❌ Limited ecosystem
  • ❌ MVVM pattern fell out of favor
  • ❌ Computed observables complex
  • ❌ Memory management issues

Migration path:

  • Knockout → Vue (easiest, similar concepts)
  • Knockout → React (more common)
  • Some enterprise apps still use it

Current status: Still maintained but not recommended for new projects


AngularJS 1.x (2010-2012)

Status: Dead (officially ended support January 1, 2022)

When it became legacy: ~2016 (when Angular 2 released)

Peak usage: 2013-2016 (used by Google, YouTube, Netflix)

Problem it solved:

  • 🎯 jQuery spaghetti code (mixing DOM manipulation with logic)
  • 🎯 No structure for large applications
  • 🎯 Manual DOM updates when data changed
  • 🎯 No two-way data binding (had to sync manually)
  • 🎯 Needed complete framework (routing, HTTP, forms, etc.)

The breakthrough:

  • Two-way data binding - Change model, view updates automatically
  • Dependency injection - Testable, modular code
  • Directives - Extend HTML with custom elements
  • Complete framework - Everything included
// Before AngularJS - jQuery spaghetti
var users = [];

$('#add-user').click(function() {
    var name = $('#user-name').val();
    users.push({ name: name });

    // Manually update DOM
    $('#user-list').append('<li>' + name + '</li>');
    $('#user-count').text(users.length);
    $('#user-name').val('');
});

// With AngularJS - Two-way binding
angular.module('app', [])
    .controller('MainCtrl', function($scope) {
        $scope.users = [];

        $scope.addUser = function() {
            $scope.users.push({ name: $scope.userName });
            $scope.userName = '';
            // View updates automatically!
        };
    });
Enter fullscreen mode Exit fullscreen mode
<!-- AngularJS template - Declarative -->
<div ng-controller="MainCtrl">
    <input ng-model="userName">
    <button ng-click="addUser()">Add</button>
    <ul>
        <li ng-repeat="user in users">{{user.name}}</li>
    </ul>
    <p>Total: {{users.length}}</p>
</div>
Enter fullscreen mode Exit fullscreen mode
// AngularJS 1.x - Full example
angular.module('app', [])
    .controller('MainCtrl', function($scope, $http) {
        $scope.users = [];

        $http.get('/api/users').then(function(response) {
            $scope.users = response.data;
        });

        $scope.addUser = function(user) {
            $scope.users.push(user);
        };
    });
Enter fullscreen mode Exit fullscreen mode

What replaced it:

  • Angular 2+ (complete rewrite, difficult migration)
  • React (most common migration)
  • Vue (easier migration)

Pros of AngularJS:

  • ✅ Two-way data binding (revolutionary at the time)
  • ✅ Dependency injection
  • ✅ Directives (reusable components)
  • ✅ Complete framework (routing, HTTP, forms)
  • ✅ Google backing
  • ✅ Large community
  • ✅ Good for enterprise apps
  • ✅ Testable (built-in testing support)

Cons of AngularJS:

  • ❌ Digest cycle performance issues (slow with >2000 watchers)
  • ❌ Two-way binding caused unpredictable updates
  • ❌ Steep learning curve (directives, $scope, digest)
  • ❌ $scope confusion (inheritance issues)
  • ❌ Dirty checking inefficient
  • ❌ No component model (initially)
  • ❌ Directive API complex
  • ❌ Version 2 was complete rewrite (no upgrade path)
  • ❌ Too much "magic"
  • ❌ Large bundle size

Migration path:

  • AngularJS → Angular 2+ (very difficult, complete rewrite)
  • AngularJS → React (most common, easier)
  • AngularJS → Vue (easiest migration)
  • Many apps stayed on AngularJS until 2022

Why the migration was painful:

  • Zero compatibility between AngularJS and Angular 2+
  • Different language (TypeScript required)
  • Different concepts (no $scope, no digest)
  • Had to rewrite entire app

Current status:

  • Dead (no security updates after 2022)
  • Legacy apps being migrated
  • Not recommended even for maintenance

Ember.js (2011)

Status: Alive but niche (declining)

When it started declining: ~2015-2016

Peak usage: 2013-2016

What's replacing it:

  • React (most migrations)
  • Vue
  • Modern frameworks

Pros of Ember:

  • ✅ Convention over configuration
  • ✅ Complete framework (batteries included)
  • ✅ Ember CLI (best CLI at the time)
  • ✅ Strong opinions (consistency)
  • ✅ Ember Data (powerful ORM)
  • ✅ Backward compatibility (smooth upgrades)
  • ✅ Great for large teams
  • ✅ Stable releases
  • ✅ Good documentation
  • ✅ Addons ecosystem

Cons of Ember:

  • ❌ Too opinionated (hard to deviate)
  • ❌ Steep learning curve
  • ❌ Large bundle size (200KB+)
  • ❌ Slower than React/Vue
  • ❌ Smaller ecosystem than React
  • ❌ Less flexible
  • ❌ Harder to hire developers
  • ❌ "The Ember Way" or nothing
  • ❌ Verbose syntax
  • ❌ Slower innovation

Current usage (2025):

  • Some large legacy apps (LinkedIn, Apple Music, Discourse)
  • Convention over configuration still valued
  • Stable but shrinking community
  • Hard to justify for new projects
  • Good for teams that value stability over innovation

Migration path:

  • Ember → React (most common)
  • Ember → Vue
  • Many apps staying on Ember (stable, works well)

Current status:

  • Still maintained and updated
  • Octane edition (2019) modernized it
  • But market share declining
  • Not recommended for new projects unless specific needs
// Ember.js - still used
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';

export default class TodoComponent extends Component {
    @tracked todos = [];

    @action
    addTodo(text) {
        this.todos = [...this.todos, { text, done: false }];
    }
}
Enter fullscreen mode Exit fullscreen mode

The Modern Framework Era (2013-Present)

React (2013-Present)

Status: Dominant

Problem it solved:

  • 🎯 jQuery/Backbone couldn't handle complex UIs (Facebook news feed)
  • 🎯 DOM manipulation was slow and hard to reason about
  • 🎯 Two-way data binding (Angular 1) caused performance issues
  • 🎯 Hard to know what changed when state updated
  • 🎯 Needed declarative UI (describe what, not how)

The breakthrough:

  • Virtual DOM - Diff and update only what changed
  • One-way data flow - Predictable state management
  • Components - Reusable, composable UI pieces
  • JSX - Write HTML in JavaScript (controversial but powerful)
// Before React - jQuery spaghetti
function updateUI(data) {
    $('#user-name').text(data.name);
    $('#user-email').text(data.email);
    if (data.premium) {
        $('#premium-badge').show();
    } else {
        $('#premium-badge').hide();
    }
    // Manually track what changed, update DOM
    // Gets messy fast!
}

// With React - Declarative
function UserProfile({ user }) {
    return (
        <div>
            <h1>{user.name}</h1>
            <p>{user.email}</p>
            {user.premium && <span className="badge">Premium</span>}
        </div>
    );
    // React figures out what changed and updates DOM
}
Enter fullscreen mode Exit fullscreen mode

Evolution:

// 2013: Class components
class Welcome extends React.Component {
    constructor(props) {
        super(props);
        this.state = { count: 0 };
    }

    render() {
        return <h1>Count: {this.state.count}</h1>;
    }
}

// 2015: ES6 classes
class Welcome extends React.Component {
    state = { count: 0 };

    increment = () => {
        this.setState({ count: this.state.count + 1 });
    };

    render() {
        return (
            <div>
                <h1>Count: {this.state.count}</h1>
                <button onClick={this.increment}>+</button>
            </div>
        );
    }
}

// 2019: Hooks (game changer)
function Welcome() {
    const [count, setCount] = useState(0);

    useEffect(() => {
        document.title = `Count: ${count}`;
    }, [count]);

    return (
        <div>
            <h1>Count: {count}</h1>
            <button onClick={() => setCount(count + 1)}>+</button>
        </div>
    );
}

// 2020: Custom hooks
function useCounter(initial = 0) {
    const [count, setCount] = useState(initial);

    const increment = () => setCount(c => c + 1);
    const decrement = () => setCount(c => c - 1);
    const reset = () => setCount(initial);

    return { count, increment, decrement, reset };
}

function Counter() {
    const { count, increment, decrement, reset } = useCounter(0);

    return (
        <div>
            <h1>{count}</h1>
            <button onClick={increment}>+</button>
            <button onClick={decrement}>-</button>
            <button onClick={reset}>Reset</button>
        </div>
    );
}

// 2024: Server Components (Next.js 13+)
// app/page.tsx
async function Page() {
    const data = await fetch('https://api.example.com/data');
    const json = await data.json();

    return <div>{json.title}</div>;
}
// Runs on server, no JavaScript sent to client
Enter fullscreen mode Exit fullscreen mode

React Ecosystem:

// State management evolution

// 2015: Redux
import { createStore } from 'redux';

const reducer = (state = { count: 0 }, action) => {
    switch (action.type) {
        case 'INCREMENT':
            return { count: state.count + 1 };
        default:
            return state;
    }
};

const store = createStore(reducer);

// 2018: Redux Toolkit (simplified)
import { createSlice, configureStore } from '@reduxjs/toolkit';

const counterSlice = createSlice({
    name: 'counter',
    initialState: { count: 0 },
    reducers: {
        increment: state => { state.count += 1; }
    }
});

const store = configureStore({
    reducer: { counter: counterSlice.reducer }
});

// 2019: Context + Hooks (built-in)
const CountContext = React.createContext();

function CountProvider({ children }) {
    const [count, setCount] = useState(0);
    return (
        <CountContext.Provider value={{ count, setCount }}>
            {children}
        </CountContext.Provider>
    );
}

function useCount() {
    return useContext(CountContext);
}

// 2020: Zustand (simple)
import create from 'zustand';

const useStore = create(set => ({
    count: 0,
    increment: () => set(state => ({ count: state.count + 1 }))
}));

function Counter() {
    const { count, increment } = useStore();
    return <button onClick={increment}>{count}</button>;
}

// 2021: Jotai (atomic)
import { atom, useAtom } from 'jotai';

const countAtom = atom(0);

function Counter() {
    const [count, setCount] = useAtom(countAtom);
    return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}

// 2022: TanStack Query (server state)
import { useQuery } from '@tanstack/react-query';

function Users() {
    const { data, isLoading } = useQuery({
        queryKey: ['users'],
        queryFn: () => fetch('/api/users').then(r => r.json())
    });

    if (isLoading) return <div>Loading...</div>;
    return <div>{data.map(user => <div key={user.id}>{user.name}</div>)}</div>;
}
Enter fullscreen mode Exit fullscreen mode

Vue.js (2014-Present)

Status: Very popular, especially in Asia

Problem it solved:

  • 🎯 React's JSX was controversial (mixing HTML in JavaScript)
  • 🎯 Angular 2 was too complex (TypeScript required, steep learning curve)
  • 🎯 Needed something easier than Angular, more approachable than React
  • 🎯 Wanted templates (like Angular) but simpler
  • 🎯 Progressive framework (use as little or as much as needed)

The breakthrough:

  • Approachable - Easiest learning curve of the big 3
  • Templates - HTML-based (familiar to designers)
  • Reactive - Like React but simpler
  • Progressive - Can use just for one component or entire app
  • Single File Components - HTML, CSS, JS in one file
// React - JSX (controversial)
function Counter() {
    const [count, setCount] = useState(0);
    return <button onClick={() => setCount(count + 1)}>{count}</button>;
}

// Angular - Complex
@Component({
    selector: 'app-counter',
    template: '<button (click)="increment()">{{count}}</button>'
})
export class CounterComponent {
    count = 0;
    increment() { this.count++; }
}

// Vue - Simple, familiar templates
<template>
    <button @click="count++">{{ count }}</button>
</template>

<script>
export default {
    data() {
        return { count: 0 };
    }
};
</script>
Enter fullscreen mode Exit fullscreen mode

Evolution:

// 2014: Vue 1.x - Options API
new Vue({
    el: '#app',
    data: {
        count: 0
    },
    methods: {
        increment() {
            this.count++;
        }
    }
});

// 2016: Vue 2.x - Improved
Vue.component('counter', {
    data() {
        return { count: 0 };
    },
    template: `
        <div>
            <h1>{{ count }}</h1>
            <button @click="increment">+</button>
        </div>
    `,
    methods: {
        increment() {
            this.count++;
        }
    }
});

// 2020: Vue 3.x - Composition API
import { ref, computed } from 'vue';

export default {
    setup() {
        const count = ref(0);
        const doubled = computed(() => count.value * 2);

        const increment = () => {
            count.value++;
        };

        return { count, doubled, increment };
    }
};

// 2023: Vue 3.3 - Script setup (cleaner)
<script setup>
import { ref, computed } from 'vue';

const count = ref(0);
const doubled = computed(() => count.value * 2);

const increment = () => {
    count.value++;
};
</script>

<template>
    <div>
        <h1>{{ count }}</h1>
        <p>Doubled: {{ doubled }}</p>
        <button @click="increment">+</button>
    </div>
</template>
Enter fullscreen mode Exit fullscreen mode

Vue Ecosystem:

  • Vuex (2015) → Pinia (2021) - State management
  • Vue Router - Official routing
  • Nuxt.js - Full-stack framework (like Next.js for React)

Angular 2+ (2016-Present)

Status: Popular in enterprise

Complete rewrite from AngularJS:

// Angular 2+ (2016)
import { Component } from '@angular/core';

@Component({
    selector: 'app-counter',
    template: `
        <div>
            <h1>{{ count }}</h1>
            <button (click)="increment()">+</button>
        </div>
    `
})
export class CounterComponent {
    count = 0;

    increment() {
        this.count++;
    }
}

// Angular 14+ (2022) - Standalone components
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';

@Component({
    selector: 'app-counter',
    standalone: true,
    imports: [CommonModule],
    template: `
        <div>
            <h1>{{ count }}</h1>
            <button (click)="increment()">+</button>
        </div>
    `
})
export class CounterComponent {
    count = 0;

    increment() {
        this.count++;
    }
}

// Angular 16+ (2023) - Signals
import { Component, signal } from '@angular/core';

@Component({
    selector: 'app-counter',
    standalone: true,
    template: `
        <div>
            <h1>{{ count() }}</h1>
            <button (click)="increment()">+</button>
        </div>
    `
})
export class CounterComponent {
    count = signal(0);

    increment() {
        this.count.update(c => c + 1);
    }
}
Enter fullscreen mode Exit fullscreen mode

Pros of Angular 2+:

  • ✅ TypeScript from the start (type safety)
  • ✅ Complete framework (routing, HTTP, forms, testing)
  • ✅ Dependency injection (enterprise-grade)
  • ✅ RxJS integration (powerful async)
  • ✅ Angular CLI (excellent tooling)
  • ✅ Opinionated (consistency in large teams)
  • ✅ Google backing (long-term support)
  • ✅ Great for enterprise
  • ✅ Strong conventions
  • ✅ Ahead-of-Time compilation
  • ✅ Built-in form validation
  • ✅ Internationalization built-in

Cons of Angular 2+:

  • ❌ Steep learning curve (RxJS, TypeScript, DI)
  • ❌ Verbose (lots of boilerplate)
  • ❌ Large bundle size (100KB+ minimum)
  • ❌ Slower than React/Vue
  • ❌ Breaking changes between versions (initially)
  • ❌ RxJS required (complex for beginners)
  • ❌ Decorator-heavy (lots of magic)
  • ❌ Zone.js overhead
  • ❌ Less flexible than React
  • ❌ Smaller ecosystem than React

Why Angular survived:

  • TypeScript from the start (ahead of its time)
  • Enterprise adoption (banks, insurance, government)
  • Complete framework (no decision fatigue)
  • Google backing (trust and stability)
  • Strong CLI and tooling
  • Good for large teams
  • Backward compatibility improved

When to use Angular (2025):

  • ✅ Enterprise applications
  • ✅ Large teams (need consistency)
  • ✅ TypeScript-first projects
  • ✅ Need complete framework
  • ✅ Long-term projects (stability)

When NOT to use Angular:

  • ❌ Small projects (too heavy)
  • ❌ Need fast learning curve
  • ❌ Performance critical (use React/Svelte)
  • ❌ Small bundle size needed
  • ❌ Startup/fast iteration

Current status (2025):

  • Alive and well (15% market share)
  • Angular 17+ with Signals (modern reactivity)
  • Standalone components (less boilerplate)
  • Still best for enterprise
  • Not growing but stable

New Generation Frameworks (2019-Present)

Svelte (2016, popular 2019+)

Status: Growing rapidly

Philosophy: Compile-time framework (no virtual DOM)

<!-- Before: React -->
import { useState } from 'react';

function Counter() {
    const [count, setCount] = useState(0);

    return (
        <div>
            <h1>{count}</h1>
            <button onClick={() => setCount(count + 1)}>+</button>
        </div>
    );
}

<!-- After: Svelte -->
<script>
    let count = 0;

    function increment() {
        count += 1; // Just mutate!
    }
</script>

<div>
    <h1>{count}</h1>
    <button on:click={increment}>+</button>
</div>

<!-- Reactive declarations -->
<script>
    let count = 0;
    $: doubled = count * 2; // Automatically updates
    $: if (count > 10) {
        alert('Count is high!');
    }
</script>

<!-- SvelteKit (2021) - Full-stack framework -->
<script>
    export let data; // From load function
</script>

<h1>{data.title}</h1>
Enter fullscreen mode Exit fullscreen mode

Pros of Svelte:

  • ✅ Less boilerplate (cleanest syntax)
  • ✅ No virtual DOM (compiles to vanilla JS)
  • ✅ Smallest bundle sizes (10-20KB typical)
  • ✅ Easier to learn (feels like HTML/CSS/JS)
  • ✅ Great developer experience
  • ✅ Reactive by default ($: syntax)
  • ✅ Built-in animations/transitions
  • ✅ Scoped CSS by default
  • ✅ Fast performance
  • ✅ No runtime overhead
  • ✅ True reactivity (not virtual DOM diffing)

Cons of Svelte:

  • ❌ Smaller ecosystem than React
  • ❌ Fewer jobs (but growing)
  • ❌ Less mature (newer framework)
  • ❌ Fewer third-party components
  • ❌ Compiler adds build step
  • ❌ Some edge cases with reactivity
  • ❌ Smaller community
  • ❌ Less Stack Overflow answers
  • ❌ Corporate backing unclear (Rich Harris at Vercel)

When to use Svelte (2025):

  • ✅ New projects (greenfield)
  • ✅ Performance critical apps
  • ✅ Small bundle size needed
  • ✅ Developer experience priority
  • ✅ Content-heavy sites
  • ✅ Interactive visualizations

Current status (2025):

  • Growing rapidly (10% market share)
  • SvelteKit stable and mature
  • Svelte 5 with Runes (new reactivity)
  • Recommended for new projects
  • Great for indie developers

Solid.js (2021)

Status: Rising star (niche but growing)

Philosophy: Fine-grained reactivity (like Svelte but JSX)

Peak performance: Fastest framework in benchmarks

// Solid.js
import { createSignal, createEffect } from 'solid-js';

function Counter() {
    const [count, setCount] = createSignal(0);

    createEffect(() => {
        console.log('Count is:', count());
    });

    return (
        <div>
            <h1>{count()}</h1>
            <button onClick={() => setCount(count() + 1)}>+</button>
        </div>
    );
}

// No virtual DOM, but JSX
// Extremely fast
// React-like API but different mental model
Enter fullscreen mode Exit fullscreen mode

Pros of Solid.js:

  • ✅ Fastest framework (beats all in benchmarks)
  • ✅ Fine-grained reactivity (no virtual DOM)
  • ✅ JSX (familiar to React developers)
  • ✅ Small bundle size (7KB)
  • ✅ No re-renders (only updates what changed)
  • ✅ React-like API (easier migration)
  • ✅ TypeScript support
  • ✅ Great performance
  • ✅ Signals-based (modern reactivity)

Cons of Solid.js:

  • ❌ Very small ecosystem
  • ❌ Few jobs
  • ❌ Different mental model (not React)
  • ❌ Signals can be confusing
  • ❌ Less mature
  • ❌ Fewer components/libraries
  • ❌ Small community
  • ❌ JSX looks like React but behaves differently
  • ❌ Learning curve (reactivity model)

When to use Solid.js (2025):

  • ✅ Performance is critical
  • ✅ Coming from React (familiar JSX)
  • ✅ Want modern reactivity
  • ✅ Small bundle size needed
  • ✅ Experimental/side projects

Current status (2025):

  • Niche but growing
  • SolidStart (meta-framework) maturing
  • Used by some performance-critical apps
  • Not recommended for most production apps yet
  • Great for learning modern reactivity

Qwik (2023)

Status: Experimental but promising

Philosophy: Resumability (instant loading, no hydration)

Creator: Miško Hevery (creator of AngularJS)

import { component$, useSignal } from '@builder.io/qwik';

export default component$(() => {
    const count = useSignal(0);

    return (
        <div>
            <h1>{count.value}</h1>
            <button onClick$={() => count.value++}>+</button>
        </div>
    );
});

// Loads only what's needed
// No hydration, just resumability
// Instant interactive
Enter fullscreen mode Exit fullscreen mode

Pros of Qwik:

  • ✅ Instant loading (resumability)
  • ✅ No hydration (faster than SSR)
  • ✅ Lazy loads everything automatically
  • ✅ Best Time to Interactive (TTI)
  • ✅ JSX (familiar)
  • ✅ Signals-based reactivity
  • ✅ Great for content-heavy sites
  • ✅ Innovative approach

Cons of Qwik:

  • ❌ Very new (experimental)
  • ❌ Tiny ecosystem
  • ❌ Almost no jobs
  • ❌ Different mental model ($ syntax)
  • ❌ Not proven at scale
  • ❌ Small community
  • ❌ Optimizer can be confusing
  • ❌ Build complexity
  • ❌ Not recommended for production yet

When to use Qwik (2025):

  • ✅ Experimental projects
  • ✅ Content-heavy sites (blogs, docs)
  • ✅ Performance is critical
  • ✅ Want to try cutting-edge tech
  • ❌ NOT for production apps yet

Current status (2025):

  • Experimental
  • QwikCity (meta-framework) available
  • Interesting concept but unproven
  • Wait for more maturity

Meta-Frameworks (Full-Stack)

Next.js (React) - 2016-Present

Status: Industry standard for React

Evolution:

// 2016: Next.js 1.0 - Simple SSR
// pages/index.js
export default function Home() {
    return <h1>Hello Next.js</h1>;
}

// 2018: Next.js 7 - Static export
export async function getStaticProps() {
    const data = await fetch('https://api.example.com/data');
    return { props: { data } };
}

// 2020: Next.js 9.3 - SSG + ISR
export async function getStaticProps() {
    return {
        props: { data },
        revalidate: 60 // Regenerate every 60 seconds
    };
}

// 2022: Next.js 13 - App Router (Server Components)
// app/page.tsx
async function Page() {
    const data = await fetch('https://api.example.com/data');
    return <div>{data.title}</div>;
}

// app/layout.tsx
export default function RootLayout({ children }) {
    return (
        <html>
            <body>{children}</body>
        </html>
    );
}

// 2023: Next.js 13.4 - Stable App Router
// Server Actions
async function createUser(formData: FormData) {
    'use server';

    const name = formData.get('name');
    await db.users.create({ name });
}

// Client component
'use client';
export default function Form() {
    return (
        <form action={createUser}>
            <input name="name" />
            <button>Submit</button>
        </form>
    );
}
Enter fullscreen mode Exit fullscreen mode

Nuxt.js (Vue) - 2016-Present

Status: Standard for Vue

// Nuxt 2 (2018)
export default {
    async asyncData({ $axios }) {
        const data = await $axios.$get('/api/data');
        return { data };
    }
};

// Nuxt 3 (2022) - Composition API
<script setup>
const { data } = await useFetch('/api/data');
</script>

<template>
    <div>{{ data }}</div>
</template>
Enter fullscreen mode Exit fullscreen mode

Remix (React) - 2021

Status: Growing, acquired by Shopify

// Loader (server-side)
export async function loader({ params }) {
    const user = await getUser(params.id);
    return json({ user });
}

// Action (server-side)
export async function action({ request }) {
    const formData = await request.formData();
    await createUser(formData);
    return redirect('/users');
}

// Component
export default function User() {
    const { user } = useLoaderData();

    return (
        <div>
            <h1>{user.name}</h1>
            <Form method="post">
                <input name="name" />
                <button>Update</button>
            </Form>
        </div>
    );
}
Enter fullscreen mode Exit fullscreen mode

Astro (2021)

Status: Popular for content sites

Philosophy: Ship zero JavaScript by default

---
// Server-side code
const posts = await fetch('https://api.example.com/posts').then(r => r.json());
---

<html>
    <body>
        <h1>Blog Posts</h1>
        {posts.map(post => (
            <article>
                <h2>{post.title}</h2>
                <p>{post.excerpt}</p>
            </article>
        ))}
    </body>
</html>

<!-- Can use any framework -->
---
import ReactCounter from './ReactCounter.jsx';
import VueCounter from './VueCounter.vue';
import SvelteCounter from './SvelteCounter.svelte';
---

<ReactCounter client:load />
<VueCounter client:visible />
<SvelteCounter client:idle />
Enter fullscreen mode Exit fullscreen mode

Utility Libraries

Lodash (2012-Present)

Status: Still widely used

// Before Lodash
var users = [
    { name: 'John', age: 30 },
    { name: 'Jane', age: 25 }
];

var names = users.map(function(u) { return u.name; });
var sorted = users.sort(function(a, b) { return a.age - b.age; });

// With Lodash
import _ from 'lodash';

const names = _.map(users, 'name');
const sorted = _.sortBy(users, 'age');
const grouped = _.groupBy(users, 'age');
const debounced = _.debounce(handleInput, 300);

// Deep clone
const clone = _.cloneDeep(obj);

// Get nested property safely
const value = _.get(obj, 'a.b.c.d', 'default');
Enter fullscreen mode Exit fullscreen mode

Why still used:

  • Comprehensive utilities
  • Battle-tested
  • Tree-shakeable (lodash-es)
  • Some functions still not in native JS

Alternatives:

  • Native JS (for many use cases)
  • Ramda (functional programming)
  • Just (smaller, modular)

Moment.js → Day.js / date-fns

Moment.js (2011-2020)

Status: Deprecated (maintenance mode)

// Moment.js
moment().format('YYYY-MM-DD');
moment().add(7, 'days');
moment().subtract(1, 'month');
Enter fullscreen mode Exit fullscreen mode

Why it died:

  • Mutable API (confusing)
  • Large bundle size
  • Not tree-shakeable

Replaced by:

// Day.js (2018) - Moment.js compatible API
import dayjs from 'dayjs';

dayjs().format('YYYY-MM-DD');
dayjs().add(7, 'day');

// date-fns (2016) - Functional, tree-shakeable
import { format, addDays, subMonths } from 'date-fns';

format(new Date(), 'yyyy-MM-dd');
addDays(new Date(), 7);

// Native Temporal (Stage 3, coming soon)
Temporal.Now.plainDateISO().toString(); // '2025-12-04'
Temporal.Now.plainDateISO().add({ days: 7 });
Enter fullscreen mode Exit fullscreen mode

Testing Ecosystem Evolution

Test Runners

QUnit (2008) - jQuery's test framework

Status: Dead for new projects

When it became legacy: ~2014-2015

What replaced it: Jasmine, Mocha, Jest

QUnit.test('addition', function(assert) {
    assert.equal(1 + 1, 2);
});
Enter fullscreen mode Exit fullscreen mode

Pros of QUnit:

  • ✅ Simple API
  • ✅ No dependencies
  • ✅ Browser-based testing
  • ✅ Good for jQuery projects

Cons of QUnit:

  • ❌ Limited features
  • ❌ No mocking built-in
  • ❌ Tied to jQuery ecosystem
  • ❌ No async/await support (initially)
  • ❌ Basic assertions only
  • ❌ No code coverage

Current status: Only for legacy jQuery projects


Jasmine (2010)

Status: Legacy/Declining

When it became legacy: ~2016-2017

Peak usage: 2011-2015

What replaced it: Jest, Mocha

describe('Calculator', function() {
    it('should add numbers', function() {
        expect(add(1, 2)).toBe(3);
    });
});
Enter fullscreen mode Exit fullscreen mode

Pros of Jasmine:

  • ✅ BDD syntax (describe/it)
  • ✅ No dependencies
  • ✅ Built-in assertions
  • ✅ Built-in spies/mocks
  • ✅ Browser and Node.js support
  • ✅ Good documentation

Cons of Jasmine:

  • ❌ Slow test execution
  • ❌ No snapshot testing
  • ❌ No code coverage built-in
  • ❌ No watch mode
  • ❌ Configuration complex
  • ❌ Async testing awkward
  • ❌ No parallel execution
  • ❌ Outdated compared to Jest

Migration path: Jasmine → Jest (easy, similar API)

Current status: Still used in Angular projects (default), but Jest preferred


Mocha (2011) + Chai

Status: Still used but declining

When it started declining: ~2017-2018

Peak usage: 2013-2017

What's replacing it: Jest, Vitest

const { expect } = require('chai');

describe('Calculator', () => {
    it('should add numbers', () => {
        expect(add(1, 2)).to.equal(3);
    });
});
Enter fullscreen mode Exit fullscreen mode

Pros of Mocha:

  • ✅ Flexible (bring your own assertion library)
  • ✅ Good async support
  • ✅ Extensible (many plugins)
  • ✅ Works in browser and Node.js
  • ✅ Mature and stable
  • ✅ Good documentation

Cons of Mocha:

  • ❌ Requires separate assertion library (Chai)
  • ❌ Requires separate mocking library (Sinon)
  • ❌ No snapshot testing
  • ❌ No code coverage built-in
  • ❌ Configuration needed
  • ❌ Slower than Jest
  • ❌ No watch mode built-in
  • ❌ More setup required

Migration path: Mocha → Jest or Vitest

Current status (2025):

  • Still maintained
  • Used in some legacy projects
  • Not recommended for new projects
  • Jest/Vitest better alternatives

Jest (2014, Facebook)

Status: Dominant for React

Problem it solved:

  • 🎯 Mocha/Jasmine required too much setup (test runner + assertion + mocking + coverage)
  • 🎯 No snapshot testing (hard to test React components)
  • 🎯 Slow test runs (no parallelization)
  • 🎯 Mocking was painful (Sinon.js separate library)
  • 🎯 No watch mode (had to rerun manually)

The breakthrough:

  • Zero config - Works out of the box
  • All-in-one - Test runner + assertions + mocking + coverage
  • Snapshot testing - Perfect for React components
  • Fast - Parallel execution, smart watch mode
  • Great DX - Interactive, clear error messages
// Before Jest - Mocha + Chai + Sinon + Istanbul
// package.json
"devDependencies": {
    "mocha": "^10.0.0",
    "chai": "^4.3.0",
    "sinon": "^15.0.0",
    "istanbul": "^0.4.5"
}

// test.js
const { expect } = require('chai');
const sinon = require('sinon');

describe('Calculator', () => {
    it('should add', () => {
        expect(add(1, 2)).to.equal(3);
    });

    it('should mock', () => {
        const mock = sinon.stub();
        mock.returns(42);
        expect(mock()).to.equal(42);
    });
});

// Run tests
mocha test.js
// Run coverage
istanbul cover _mocha

// With Jest - Zero config, all-in-one
// package.json
"devDependencies": {
    "jest": "^29.0.0"
}

// test.js
describe('Calculator', () => {
    test('adds numbers', () => {
        expect(add(1, 2)).toBe(3);
    });

    test('mocks functions', () => {
        const mock = jest.fn().mockReturnValue(42);
        expect(mock()).toBe(42);
    });
});

// Run tests with coverage
jest --coverage
Enter fullscreen mode Exit fullscreen mode
// Jest - batteries included
describe('Calculator', () => {
    test('adds numbers', () => {
        expect(add(1, 2)).toBe(3);
    });

    test('mocks functions', () => {
        const mock = jest.fn();
        mock('hello');
        expect(mock).toHaveBeenCalledWith('hello');
    });
});

// Snapshot testing
test('renders correctly', () => {
    const tree = renderer.create(<Component />).toJSON();
    expect(tree).toMatchSnapshot();
});

// Async testing
test('fetches data', async () => {
    const data = await fetchData();
    expect(data).toEqual({ name: 'John' });
});
Enter fullscreen mode Exit fullscreen mode

Pros of Jest:

  • ✅ Zero config (works out of the box)
  • ✅ Fast (parallel execution)
  • ✅ Built-in mocking (jest.fn(), jest.mock())
  • ✅ Snapshot testing (unique feature)
  • ✅ Great error messages
  • ✅ Watch mode (interactive)
  • ✅ Code coverage built-in
  • ✅ Works with React (React Testing Library)
  • ✅ Large community
  • ✅ Facebook backing
  • ✅ Great documentation
  • ✅ Timer mocking (jest.useFakeTimers())

Cons of Jest:

  • ❌ Slow startup (especially large projects)
  • ❌ ESM support problematic (until recently)
  • ❌ Heavy (large dependency)
  • ❌ Can be slow on large codebases
  • ❌ Snapshot tests can be overused
  • ❌ Mocking can be confusing
  • ❌ Global namespace pollution
  • ❌ Configuration can get complex

When to use Jest (2025):

  • ✅ React projects (standard)
  • ✅ Need snapshot testing
  • ✅ Want zero config
  • ✅ Large community support needed

What's replacing Jest: Vitest (for Vite projects)

Current status (2025):

  • Still dominant (40% market share)
  • Standard for React
  • Being challenged by Vitest
  • Still recommended for most projects

Vitest (2021)

Status: Rising fast (replacing Jest in Vite projects)

Created by: Vite team (Anthony Fu)

Problem it solved:

  • 🎯 Jest was slow with Vite (different module systems)
  • 🎯 Jest's ESM support was problematic
  • 🎯 Vite config and Jest config were separate (duplication)
  • 🎯 Jest startup was slow (especially on large projects)
  • 🎯 Wanted same speed as Vite dev server for tests

The breakthrough:

  • Uses Vite - Same config, same transforms, same plugins
  • Extremely fast - Instant startup, fast execution
  • Jest-compatible API - Easy migration from Jest
  • Native ESM - No transformation needed
  • HMR for tests - Tests rerun instantly on change
// Jest with Vite - Slow, separate config
// vite.config.js
export default {
    plugins: [react()],
    // Vite config
};

// jest.config.js
export default {
    transform: {
        '^.+\\.tsx?$': 'ts-jest',
    },
    // Duplicate config!
};

// Slow startup (5-10 seconds)
npm run test

// Vitest - Fast, shared config
// vite.config.js
export default {
    plugins: [react()],
    test: {
        // Test config in same file!
    }
};

// Instant startup (200ms)
npm run test
Enter fullscreen mode Exit fullscreen mode
import { describe, test, expect } from 'vitest';

describe('Calculator', () => {
    test('adds numbers', () => {
        expect(add(1, 2)).toBe(3);
    });
});
Enter fullscreen mode Exit fullscreen mode

Pros of Vitest:

  • ✅ Extremely fast (uses Vite)
  • ✅ Jest-compatible API (easy migration)
  • ✅ Native ESM support
  • ✅ Vite config reuse (no separate config)
  • ✅ Fast HMR for tests
  • ✅ TypeScript support out of the box
  • ✅ Component testing built-in
  • ✅ Watch mode (instant)
  • ✅ Snapshot testing
  • ✅ Code coverage (c8/istanbul)
  • ✅ Multi-threading
  • ✅ Better error messages than Jest

Cons of Vitest:

  • ❌ Newer (less mature than Jest)
  • ❌ Smaller community
  • ❌ Requires Vite (or Vite config)
  • ❌ Some Jest features missing
  • ❌ Less Stack Overflow answers
  • ❌ Fewer plugins

When to use Vitest (2025):

  • ✅ Using Vite (obvious choice)
  • ✅ New projects
  • ✅ Want speed
  • ✅ TypeScript projects
  • ✅ Vue/Svelte/Solid projects

Migration from Jest:

  • Very easy (API compatible)
  • Change imports
  • Update config
  • Most tests work as-is

Current status (2025):

  • Rapidly growing (20% market share)
  • Standard for Vite projects
  • Recommended for new projects
  • Will likely overtake Jest

Bun Test (2023)

Status: New, very fast, experimental

import { test, expect } from 'bun:test';

test('addition', () => {
    expect(1 + 1).toBe(2);
});
Enter fullscreen mode Exit fullscreen mode

Pros of Bun Test:

  • ✅ Extremely fast (fastest test runner)
  • ✅ Built into Bun (no install)
  • ✅ Jest-compatible API
  • ✅ TypeScript native
  • ✅ Snapshot testing
  • ✅ Watch mode
  • ✅ Mocking built-in
  • ✅ Zero config

Cons of Bun Test:

  • ❌ Very new (immature)
  • ❌ Requires Bun runtime
  • ❌ Small ecosystem
  • ❌ Missing some Jest features
  • ❌ Not production-ready yet
  • ❌ Limited documentation
  • ❌ Bugs and edge cases

When to use Bun Test (2025):

  • ✅ Already using Bun
  • ✅ Experimental projects
  • ✅ Want maximum speed
  • ❌ NOT for production yet

Current status (2025):

  • Experimental
  • Very promising
  • Wait for more maturity
  • Good for side projects

End-to-End Testing

Selenium (2004)

Status: Legacy but still used (especially enterprise)

When it became legacy: ~2017-2018

Peak usage: 2008-2016

What's replacing it: Cypress, Playwright

const { Builder, By } = require('selenium-webdriver');

const driver = await new Builder().forBrowser('chrome').build();
await driver.get('http://localhost:3000');
await driver.findElement(By.id('username')).sendKeys('john');
await driver.findElement(By.id('submit')).click();
Enter fullscreen mode Exit fullscreen mode

Pros of Selenium:

  • ✅ Multi-browser support (all browsers)
  • ✅ Multi-language (Java, Python, C#, JS)
  • ✅ Mature and stable
  • ✅ Large community
  • ✅ Works with mobile (Appium)
  • ✅ Grid support (parallel testing)
  • ✅ Industry standard (for years)

Cons of Selenium:

  • ❌ Slow and flaky
  • ❌ Complex setup
  • ❌ No automatic waiting (manual waits needed)
  • ❌ Poor error messages
  • ❌ Verbose API
  • ❌ No time-travel debugging
  • ❌ No automatic screenshots/videos
  • ❌ WebDriver protocol overhead
  • ❌ Hard to debug
  • ❌ Outdated compared to modern tools

When to use Selenium (2025):

  • ✅ Legacy projects already using it
  • ✅ Need Java/Python/C# (not JavaScript)
  • ✅ Enterprise with existing Selenium Grid
  • ❌ NOT recommended for new JavaScript projects

Current status (2025):

  • Still maintained
  • Used in enterprise (legacy)
  • Being replaced by Playwright/Cypress
  • Not recommended for new projects

Protractor (2013, Angular)

Status: Dead (officially deprecated 2021, end of life 2022)

When it became legacy: 2020-2021

What replaced it:

  • Cypress (recommended by Angular team)
  • Playwright
  • WebdriverIO

Pros of Protractor:

  • ✅ Built for Angular (automatic waiting)
  • ✅ Angular-specific locators
  • ✅ Good for Angular apps

Cons of Protractor:

  • ❌ Only good for Angular
  • ❌ Built on Selenium (slow)
  • ❌ Flaky tests
  • ❌ Complex configuration
  • ❌ Deprecated (no future)

Migration path:

  • Protractor → Cypress (Angular team recommendation)
  • Protractor → Playwright

Current status: Dead, migrate immediately


Cypress (2017)

Status: Very popular

describe('Login', () => {
    it('should login successfully', () => {
        cy.visit('/login');
        cy.get('#username').type('john');
        cy.get('#password').type('secret');
        cy.get('#submit').click();
        cy.url().should('include', '/dashboard');
        cy.contains('Welcome, John');
    });
});

// Time travel debugging
// Automatic waiting
// Great DX
// Screenshots/videos
Enter fullscreen mode Exit fullscreen mode

Pros of Cypress:

  • ✅ Amazing developer experience
  • ✅ Time-travel debugging (see every step)
  • ✅ Automatic waiting (no manual waits)
  • ✅ Real-time reloading
  • ✅ Screenshots/videos automatic
  • ✅ Great documentation
  • ✅ Easy to learn
  • ✅ Network stubbing built-in
  • ✅ Great error messages
  • ✅ Interactive test runner
  • ✅ Large community
  • ✅ Component testing

Cons of Cypress:

  • ❌ Runs in browser (architecture limitation)
  • ❌ No multi-tab support
  • ❌ No native events (synthetic events)
  • ❌ Slower than Playwright
  • ❌ Chrome/Edge/Firefox only (no Safari)
  • ❌ Can't test multiple domains easily
  • ❌ Parallel execution requires paid plan
  • ❌ Some limitations due to browser-based architecture

When to use Cypress (2025):

  • ✅ Developer experience priority
  • ✅ Need time-travel debugging
  • ✅ Single-page applications
  • ✅ Don't need Safari testing
  • ✅ Want easy learning curve

Current status (2025):

  • Still very popular (30% market share)
  • Great for SPAs
  • Being challenged by Playwright
  • Still recommended for many projects

Playwright (2020, Microsoft)

Status: Growing rapidly

import { test, expect } from '@playwright/test';

test('login', async ({ page }) => {
    await page.goto('/login');
    await page.fill('#username', 'john');
    await page.fill('#password', 'secret');
    await page.click('#submit');
    await expect(page).toHaveURL(/dashboard/);
    await expect(page.locator('text=Welcome, John')).toBeVisible();
});

// Multi-browser (Chrome, Firefox, Safari, Edge)
// Parallel execution
// Auto-wait
// Network interception
// Mobile emulation
Enter fullscreen mode Exit fullscreen mode

Pros of Playwright:

  • ✅ Multi-browser (Chrome, Firefox, Safari, Edge)
  • ✅ Faster than Cypress
  • ✅ Parallel execution built-in
  • ✅ Auto-wait (smart waiting)
  • ✅ Network interception
  • ✅ Mobile emulation
  • ✅ Multiple tabs/contexts
  • ✅ Native events (real browser events)
  • ✅ Screenshots/videos/traces
  • ✅ Great for CI/CD
  • ✅ Microsoft backing
  • ✅ Codegen (record tests)
  • ✅ Component testing
  • ✅ API testing built-in
  • ✅ Better architecture than Cypress

Cons of Playwright:

  • ❌ No time-travel debugging (like Cypress)
  • ❌ Steeper learning curve
  • ❌ Less interactive test runner
  • ❌ Smaller community than Cypress
  • ❌ Newer (less mature)
  • ❌ Fewer resources/tutorials

When to use Playwright (2025):

  • ✅ Need Safari testing
  • ✅ Need multi-tab support
  • ✅ Performance critical
  • ✅ CI/CD focused
  • ✅ Need parallel execution
  • ✅ Enterprise projects
  • ✅ New projects (recommended)

Why Playwright is winning:

  • Faster than Cypress (2-3x)
  • Better browser support (Safari!)
  • More powerful API
  • Better for CI/CD
  • Microsoft backing (long-term support)
  • Better architecture (no browser limitations)

Migration from Cypress:

  • API similar but different
  • Rewrite tests (no automatic migration)
  • Worth it for better features

Current status (2025):

  • Rapidly growing (40% market share)
  • Industry standard for new projects
  • Recommended over Cypress for most cases
  • Will likely become dominant

Puppeteer (2017, Google)

Status: Still used for automation

const puppeteer = require('puppeteer');

const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://example.com');
await page.screenshot({ path: 'screenshot.png' });
await browser.close();

// Headless Chrome automation
// PDF generation
// Scraping
// Not primarily for testing
Enter fullscreen mode Exit fullscreen mode

Pros of Puppeteer:

  • ✅ Chrome DevTools Protocol (powerful)
  • ✅ Great for automation (scraping, PDFs)
  • ✅ Headless Chrome control
  • ✅ Google backing
  • ✅ Good documentation
  • ✅ Fast

Cons of Puppeteer:

  • ❌ Chrome/Chromium only
  • ❌ Not designed for testing (use Playwright)
  • ❌ No multi-browser
  • ❌ Less testing features

When to use Puppeteer (2025):

  • ✅ Web scraping
  • ✅ PDF generation
  • ✅ Screenshots
  • ✅ Browser automation (non-testing)
  • ❌ NOT for E2E testing (use Playwright)

Current status (2025):

  • Still maintained
  • Good for automation
  • Playwright better for testing
  • Use for scraping/PDFs, not testing

Build Tools & Module Systems

Module Bundlers Evolution

RequireJS (2010) - AMD

Status: Dead

// AMD (Asynchronous Module Definition)
define(['jquery', 'lodash'], function($, _) {
    return {
        doSomething: function() {
            // ...
        }
    };
});
Enter fullscreen mode Exit fullscreen mode

Browserify (2011)

Status: Dead

When it became legacy: ~2015-2016

Peak usage: 2012-2015

What replaced it: Webpack, Rollup

Problem it solved:

  • 🎯 Node.js had require() for modules, browsers didn't
  • 🎯 Wanted to use npm packages in the browser
  • 🎯 No standard module system for browsers (before ES6 modules)
  • 🎯 Had to manually include <script> tags in order
// Before Browserify - Manual script tags in HTML
<script src="jquery.js"></script>
<script src="lodash.js"></script>
<script src="app.js"></script>
// Order matters! Dependencies must load first

// With Browserify - Use Node.js style require()
const $ = require('jquery');
const _ = require('lodash');

// Build
browserify app.js -o bundle.js
// Creates single bundle with all dependencies
Enter fullscreen mode Exit fullscreen mode

Pros of Browserify:

  • ✅ First to bring Node.js modules to browser
  • ✅ Simple concept
  • ✅ npm packages in browser
  • ✅ Unix philosophy (do one thing)
  • ✅ Streaming

Cons of Browserify:

  • ❌ No code splitting
  • ❌ No hot module replacement
  • ❌ Slower than Webpack
  • ❌ Limited features
  • ❌ No tree shaking
  • ❌ No asset handling (CSS, images)
  • ❌ Webpack more powerful

Current status: Dead, use Webpack/Vite/Rollup


Webpack (2012-Present)

Status: Still dominant but challenged

Problem it solved:

  • 🎯 Browserify only handled JavaScript, not CSS/images/fonts
  • 🎯 No code splitting (one giant bundle = slow loading)
  • 🎯 No Hot Module Replacement (had to refresh entire page)
  • 🎯 Complex apps needed asset pipeline (CSS, images, fonts, etc.)
  • 🎯 Needed build optimizations (minification, tree shaking)

Evolution:

// Webpack 1 (2014)
module.exports = {
    entry: './app.js',
    output: {
        filename: 'bundle.js'
    },
    module: {
        loaders: [
            { test: /\.js$/, loader: 'babel-loader' }
        ]
    }
};

// Webpack 2 (2017) - Tree shaking
module.exports = {
    entry: './app.js',
    output: {
        filename: 'bundle.js'
    },
    module: {
        rules: [
            { test: /\.js$/, use: 'babel-loader' }
        ]
    }
};

// Webpack 4 (2018) - Zero config
// No config needed for simple cases
module.exports = {
    mode: 'production', // or 'development'
    entry: './src/index.js'
};

// Webpack 5 (2020) - Module Federation
module.exports = {
    plugins: [
        new ModuleFederationPlugin({
            name: 'app1',
            remotes: {
                app2: 'app2@http://localhost:3002/remoteEntry.js'
            }
        })
    ]
};

// Code splitting
import(/* webpackChunkName: "lodash" */ 'lodash').then(_ => {
    // Use lodash
});
Enter fullscreen mode Exit fullscreen mode

Pros of Webpack:

  • ✅ Powerful plugin system (thousands of plugins)
  • ✅ Code splitting (dynamic imports)
  • ✅ Hot Module Replacement (HMR)
  • ✅ Handles everything (CSS, images, fonts, etc.)
  • ✅ Large ecosystem
  • ✅ Tree shaking
  • ✅ Production optimizations
  • ✅ Module Federation (micro-frontends)
  • ✅ Mature and battle-tested
  • ✅ Works with any framework
  • ✅ Great for complex projects

Cons of Webpack:

  • ❌ Slow builds (especially large projects)
  • ❌ Complex configuration (webpack.config.js hell)
  • ❌ Steep learning curve
  • ❌ Slow HMR (compared to Vite)
  • ❌ Large config files
  • ❌ Hard to debug
  • ❌ Newer tools much faster

When to use Webpack (2025):

  • ✅ Legacy projects already using it
  • ✅ Need Module Federation
  • ✅ Complex build requirements
  • ✅ Enterprise projects (proven at scale)
  • ❌ NOT recommended for new projects (use Vite)

What's replacing Webpack:

  • Vite (most new projects)
  • Turbopack (Next.js)
  • esbuild (some projects)

Current status (2025):

  • Still widely used (50% of projects)
  • Webpack 5 stable
  • Being replaced by Vite in new projects
  • Still good for complex needs
  • Not recommended for new projects unless specific needs

Rollup (2015)

Status: Popular for libraries

Problem it solved:

  • 🎯 Webpack bundles were too large for libraries
  • 🎯 Webpack's tree-shaking wasn't good enough
  • 🎯 Libraries needed multiple output formats (ESM, CJS, UMD)
  • 🎯 Webpack added too much runtime code (overhead)
  • 🎯 ES6 modules needed a bundler that understood them natively
// rollup.config.js
export default {
    input: 'src/main.js',
    output: {
        file: 'bundle.js',
        format: 'esm' // or 'cjs', 'umd', 'iife'
    },
    plugins: [
        resolve(),
        commonjs(),
        babel()
    ]
};

// Better tree-shaking than Webpack
// Smaller bundles
// ES modules native
Enter fullscreen mode Exit fullscreen mode

Pros of Rollup:

  • ✅ Better tree-shaking than Webpack
  • ✅ Smaller bundles
  • ✅ ES modules native
  • ✅ Simpler config than Webpack
  • ✅ Great for libraries
  • ✅ Multiple output formats (ESM, CJS, UMD)
  • ✅ Fast builds

Cons of Rollup:

  • ❌ Not great for applications (use Vite)
  • ❌ Less features than Webpack
  • ❌ Smaller plugin ecosystem
  • ❌ No HMR (use Vite for that)
  • ❌ Code splitting less mature

When to use Rollup (2025):

  • ✅ Building libraries (npm packages)
  • ✅ Need multiple output formats
  • ✅ Tree-shaking critical
  • ❌ NOT for applications (use Vite)

Use case: Libraries (React, Vue, Svelte all use Rollup)

Current status (2025):

  • Standard for library builds
  • Used by Vite for production builds
  • Not for applications directly

Parcel (2017)

Status: Niche/Declining

When it started declining: ~2020-2021

What replaced it: Vite

Problem it solved:

  • 🎯 Webpack configuration was too complex (webpack.config.js hell)
  • 🎯 Beginners struggled with build tools
  • 🎯 Wanted "just work" experience (zero config)
  • 🎯 Setting up Webpack + Babel + loaders took hours
  • 🎯 Needed faster builds (multi-core processing)
# Before Parcel - Webpack config nightmare
// webpack.config.js (100+ lines)
// .babelrc
// package.json with 20+ dev dependencies
// Hours of setup

# With Parcel - Zero config
parcel index.html

# That's it! Automatically handles:
# - JavaScript (Babel)
# - TypeScript
# - CSS/SCSS
# - Images
# - Hot reload
Enter fullscreen mode Exit fullscreen mode

Pros of Parcel:

  • ✅ Zero config (truly zero)
  • ✅ Fast (multi-core)
  • ✅ Handles everything automatically
  • ✅ Great for beginners
  • ✅ Built-in dev server
  • ✅ HMR out of the box

Cons of Parcel:

  • ❌ Less control than Webpack
  • ❌ Smaller ecosystem
  • ❌ Some edge cases buggy
  • ❌ Slower than Vite
  • ❌ Less mature
  • ❌ Configuration limited when needed

Why it didn't win:

  • Webpack too entrenched (at the time)
  • Less control for complex projects
  • Vite came along (faster, better)

Current status (2025):

  • Parcel 2 released but not popular
  • Vite won the "fast bundler" race
  • Not recommended (use Vite instead)

Vite (2020)

Status: Rising rapidly

Problem it solved:

  • 🎯 Webpack dev server was SLOW (30+ seconds to start on large projects)
  • 🎯 Hot Module Replacement was slow (seconds to see changes)
  • 🎯 Rebuilding entire bundle on every change was wasteful
  • 🎯 Browsers now support ES modules natively - why bundle in dev?
  • 🎯 Developer experience was painful on large projects

The breakthrough:

  • Dev: Use native ES modules (no bundling!) = instant start
  • Prod: Use Rollup (optimized bundles)
  • Pre-bundle dependencies with esbuild (100x faster than Webpack)
// Before Vite - Webpack dev server
npm run dev
// Wait 30-60 seconds...
// Make change
// Wait 3-5 seconds for HMR...

// With Vite
npm run dev
// Server starts in 200ms!
// Make change
// See update in 50ms!

// vite.config.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
    plugins: [react()]
});

// Dev server
vite

// Build
vite build
Enter fullscreen mode Exit fullscreen mode

Pros of Vite:

  • ✅ Extremely fast dev server (instant start)
  • ✅ Lightning-fast HMR (instant updates)
  • ✅ Native ESM in development
  • ✅ Rollup for production (optimized)
  • ✅ Simple configuration
  • ✅ Great developer experience
  • ✅ Plugin ecosystem growing
  • ✅ TypeScript support
  • ✅ CSS/Asset handling
  • ✅ Framework agnostic
  • ✅ Pre-bundling (esbuild)

Cons of Vite:

  • ❌ Newer (less mature than Webpack)
  • ❌ Some edge cases with legacy code
  • ❌ Smaller plugin ecosystem than Webpack
  • ❌ Dev/prod differences (ESM vs bundled)
  • ❌ Some libraries don't work well with ESM

Why Vite is winning:

  • 10-100x faster dev server than Webpack
  • Instant HMR (no rebuild)
  • Better developer experience
  • Simple configuration
  • Modern approach (ESM-first)

How it works:

// Development: Native ESM (no bundling!)
import { createApp } from 'vue';
// Browser loads this directly via HTTP/2

// Production: Rollup bundling
// Optimized, tree-shaken, code-split
Enter fullscreen mode Exit fullscreen mode

When to use Vite (2025):

  • ✅ New projects (highly recommended)
  • ✅ Any framework (React, Vue, Svelte, etc.)
  • ✅ Want fast development
  • ✅ Modern browsers target
  • ✅ TypeScript projects

Adoption:

  • Vue 3 (official build tool)
  • Svelte (SvelteKit)
  • Solid.js (SolidStart)
  • Qwik
  • React (growing adoption)
  • Recommended by most frameworks

Current status (2025):

  • Rapidly growing (30% market share)
  • Standard for new projects
  • Recommended over Webpack
  • Will likely become dominant

esbuild (2020)

Status: Used as build tool in other tools (not standalone)

Creator: Evan Wallace (Figma)

// Extremely fast (written in Go)
require('esbuild').build({
    entryPoints: ['app.js'],
    bundle: true,
    outfile: 'out.js'
});
Enter fullscreen mode Exit fullscreen mode

Pros of esbuild:

  • ✅ Extremely fast (10-100x faster than Webpack)
  • ✅ Written in Go (native performance)
  • ✅ TypeScript/JSX built-in
  • ✅ Tree shaking
  • ✅ Minification
  • ✅ Source maps
  • ✅ Simple API

Cons of esbuild:

  • ❌ Limited features (by design)
  • ❌ No HMR
  • ❌ No code splitting (initially)
  • ❌ Not a complete build tool
  • ❌ Limited plugin ecosystem
  • ❌ Not meant for direct use

When to use esbuild (2025):

  • ✅ As part of other tools (Vite uses it)
  • ✅ Simple bundling needs
  • ✅ Speed critical
  • ❌ NOT for full applications (use Vite)

How it's used:

  • Vite uses esbuild for pre-bundling dependencies
  • Some projects use it directly for simple builds
  • Building blocks for other tools

Current status (2025):

  • Widely used indirectly (via Vite, etc.)
  • Not meant as standalone build tool
  • Proven technology

Turbopack (2022, Vercel)

Status: In development (beta)

Creator: Vercel (Tobias Koppers, creator of Webpack)

// Webpack successor (written in Rust)
// Used in Next.js 13+
// next dev --turbo
Enter fullscreen mode Exit fullscreen mode

Pros of Turbopack:

  • ✅ Extremely fast (written in Rust)
  • ✅ Incremental computation (caches everything)
  • ✅ Webpack successor (by same author)
  • ✅ Designed for scale
  • ✅ Vercel backing

Cons of Turbopack:

  • ❌ Still experimental (not production-ready)
  • ❌ Only for Next.js (currently)
  • ❌ Not standalone yet
  • ❌ Limited documentation
  • ❌ Unproven at scale

When to use Turbopack (2025):

  • ✅ Next.js 13+ (experimental)
  • ❌ NOT for production yet
  • ❌ NOT standalone

Current status (2025):

  • Beta in Next.js
  • Promising but not ready
  • Wait for stable release
  • Future looks bright

Bun (2023)

Status: All-in-one tool

# Bundler built-in
bun build ./index.tsx --outdir ./dist

# Extremely fast
# No configuration needed
Enter fullscreen mode Exit fullscreen mode

Transpilers

Babel (2014-Present)

Status: Still essential

Problem it solved:

  • 🎯 ES6 released but browsers didn't support it (2015)
  • 🎯 Wanted to use new JavaScript features (arrow functions, classes, etc.)
  • 🎯 Had to wait years for browser support
  • 🎯 Different browsers supported different features
  • 🎯 JSX needed transformation (React)

The breakthrough:

  • Write modern JavaScript, run anywhere - Transpiles ES6+ to ES5
  • Plugin system - Transform any syntax
  • Polyfills - Add missing features (Promise, Array.from, etc.)
  • JSX support - Transform React JSX to JavaScript
  • Target specific browsers - Only transpile what's needed
// 2015 - Problem: Browsers don't support ES6
// Write this (ES6)
const add = (a, b) => a + b;
class Person {
    constructor(name) {
        this.name = name;
    }
}

// But browsers only understand this (ES5)
var add = function(a, b) {
    return a + b;
};
function Person(name) {
    this.name = name;
}

// Babel solves this - Write ES6, Babel converts to ES5
// You write modern code, users get compatible code
Enter fullscreen mode Exit fullscreen mode

Evolution:

// 2015: Babel 6 - Modular
{
    "presets": ["es2015", "react"],
    "plugins": ["transform-class-properties"]
}

// 2018: Babel 7 - @babel/
{
    "presets": [
        ["@babel/preset-env", {
            "targets": "> 0.25%, not dead"
        }],
        "@babel/preset-react"
    ]
}

// What it does
// Input (ES6+)
const add = (a, b) => a + b;
class Person {
    name = "John";
}

// Output (ES5)
var add = function(a, b) {
    return a + b;
};
var Person = function Person() {
    this.name = "John";
};
Enter fullscreen mode Exit fullscreen mode

Why still needed:

  • Browser compatibility
  • JSX transformation
  • TypeScript (alternative to tsc)
  • Experimental features

SWC (2020)

Status: Growing (Rust-based, faster)

// 20x faster than Babel
// Used by Next.js, Parcel, Deno

// .swcrc
{
    "jsc": {
        "parser": {
            "syntax": "typescript",
            "tsx": true
        },
        "transform": {
            "react": {
                "runtime": "automatic"
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Task Runners

Task Runners: Grunt (2012) → Gulp (2013) → npm scripts

Grunt (2012)

Status: Dead

When it became legacy: ~2015-2016

What replaced it: Gulp, then npm scripts

Problem it solved:

  • 🎯 No standard way to automate build tasks (minify, concat, compile)
  • 🎯 Had to manually run multiple commands
  • 🎯 Build scripts were bash/shell scripts (not cross-platform)
  • 🎯 Needed task automation in JavaScript
// Before Grunt - Manual commands
uglifyjs app.js -o app.min.js
sass styles.scss styles.css
imagemin images/* -o dist/images
// Run each command manually!

// With Grunt - Automated tasks
module.exports = function(grunt) {
    grunt.initConfig({
        uglify: {
            build: {
                src: 'app.js',
                dest: 'app.min.js'
            }
        },
        sass: {
            dist: {
                files: {
                    'styles.css': 'styles.scss'
                }
            }
        }
    });

    grunt.loadNpmTasks('grunt-contrib-uglify');
    grunt.loadNpmTasks('grunt-contrib-sass');

    grunt.registerTask('default', ['uglify', 'sass']);
};

// Run all tasks
grunt
Enter fullscreen mode Exit fullscreen mode

Pros of Grunt:

  • ✅ First popular task runner
  • ✅ Large plugin ecosystem
  • ✅ Configuration-based (declarative)
  • ✅ Easy to understand

Cons of Grunt:

  • ❌ Slow (file I/O heavy)
  • ❌ Verbose configuration
  • ❌ Temporary files everywhere
  • ❌ Not streaming
  • ❌ Complex for simple tasks

Current status: Dead, don't use


Gulp (2013)

Status: Legacy/Declining

When it became legacy: ~2017-2018

What replaced it: npm scripts, build tools (Webpack, Vite)

Problem it solved:

  • 🎯 Grunt was too slow (wrote temp files to disk constantly)
  • 🎯 Grunt configuration was verbose and hard to read
  • 🎯 Wanted streaming (in-memory processing)
  • 🎯 Needed code over configuration (more flexible)
// Grunt - Configuration-based (slow, verbose)
grunt.initConfig({
    uglify: {
        build: {
            src: 'app.js',
            dest: 'app.min.js'
        }
    }
});
// Writes temp files to disk

// Gulp - Code-based (fast, streaming)
const gulp = require('gulp');
const uglify = require('gulp-uglify');
const sass = require('gulp-sass');

gulp.task('scripts', function() {
    return gulp.src('src/**/*.js')
        .pipe(uglify())           // In-memory streaming
        .pipe(gulp.dest('dist'));
});

gulp.task('styles', function() {
    return gulp.src('src/**/*.scss')
        .pipe(sass())
        .pipe(gulp.dest('dist'));
});

gulp.task('default', gulp.parallel('scripts', 'styles'));
Enter fullscreen mode Exit fullscreen mode

Pros of Gulp:

  • ✅ Streaming (faster than Grunt)
  • ✅ Code over configuration
  • ✅ Simpler than Grunt
  • ✅ Good plugin ecosystem
  • ✅ Flexible

Cons of Gulp:

  • ❌ Another abstraction layer
  • ❌ npm scripts can do most things
  • ❌ Build tools handle bundling better
  • ❌ Maintenance overhead
  • ❌ Less relevant with modern tools

Current status: Still used in some legacy projects, not recommended


npm scripts (2010-Present)

Status: Standard

What replaced: Grunt, Gulp

// package.json
{
    "scripts": {
        "dev": "vite",
        "build": "vite build",
        "test": "vitest",
        "lint": "eslint src",
        "format": "prettier --write src",
        "preview": "vite preview"
    }
}
Enter fullscreen mode Exit fullscreen mode

Pros of npm scripts:

  • ✅ No extra dependencies
  • ✅ Simple and direct
  • ✅ Standard across projects
  • ✅ Easy to understand
  • ✅ No abstraction layer
  • ✅ Works with any tool

Cons of npm scripts:

  • ❌ Limited for complex tasks
  • ❌ Cross-platform issues (Windows vs Unix)
  • ❌ No streaming
  • ❌ Can get messy for complex workflows

When to use (2025):

  • ✅ Always (standard approach)
  • ✅ Simple task running
  • ✅ Calling build tools

Modern approach:

  • npm/pnpm/yarn scripts for task running
  • Build tools (Vite, Webpack) for bundling
  • No need for Grunt/Gulp

Package Management

npm (2010-Present)

Status: Default, still dominant

Problem it solved:

  • 🎯 No standard way to share JavaScript libraries
  • 🎯 Had to manually download and include libraries
  • 🎯 No dependency management (had to track versions manually)
  • 🎯 No way to publish/discover packages
  • 🎯 Dependency hell (conflicting versions)

The breakthrough:

  • Central registry - One place for all packages (npmjs.com)
  • Automatic dependencies - Install package + all its dependencies
  • Version management - Semantic versioning (^, ~, exact)
  • Scripts - Run tasks (npm run build, npm test)
  • Largest ecosystem - 2+ million packages
// Before npm - Manual download
// 1. Go to jQuery website
// 2. Download jquery.min.js
// 3. Copy to project
// 4. Add <script src="jquery.min.js"></script>
// 5. Repeat for every library
// 6. Track versions manually
// 7. Update manually

// With npm - Automatic
npm install jquery
// Downloads jquery + all dependencies
// Tracks versions in package.json
// Updates with npm update

# Evolution
npm install express # 2010

npm install --save express # 2012-2016

npm install express # 2017+ (--save default)

npm ci # 2018 (clean install for CI)

# package.json
{
    "dependencies": {
        "express": "^4.18.0"
    },
    "devDependencies": {
        "typescript": "^5.0.0"
    }
}

# package-lock.json (2016)
# Ensures consistent installs
Enter fullscreen mode Exit fullscreen mode

Yarn (2016)

Status: Still popular

Problem it solved:

  • 🎯 npm was slow (sequential installs)
  • 🎯 npm was non-deterministic (different installs could differ)
  • 🎯 No offline mode
  • 🎯 Security concerns (no checksums)
  • 🎯 Poor monorepo support

The breakthrough:

  • Parallel installs - Much faster than npm
  • Lockfile - Deterministic installs (yarn.lock)
  • Offline cache - Install without internet
  • Workspaces - Better monorepo support
  • Security - Checksums for packages
# npm (2016) - Slow
npm install
# Takes 2-3 minutes, sequential

# Yarn (2016) - Fast
yarn install
# Takes 30 seconds, parallel!

# Yarn 1 (2016) - Faster than npm
yarn add express

# Yarn 2/3 (Berry) (2020) - Plug'n'Play
yarn set version berry

# .yarnrc.yml
nodeLinker: pnp # No node_modules!

# Workspaces
{
    "workspaces": ["packages/*"]
}
Enter fullscreen mode Exit fullscreen mode

Why Yarn:

  • Faster (initially)
  • Workspaces (monorepos)
  • Offline mode
  • Deterministic

pnpm (2017)

Status: Growing rapidly

# Efficient disk usage (hard links)
pnpm install express

# Strict node_modules structure
# Prevents phantom dependencies

# Workspaces
{
    "pnpm": {
        "workspaces": ["packages/*"]
    }
}
Enter fullscreen mode Exit fullscreen mode

Why pnpm is winning:

  • Saves disk space (shared dependencies)
  • Faster than npm
  • Strict (catches bugs)
  • Great for monorepos

Disk usage comparison:

  • npm: 1 GB for 3 projects
  • Yarn: 1 GB for 3 projects
  • pnpm: 350 MB for 3 projects

Bun (2023)

Status: Fastest

# Extremely fast
bun install

# Compatible with npm
# 10-20x faster
Enter fullscreen mode Exit fullscreen mode

Current Landscape (2025)

Frontend Frameworks

Market Share (approximate):

  1. React (40%) - Still dominant

    • Huge ecosystem
    • Job market leader
    • Meta backing
    • Next.js standard
  2. Vue (20%) - Strong second

    • Easier learning curve
    • Great documentation
    • Popular in Asia
    • Nuxt.js mature
  3. Angular (15%) - Enterprise

    • TypeScript native
    • Complete framework
    • Enterprise adoption
    • Google backing
  4. Svelte (10%) - Rising

    • Best DX
    • Smallest bundles
    • SvelteKit mature
    • Growing fast
  5. Others (15%)

    • Solid.js (performance)
    • Qwik (resumability)
    • Preact (React alternative)
    • Alpine.js (lightweight)

Meta-Frameworks (Recommended)

Don't use frameworks alone anymore!

  • ReactNext.js or Remix
  • VueNuxt.js
  • SvelteSvelteKit
  • SolidSolidStart
  • AngularAnalog (new)

Why meta-frameworks:

  • SSR/SSG out of the box
  • Routing
  • API routes
  • Optimizations
  • Better DX

State Management (2025)

React:

  1. TanStack Query - Server state
  2. Zustand - Client state (simple)
  3. Jotai - Atomic state
  4. Redux Toolkit - Complex apps
  5. Context + Hooks - Simple apps

Vue:

  1. Pinia - Official (replaced Vuex)

Angular:

  1. NgRx - Redux-like
  2. Signals - Built-in (Angular 16+)

Build Tools (2025)

Recommended:

  1. Vite - Modern projects
  2. Webpack - Legacy/complex needs
  3. Turbopack - Next.js
  4. Bun - All-in-one

For libraries:

  • Rollup or tsup

Testing (2025)

Unit/Integration:

  1. Vitest - Modern (Vite projects)
  2. Jest - Established (React)
  3. Bun Test - Bun projects

E2E:

  1. Playwright - Best overall
  2. Cypress - Great DX

Package Managers (2025)

Recommended:

  1. pnpm - Best for most
  2. npm - Default, reliable
  3. Yarn - Workspaces
  4. Bun - Speed

TypeScript (2025)

Status: Industry standard

Adoption:

  • 80%+ of new projects
  • Required for most jobs
  • All major frameworks support it
  • Better tooling

Recommendation: Use TypeScript for everything

Runtime (2025)

Server-side:

  1. Node.js - Standard
  2. Bun - Fast alternative
  3. Deno - Secure alternative

Edge:

  • Cloudflare Workers
  • Vercel Edge
  • Deno Deploy

What to Learn in 2025

For Beginners

  1. JavaScript fundamentals

    • ES6+ features
    • Async/await
    • Modules
    • Array methods
  2. TypeScript basics

    • Types, interfaces
    • Generics
    • Type inference
  3. React + Next.js

    • Hooks
    • Server Components
    • App Router
  4. Modern tooling

    • Vite
    • pnpm
    • Vitest

For Returning Developers

If you left in 2010 (jQuery era):

Learn:

  • ES6+ (let/const, arrow functions, classes, modules)
  • async/await
  • React or Vue
  • TypeScript
  • Next.js or Nuxt.js
  • Modern build tools (Vite)

If you left in 2015 (Early React):

Learn:

  • React Hooks (replaced classes)
  • TypeScript
  • Server Components (Next.js 13+)
  • Modern state management (TanStack Query, Zustand)
  • Vite

If you left in 2020:

Learn:

  • Server Components
  • Signals (Angular, Solid)
  • Bun/Deno
  • Astro (for content sites)
  • Playwright (testing)

Key Takeaways

What Died

  • jQuery - Native APIs good enough
  • AngularJS 1.x - Replaced by Angular 2+
  • Backbone.js - Too minimal
  • Grunt/Gulp - npm scripts enough
  • Moment.js - Too large, mutable
  • Bower - npm won
  • CoffeeScript - ES6+ made it obsolete

What Survived

  • React - Adapted (Hooks, Server Components)
  • Vue - Evolved (Composition API)
  • Angular - Complete rewrite
  • Node.js - Still standard
  • npm - Still default
  • Webpack - Still used (but challenged)
  • TypeScript - Now essential

What's New and Hot

  • Vite - Fast builds
  • pnpm - Efficient packages
  • Vitest - Fast testing
  • Playwright - Best E2E
  • Bun - Fast runtime
  • Server Components - New paradigm
  • Signals - Fine-grained reactivity
  • Astro - Content sites

The Biggest Changes

  1. TypeScript everywhere (2012 → 2025)
  2. Frameworks required (jQuery → React/Vue/Angular)
  3. Build tools essential (None → Webpack → Vite)
  4. Server-side rendering (SPA → SSR/SSG)
  5. Component-based (jQuery → Components)
  6. Type safety (Dynamic → Static typing)
  7. Modern JavaScript (ES5 → ES2025)
  8. Testing standard (Optional → Required)
  9. Monorepos common (Single → Workspaces)
  10. Edge computing (Server → Edge)

Conclusion

JavaScript has evolved from a simple scripting language to a comprehensive ecosystem powering everything from websites to servers to mobile apps to desktop applications.

The journey:

  • 1995-2005: Birth and browser wars
  • 2005-2009: AJAX and jQuery revolution
  • 2009-2015: Node.js and modern frameworks
  • 2015-2020: ES6+ and React dominance
  • 2020-2025: TypeScript, Server Components, and performance

The future:

  • More server-side rendering
  • Better performance (Signals, Resumability)
  • Type safety everywhere
  • Edge computing
  • AI integration
  • WebAssembly integration

For developers returning to JavaScript: The ecosystem is more mature, more powerful, and more complex. But the fundamentals remain: build great user experiences, write maintainable code, and keep learning.

Welcome back to JavaScript! 🎉


Technology Status Summary Table

Frontend Frameworks

Technology Status Legacy Since Replaced By Recommendation 2025
jQuery Legacy 2017-2018 Native APIs, React, Vue ❌ Don't use for new projects
Backbone.js Dead 2015-2016 React, Vue, Angular ❌ Migrate away
Knockout.js Legacy 2016-2017 Vue, React ❌ Migrate away
AngularJS 1.x Dead 2016 (EOL 2022) Angular 2+, React, Vue ❌ Migrate immediately
Ember.js Declining 2015-2016 React, Vue ⚠️ Only if already using
React Dominant - - ✅ Highly recommended
Vue Strong - - ✅ Highly recommended
Angular 2+ Stable - - ✅ Good for enterprise
Svelte Growing - - ✅ Recommended for new projects
Solid.js Niche - - ⚠️ Experimental/side projects
Qwik Experimental - - ⚠️ Not production-ready

Testing Frameworks

Technology Status Legacy Since Replaced By Recommendation 2025
QUnit Dead 2014-2015 Jest, Mocha ❌ Don't use
Jasmine Declining 2016-2017 Jest, Vitest ⚠️ Only for Angular legacy
Mocha Declining 2017-2018 Jest, Vitest ⚠️ Legacy projects only
Jest Dominant - Vitest (for Vite) ✅ Standard for React
Vitest Growing - - ✅ Recommended for Vite projects
Bun Test Experimental - - ⚠️ Not production-ready

E2E Testing

Technology Status Legacy Since Replaced By Recommendation 2025
Selenium Legacy 2017-2018 Playwright, Cypress ⚠️ Only for legacy/multi-language
Protractor Dead 2020 (EOL 2022) Playwright, Cypress ❌ Migrate immediately
Cypress Popular - Playwright (partially) ✅ Good for SPAs
Playwright Growing - - ✅ Recommended for new projects
Puppeteer Niche - - ✅ For automation, not testing

Build Tools

Technology Status Legacy Since Replaced By Recommendation 2025
Browserify Dead 2015-2016 Webpack, Rollup ❌ Don't use
Webpack Mature - Vite (for apps) ⚠️ Legacy projects, complex needs
Rollup Stable - - ✅ For libraries only
Parcel Declining 2020-2021 Vite ❌ Use Vite instead
Vite Growing - - ✅ Highly recommended for apps
esbuild Tool - - ✅ As part of other tools
Turbopack Experimental - - ⚠️ Not production-ready

Task Runners

Technology Status Legacy Since Replaced By Recommendation 2025
Grunt Dead 2015-2016 Gulp, npm scripts ❌ Don't use
Gulp Legacy 2017-2018 npm scripts ⚠️ Legacy projects only
npm scripts Standard - - ✅ Always use

Package Managers

Technology Status Legacy Since Replaced By Recommendation 2025
Bower Dead 2015 npm ❌ Don't use
npm Standard - - ✅ Default, reliable
Yarn Stable - - ✅ Good for monorepos
pnpm Growing - - ✅ Recommended for most
Bun Experimental - - ⚠️ Experimental

Meta-Frameworks

Technology Status Legacy Since Replaced By Recommendation 2025
Next.js Dominant - - ✅ Standard for React
Nuxt.js Strong - - ✅ Standard for Vue
Remix Growing - - ✅ Good alternative to Next.js
SvelteKit Stable - - ✅ Standard for Svelte
Astro Growing - - ✅ Great for content sites

State Management (React)

Technology Status Legacy Since Replaced By Recommendation 2025
Redux Mature - Redux Toolkit ⚠️ Use Redux Toolkit instead
Redux Toolkit Standard - - ✅ For complex state
MobX Declining - Zustand, Jotai ⚠️ Legacy projects only
Context API Built-in - - ✅ For simple state
Zustand Growing - - ✅ Recommended for client state
Jotai Growing - - ✅ Recommended for atomic state
TanStack Query Standard - - ✅ Required for server state

Utility Libraries

Technology Status Legacy Since Replaced By Recommendation 2025
Lodash Stable - Native JS (partial) ✅ Still useful
Underscore Legacy 2015 Lodash ❌ Use Lodash
Moment.js Deprecated 2020 date-fns, Day.js ❌ Migrate away
date-fns Standard - - ✅ Recommended
Day.js Standard - - ✅ Recommended (smaller)

Runtimes

Technology Status Legacy Since Replaced By Recommendation 2025
Node.js Standard - - ✅ Industry standard
Deno Niche - - ⚠️ Experimental/specific use cases
Bun Experimental - - ⚠️ Promising but not production-ready

Quick Decision Guide (2025)

Starting a New Project?

Frontend Framework:

  • React → Use Next.js (most jobs, largest ecosystem)
  • Vue → Use Nuxt.js (easier learning curve, great DX)
  • Svelte → Use SvelteKit (best DX, smallest bundles)
  • Angular → Only if enterprise/large team

Build Tool:

  • Vite (recommended for 90% of projects)
  • Webpack (only if specific needs)

Testing:

  • Unit: Vitest (if using Vite) or Jest (if React)
  • E2E: Playwright (recommended) or Cypress (if prefer DX)

Package Manager:

  • pnpm (recommended for most)
  • npm (default, reliable)
  • yarn (if need workspaces)

State Management (React):

  • Server state: TanStack Query (required)
  • Client state: Zustand (simple) or Jotai (atomic)
  • Complex: Redux Toolkit

TypeScript:

  • ✅ Always use TypeScript (industry standard)

Migration Priority Guide

Migrate Immediately (Dead/EOL)

  1. AngularJS 1.x → Angular/React/Vue (EOL 2022)
  2. Protractor → Playwright/Cypress (EOL 2022)
  3. Moment.js → date-fns/Day.js (deprecated)
  4. Bower → npm/pnpm (dead)
  5. Grunt → npm scripts (dead)

Migrate Soon (Legacy)

  1. jQuery → React/Vue + Native APIs
  2. Backbone/Knockout → Modern framework
  3. Mocha/Jasmine → Jest/Vitest
  4. Selenium → Playwright (if JavaScript)
  5. Gulp → npm scripts
  6. Webpack → Vite (for new features)

Can Wait (Still Supported)

  1. Redux → Redux Toolkit (same ecosystem)
  2. Jest → Vitest (if moving to Vite)
  3. Cypress → Playwright (both good)
  4. npm → pnpm (both work fine)

Last updated: December 4, 2025 - 30 Years of JavaScript

Top comments (3)

Collapse
 
pengeszikra profile image
Peter Vivo • Edited

Greatest collection of JS ecosystem by history, I just miss the jsDock because I think that can be a better typesafe option compare to typescript: jsDoc evangelism

Collapse
 
gochev profile image
Nayden Gochev

I actually think thet Dart should have been the better option but the Angular team chosed TS and basically kicking the TS revolution and kinda killing their own colleges in the meantime :(

P.s. that's the main reason for me not liking angular :(

Collapse
 
pengeszikra profile image
Peter Vivo

I am a JS fanatic special after ES6. That why great the jsDoc because that is don't want modify to code just give a type hint under development.

My main missing from javascript is a simple pipeline operator. I worked with optional-pipeline-operator ( babel ) with 2 years and that is will be really hugh improvement of JS. Saddly cannot able to agree with a simple pipeline where just one parameter eneabled.