Use AMD for JS architecture

Created on 22 April 2012, about 12 years ago
Updated 1 May 2023, about 1 year ago

We don't have an helpful way of structuring JS in Drupal core. We have Drupal.behaviors and drupal_add_js. This makes code reuse hard and it's an issue when a script has dependencies. Usually you have to play with the weight option of drupal_add_js to make sure the script is loaded at the right place. This is not a good way to manage dependencies. See at the end what this means for maintainers and contrib.

Using AMD would allow us and contrib to rely on an open and well tested specification https://github.com/amdjs/amdjs-api/wiki/AMD. Several implementations exists, we/contrib won't be locked in with a particular library. Having pluggable JS processing will make it possible if not easy to change the library implementing AMD if someone don't like what is used by core.

AMD loaders have scripts to bundle all the dependencies into one single file removing the problem of having a lot of small files to load. This might need some work since most of them work with node.js/rhino and not PHP.

Having an AMD loader means we'll be loading a 5kb-ish (and in some cases, much less) file to bootstrap the loading process of the rest of the files async. This reduces the time it takes to parse and load the page by a very significant %. Loading everything on page load is also an option if there is a need to support that.

  • The AMD spec has options making some level of sandboxing available (it's possible to use several versions on jQuery on the same page for example).
  • Dependencies can be overriten by config to point to another file, providing a clean way to do the Drupal.behaviors monkey-patching we currently have.
  • Will replace most if not all of Drupal.settings by targeting settings for specific JS modules (or not).
  • There is a way to turn non-AMD library into AMD modules to help with the transition. It's the use! plugin.

Drupal needs to be more involved in the way JS is managed to make JS fast by default for core and especially for contrib. AMD is the only solution we have right now to have Javascript modules that are not bound to a specific JS library or implementation, only to a spec.

Some examples

This is what it looks like:
MyModule.js

define(["jquery", "drupal"], function ($, Drupal) {
  return {
    "run": function () {
      /* do something */
    }
  };
});

SomeOtherFile.js

// using the previous module somewhere else
require(["MyModule"], function (MyModule) {
  MyModule.run();
});

You can see that there is no need to file-level closure anymore.

Using an AMD-compatible loader makes on-demand loading easy and could look something like this:

require(['require', 'jquery'], function (require, $) {

  $('#ajax-link').click(function (e) {
    e.preventDefault();
    var href = this.href;
    
    require(['ui.modal'], function (modal) {
      modal.open(href);
    });
    
  });
  
});

This particular example loads the modal component and all it's dependencies (it can be CSS, JS, JSON or some TXT file for templating) when the link is clicked, not on page load.

Some slides that can help grasp the issue: http://unscriptable.com/code/Using-AMD-loaders/

Changes for Contrib

This will make contrib life easier.

  • Instead of declaring a library on the PHP side all the dependencies will be declared inside the JS file.
  • There is no more need for file-level closures.
  • There will be no need to define a library but there will be a need to define a path to a module, to declare that the module "drupal" refers to the file at "core/misc/drupal.js". We'll have to introduce a hook in PHP to make that work.
  • There is an automatic way to transform non-AMD js files into AMD modules, see the use! plugin linked to earlier.
  • There are ways to override dependencies of other modules with config options.
  • You won't have to play with the weight option to make you JS execute at the right time, just declare a dependency in your module and you code will be executed after the dependency has loaded.

Example of contrib js:

(function ($, Drupal) {

  Drupal.behaviors.myCustomBehavior = {
    attach: function (context, settings) {  
      
    },
    detach: function (context, settings, trigger) {
    
    }
  };
  
}(jQuery, Drupal));

What it will look like with AMD:

define(["jquery", "drupal"], function ($, Drupal) {

  Drupal.behaviors.myCustomBehavior = {
    attach: function (context, settings) {
            
    },
    detach: function (context, settings, trigger) {
    
    }
  };

});

You're replacing the closure with a function. That's about the only change it will involve on your javascript files.

This change is a proposal to use AMD as it was intended. This is not another NIH issue. We chose a JS loader implementing AMD among the good number of already existing/working/having a test suite AMD loaders, and use that.

Here is the sandbox: https://drupal.org/sandbox/nod_/1545078

The current state of things

The sandbox is working and all JS is turned into AMD. like mfer noticed, there are a couple of non-Drupal JS that has been modified to declare them as AMD modules. Aggregation is not possible yet. Currently using 10 js files will load 10 js files, that's no good.

TODO

  • Tweak/extend the library hook to have enough information about a non-AMD JS library to use! them properly with the loader.
  • Once we have this working, remove the AMD wrapping of non-Drupal libs (fabtarstic, cookie.jquery, form.jquery, etc.).
  • Look up some way to minimize as much as possible the work non-AMD js will have to do to be used properly (ideally to a point where a script line in an info file would suffice).
  • Work on making aggregation possible. So we have a simple PHP version of r.js that does "on the fly" optimization. See RobLoach link in #7 for a possible starting point.
πŸ“Œ Task
Status

Closed: won't fix

Version

9.0

Component
JavascriptΒ  β†’

Last updated about 5 hours ago

Created by

πŸ‡«πŸ‡·France nod_ Lille

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Comments & Activities

Not all content is available!

It's likely this issue predates Contrib.social: some issue and comment data are missing.

Production build 0.69.0 2024