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';
MethodSize (min, gzip)Trade-off
Full build from a CDN~30 KBA separate blocking request; cached across sites
Full build bundled~30 KB in your bundleOne request; no cross-site cache benefit
Slim build~24 KBNo ajax, no effects β€” you must use fetch
Bundled + tree-shakenNot possiblejQuery is a single monolith; nothing tree-shakes away
No jQuery0 KBYou 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 all
  • noConflict() restores whatever window.$ held before jQuery loaded, and returns the jQuery object.
  • noConflict(true) also gives up the jQuery global β€” 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

AreajQuery 3jQuery 4
IE supportIE 9+Removed entirely
$.ajax internalsUses XMLHttpRequestPrefers fetch with an XHR fallback
Prototype pollution$.extend(true, ...) could pollute__proto__ keys are blocked
Focus eventsfocusin/focusout shimmed for IENative behaviour only; the IE workaround is gone
$.isArray, $.isFunctionPresentRemoved β€” use Array.isArray
$.trimPresentRemoved β€” use String.prototype.trim
jQuery.fn.bind/unbind/delegateDeprecated aliasesRemoved β€” use on/off
AnimationjQuery effectsEffects 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.

Selectors and traversal Migrating off jQuery to vanilla JavaScript

Last refreshed 2026-09-18.