Guided Tutorials
Refactor Legacy jQuery to Modern Framework
Migrate jQuery code to a modern framework while preserving all existing behavior and test coverage. This tutorial uses vanilla JavaScript with modern DOM APIs as the target, but the same approach applies when targeting Vue, React, or any other framework.
What you'll learn
- How to audit a codebase for jQuery usage and categorize each occurrence
- How to replace jQuery DOM manipulation, events, and AJAX with modern APIs
- How to handle edge cases like animations, plugins, and implicit iteration
- How to migrate file by file with test verification after each step
Before you start
- Autohand Code installed. Run
autohand --versionto confirm. See Your First Autohand Session if you need to install it. - A project with jQuery. This tutorial works on any codebase using jQuery 1.x, 2.x, or 3.x.
- Existing tests, if any. Even basic smoke tests will help you verify the refactoring did not change behavior.
- Git with a clean working tree. Run
git stashor commit any changes before starting so you can diff the results clearly.
Assess the jQuery codebase
Before touching any code, get a clear picture of what jQuery is doing in the project. Some usages are straightforward to replace; others need more thought.
Ask Autohand to map out the jQuery usage:
bash
autohand "Scan all JavaScript files in src/ and categorize every jQuery usage by type: DOM selection, event binding, AJAX, animations, and utilities. List the file and line for each one."A typical output looks like this:
text
jQuery usage in src/ (47 total)
DOM selection (22 occurrences)
src/ui/form.js:12 $('input[name="email"]')
src/ui/form.js:34 $('#submit-btn')
src/ui/modal.js:8 $('.modal-overlay')
...
Event binding (15 occurrences)
src/ui/form.js:45 $(document).on('click', '.btn', handler)
src/ui/tabs.js:19 $tabs.on('click', switchTab)
...
AJAX (5 occurrences)
src/api/client.js:23 $.ajax({ url, type: 'POST' })
src/api/client.js:67 $.get(url, callback)
...
Animations (5 occurrences)
src/ui/modal.js:33 $('.modal').fadeIn(200)
src/ui/dropdown.js:14 $(el).slideDown('fast')
...This inventory tells you where to focus. DOM selection and event binding are trivial to replace. AJAX calls using $.ajax map directly to fetch. Animations require the most judgment since jQuery animations have no direct equivalent without a library.
Tip: Start with files that only use DOM selection and event binding. Leave files with complex animations or plugins for last.
Run the refactoring prompt
Pick a single file to start with. Isolated files with no jQuery plugins are the easiest entry point.
bash
autohand "Refactor src/ui/form.js from jQuery to vanilla JavaScript with modern DOM APIs. Keep all existing behavior identical. Show me a before/after comparison."Autohand will read the file, identify every jQuery call, and produce a refactored version. The response includes an explanation of each substitution so you understand what changed and why.
For files that have AJAX calls, use a more targeted prompt:
bash
autohand "Refactor src/api/client.js. Replace all $.ajax and $.get calls with the native fetch API. Preserve existing error handling and response parsing logic. Use async/await syntax."Review the changes
Autohand produces a side-by-side comparison in its output. Here is what a typical DOM manipulation replacement looks like.
Before (jQuery):
javascript
// Showing/hiding an element
$('#error-message').hide();
$('#error-message').text('Email is required').show();
// Adding/removing classes
$('#submit-btn').addClass('loading').prop('disabled', true);
// Reading form values
const email = $('input[name="email"]').val().trim();
// Event delegation
$(document).on('click', '.delete-btn', function() {
const id = $(this).data('id');
deleteItem(id);
});After (vanilla JS):
javascript
// Showing/hiding an element
const errorMsg = document.getElementById('error-message');
errorMsg.hidden = true;
errorMsg.textContent = 'Email is required';
errorMsg.hidden = false;
// Adding/removing classes
const submitBtn = document.getElementById('submit-btn');
submitBtn.classList.add('loading');
submitBtn.disabled = true;
// Reading form values
const email = document.querySelector('input[name="email"]').value.trim();
// Event delegation
document.addEventListener('click', (e) => {
const btn = e.target.closest('.delete-btn');
if (!btn) return;
const id = btn.dataset.id;
deleteItem(id);
});For AJAX, the replacement is equally direct:
Before:
javascript
$.ajax({
url: '/api/users',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify(payload),
success: (res) => handleSuccess(res),
error: (xhr) => handleError(xhr.responseJSON)
});After:
javascript
fetch('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
})
.then(res => res.ok ? res.json() : res.json().then(err => Promise.reject(err)))
.then(handleSuccess)
.catch(handleError);Verify behavior is preserved
After applying each file's changes, run your test suite immediately. Do not batch multiple files and run tests once at the end.
bash
# After applying the refactored form.js
npm test -- --testPathPattern=form
# Or if you are using Vitest
npx vitest run src/ui/formIf tests pass, commit that file before moving to the next one. This keeps your git history clean and makes any regression easy to bisect.
bash
git add src/ui/form.js
git commit -m "refactor: migrate form.js from jQuery to vanilla JS"Tip: If you have no automated tests, open the page in a browser after each file and manually exercise the interactions the file handles. Click buttons, submit forms, check that animations still occur. Write down what you checked so you do not forget between files.
Handle edge cases
Some jQuery patterns need extra attention. The three most common ones you will encounter are animation replacements, plugin dependencies, and implicit iteration.
jQuery animations
jQuery's fadeIn, fadeOut, and slideDown have no direct vanilla equivalent. The cleanest replacement is a CSS transition paired with a class toggle:
css
/* Add to your stylesheet */
.fade-target {
opacity: 1;
transition: opacity 200ms ease;
}
.fade-target.is-hidden {
opacity: 0;
pointer-events: none;
}javascript
// Instead of $('.modal').fadeIn(200)
document.querySelector('.modal').classList.remove('is-hidden');
// Instead of $('.modal').fadeOut(200)
document.querySelector('.modal').classList.add('is-hidden');Ask Autohand to handle this for you:
bash
autohand "Replace all jQuery animation calls in src/ui/modal.js with CSS class toggles. Add the required CSS transition rules to src/styles/modal.css."jQuery plugins
If your code uses third-party jQuery plugins like Select2, Flatpickr (old versions), or DataTables, you have two options: replace them with framework-independent equivalents, or keep the plugin and only remove the jQuery wrapping code around it.
bash
autohand "The codebase uses Select2 for dropdown enhancement. Find all Select2 initializations and suggest vanilla-compatible replacements that preserve the same UX."Implicit iteration
jQuery silently applies operations to all matched elements. Vanilla JS requires explicit iteration:
javascript
// jQuery - applies to all .tab elements at once
$('.tab').addClass('inactive');
// Vanilla - must iterate explicitly
document.querySelectorAll('.tab').forEach(tab => {
tab.classList.add('inactive');
});Autohand handles this automatically when refactoring, but it is useful to know when reviewing the output.
Iterate on remaining files
Once you have one or two files done, you have a pattern. Apply it to the rest of the codebase file by file, running tests after each one.
To speed up files that are similar in structure, describe the pattern you established:
bash
autohand "Refactor src/ui/tabs.js using the same approach we used for form.js: querySelector for selection, classList for class manipulation, addEventListener for events, fetch for AJAX. Apply it consistently."When all files are done, check that jQuery is no longer imported anywhere:
bash
autohand "Search the entire codebase for any remaining jQuery imports or usages. List them."If the list is empty, remove jQuery from your dependencies:
bash
npm uninstall jquery
# or
bun remove jqueryRun your full test suite one final time. If everything passes, you are done.
What you learned
- Scanned a codebase and categorized every jQuery usage by type
- Replaced jQuery DOM selection, event binding, and AJAX with modern vanilla JavaScript
- Handled animations with CSS transitions and class toggles instead of jQuery effects
- Migrated file by file with test runs after each change to preserve behavior