Changes to “require()” syntax

In version 1.12 we changed the structure of the SDK, removing the idea of “packages” and relocating all the SDK’s modules under the “lib” directory. This changed, and greatly simplified, the require() syntax used to import modules.

This change would break backwards compatibility, so we implemented a shim to ensure that most common usages of require() would be unaffected by the change. Even so, it’s important to update to the new form when you can, and to use the new form for new code.

What’s require()?

With the SDK, you import objects from other modules using the require() statement. This is how you use modules supplied by the SDK itself, like page-mod and panel, and how you use modules supplied by the wider community, like menuitems and toolbarbuttons. You can also structure your own code into separate modules, then use require() to import objects from these local modules.

The require() statement takes a single argument, which tells the SDK which module you need. In version 1.12, we changed the way the SDK interprets this argument.

How did require() work before?

Before version 1.12 of the SDK, the algorithm used to find modules was based on the idea of “packages”. A package is a collection of modules. Most of the modules in the SDK belonged to either the addon-kit or the api-utils packages. An add-on was itself a package, and community-developed modules like menuitems were delivered in packages of their own. A package declares a dependency on another packages, if it wants to use modules from that package.

The module search algorithm was pretty complicated, but in its most basic form: the SDK would build a list of packages, always starting with the package that contained the module doing the require() and followed by the dependencies of that package. The SDK would treat the argument to require() as a path to a module file, and would search all packages in its list for a matching file, starting at the package’s “lib” directory.

So if an add-on called “my-addon” declares one additional dependency, on the “menuitems” package, it would have four packages in its search list:

["my-addon", "addon-kit", "api-utils", "menuitems"]

If a module in “my-addon” contains a require statement like:

require("some-module");

The SDK will search the following paths in order:

my-addon/lib/some-module.js
addon-kit/lib/some-module.js
api-utils/lib/some-module.js
menuitems/lib/some-module.js

This means that add-ons can import modules from all four packages using only the module name as an argument:

require("my-local-module"); // load from my-addon
require("panel");           // load from addon-kit
require("match-pattern");   // load from api-utils
require("menuitems");       // load from menuitems

This seems like a nice feature, but it’s tricky, because you can’t tell just from looking at a require() statement which module will be imported. If module names clash, unexpected modules might be imported.

How does require() work now?

In version 1.12 of the SDK we removed the concept of “packages”. All SDK modules were relocated under a new “lib” directory directly under the SDK root. Along with this, a much simpler algorithm for importing from modules was implemented:

  • to import objects from SDK modules, specify the full path to the module starting from, but not including, the “lib” directory:

require("sdk/panel");
require("sdk/page-mod/match-pattern");
  • to import objects from modules in your add-on, specify a path relative to the importing module:

require("./my-module");
require("./subdirectory/another-module");

Obviously, this change would break every SDK add-on in existence. To prevent this we added a file, “mapping.json”, which maps old-style require() statements to their new counterparts. For example:

"panel": "sdk/panel"

With this in place, most users of the SDK are unaffected by the change.

Although “mapping.json” means existing add-ons will still work without needing an update, it’s important to update your code to the new style when you can, and to use the new style in future. For one thing, any new modules we add, like IndexedDB, won’t be added to “mappings.json”, so you’ll have to use the new style if you want to use these modules.

Community-developed modules

Eventually, you’ll be able to import objects from modules outside the SDK by passing a URL to require(), but we don’t support that, yet. In the meantime there are two alternative approaches:

  • you can still use third-party packages, copying them into the “packages” directory under the SDK root and declaring your dependency on them, as the SDK tutorial outlines.
  • you can copy the modules you need to use (and any additional modules that these modules require()) into your add-on, and treat them as local modules, using relative paths in require(). But if you do this you may need to rewrite any require() statements inside these modules to be in line with the new form.

2 comments on “Changes to “require()” syntax”

  1. Brett Zamir wrote on

    I’m sorry I did not mention this in the bug report to which I contributed, but why can’t the shorter form be allowed and even recommended for the high-level APIs or even low-level ones?

    Just ensure the names stay unique within the API (or make them unique), and people can reference their own modules by relative path to avoid clashes. It was the succinctness which made the API not only easier to type, but I think appears more welcoming to newcomers (as perhaps also demonstrated by the mass appeal of the likes of PHP, an API which also does not impose namespacing on built-in modules thus freeing users from needing to remember paths or other such implementation details).

    (And I’m also biased in wanting the API to be more friendly to cross-browser usage via my AsYouWish addon: https://github.com/brettz9/asyouwish/.)

    Thanks!

    1. Jeff Griffiths wrote on

      Hi Brett,

      We had this exact discussion. To be clear, require(‘panel’) will still work but we decided to provide examples that preferred explicit paths vs simpler paths, for two reasons:

      * people just learning the SDK will get an explicit idea of how the SDK’s relatively simple internal paths work
      * people already familiar with the SDK pre-1.12 can refer to the documentation and get an explicit example that uses the new paths

      In addition ( and as Will pointed out ) existing code will continue to work without changes.

      Jeff