How to Convert jQuery to JavaScript: A TypeScript-Powered Migration Guide for Beginners
Converting jQuery to vanilla JavaScript involves installing type definitions to map existing usage, enabling strict TypeScript checks to catch unsafe patterns, then systematically replacing $ selectors with native DOM APIs while leveraging the compiler's precise diagnostics to ensure complete migration.
Moving away from jQuery reduces bundle size and eliminates unnecessary abstractions, and the TypeScript compiler from the microsoft/TypeScript repository provides built-in tooling to guide this transition safely. By treating the migration as a type-driven refactoring, you can convert legacy code to modern standards without breaking functionality. Below is a proven workflow that uses the compiler's own error detection and native API typings to replace jQuery step-by-step.
Step 1: Map Existing jQuery Usage with Type Definitions
Before removing any code, you must establish a baseline of where jQuery is actually used. Installing the official type definitions allows the compiler to recognize every $ reference in your project.
Install the types as a dev dependency:
npm install -D @types/jquery
This step is critical because, as implemented in microsoft/TypeScript, the language service contains a specific diagnostic that prompts users to install these exact typings when $ is missing. In src/services/codefixes/importFixes.ts (lines 176–182), the compiler defines the message: "Cannot find name '$'. Do you need to install type definitions for jQuery?" This same mechanism will later help you confirm when you have successfully removed all jQuery references.
Step 2: Enable Strict Type Checking to Expose Unsafe Patterns
With type definitions in place, configure your tsconfig.json to surface hidden assumptions in the jQuery code. Enable strict mode to prevent the $ object from silently returning any types.
Set the following compiler options:
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true
}
}
These settings force explicit typing on DOM interactions. Once you remove the jQuery types in later steps, the compiler will immediately flag every location where unsafe shortcuts were previously allowed, creating a precise checklist of required changes.
Step 3: Identify Every $ Reference for Systematic Replacement
After uninstalling @types/jquery and removing the library from your package.json, run the compiler to generate an exhaustive list of remaining jQuery calls.
Execute a type check without emitting files:
tsc --noEmit
The resulting "Cannot find name '$'" errors provide a line-by-line inventory of what needs rewriting. This pattern is tested explicitly in the TypeScript source itself; in src/testRunner/unittests/tsserver/typingsInstaller.ts (lines 90–101), the test suite demonstrates a mock project that declares $ and then validates the missing-identifier diagnostic. You can use this same approach to track your migration progress programmatically.
Step 4: Replace jQuery DOM Helpers with Native Browser APIs
With your checklist of $ usages ready, replace each jQuery method with its native equivalent. The TypeScript DOM library—defined in lib.dom.d.ts—provides full type definitions for these replacements.
Selectors and Event Listeners
Replace jQuery's $() wrapper with standard selection methods and attach listeners using the native event API:
// jQuery approach
const items = $('.menu-item');
$('#btn').on('click', (e) => console.log(e.target));
// Vanilla JavaScript with TypeScript types
const items: NodeListOf<HTMLElement> = document.querySelectorAll('.menu-item');
const btn = document.getElementById('btn');
btn?.addEventListener('click', (e) => console.log(e.target));
Using document.querySelectorAll returns a NodeListOf<HTMLElement>, which provides typed access to element properties without runtime wrappers.
AJAX and Network Requests
Swap jQuery's $.ajax, $.get, and $.post with the standard fetch API. This eliminates callback-based patterns in favor of native Promise objects that integrate cleanly with TypeScript's async/await typing:
// jQuery AJAX
$.get('/api/data', (data) => {
console.log(data);
});
// Modern fetch with async/await
async function loadData(): Promise<void> {
const response = await fetch('/api/data');
const data = await response.json();
console.log(data);
}
loadData();
The fetch function and Response object are fully typed by the DOM lib included with TypeScript, ensuring type safety for headers, body parsing, and error handling.
Animations and Effects
Replace jQuery's .animate() chains with CSS transitions or the Web Animations API for hardware-accelerated performance:
// jQuery animation
$('#box').animate({ opacity: 0 }, 500);
// Web Animations API
const box = document.getElementById('box');
box?.animate([{ opacity: 1 }, { opacity: 0 }], { duration: 500 });
The Element.animate method is defined in the TypeScript DOM library and accepts typed keyframe objects and timing parameters.
Step 5: Remove the jQuery Dependency and Validate
Once all compiler errors are resolved, eliminate the runtime dependency entirely. Remove import statements and script tags, then prune your dependencies:
npm uninstall jquery @types/jquery
npm prune
Run your full test suite to verify functional parity. The microsoft/TypeScript repository demonstrates robust CI practices in .github/workflows/ci.yml, which you can emulate to automate testing in your own pipeline. Additionally, configure ESLint with rules like no-undef to catch any stray global $ references that the type checker might have missed in non-TypeScript files.
Summary
- Install type definitions first (
@types/jquery) to establish a baseline of all jQuery usage, leveraging the diagnostic logic found insrc/services/codefixes/importFixes.ts. - Enable strict mode in
tsconfig.jsonto force explicit typing and expose unsafe jQuery shortcuts before you begin rewriting. - Use
--noEmiterrors as a migration checklist, following the testing pattern shown insrc/testRunner/unittests/tsserver/typingsInstaller.ts. - Map jQuery methods to native APIs:
querySelectorAllfor selection,addEventListenerfor events,fetchfor AJAX, and the Web Animations API for effects—all fully typed inlib.dom.d.ts. - Remove the dependency only after the compiler reports zero
$errors, then validate with automated tests and linting.
Frequently Asked Questions
Do I need to rewrite everything at once when converting jQuery to JavaScript?
No. A phased approach is safer. Keep the jQuery types installed initially to see the full scope of usage, then remove them section by section. The TypeScript compiler will emit "Cannot find name '$'" errors only for the files you have processed, allowing you to migrate incrementally while maintaining a working build.
Why does TypeScript suggest installing jQuery types automatically?
The language service includes a specific code fix, defined in src/services/codefixes/importFixes.ts (lines 176–182), that detects the absence of the $ identifier and suggests installing @types/jquery. This diagnostic exists because jQuery is historically common, and the compiler is designed to help developers quickly resolve missing identifier errors for popular libraries.
Can I use TypeScript to migrate jQuery code even if the project isn't fully typed yet?
Yes. Start by enabling noImplicitAny rather than full strict mode. This will catch the most dangerous implicit any returns from jQuery methods without forcing you to type every variable immediately. You can tighten the rules gradually as you replace jQuery calls with explicitly typed native DOM APIs.
What is the performance benefit of replacing jQuery animations with native APIs?
Native animations using the Web Animations API or CSS transitions are GPU-accelerated and do not incur the overhead of jQuery's animation queue or property parsing. As shown in the lib.dom.d.ts definitions, the native Element.animate method operates directly on the browser's compositor thread, resulting in smoother frame rates and lower memory usage compared to jQuery's JavaScript-based tweening engine.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →