Skip to content
unscriptable edited this page Sep 24, 2012 · 7 revisions

Every module or plugin-based resource that curl.js loads goes through a resolution lifecycle. So, because all modules and resources are loaded asynchronously and in parallel, when modules require other dependent modules, they may find those dependencies in any of six states. (Yes, six!) curl.js uses promises internally to help simplify the interaction of these modules.

Resolution lifecycle states

Not all modules or resources enter all of these states, as explained below:

  1. Unknown: The module does not exist in the loader, yet.
  2. In-flight: The module is being fetched from a server. When using AMD-optimized files, this state won't be entered if the module is built into the optimized file.
  3. Waiting or Exportable: Once a module is fetched, it is scanned for dependencies. Any module that has dependencies may go into a waiting state if any of those dependencies are not yet resolved. Plugin-based resources may be waiting for a text file or a non-AMD file to be fetched at this point. Certain modules that use the exports pattern (a.k.a. "exportable" modules) can expose their exports to other modules at this point in the resolution lifecycle. Any module that explicitly or implicitly declares the "exports" pseudo-module as a dependency is assumed to be exportable and enters the exportable state instead of waiting. Exportable modules allow circular dependencies to be resolved by passing their exports objects to dependent modules before they have been assigned methods or properties. (Modules that work in this fashion must be coded carefully so they don't try to use their dependencies too early. You would never, ever purposely code a circular dependency into your packages anyways, right?)
  4. Executed: when exportable modules receive all of the exports from their dependencies (or return values from non-exportable dependencies), they execute their factory function. (Unwrapped CommonJS Modules are given a factory automatically.) At this point, any other, dependent exportable modules are notified.
  5. Resolved: once a module is notified that all its exportable dependencies are executed and that all of it's non-exportable dependencies, such as normal AMD modules or plugins, are resolved, it enters the resolved state. The factories of non-exportable modules (normal AMD modules) are executed and their return values are returned to any other waiting modules. Plugin-based resources are simply returned in whatever form they take (text strings, pointers to style sheets, functions, etc.).
  6. Cached: Finally, the module's exports or return value or the plugin-based resource's return result is cached, and any future request for this module is fetched from the cache directly.

Typical Lifecycles

Normal AMD modules go through the following steps:

Unknown --> In-flight (if not in optimized file) --> Waiting --> Resolved --> Cached

Pure CommonJS modules go through all of the steps:

Unknown --> In-flight (if not in optimized file) --> Exportable --> Executed --> Resolved --> Cached

AMD modules that explicitly depend on the "exports" or "module" pseudo-modules are treated as CommonJS modules:

Unknown --> In-flight (if not in optimized file) --> Exportable --> Executed --> Resolved --> Cached

Plugin-based resources go through a much shorter lifecycle:

Unknown --> In-flight (if not in optimized file) --> Resolved --> Cached

curl.js advantages

Because of the complexity of dealing with CommonJS modules, circular dependencies, and plugins, many other AMD tools just don't bother. Some handle the complexity by introducing next-turn semantics. Rather than attempt to deal with the asynchronous nature of script loading, next-turn tools defer dependency resolution until some future time when the state of the modules is more clear. Some times this is done with a setTimeout() (ewww). Other times it is done by looping over and rechecking all unresolved modules any time a new module is loaded (ouch, my cpu).

In either case, next-turn tricks slow down the loading process by introducing unnecessary delays. These delays can add up. For instance, during development time, if you're loading an unoptimized project, curl.js is significantly faster. We did a very unofficial test with approximately 100 modules and/or plugin resources in IE7, and found that curl.js loaded the project up to one minute faster than loaders that use next-turn tricks. Debugging is already slow enough, isn't it?

At the time this document was written, curl.js is not only the fastest AMD loader, it's also the only AMD loader that doesn't use next-turn tricks and offers all of the following:

  • Circular dependency resolution within graphs of exportable modules.
  • CommonJS modules that depend on AMD modules and plugins.
  • AMD modules and plugins-based resources that depend on CommonJS modules (whether or not those modules are in a circular dependency).
  • AMD modules that mimic CommonJS module semantics and are "exportable".