Modules in Doh are executable units of code that extend the Package concept by adding a runtime function (callback). While Packages define the structural organization and dependencies, Modules implement the actual behavior and functionality.
In the Doh architecture:
A Module is essentially a Package with code, leveraging the same dependency system while adding execution logic.
Modules are directly tied to the Package system:
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:
Doh.Module('ESMUser', ['import { func } from "esm-module"'], function(func) {
// func is imported from an ES module
});
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.
Doh uses a unique approach to handle module communication through a global shared scope:
Doh.Globals
by nameDoh.Globals
, it's created as an empty objectThis system:
Example:
// ModuleA.js
Doh.Module('ModuleA', function(sharedObject) {
// sharedObject is automatically created if it doesn't exist
sharedObject.message = 'Hello from ModuleA';
});
// ModuleB.js
Doh.Module('ModuleB', ['ModuleA'], function(sharedObject) {
// This is the SAME sharedObject instance from ModuleA
console.log(sharedObject.message); // Outputs: "Hello from ModuleA"
});
It's important to understand that parameter resolution in Doh modules is based on name, not position. Here's how it works:
Doh.Globals
namespaceDoh.Globals
and passed to the callbackThis allows you to:
Example:
// Note how parameter names (auth, core) don't match the dependency order
// The system resolves them by name from Doh.Globals
Doh.Module('ParameterExample', [
'await core-library', // Loads first
'await auth-system', // Loads second
'plugin-a' // Loads third
], function(plugin_a, auth, core) {
// Parameters are resolved by name, not position:
// - plugin_a maps to plugin-a
// - auth maps to auth-system
// - core maps to core-library
});
This globalization approach differs from traditional ES modules but offers significant advantages for large-scale applications with complex dependencies.
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
});
Modules can dynamically load additional dependencies at runtime:
Doh.Module('DynamicModule', async function(features) {
// Core module functionality
features.core = initializeCore();
// Conditionally load additional features
if (features.shouldEnableAdvanced) {
// Dynamically load another module
await Doh.load('advanced-feature-module');
// Or load with decorators
await Doh.load('browser?? browser-specific-features');
features.advanced = initializeAdvancedFeatures();
}
});
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(User) {
User = 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