Loading jQuery and Bootstrap in Electron app has a curious bug

; Date: September 2, 2017

Tags: Node.JS »»»» Electron

What about using jQuery/Bootstrap to design an Electron app? It's easy to get started, right? You just load the jQuery and Bootstrap JavaScript libraries in that order. The process is well documented on the Bootstrap website, and since Electron is a web browser the same technique should work. But -- you get an inscrutible error that jQuery needs to be loaded. Which is odd because the HTML does indeed load jQuery, and in the Developer tools you can inspect the loaded files and see that jQuery is loaded. WTF?

The most useful way to load jQuery and Bootstrap in Electron is to install the npm modules and directly use the files in the respective dist directories. That way you're not dependent on a 3rd party service, and are instead in control of your destiny.

In your Electron app project directory install the required modules as so:

$ npm install jquery --save
$ npm install bootstrap@4.0.0-beta --save
$ npm install popper.js --save

As of this writing Bootstrap 4 has been released as a Beta, and therefore we can install it using that tag. With Bootstrap v4 they've introduced Popper, which must be installed this way.

Let's now inspect what we've installed:

$ ls node_modules/jquery/dist/
core.js			jquery.min.js		jquery.slim.js		jquery.slim.min.map
jquery.js		jquery.min.map		jquery.slim.min.js

$ ls node_modules/bootstrap/dist/js
bootstrap.js		bootstrap.min.js

$ ls node_modules/bootstrap/dist/css/
bootstrap-grid.css		bootstrap-reboot.css		bootstrap.css
bootstrap-grid.css.map		bootstrap-reboot.css.map	bootstrap.css.map
bootstrap-grid.min.css		bootstrap-reboot.min.css	bootstrap.min.css
bootstrap-grid.min.css.map	bootstrap-reboot.min.css.map	bootstrap.min.css.map

$ ls node_modules/popper.js/dist/umd/
popper-utils.js		popper-utils.min.js	popper.js		popper.min.js
popper-utils.js.map	popper-utils.min.js.map	popper.js.map		popper.min.js.map

Therefore, you can add this to your HTML file(s) in your Electron app.

<html>
<head>
    ... 
        <link rel="stylesheet" href="../node_modules/bootstrap/dist/css/bootstrap.min.css">
    ...
</head>
<body>
    ...

    <script src="../node_modules/jquery/dist/jquery.js"></script>
    <script src="../node_modules/popper.js/dist/umd/popper.js"></script>
    <script src="../node_modules/bootstrap/dist/js/bootstrap.js"></script>
</body>
</html>

That's a straight translation of the HTML snippets in the Bootstrap installation instructions into the conditions we have in the Electron app. BTW, if you want to use the minified versions of these libraries, modify the paths to include .min.js to reference the minified files.

Start the application and you'll see this in the JavaScript console:

Uncaught Error: Bootstrap's JavaScript requires jQuery

What's up? Again, you can see clearly that jQuery is loaded first, and you can see clearly in the developer tools that the jQuery module was indeed loaded first.

The issue is clear once you view the source code. The jQuery library starts with this:

if ( typeof module === "object" && typeof module.exports === "object" ) {

    // For CommonJS and CommonJS-like environments where a proper `window`
    // is present, execute the factory and get jQuery.
    // For environments that do not have a `window` with a `document`
    // (such as Node.js), expose a factory as module.exports.
    // This accentuates the need for the creation of a real `window`.
    // e.g. var jQuery = require("jquery")(window);
    // See ticket #14549 for more info.
    module.exports = global.document ?
        factory( global, true ) :
        function( w ) {
            if ( !w.document ) {
                throw new Error( "jQuery requires a window with a document" );
            }
            return factory( w );
        };
} else {
    factory( global );
}

The code is a little obtuse. The effect is that if jQuery is executing in a Node.js/CommonJS environment it does not create a global jQuery object. While Electron is a Chrome browser, it also has Node.js support including the module and module.exports objects, and the require function, and so forth. Therefore when executed under Electron, jQuery runs the first branch and does not add itself to the global object, and instead exports itself via module.exports.

When Bootstrap runs, this test then fails to find jQuery because there's no global jQuery object.

if (typeof jQuery === 'undefined') {
  throw new Error('Bootstrap\'s JavaScript requires jQuery. jQuery must be included before Bootstrap\'s JavaScript.')
}

Okay, we now see the problem. When run under Node.js or Electron, jQuery doesn't add itself to the global object. Hurm...

This is the very simple solution. Instead of loading jQuery the normal way, load it this way:

<script>
window.jQuery = window.$ = require('jquery');
</script>

<!-- <script src="../node_modules/jquery/dist/jquery.js"></script> -->

You'll see in the developer tools JavaScript console no inscrutible error message about how jQuery must be loaded before Bootstrap.

Hat tip to: (stackoverflow.com) stackoverflow bootstrap-wont-detect-jquery-1-11-0-uncaught-error-bootstraps-javascript-re