« Previous Next »
02 September 2015

Bundling widgets with AMD

We’ve gotten a lot of questions recently about Mendix widgets. Specifically, about how and why we bundle them. In this post we’ll go through the reasons why and end up with some pointers on how custom widget developers can make sure their widgets work and keep on working in the future.

Need for speed

Performance is a big topic for us this year. Scaling and runtime performance are getting a lot of love this year, but we’re also doing a lot on the clientside. One thing a lot of our users were complaining about was client load times. When we benchmarked the appstore, we noticed that a lot of time was going into downloading a zillion little files, mostly custom widgets.

When we did some benchmarks with all widgets bundled into one single file, we noticed huge improvements: initial load of our appstore (which is a Mendix app, obviously) decreased by more than 45%. Even more impressive, reloads were 75% faster due to the combination of improved caching and bundling.

Combining JS

So, how do we bundle everything in one file? One solution we could have gone for was to simply concat all files together. Unfortunately, this causes new problems: what if your widget requires some other library to be loaded before your widget is loaded?

There are a bunch of ways to bundle javascript. Going through all of them is probably more suited for a separate blog post, we chose to go for the dojo build system and advising people to structure their code as AMD modules. AMD is not the newest kid on the block, but is mature and widely supported. This setup also allowed us to bridge the gap between our legacy dojo architecture while allowing us to move forward. Note that although we advise people to adhere to AMD, the build system is actually still compatible with dojo require and the ‘old’ way of loading and declaring modules.

AMD

Small detour: how does AMD work? Well, let’s say you have a javascript, MyWidgetLibrary.js, that you’d like to use in another piece of code, MyWidget.js AMD let’s you express this via define:

define("MyWidget",["MyWidgetLibrary"], function(myWidgetLibrary){
    // widget implementation
});

This basically says: “declare MyWidget so that other people can depend on me, and I depend on MyWidgetLibrary”. MyWidgetLibrary is loaded first and passed as a parameter to your widget implementation, so that it can be used there. Note that the first argument of the define call (“MyWidget”) is optional (!): if you omit this it will simply take the name of the file and declare it as such. Ie: if your file structure looks like this:

.
|-- MyWidget.js
`-- MyWidgetDirectory
    `-- MyWidgetLibrary.js

Your MyWidget.js file would probably contain something like: define([“MyWidgetDirectory/MyWidgetLibrary”], function(myWidgetLibrary){ // widget implementation });

and your MyWidgetLibrary.js file would contain something like this:

define([], function(){
    // widget library implementation
});

In the Mendix client, we load widgets by having a widget.js file which simply contains the names of all widgets as defined in package.xml. An example would be:

define(["calendar/widget/calendar"], {});

A question we got a lot when introducing this behavior was about why we were no longer advising people to use require, instead opting for define. The difference between these two keywords is very subtle:

Using **`define()`** you are asking something like *"run the function that I am passing as a parameter and assign whatever returns to the ID that I am passing but, before, check that these dependencies are loaded"*.

Using **`require()`** you are saying something like *"the function that I pass has the following dependencies, check that these dependencies are loaded before running it"*.

Main screen

Wait wut? Why would these be different? It turns out, require can actually also be described as: “run this function someday, but if you do, make sure my dependencies are loaded” whereas define is “if someone asks for me, load me immediately”.

You actually normally don’t really notice the difference in (desktop) browsers, but it really causes issues when loading in crossdomain mode, which Mendix hybrid mobile applications do. In crossdomain mode, race conditions occur due to the fact that the require implementation uses <script> tags to load libraries, which is asynchronous, causing apps to fail to load altogether.

jQuery

Another issue that some of our users have run into is the use of jQuery. Although it’s a very useful library, jQuery is terrible at playing well with others. The reason for this is the following on line 10312 of the jQuery source

define( "jquery", [], function() {

Although this looks relatively harmless, this causes havoc if you want to load multiple jQuery versions. The reason is that they explicitly define their library as “jquery” instead of letting the filepath determine an appropriate name. This causes two problems.

First of all, you run into an issue if you want to put jQuery in a different directory: if you have a directory structure such as:

put jQuery in a different directory: if you have a directory structure such as:

.
|-- MyWidget.js
|-- MyWidgetDirectory
|   `-- MyWidgetLibrary.js
`-- lib
    `-- jquery-1.11.3.js

and you want to depend on jQuery from your MyWidget.js file, you’d get:

define(["lib/jquery-1.11.3"], function(myWidgetLibrary){
    // widget implementation
});

Which loads the jQuery file but doesn’t find anything, because the only thing that the file declares is “jquery”, which we’re not looking for… You can work around this by applying some magic in your toplevel require (a javascript app should have one and only one require function call) where you tell your loader to match “jquery” to “lib/jquery-1.11.3”, but that doesn’t allow custom widget writers to load their own version (some people want jquery 2.x, others want 1.x). In fact, you’re creating situations where jQuery will try to either overwrite another instance or not load the version you really want to get to.

This also results in unpredictable behavior when you’re using jQuery because you want to load a jQuery plugin: which instance of jQuery is the plugin going to bind to?

Second problem we run into is that loading two jqueries usually results in a “multipleDefine” error being thrown due to the fact that two different javascript files both claim to be “jquery”.

For the time being, we recommend that custom widget writers take an uncompressed jQuery version and strip the “jquery” identifier from the define call, as so:

define( [], function() {

and then depend on the full path to your copy of jQuery. Unfortunately, if you’re planning on using more libraries which in turn depend on jquery, you’ll have to update the paths to jquery there as well.

We hope this clears up why we bundle javascript and which pitfalls to avoid (and more importantly, how to work around them). Leave a comment if you have any questions!

Posted by Achiel

Product Manager for the web

blog comments powered by Disqus