Search code examples
jqueryrequirejsamdvelocity.js

velocity.ui intermittently fails with the error "Velocity UI Pack: velocity must be loaded first."


When I load velocity.ui, I get an intermittent error on the console:

Velocity UI Pack: Velocity must be loaded first. Aborting.

and none of velocity.ui's animations are available.

I can reproduce the issue with something as simple as this:

<script>
require.config({
  baseUrl: "/static/lib/",
  paths: {
    jquery: "external/jquery",
    velocity: "external/velocity/velocity.min",
    "velocity.ui": "external/velocity/velocity.ui.min",
  }
});

require(["jquery", "velocity.ui"]);
</script>

When I look at the network requests, I see that velocity has loaded before velocity.ui. And velocity.ui does have define(["velocity"], ....


Solution

  • Solution

    If your project uses jQuery at all, for any reason, then you must make velocity dependent on the jquery module. Otherwise, you'll run into the problem you ran into. Create a module called velocity-glue:

    define(['velocity', 'jquery'], function (velocity) {
      return velocity;
    });
    

    Modify your configuration so that it contains these two entries.

    map: {
      "*": {
        velocity: "velocity-glue",
      },
      "velocity-glue": {
        velocity: "velocity",
      },
    }
    

    The first entry makes it so that velocity-glue is used whenever velocity is required. The second entry makes sure that velocity-glue can get the original velocity.

    Explanation

    I'm going to use "Velocity" to refer to the product globally: this includes both modules (velocity and velocity-ui).

    The problem is that Velocity is not a good AMD citizen. By this, I mean that its modules depend on globals and export themselves as globals, even when they are loaded through an AMD loader, which is bad behavior for AMD modules.

    Velocity can be used with or without jQuery. When Velocity is used without jQuery, it uses a kind of minimal polyfill that provides only as much of jQuery's functions as Velocity needs. Since Velocity can be used with or without jQuery, its modules not not list jQuery in their dependencies when they call define, otherwise, they would require you to use jQuery.

    Whether or not Velocity is loaded as an AMD module, it installs itself so that it will be accessible as the Velocity property on one of these if they existr: window.jQuery window.Zepto, or window. Velocity will use the first one that it can use in the order given here. So if neither jQuery nor Zepto are installed, it will be accessible as window.Velocity. But if jQuery is installed, it will be accessible as window.jQuery.Velocity. The problem is that both velocity and velocity.ui perform the computation choosing between the 3 possible locations independently. Let's consider some scenarios:

    Scenario A:

    1. jquery loads.
    2. velocity installs as window.jQuery.Velocity.
    3. velocity.ui looks for window.jQuery.Velocity. Fine.

    Scenario B:

    [jquery is not loaded yet.]

    1. velocity installs as window.Velocity.
    2. velocity.ui looks for window.Velocity. Fine.

    (jquery may or may not load later. It does not matter.)

    Scenario C:

    1. velocity installs itself as window.Velocity because jQuery is not loaded.
    2. jquery loads.
    3. velocity.ui looks for window.jQuery.Velocity because window.jQuery does exist, but window.jQuery.Velocity does not exist! You get an error.

    Note that in the scenarios above velocity will always load before velocity.ui because velocity.ui lists velocity as its dependency. However, the loading order of jQuery relative to the two other modules is not constrained because (as already mentioned) they do not depend on jquery.

    The solution I've given in the first section to this answer fixes the issue by constraining the load order: jquery will always load before both modules.