How to Migrate Legacy jQuery to Vanilla JavaScript

Migrating a legacy jQuery codebase to modern vanilla JavaScript improves performance, reduces bundle sizes, and future-proofs web applications without sacrificing functionality. This guide outlines a step-by-step strategy for executing an incremental transition, covering code audits, equivalent modern web APIs, dependency replacement, and testing methods to ensure your application remains stable throughout the process.

1. Audit the Codebase and Dependencies

Before writing code, inventory how jQuery is used across your project.

  • Identify Third-Party Plugins: Search for plugins like sliders, modals, or date pickers that rely directly on the jQuery global ($ or jQuery). These require modern native alternatives or standard micro-libraries.
  • Determine Usage Scope: Check if jQuery is loaded globally via a <script> tag or bundled via npm/Webpack.
  • Assess Browser Support: Verify your project's browser support targets. Modern JavaScript (ES6+) and modern DOM APIs are fully supported across all modern browsers, making polyfills largely unnecessary unless legacy browsers like Internet Explorer are strictly required.

2. Adopt an Incremental Migration Strategy

A complete rewrite introduces high risk. Instead, migrate your codebase incrementally while running jQuery and vanilla JavaScript side-by-side.

  • Leave the jQuery library loaded during development.
  • Convert isolated features, utilities, or components one at a time.
  • Use native JavaScript inside existing jQuery callbacks where possible, progressively reducing calls to $ methods.

3. Replace Common jQuery Patterns with Native APIs

Modern browsers provide clean, native APIs that directly match jQuery’s functionality.

DOM Selection

Replace generic selector calls with targeted native methods:

  • Single element:
    • jQuery: $('#container') or $('.item')
    • Vanilla JS: document.querySelector('#container') or document.querySelector('.item')
  • Multiple elements:
    • jQuery: $('.item')
    • Vanilla JS: document.querySelectorAll('.item') (returns a NodeList that supports .forEach())

DOM Manipulation

Standard DOM APIs provide direct methods to manipulate elements and classes:

  • Class handling:
    • Add: element.classList.add('active')
    • Remove: element.classList.remove('active')
    • Toggle: element.classList.toggle('active')
  • Content:
    • HTML: element.innerHTML = '<span>Text</span>'
    • Text: element.textContent = 'Text'
  • Insertion and Removal:
    • Append: parent.append(child)
    • Prepend: parent.prepend(child)
    • Remove: element.remove()

Event Handling

Replace .on(), .off(), and shorthand event methods with standard event listeners:

  • Basic listener:
    // jQuery
    $('#btn').on('click', handler);
    
    // Vanilla JS
    document.querySelector('#btn').addEventListener('click', handler);
  • Event Delegation:
    // jQuery
    $('#list').on('click', 'li', function(e) { /* ... */ });
    
    // Vanilla JS
    document.querySelector('#list').addEventListener('click', (e) => {
      const target = e.target.closest('li');
      if (target && e.currentTarget.contains(target)) {
        // Handle event with target
      }
    });

Asynchronous Requests

Replace $.ajax(), $.get(), and $.post() with the native Fetch API:

// jQuery
$.ajax({
  url: '/api/data',
  method: 'GET',
  dataType: 'json'
}).done(data => console.log(data));

// Vanilla JS
fetch('/api/data')
  .then(response => {
    if (!response.ok) throw new Error('Network error');
    return response.json();
  })
  .then(data => console.log(data))
  .catch(error => console.error(error));

4. Replace Legacy Plugins

Decouple jQuery-dependent UI widgets. For each plugin:

  • Evaluate Native HTML Elements: Use modern native elements such as <dialog> for modals, <details> for accordions, or native input types (e.g., type="date").
  • Use Framework-Agnostic Packages: If a custom component is necessary, swap the jQuery plugin for a lightweight, dependency-free alternative available via npm.
  • Write Custom Micro-Functions: Basic animations (e.g., slide down, fade in) can be handled using native CSS transitions triggered by toggling JavaScript classes.

5. Validate, Test, and Remove the Library

  1. Automated Testing: Run end-to-end (E2E) and integration tests across rewritten modules to catch regressions.
  2. Search for Stragglers: Use global project search for $ and jQuery references to ensure no calls remain.
  3. Remove the Source: Delete the jQuery <script> tag or run npm uninstall jquery.
  4. Monitor Console Errors: Load the application and verify that no ReferenceError: $ is not defined messages appear during standard user flows.