Modules in Doh are executable units of code that extend the Package concept by adding a runtime function (callback). Packages and Modules share the same structure and registry; a Module is a Package with a callback, while a Package is a Module without one (declarative only). Both can participate in dependency graphs as dependents and dependencies.
Important: A module is a package with a callback. Both modules and packages live in the same Doh.Packages
registry and share the same name namespace. Doh.Module('X', ...)
defines both the package metadata and the executable callback in one atomic declaration. Do not mix Doh.Package('X', ...)
and Doh.Module('X', ...)
for the same name in separate places; the Auto-Packager treats each declaration as its own entity and overlapping names are considered conflicts.
In the Doh architecture:
load
) and optional metadata and can be consumed as first-class dependenciesA Module is essentially a Package with code, leveraging the same dependency system while adding execution logic. It is registered in Doh.Packages
under its module name.
Modules are directly tied to the Package system:
Doh.Packages[name]
with the same name, including the callback. Avoid a separate Doh.Package(name, ...)
for the same name.This integration means you can use Doh.Module()
to define both a Package and its implementation in one step.
Modules are defined using the Doh.Module
function:
Doh.Module(moduleName, dependencies, callback);
moduleName
: A string identifying the module (becomes the Package name as well)dependencies
: An optional array of dependency strings or a single dependency stringcallback
: A function that defines the module's behavior, receiving globals as parametersExample:
Doh.Module('MyModule', ['dependency1', 'dependency2'], function(SharedObject) {
// Module implementation using SharedObject
});
Dependencies can be specified in several ways:
No dependencies:
Doh.Module('SimpleModule', function() {
// No dependencies
});
Single dependency as a string:
Doh.Module('SingleDependencyModule', 'dependency', function() {
// Single dependency
});
Multiple dependencies as an array:
Doh.Module('MultiDependencyModule', ['dep1', 'dep2'], function() {
// Multiple dependencies
});
Doh supports various types of dependencies, all using the Load System:
Other Doh modules:
Doh.Module('ModuleA', ['ModuleB'], function() {
// ModuleB is another Doh module
});
ES modules (with Auto-Installer):
Doh.Module('ESMUser', ['import { func } from "esm-module"'], function(func) {
// func is imported from an ES module
// NPM packages are auto-installed when you run 'doh upgrade'
});
// NPM packages with version pinning
Doh.Module('NPMUser', [
'import React from "react@^18.0.0"', // Auto-install React 18.x.x
'import { format } from "date-fns@~2.30.0"' // Auto-install date-fns 2.30.x
], function(React, format) {
// Dependencies are automatically installed by the Auto-Installer
});
Auto-Installer Note: NPM packages referenced in import statements are automatically installed when you run doh upgrade
(which runs doh update
before and after installations to ensure manifests are current). This makes most Doh.Install()
statements obsolete. The Auto-Installer supports full semver version specifications directly in import statements.
JavaScript files:
Doh.Module('ScriptUser', ['path/to/script.js'], function() {
// script.js is loaded before this module
});
CSS files:
Doh.Module('StyleUser', ['path/to/styles.css'], function() {
// styles.css is loaded before this module
});
Modules leverage Doh's powerful Load System with decorators:
Doh.Module('AdvancedModule', [
// Standard module dependency
'core-module',
// ES module import
'import { component } from "ui-library"',
// Await decorator - ensures this loads before subsequent dependencies
'await critical-dependency',
// Async decorator - starts loading immediately
'async non-blocking-dependency',
// Environment conditional - only loads in browser
'browser?? browser-specific-module',
// Environment conditional - only loads in Node.js
'nodejs?? node-specific-module',
// Configuration conditional - only loads in debug mode
'config.debug?? debug-tools',
// Explicit file type declaration
'dynamic-content.php > js'
], function(component) {
// Module implementation
});
For complete details on load decorators and syntax, see the Load System documentation.
A key strength of Doh Modules is their ability to handle multiple environments efficiently. The most common pattern is to bifurcate a module into environment-specific implementations with shared common code:
// Main package that branches by environment
Doh.Package('feature_name', {
load: [
// Common functionality shared across environments
'feature_name_common',
// Environment-specific implementations loaded conditionally
'browser?? feature_name_ui', // Only loads in browser environments
'nodejs?? feature_name_server' // Only loads in Node.js environments
]
});
// Common module with functionality shared by all environments
Doh.Module('feature_name_common', [], function(FeatureName) {
// Create shared interface
FeatureName = FeatureName || {};
// Add common utilities, data structures, and logic
FeatureName.config = {
// Shared configuration
};
FeatureName.utils = {
// Common utility functions
formatData: function(data) {
// Implementation
}
};
// Return the shared object for use by environment-specific modules
return FeatureName;
});
// Browser-specific implementation
Doh.Module('feature_name_ui', [
// Always load common functionality first
'feature_name_common',
// Browser-specific dependencies
'html',
'^/feature_name.css'
], function(FeatureName) {
// Extend the shared object with browser-specific functionality
FeatureName.renderUI = function(container) {
// Browser-specific UI rendering
};
// Create DOM-related components
FeatureName.components = {
// Component implementations
};
});
// Server-specific implementation
Doh.Module('feature_name_server', [
// Always load common functionality first
'feature_name_common',
// Server-specific dependencies
'express_router',
'import fs from "fs-extra"'
], function(FeatureName, Router, fs) {
// Add API routes
Router.AddRoute('/api/feature', function(data, req, res) {
// Handle API request
const result = FeatureName.utils.formatData(data);
Router.SendJSON(res, result);
});
// Add server-specific methods
FeatureName.saveToFile = async function(data, path) {
// Implementation using Node.js fs module
await fs.writeFile(path, JSON.stringify(data));
};
});
This pattern keeps code organized while ensuring that browser-specific code (like UI components) is only loaded in browsers, and server-specific code (like file system operations) is only loaded in Node.js.
SPR is how module callback parameters are wired in Doh: your function asks by name and the system resolves each name against a layered scope created by your load arrays. No DI tokens—just names.
For every requested name (including properties inside destructured params), resolution follows:
Doh.Globals[name]
(Shared state collection)globalThis[name]
If still not found, the parameter is undefined
and a diagnostic is logged. Legacy compatibility: enabling Doh.pod.ignore_missing_module_params
(discouraged) will auto‑create a placeholder {}
in Doh.Globals[name]
. Prefer explicit construction via parameter defaults (e.g., param = {}
or param = { globalThis: {} }
).
function(core, auth) { … }
— each resolved by name.function({ format, parse }) { … }
— properties are resolved independently, then re‑packed.function(db = 'Database') { … }
— resolve using the lookup name "Database".function(opts = { format: 'dateFnsFormat', parse: 'dateFnsParse' }) { … }
— build opts
by resolving each key’s lookup name.function(Store = {}) { … }
— ensures Doh.Globals.Store
exists and injects it.function(Registry = { globalThis: {} }) { … }
— ensures globalThis.Registry
exists and injects it.$
resolves to jQuery
; DohPath
is injected as a file‑bound Overloaded instance (not globalized).Store = {}
) and consume by name.// A. Reuse the same ESM export in children
Doh.Module('UsingESM_common', [
'import { format, parse } from "date-fns"'
], function(format, parse) { /* both resolved by name */ });
Doh.Module('consumer', [
'UsingESM_common'
], function(format) { /* resolved by name from ancestor producer list */ });
// B. Create a shared container if missing (Doh.Globals)
Doh.Module('State', [
], function(Store = {}) {
Store.count = (Store.count || 0) + 1;
});
// Add a participant of the share:
Doh.Module('StateHelper', [
], function(Store = {}) {
Store.count = (Store.count || 0) + 1;
});
// C. Alias a parameter name
Doh.Module('AliasExample', [
'import Database from "sqlite"'
], function(db = 'Database') {
// Resolves using lookup name "Database"
});
// D. Repack by destructuring (equivalent to asking for format and parse by name)
Doh.Module('Formats', [
'import { format, parse } from "date-fns"'
], function(utils = { format, parse }) {
// utils.format and utils.parse are the references here
});
Doh.load()
. SPR governs callback parameters; load arrays are declarative imports, independent of parameter order.The callback’s return value becomes the module’s export. It is stored in Doh.ModuleExports[name]
and available via Doh.Loaded['name']
/await Doh.load('name')
. There is no automatic merging with the parameter map; if you want to export a composed API object, return it explicitly from your module, or attach to/create a shared global or globalThis.
Modules follow a specific lifecycle managed by the Doh runtime:
Doh.Module
is called, registering the module with the systemOnly after a module's callback has completely finished execution is the module considered loaded, which may trigger other modules waiting on it.
Modules can have asynchronous callbacks, allowing for await
operations:
Doh.Module('AsyncModule', async function(database) {
// Initialize asynchronously
database.connection = await establishDatabaseConnection();
// Set up tables
await database.createTablesIfNotExist();
// Only after these async operations is the module considered loaded
});
The Auto-Packager deeply integrates with the Module system:
Doh.Module()
calls in your codebaseThis integration ensures:
Modules can adapt to different JavaScript environments using conditional dependencies and runtime checks:
Doh.Module('CrossPlatformModule', [
// Core dependencies for all environments
'core-lib',
// Browser-specific dependencies
'browser?? dom-helpers',
'browser?? styles/main.css',
// Node.js-specific dependencies
'nodejs?? fs-utilities',
// Deno-specific dependencies
'deno?? deno-specific-helper',
// Bun-specific dependencies
'bun?? bun-optimized-helper'
], function(coreFunctions) {
// Environment-specific initialization
if (IsBrowser()) {
initializeBrowserFeatures();
} else if (IsNode()) {
initializeNodeFeatures();
if (IsDeno()) {
initializeDenoFeatures();
} else if (IsBun()) {
initializeBunFeatures();
}
}
// Common functionality
coreFunctions.initialize();
});
When using the bifurcated pattern (common + environment-specific modules), it's important to understand how data flows between modules:
Example workflow:
// 1. Common module creates shared object (Doh auto-initializes unknown paramaters)
Doh.Module('feature_common', function(Feature={}) {
// Define shared data structures
Feature.data = {};
// Define common methods
Feature.initialize = function(config) {
Feature.data.config = config;
};
return { Feature };
});
// 2. Browser module extends with UI capabilities (Feature is known now, so Doh links it to the original)
Doh.Module('feature_browser', ['feature_common'], function(Feature={}) {
// Add browser-specific method
Feature.renderUI = function(container) {
// Create UI based on Feature.data.config
};
// Listen for UI events that modify shared data
Feature.onSubmit = function(formData) {
// Update shared data
Feature.data.userInput = formData;
};
});
// 3. Server module extends with data persistence (And here again)
Doh.Module('feature_server', ['feature_common'], function(Feature={}) {
// Add server-specific method
Feature.saveData = function() {
// Save Feature.data to database
};
// Process data submitted from browser
Feature.processUserInput = function() {
if (Feature.data.userInput) {
// Process the data submitted from the browser
}
};
});
Modules commonly define Patterns, which are reusable component templates:
Doh.Module('UIModule', function() {
Pattern('Button', 'html', {
moc: {
click: 'method'
},
tag: 'button',
html: 'Click me',
click: function() {
this.html = 'Clicked!';
},
html_phase: function(){
this.e.on('click', this.click);
}
});
});
Modules can specify installation requirements using Doh.Install()
:
Doh.Install('MyModule', {
// NPM package dependency
'npm:some-package': '^1.0.0',
});
Modules can define configuration data using Doh.Pod()
:
Doh.Pod('MyModule', {
// Module configuration
options: {
theme: 'light',
features: ['search', 'filter']
}
});
A typical feature module with environment-specific functionality:
// Main package that branches by environment
Doh.Package('user_system', {
load: [
'user_common', // Common user functionality
'browser?? user_browser', // Browser UI implementation
'nodejs?? user_server' // Server API implementation
]
});
// Common user functionality
Doh.Module('user_common', function() {
const User = {};
// Shared data structures and validation logic
User.validateEmail = function(email) {
return /\S+@\S+\.\S+/.test(email);
};
User.validatePassword = function(password) {
return password.length >= 8;
};
return User;
});
// Browser-specific implementation
Doh.Module('user_browser', [
'user_common',
'html',
'^/styles/user.css'
], function(User) {
// Add browser-specific UI functionality
User.renderLoginForm = function(container) {
const form = $('<form>').html(`
<input type="email" id="email" placeholder="Email">
<input type="password" id="password" placeholder="Password">
<button type="submit">Login</button>
`);
form.on('submit', function(e) {
e.preventDefault();
const email = $('#email').val();
const password = $('#password').val();
if (User.validateEmail(email) && User.validatePassword(password)) {
// Submit login request
User.login(email, password);
}
});
$(container).append(form);
};
User.login = function(email, password) {
// Send login request to server
$.post('/api/login', { email, password })
.then(response => {
if (response.success) {
User.currentUser = response.user;
// Trigger login event
$(document).trigger('user:login', User.currentUser);
}
});
};
});
// Server-specific implementation
Doh.Module('user_server', [
'user_common',
'express_router',
'import bcrypt from "bcrypt"',
'dataforge'
], function(User, Router, bcrypt) {
// Set up user database
User.db = df.Create('users');
// Add server-specific API endpoints
Router.AddRoute('/api/login', async function(data, req, res) {
const { email, password } = data;
if (!User.validateEmail(email) || !User.validatePassword(password)) {
return Router.SendJSON(res, {
success: false,
error: 'Invalid email or password format'
});
}
// Look up user by email
const user = await User.db.ops.Find({ email }).first();
if (!user) {
return Router.SendJSON(res, {
success: false,
error: 'User not found'
});
}
// Verify password
const passwordMatch = await bcrypt.compare(password, user.passwordHash);
if (!passwordMatch) {
return Router.SendJSON(res, {
success: false,
error: 'Invalid password'
});
}
// Create session
req.session.userId = user.id;
// Return success
Router.SendJSON(res, {
success: true,
user: {
id: user.id,
email: user.email,
name: user.name
}
});
});
});
Using pod configuration to customize module behavior:
// Define default configuration
Doh.Pod('feature_system', {
features: {
advanced: true,
experimental: false
},
theme: 'light'
});
// Module that adapts based on configuration
Doh.Module('feature_system', [
// Basic functionality
'core_components',
// Advanced features conditionally loaded based on configuration
'Doh.pod.features.advanced?? advanced_features',
// Experimental features conditionally loaded based on configuration
'Doh.pod.features.experimental?? experimental_features',
], function(core) {
// Initialize with configuration
core.initialize(Doh.pod.features);
});
Common issues and solutions:
Module Not Loading
Doh.loadstatus()
to see which modules are loaded and loadingUndefined Parameters
Load Order Issues
await
decorator for critical dependenciesCross-Environment Problems
Parameter Resolution Confusion
Doh.Globals
to see what objects are available for parameter resolution