Setting up jQuery and the module story
Load jQuery from a CDN or a bundler, pick the slim build, handle a page that already uses a different dollar sign, and know what changed in jQuery 4.
Four ways to load it
<!-- 1. CDN, classic script. Must come before any script that uses $ -->
<script src="https://code.jquery.com/jquery-3.7.1.min.js"
integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo="
crossorigin="anonymous"></script>
<script src="/js/app.js"></script># 2. npm, for a bundler
npm install jquery
npm install --save-dev @types/jquery
# the slim build omits ajax and effects, saving about 20 KB minified
npm install [email protected]/dist/jquery.slim.min.js// 3. ESM through a bundler
import $ from 'jquery';
$(function () {
$('.js-toggle').on('click', function () { $(this).toggleClass('is-open'); });
});
// 4. CommonJS in Node, for tests or a legacy build pipeline
const $ = require('jquery');
module.exports = $; // some legacy plugins expect this shape
// The slim build has no $.ajax, no .animate, no .fadeIn/.slideUp.
// Everything else β selectors, traversal, manipulation, events β is present.
import $slim from 'jquery/dist/jquery.slim';| Method | Size (min, gzip) | Trade-off |
|---|---|---|
| Full build from a CDN | ~30 KB | A separate blocking request; cached across sites |
| Full build bundled | ~30 KB in your bundle | One request; no cross-site cache benefit |
| Slim build | ~24 KB | No ajax, no effects β you must use fetch |
| Bundled + tree-shaken | Not possible | jQuery is a single monolith; nothing tree-shakes away |
| No jQuery | 0 KB | You rewrite the call sites |
π‘
If the page already loads jQuery from a CDN, do not bundle a second copy. Two jQuery instances on one page produce a plugin that registers on one and is called on the other β which fails with the maddening message "$(...).plugin is not a function".
$ versus jQuery and noConflict
// jQuery always exposes both names. $ is a convenience alias.
console.log($ === jQuery); // true
// If another library owns $ (an old Prototype.js page, for example):
$.noConflict(); // releases $, keeps jQuery
jQuery('.card').addClass('ready');
// Release $ and immediately rebind it to your own scope:
(function ($) {
$('.card').addClass('ready');
})(jQuery);
// Release both names and capture jQuery under a private one:
const $jq = jQuery.noConflict(true);
$jq('.card').addClass('ready');
// Inside a module, noConflict is unnecessary: the import is already local.
// import $ from 'jquery'; // no global is created at allnoConflict()restores whateverwindow.$held before jQuery loaded, and returns the jQuery object.noConflict(true)also gives up thejQueryglobal β needed when a second jQuery version is going to load.- Calling noConflict does not change anything about the internal API; it only removes the global alias.
- In a bundler setup none of this matters, which is one more reason to migrate to imports even if you keep using jQuery.
What changed in jQuery 4
| Area | jQuery 3 | jQuery 4 |
|---|---|---|
| IE support | IE 9+ | Removed entirely |
$.ajax internals | Uses XMLHttpRequest | Prefers fetch with an XHR fallback |
| Prototype pollution | $.extend(true, ...) could pollute | __proto__ keys are blocked |
| Focus events | focusin/focusout shimmed for IE | Native behaviour only; the IE workaround is gone |
$.isArray, $.isFunction | Present | Removed β use Array.isArray |
$.trim | Present | Removed β use String.prototype.trim |
jQuery.fn.bind/unbind/delegate | Deprecated aliases | Removed β use on/off |
| Animation | jQuery effects | Effects moved to a separate module you can omit |
// Things that stop working in jQuery 4, with the replacement
// $.isFunction(fn) -> typeof fn === 'function'
// $.isArray(list) -> Array.isArray(list)
// $.trim(str) -> str.trim()
// $.now() -> Date.now()
// $('#x').bind('click', fn) -> $('#x').on('click', fn)
// $('#x').delegate('.y', 'c', fn) -> $('#x').on('c', '.y', fn)
// $.ajax still exists in the default build; the fetch-based path changes
// some edge cases around progress events and aborting, so test uploads.
$.ajax({ url: '/api/items', method: 'GET' }).done((items) => console.log(items));Nothing else in the rest of this course changes between versions 3 and 4. If your code uses on, attr and the traversal methods rather than the deprecated aliases, the upgrade is close to mechanical β which is itself an argument for writing jQuery in its modern style even in a legacy codebase.
FAQ
Should I use the slim build?
Yes if you have already moved to
fetch and you are not using jQuery's animation shortcuts. It removes the two largest modules. Check for .animate, .fadeIn, .slideUp, $.ajax, $.get and $.post first, because they fail silently as undefined methods.Why is my plugin undefined after an upgrade?
Usually two jQuery instances: the CDN copy and a bundled copy, or a plugin loaded before jQuery itself. Check with
window.jQuery.fn.jquery and the plugin's own $.fn property, then make sure exactly one jQuery loads first.Related
Selectors and traversal Migrating off jQuery to vanilla JavaScript
Last refreshed 2026-09-18.