PHASE 1

AMD Module System in Dojo 1.17.3

How Dojo loads every dependency on demand — and how to structure your own modules the right way

AMD require define dojoConfig Loader Plugins Packages
What you'll build by the end: A fully modular Dojo app split across 3 packages — application logic, custom widgets, and shared utilities — all loaded on demand with zero global variables.
1

Why AMD? The Global Script Problem

Before AMD, JavaScript apps loaded dependencies using plain <script> tags. This approach has three fatal flaws at scale:

<!-- Old-school script loading — 4 problems -->
<script src="utils.js"></script>      <!-- 1. Order matters: must load before dependents -->
<script src="widgets.js"></script>    <!-- 2. Blocks HTML parsing while downloading -->
<script src="app.js"></script>        <!-- 3. Everything on window — name collisions -->
                                        <!-- 4. No way to load only what's needed -->

// Inside widgets.js — global pollution
window.MyWidget = function() { ... };
window.MyHelper = function() { ... };
// Any other script can accidentally overwrite these
Before AMD → scripts in order → globals on window → ALL code loads upfront ──────────────────────────────────────────────────────────────────── With AMD → declare deps → private scope → only needed modules load

AMD (Asynchronous Module Definition) solves all four problems:

  • Order: Dependencies are declared in code — the loader figures out the order
  • Blocking: Modules load in parallel, asynchronously
  • Globals: Each module has a private scope — nothing leaks to window
  • On-demand: Only the modules your code actually uses are loaded

Dojo adopted AMD with version 1.7 (2011) and it remains the module system in 1.17.3. Every Dojo module — dojo/on, dijit/form/Select, dojox/charting/Chart — is an AMD module.

2

require() — Loading Modules

require() is how you consume modules. You declare an array of module IDs you need and receive them as arguments to a callback.

// Basic form — load modules, use in callback
require([
  "dojo/dom",           // module IDs — forward slash separates package/module
  "dojo/on",
  "dijit/form/Button"
], function(dom, on, Button) {
  // Arguments match the array positions exactly
  // dom   �? the dojo/dom module
  // on    �? the dojo/on module
  // Button �? the dijit/form/Button constructor

  var btn = new Button({
    label: "Click Me",
    onClick: function() {
      dom.byId("output").textContent = "Clicked!";
    }
  }, "btnNode");
  btn.startup();
});

Nested require() — Conditional Loading

You can call require() anywhere in your code — inside functions, event handlers, conditionals. This enables true lazy loading.

// Load a heavy charting module only when the user opens the chart tab
function onChartTabOpen() {
  require(["dojox/charting/Chart", "dojox/charting/themes/Claro"],
  function(Chart, theme) {
    // Chart module loaded only NOW — not at app startup
    var chart = new Chart("chartDiv");
    chart.setTheme(theme);
    chart.addPlot("default", { type: "Lines" });
    chart.render();
  });
}

Synchronous require() — For Already-Loaded Modules

// If you KNOW a module is already loaded, require returns it synchronously
// (no callback form — returns the module directly)
var dom = require("dojo/dom");   // only safe if dojo/dom was already loaded

// The safe pattern: always use the callback form
// unless you are 100% sure the module is cached
⚠ GOTCHA — The Return Value Trap
require() with a callback returns undefined — NOT the modules. The modules are only available inside the callback. Never do:

var dom = require(["dojo/dom"], function(dom) { return dom; });
// dom is undefined here — require is async!

Module ID Rules

ID FormResolves ToExample
package/modulepackage/module.js"dojo/on"dojo/on.js
package/sub/modulepackage/sub/module.js"dijit/form/Button"
./siblingRelative to current module"./utils" from myapp/views/list
../parentOne directory up"../models/user"
plugin!resourcePlugin handles the resource"dojo/text!./tmpl.html"
3

define() — Creating Modules

define() is how you create a module. A module file should contain exactly one define() call. Whatever you return becomes the module's exported value.

// myapp/utils/formatter.js
define([
  "dojo/_base/lang"    // dependencies this module needs
], function(lang) {

  // Private — not accessible outside this module
  var _prefix = "USD ";

  // Public API — what this module exports
  return {
    currency: function(amount) {
      return _prefix + amount.toFixed(2);
    },
    truncate: function(str, maxLen) {
      if (str.length <= maxLen) return str;
      return str.substring(0, maxLen) + "...";
    }
  };
});

Exporting a Constructor (Class)

// myapp/models/User.js
define([
  "dojo/_base/declare",
  "dojo/Stateful"
], function(declare, Stateful) {

  // Return the constructor directly — not wrapped in an object
  return declare([Stateful], {
    name: "",
    email: "",
    role: "viewer",

    constructor: function(props) {
      lang.mixin(this, props);
    },

    isAdmin: function() {
      return this.role === "admin";
    }
  });
});

Module With No Dependencies

// myapp/config/constants.js — no dependencies needed
define({
  API_BASE:    "/api/v2",
  PAGE_SIZE:   25,
  DATE_FORMAT: "yyyy-MM-dd",
  THEMES:      ["claro", "tundra", "soria"]
});
// Note: define(object) — shorthand when factory function isn't needed

The Three Signatures of define()

SignatureUse When
define(id, deps, factory) Named module — only used inside build layers. Never name modules in source files.
define(deps, factory) Standard form — deps array + factory function. Use this 99% of the time.
define(value) Simple export — a plain object or primitive. No factory needed.
⚠ GOTCHA — Never Name Your Modules
Do not write define("myapp/utils", [...], function() {...}) in your source files. Named modules cannot be moved or renamed without breaking the loader. The build system assigns names automatically during compilation.

require vs define — When to Use Each

require()define()
PurposeConsume modulesCreate a module
Used inApp bootstrap, inline scripts, HTMLModule files only (one per file)
Returnsundefined (async)Registers the module
File has one?Can have many require callsExactly one define per file
4

dojoConfig — Loader Configuration

dojoConfig (or data-dojo-config attribute) controls how the Dojo loader finds, caches, and loads modules. It must be defined before dojo.js is loaded.

// Full dojoConfig with all common options explained
var dojoConfig = {

  // ── Core loader settings ──
  async:       true,     // REQUIRED: use AMD loader (always true for Dojo 1.7+)
  parseOnLoad: false,    // ALWAYS false: parse declarative widgets manually

  // ── Path resolution ──
  baseUrl: "/js/",       // base path for all relative module IDs
                         // default: the directory containing dojo.js

  // ── Named packages ──
  packages: [
    // Built-in packages (dojo, dijit, dojox) are auto-registered
    // Register YOUR app code:
    { name: "myapp",    location: "/js/myapp" },
    { name: "widgets",  location: "/js/shared-widgets" },
    { name: "vendor",   location: "/js/vendor" }
  ],

  // ── Path aliases (for individual files) ──
  paths: {
    "myapp/config": "/js/config/production"   // override single module path
  },

  // ── Locale ──
  locale: "en-us",       // affects dojo/i18n bundles

  // ── Debug ──
  isDebug: false,        // set true in dev: enables console logging from loader

  // ── Cache busting ──
  cacheBust: "v2.1.0",   // appends ?v2.1.0 to every module URL (clears browser cache)

  // ── Dojo module config (per-module settings) ──
  config: {
    "dojo/i18n": { locale: "fr" }
  }
};

// Load dojo.js AFTER dojoConfig is defined
// <script src="/js/dojo/dojo.js"></script>

Inline Config via HTML Attribute

<!-- Alternative: set config directly on the script tag -->
<script
  data-dojo-config="async:true, parseOnLoad:false, locale:'fr'"
  src="dojo/dojo.js">
</script>
<!-- This form is useful for single-page apps where JS config isn't convenient -->

Package Configuration Deep-Dive

// Package config controls how module IDs map to file paths
var dojoConfig = {
  packages: [
    {
      name:     "myapp",       // the package prefix in require/define calls
      location: "myapp",       // relative to baseUrl (or absolute path)
      main:     "main"         // default module when you require("myapp") — optional
    },
    {
      name:     "lodash",
      location: "../node_modules/lodash",
      main:     "lodash"
    }
  ]
};

// With the above config:
// require("myapp/views/list")  → loads /js/myapp/views/list.js
// require("lodash")            → loads /node_modules/lodash/lodash.js
5

Loader Plugins

Loader plugins extend require() to load non-JavaScript resources. They use the plugin!resource syntax — the plugin module handles loading the resource and returns a usable value.

dojo/text!

Load any text file (HTML, SVG, CSS). Returns the raw file contents as a string. Used for widget templates.

dojo/i18n!

Load a locale-aware NLS bundle. Returns the correct strings for the user's locale.

dojo/domReady!

Waits for DOM to be ready. No return value — used as the last dep when you need the DOM.

dojo/has!

Feature detection. Conditionally loads different modules based on browser capabilities.

dojo/query!

Load a specific CSS selector engine (acme, lite). Rarely needed directly.

dojo/request/default!

Platform-aware XHR — loads the right transport for browser or Node.

dojo/text! — Template Loading

// myapp/widgets/UserCard.js
define([
  "dojo/_base/declare",
  "dijit/_Widget",
  "dijit/_TemplatedMixin",
  "dojo/text!./templates/UserCard.html"   // �? plugin loads the HTML file
], function(declare, _Widget, _TemplatedMixin, template) {

  return declare([_Widget, _TemplatedMixin], {
    templateString: template,   // the loaded HTML string
    name: "Unknown",
    role: "viewer"
  });
});
<!-- myapp/widgets/templates/UserCard.html -->
<div class="user-card ${role}">
  <span class="name">${name}</span>
  <span data-dojo-attach-point="roleNode"></span>
</div>

dojo/i18n! — Locale-Aware Strings

// myapp/nls/messages.js  (root bundle)
define({ root: {
  greeting:  "Hello",
  save:      "Save",
  cancel:    "Cancel",
  deleteMsg: "Are you sure you want to delete this?"
}, fr: true, ja: true, de: true });

// myapp/nls/fr/messages.js  (French override)
define({
  greeting:  "Bonjour",
  save:      "Enregistrer",
  cancel:    "Annuler",
  deleteMsg: "Êtes-vous sûr de vouloir supprimer ceci ?"
});

// Usage in any widget
define([
  "dojo/i18n!myapp/nls/messages"
], function(strings) {
  console.log(strings.greeting);  // "Bonjour" if locale is fr, "Hello" otherwise
});

dojo/domReady! — DOM-Dependent Code

require([
  "dijit/registry",
  "dojo/parser",
  "dojo/domReady!"   // trailing ! = the bang. No return value (hence no arg name needed)
], function(registry, parser) {
  // DOM is guaranteed to be fully parsed here
  parser.parse();    // manually parse declarative widgets

  // Safe to query the DOM
  var widgets = registry.findWidgets(document.body);
  console.log("Widgets found:", widgets.length);
});

dojo/has! — Feature-Based Loading

define([
  // Load touch.js on touch devices, mouse.js on desktop
  "dojo/has!touch?myapp/input/touch:myapp/input/mouse"
], function(inputHandler) {
  // inputHandler is the right module for this device
  inputHandler.init();
});

// Syntax: "dojo/has!feature?trueModule:falseModule"
// Multiple conditions:
// "dojo/has!ie?myapp/ie-shim:dojo/has!webkit?myapp/webkit-fix:myapp/default"
6

Package Configuration — Multi-Package App Structure

A well-structured Dojo app organizes code into discrete packages. Each package is a directory with a clear responsibility. Here's the recommended structure for a production app:

project/
├── index.html
├── js/
│   ├── dojo/           �? Dojo SDK (do not edit)
│   ├── dijit/          �? Dijit SDK (do not edit)
│   ├── dojox/          �? DojoX SDK (do not edit)
│   ├── myapp/          �? Your application logic
│   │   ├── main.js     �? App entry point
│   │   ├── App.js      �? Root widget
│   │   ├── views/
│   │   │   ├── Dashboard.js
│   │   │   └── UserList.js
│   │   ├── models/
│   │   │   └── User.js
│   │   ├── stores/
│   │   │   └── UserStore.js
│   │   └── nls/
│   │       └── messages.js
│   ├── widgets/        �? Shared/reusable widgets (used across apps)
│   │   ├── DataGrid.js
│   │   └── templates/
│   └── utils/          �? Utility functions
│       ├── formatter.js
│       └── validator.js
// dojoConfig for the above structure
var dojoConfig = {
  async: true,
  parseOnLoad: false,
  baseUrl: "/js/",
  packages: [
    { name: "dojo",    location: "dojo" },
    { name: "dijit",   location: "dijit" },
    { name: "dojox",   location: "dojox" },
    { name: "myapp",   location: "myapp", main: "main" },
    { name: "widgets", location: "widgets" },
    { name: "utils",   location: "utils" }
  ]
};

// Now module IDs resolve cleanly:
// require("myapp/views/Dashboard")  → /js/myapp/views/Dashboard.js
// require("widgets/DataGrid")       → /js/widgets/DataGrid.js
// require("utils/formatter")        → /js/utils/formatter.js

The App Entry Point Pattern

// myapp/main.js — app bootstrap
define([
  "myapp/App",
  "dojo/domReady!"
], function(App) {
  // Create root app widget and place in body
  new App().placeAt(document.body).startup();
});

// index.html — single require call kicks off everything
require(["myapp/main"]);
7

Custom Loader Plugins

You can create your own loader plugin to handle any resource type. A plugin is an AMD module that exposes a load() function. The loader calls it when it encounters yourPlugin!resourceId.

// myapp/plugins/json.js — a plugin to load and parse JSON files
define(["dojo/request/xhr"], function(xhr) {
  return {
    // Called by the AMD loader when it sees: "myapp/plugins/json!path/to/file"
    load: function(resourceId, require, onLoad, config) {
      if (config.isBuild) {
        // During build: just signal success (actual content inlined separately)
        onLoad(null);
        return;
      }

      // During runtime: fetch and parse the JSON
      xhr.get(require.toUrl(resourceId + ".json"), {
        handleAs: "json"
      }).then(function(data) {
        onLoad(data);   // pass the parsed object to the requiring module
      }, function(err) {
        onLoad.error(err);
      });
    }
  };
});

// Usage: any module can now load JSON via this plugin
define([
  "myapp/plugins/json!data/config"
], function(config) {
  console.log(config.apiBase);   // parsed JSON object, ready to use
});
When to write a custom plugin: When you have a resource type (JSON, Markdown, YAML, SVG sprites) that you want to load uniformly across many modules, with the same syntax as built-in plugins. Don't write one just to load a single file — use dojo/text! + manual parsing for one-offs.
8

Cross-Domain Loading & CDN Patterns

Loading Dojo from a CDN works for prototyping but has important limitations in production.

<!-- CDN loading (prototyping only) -->
<script>
  var dojoConfig = {
    async: true,
    parseOnLoad: false,
    // When loading from CDN, your app package must use a full path
    packages: [
      { name: "myapp", location: "http://myserver.com/js/myapp" }
    ]
  };
</script>
<script src="//ajax.googleapis.com/ajax/libs/dojo/1.17.3/dojo/dojo.js"></script>

Why CDN Breaks in Production

IssueDetail
No build layers CDN serves individual files. You lose the benefit of bundled layers — hundreds of separate HTTP requests.
Cross-origin dojo/text! Loading templates from CDN requires CORS headers on the CDN server.
Version lock CDN URL bakes the version number. Upgrading requires changing every HTML file.
Offline / intranet CDN unavailable = app broken. Enterprise apps must use local copies.

Cross-Origin Module Loading (CORS)

// If your app loads AMD modules from a different origin,
// the server serving those modules must return CORS headers:
// Access-Control-Allow-Origin: https://yourapp.com
//
// The Dojo loader uses XHR to fetch module text in some configurations,
// which is subject to the same-origin policy.
//
// Best practice: serve all Dojo files from the same origin as your app,
// or use the build system to bundle everything before deployment.

🔧 Hands-On Exercise 1.9 — Multi-Module App with Lazy Loading

Build a small app with 4 modules. The ChartView module should load only when the user clicks the "Load Chart" button — demonstrating true lazy loading.

Create these files:

js/
├── myapp/
│   ├── main.js         �? bootstrap
│   ├── App.js          �? root widget (has a button)
│   ├── views/
│   │   └── ChartView.js �? heavy module (lazy loaded)
│   └── utils/
│       └── logger.js   �? simple utility
// myapp/utils/logger.js
define(function() {
  return {
    log: function(msg) {
      console.log("[APP] " + new Date().toISOString() + " — " + msg);
    }
  };
});

// myapp/views/ChartView.js  (simulated heavy module)
define(["myapp/utils/logger"], function(logger) {
  logger.log("ChartView module loaded");
  return {
    render: function(container) {
      container.innerHTML = "<p style='color:cyan'>Chart rendered!</p>";
    }
  };
});

// myapp/App.js
define([
  "dojo/_base/declare",
  "dijit/_Widget",
  "dojo/on",
  "dojo/dom-construct",
  "myapp/utils/logger"
], function(declare, _Widget, on, domConstruct, logger) {
  return declare([_Widget], {
    buildRendering: function() {
      this.domNode = domConstruct.create("div");
      this.btn = domConstruct.create("button",
        { textContent: "Load Chart", className: "btn btn-primary" },
        this.domNode
      );
      this.chartArea = domConstruct.create("div", {}, this.domNode);
    },
    postCreate: function() {
      var self = this;
      on(this.btn, "click", function() {
        // Lazy load ChartView only on click
        require(["myapp/views/ChartView"], function(ChartView) {
          logger.log("ChartView loaded lazily");
          ChartView.render(self.chartArea);
          self.btn.disabled = true;
        });
      });
    }
  });
});

// myapp/main.js
define(["myapp/App", "dojo/domReady!"], function(App) {
  new App().placeAt(document.body).startup();
});

// index.html script
require(["myapp/main"]);
10

Mini Demo — 3-Package App

A complete working structure for a production-style Dojo app with three distinct packages: application, shared widgets, and utilities.

<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <link rel="stylesheet" href="dojo/resources/dojo.css">
  <link rel="stylesheet" href="dijit/themes/claro/claro.css">
  <script>
    var dojoConfig = {
      async: true,
      parseOnLoad: false,
      baseUrl: "/js/",
      isDebug: true,    // remove in production
      packages: [
        { name: "dojo",    location: "dojo" },
        { name: "dijit",   location: "dijit" },
        { name: "dojox",   location: "dojox" },
        { name: "app",     location: "app",     main: "boot" },
        { name: "widgets", location: "widgets" },
        { name: "util",    location: "util" }
      ]
    };
  </script>
  <script src="/js/dojo/dojo.js"></script>
</head>
<body class="claro">
  <div id="root"></div>
  <script>require(["app"]);</script>  <!-- loads app/boot.js via main config -->
</body>
</html>
// util/format.js — pure utility, no Dojo deps
define(function() {
  return {
    date:     function(d) { return d.toLocaleDateString("en-US"); },
    currency: function(n) { return "$" + n.toFixed(2); },
    initials: function(name) {
      return name.split(" ").map(function(w) { return w[0]; }).join("");
    }
  };
});

// widgets/Avatar.js — reusable across any app
define([
  "dojo/_base/declare",
  "dijit/_Widget",
  "dijit/_TemplatedMixin",
  "dojo/text!widgets/templates/Avatar.html",
  "util/format"
], function(declare, _Widget, _TemplatedMixin, template, fmt) {
  return declare([_Widget, _TemplatedMixin], {
    templateString: template,
    name:  "User",
    color: "#6366f1",
    _initials: "",
    postMixInProperties: function() {
      this._initials = fmt.initials(this.name);
    }
  });
});

// app/boot.js — ties everything together
define([
  "dojo/_base/declare",
  "dijit/_Widget",
  "widgets/Avatar",
  "util/format",
  "dojo/dom-construct",
  "dojo/domReady!"
], function(declare, _Widget, Avatar, fmt, domConstruct) {

  var users = [
    { name: "Alice Chen",  role: "admin"  },
    { name: "Bob Smith",   role: "editor" },
    { name: "Carol Davis", role: "viewer" }
  ];

  var container = domConstruct.byId("root");

  users.forEach(function(user) {
    var avatar = new Avatar({ name: user.name });
    avatar.placeAt(container);
    avatar.startup();
  });
});
Key takeaway: Notice that app/boot.js depends on widgets/Avatar and util/format, but it has no direct knowledge of how those packages are located on disk. The package config in dojoConfig handles all path resolution. This separation makes the code portable and testable.

Phase 1 — Quick Reference

ConceptKey Rule
require(deps, cb)Consume modules — never name the return variable the same as the module ID
define(deps, factory)Create a module — one per file, never name it explicitly
dojoConfigMust be defined before dojo.js loads
async: trueAlways set. Required for AMD.
parseOnLoad: falseAlways set. Call parser.parse() manually.
dojo/text!Load HTML templates, returns string
dojo/i18n!Load locale bundles, returns strings object
dojo/domReady!Wait for DOM, no return value — use as last dep
dojo/has!Feature-detect conditional module loading
Lazy loadingNest require() inside event handlers for on-demand loading
CDNPrototyping only. Local copy required for production builds.

Ready for Phase 2?

You now know how every Dojo module is loaded. Next: how to create classes, mixins, and widgets using Dojo's powerful OOP system.

Continue to Phase 2: OOP with declare →