jQuery global. So, what to do?
Just shim it
If you’re dealing with code that wants a global variable, just give it the global variable! Webpack does not break global resolution, so code that uses globals will work fine inside a Webpack build.
// jquery-shim.js import jQuery from "jquery"; window.$ = window.jQuery = jQuery; // now this code will continue to work $(() => console.log("Document ready"));
It is tempting to put these shims into the root file of your Webpack entry. However, be aware that imported modules are always evaluated before the module itself. So if you need to shim something for an import, put the shim in an import too:
// root.js console.log("root is evaluated"); import "shim"; import "legacy_module"; // shim.js console.log("shim is evaluated"); // legacy_module.js console.log("legacy_module is evaluated");
shim is evaluated legacy_module is evaluated root is evaluated
As you can see, a shim defined in
root.js will be evaluated too late to take effect. But a shim in a separate import evaluates in time.
This goes for any kind of order-dependent initialization, too — error reporting, etc — don’t put it directly into the root module.
ProvidePlugin is an alternative to creating global variables. It will find free variable usage (like
jQuery), and substitute it with a module import.
Pros: no global variables! It does work with
window. properties too.
Cons: you cannot enable it for one entry only. In my example, the admin JS bundle uses jQuery, but the main app bundle doesn’t. But now any reference to
jQuery will create an dependency. Unfortunately, many legacy libraries will check if
jQuery is present in global scope, to inject their features — even if you don’t use jQuery and don’t need the features. So, the app bundle will inadvertently include a hefty jQuery module.
Also, ProvidePlugin requires code parsing, so it slows down the build — in our case, by about 5%.
My suggestion is to avoid ProvidePlugin unless you absolutely want to avoid exposing the global variables.
Sideload the shim + Externals
You might want to keep the shim outside of the Webpack entry. For example, you could load it from a CDN. Or, many legacy libraries fail to build within Webpack - for a number of reasons. You can load the library with a separate
As long as the sideloaded script creates a global variable, and your code uses it, you’re good. But if the same bundle has other, modern modules that depend on the jQuery NPM package and do
import 'jquery' — you end up with two jQuery versions. Not only does this bloat the build, but also the jQueries don’t share the same namespace — so each will have a separate set of settings and plugins.
The feature to solve this is Webpack externals. Externals are, in a way, opposite to ProvidePlugin - they let you “reroute” a module import to a global variable. Note, Webpack does nothing to make sure the global is defined - that’s your job.
You would need externals if…
- …you are loading a library into a global variable — from CDN, or a sideloaded script,
- …but you still want to reference it with
importstatements (or have dependencies that do).
If you are shimming a global from a module (like
window.$ = require('jquery')), you don’t need externals.
- Try solving the legacy dependency issue by assigning a module import to a global first.
- If you still get “xxx is undefined” errors, check the order of evaluation.
- If the dependency is so problematic that you cannot
importit, load it in a separate
<script>tag, and use as a global.