JavaScript

Migrating Legacy jQuery to Modern JavaScript (jQuery 4.0 Update)

A practical guide to migrating jQuery-dependent PHP projects to modern JavaScript, including jQuery 4.0 changes, vanilla JS replacements, and incremental strategies.

jQuery to vanilla JavaScript migration code comparison

If you maintain a PHP application built in the 2010s, jQuery is probably in there somewhere. It might be powering form validation, AJAX calls to PHP endpoints, date pickers, or dynamic table sorting. The code works, users do not complain, and touching it feels risky.

But jQuery 4.0 dropped legacy browser support and removed deprecated APIs. If you are running jQuery 1.x or 2.x, upgrading directly to 4.0 will break things. And for many use cases, modern JavaScript now does what jQuery does with less code and no dependency.

This guide covers both paths: upgrading to jQuery 4.0 and migrating away from jQuery entirely.

What changed in jQuery 4.0

Removed methods

jQuery 4.0 removes methods that were deprecated in 3.x:

Removed Replacement
.bind() .on()
.unbind() .off()
.delegate() .on() with selector
.undelegate() .off() with selector
.click() (shorthand) .on('click', ...)
$.isArray() Array.isArray()
$.isFunction() typeof fn === 'function'
$.isNumeric() Custom check or Number.isFinite()
$.type() typeof + instanceof
$.trim() String.prototype.trim()
$.parseJSON() JSON.parse()

AJAX changes

The biggest breaking change for PHP developers: jQuery 4.0 aligns AJAX promises with native Promises. The old success, error, and complete callbacks still work, but the jqXHR object’s .done(), .fail(), and .always() methods now follow standard Promise behavior.

1
2
3
4
5
6
7
// jQuery 3.x — this worked but was jQuery-specific
$.ajax('/api/users')
.done(function(data) { /* success */ })
.fail(function(jqXHR) { /* error */ });

// jQuery 4.0 — same pattern, but timing of resolution may differ
// for certain edge cases with synchronous AJAX (now fully removed)

Synchronous AJAX (async: false) is completely removed in jQuery 4.0. If any of your PHP-rendered pages use synchronous requests, they will fail.

Slim build available

jQuery 4.0 offers a slim build that removes AJAX and effects, reducing file size to about 6KB gzipped. If you only use jQuery for DOM manipulation and event handling, the slim build saves bandwidth.

Auditing your jQuery usage

Before deciding whether to upgrade or migrate, understand what jQuery actually does in your project:

1
2
3
# Find all jQuery patterns in your PHP templates
grep -rn '\$\.' source/ themes/ --include="*.ejs" --include="*.php" --include="*.js"
grep -rn 'jQuery\.' source/ themes/ --include="*.ejs" --include="*.php" --include="*.js"

Categorize findings into:

  1. DOM selection and manipulation$('.class'), .html(), .addClass()
  2. Event handling.on(), .click(), .submit()
  3. AJAX$.ajax(), $.get(), $.post()
  4. Animations.fadeIn(), .slideUp(), .animate()
  5. Plugins — jQuery UI, DataTables, Select2, etc.

The plugin dependency trap

If your project uses jQuery UI (as many PHP projects do), you cannot simply remove jQuery. jQuery UI depends on it. The migration path for plugins is:

  • jQuery UI datepicker → native <input type="date"> or Flatpickr
  • jQuery UI dialog → native <dialog> element
  • jQuery UI sortable → SortableJS
  • DataTables → No direct replacement; keep jQuery for DataTables or switch to AG Grid

Migration path 1: Upgrade to jQuery 4.0

This is the lowest-risk approach. Keep jQuery but update it.

Step 1: Install jQuery Migrate

1
2
<script src="jquery-4.0.0.min.js"></script>
<script src="jquery-migrate-4.0.0.min.js"></script>

jQuery Migrate logs warnings for deprecated API usage without breaking your code. Run your application and check the browser console for warnings.

Step 2: Fix deprecated calls

1
2
3
4
5
6
7
8
9
10
11
// Before: deprecated shorthand
$('#submit-btn').click(function() { ... });

// After: use .on()
$('#submit-btn').on('click', function() { ... });

// Before: $.trim()
var clean = $.trim(input);

// After: native trim
var clean = input.trim();

Step 3: Replace synchronous AJAX

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Before: synchronous (removed in 4.0)
$.ajax({
url: '/api/validate',
async: false, // THIS BREAKS IN 4.0
success: function(data) {
isValid = data.valid;
}
});

// After: use async/await
async function validate() {
const response = await $.ajax({ url: '/api/validate' });
return response.valid;
}

Step 4: Remove jQuery Migrate and test

Once all warnings are resolved, remove jQuery Migrate and run your full test suite.

Migration path 2: Replace jQuery with vanilla JS

Modern JavaScript covers most jQuery use cases natively.

DOM selection

1
2
3
4
5
6
7
8
9
// jQuery
$('.article-card')
$('#main-content')
$('input[name="email"]')

// Vanilla JS
document.querySelectorAll('.article-card')
document.getElementById('main-content')
document.querySelector('input[name="email"]')

DOM manipulation

1
2
3
4
5
6
7
8
9
// jQuery
$('#title').html('New Title');
$('.card').addClass('active');
$('#panel').hide();

// Vanilla JS
document.getElementById('title').innerHTML = 'New Title';
document.querySelector('.card').classList.add('active');
document.getElementById('panel').style.display = 'none';

Event handling

1
2
3
4
5
6
7
8
9
10
11
// jQuery
$(document).on('click', '.dynamic-btn', function() {
$(this).toggleClass('active');
});

// Vanilla JS (event delegation)
document.addEventListener('click', function(e) {
if (e.target.matches('.dynamic-btn')) {
e.target.classList.toggle('active');
}
});

AJAX calls to PHP endpoints

This is the most important migration for PHP developers:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// jQuery
$.post('/api/articles', {
title: 'New Article',
body: content
}).done(function(data) {
showSuccess(data.message);
}).fail(function(xhr) {
showError(xhr.responseJSON.error);
});

// Vanilla JS with fetch
try {
const response = await fetch('/api/articles', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title: 'New Article', body: content })
});

if (!response.ok) {
const error = await response.json();
showError(error.error);
return;
}

const data = await response.json();
showSuccess(data.message);
} catch (err) {
showError('Network error');
}

Note that fetch does not reject on HTTP errors (404, 500). You must check response.ok. This is the most common mistake when migrating from jQuery AJAX.

Animations

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// jQuery
$('#panel').fadeIn(300);

// CSS (preferred)
#panel {
transition: opacity 300ms ease;
}
#panel.visible {
opacity: 1;
}

// Or Web Animations API
document.getElementById('panel').animate(
[{ opacity: 0 }, { opacity: 1 }],
{ duration: 300, fill: 'forwards' }
);

CSS transitions are more performant than JavaScript animations and should be the default choice.

Incremental migration strategy

Do not rewrite everything at once. Use this approach:

  1. New code: Write all new JavaScript without jQuery
  2. Bug fixes: When fixing a jQuery-dependent file, convert that file to vanilla JS
  3. Feature additions: When adding features to a jQuery file, convert the affected section
  4. Dedicated sprints: Periodically convert remaining jQuery files, starting with the simplest ones

Keep jQuery loaded until the last usage is removed. The loading cost (~30KB gzipped) is less than the risk of a big-bang rewrite.

Common mistakes

Forgetting fetch does not throw on HTTP errors: This causes silent failures where the error handling code never runs.

Removing jQuery before removing jQuery plugins: If you use DataTables or Select2, jQuery must stay until those plugins are replaced or removed.

Not testing form submissions: PHP applications often have CSRF tokens in jQuery AJAX headers. Make sure your fetch calls include the same headers.

1
2
3
4
5
6
7
8
9
// Include CSRF token in fetch (Laravel example)
fetch('/api/data', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content
},
body: JSON.stringify(data)
});

FAQ

Should I use a micro-library instead of jQuery?

Libraries like Cash or Zepto provide jQuery-like syntax at smaller sizes. But if you are migrating away from jQuery, moving to another library just delays the same transition. Go directly to vanilla JS.

Is jQuery still maintained?

Yes. The jQuery team is active and jQuery 4.0 was a significant release. Using jQuery is not inherently bad—it is a well-tested, stable library. The migration motivation is usually reducing dependencies, not avoiding a broken library.

What about TypeScript?

If you are migrating JavaScript anyway, consider writing new code in TypeScript. The type safety catches bugs that both jQuery and vanilla JS miss.

Next steps

Start by auditing your jQuery usage. If you have fewer than 20 $.ajax calls and no heavy plugins, you can probably migrate to vanilla JS in a few days. If you have DataTables across 50 pages, upgrade to jQuery 4.0 instead and save the full migration for a future sprint.

For PHP developers working with jQuery UI widgets, the existing jQuery UI guides on this site cover the functionality you might be replacing. The JavaScript redirect guide shows simple vanilla JS patterns that replace common jQuery patterns.