Skip to content

Make optional jQuery require bundler-friendly (#4184)#4304

Open
BigBalli wants to merge 1 commit intojashkenas:masterfrom
BigBalli:fix-4184-jquery-bundling
Open

Make optional jQuery require bundler-friendly (#4184)#4304
BigBalli wants to merge 1 commit intojashkenas:masterfrom
BigBalli:fix-4184-jquery-bundling

Conversation

@BigBalli
Copy link
Copy Markdown

@BigBalli BigBalli commented Apr 6, 2026

Fixes #4184.

Problem

The CommonJS branch of the UMD wrapper does:

var _ = require('underscore'), $;
try { $ = require('jquery'); } catch (e) {}

The try/catch is correct at runtime in Node.js, but Webpack, Rollup,
esbuild, packd and other bundlers statically analyze the literal
require('jquery') call before the try/catch ever runs. Users who
don't actually want jQuery still see their bundles fail with
Cannot find module 'jquery'.

Fix

Indirect the optional require through a variable so the bundler's
static analysis does not follow it:

var nodeRequire = typeof require === 'function' && require;
try { if (nodeRequire) $ = nodeRequire('jquery'); } catch (e) {}

This is the same trick used by ws and other libraries with optional
native deps. Runtime behavior is unchanged: the require still happens
in Node, the try/catch still swallows ENOENT, and Backbone.$ is
still populated when jQuery is installed.

Also declare jquery as an optional peer dependency in package.json
so npm/yarn/pnpm document the relationship cleanly without forcing the
install:

"peerDependencies": { "jquery": ">=1.11.0" },
"peerDependenciesMeta": { "jquery": { "optional": true } }

Test

  • npm run lint passes.
  • Smoke-tested require('./backbone.js') in a fresh Node process with
    no jQuery installed: loads cleanly, Backbone.VERSION === '1.6.1',
    Backbone.$ === undefined as expected.

The literal `require('jquery')` form (even inside try/catch) is
statically analyzed by Webpack, Rollup, esbuild and packd, so users
who do not actually want jQuery still see their bundles fail with
"Cannot find module 'jquery'".

Indirect the optional require through a variable that bundlers do
not follow:

  var nodeRequire = typeof require === 'function' && require;
  try { if (nodeRequire) $ = nodeRequire('jquery'); } catch (e) {}

Behavior is unchanged: jQuery remains optional, the try/catch still
swallows the runtime ENOENT in Node, and `Backbone.$` is still set
when jQuery is installed.

Also declare `jquery` as an optional peerDependency in package.json
so npm/yarn/pnpm document the relationship cleanly without forcing
the install.
@jgonggrijp
Copy link
Copy Markdown
Collaborator

Thank you for the awesome work in this pull request and the previous four!

I made a superficial glance over all five PRs and they look very good to me. I will be reviewing them in more detail (and almost certainly also merging them) over the next two weeks or so. Do you expect any merge conflicts between them, and if so, which order would you recommend?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Cannot find module 'jquery'

2 participants