
We found that combining Package structure with executable logic works well. A Module is simply a Package with a callback—it combines declarative structural relationships with runtime behavior in a single atomic declaration, removing the artificial separation between "what it needs" and "what it does."
This approach has enabled static analysis: the Auto-Packager analyzes the Module's structure at build-time while the runtime executes its callback with precise dependency injection. You declare both structure and behavior in one place; the system handles the orchestration.
The system unifies:
load), metadata, and installation requirements Modules share the Doh.Packages registry with Packages, representing the principle that structure and behavior are complementary aspects of the same organizational primitive. The distinction is that Packages provide structure only, while Modules provide structure plus execution.
Modules integrate seamlessly with the Package system through shared primitives:
Doh.Packages[name], eliminating the need for separate Package declarationsThis architectural unity means Doh.Module() provides both organizational structure and behavioral implementation as a single, atomic primitive—eliminating the artificial separation between "what it needs" and "what it does."
Modules use the Doh.Module function to define both dependencies and implementation in a single declaration:
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, using SPR for callback 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: Automatic NPM Dependency Management
Doh includes an Auto-Installer that automatically manages npm dependencies based on import statements in your module load blocks. This eliminates the need for manual Doh.Install() declarations in most cases.
react@^18.0.0)doh upgrade is run (which first runs doh update to get current manifest info)import React from "react" - Installs latest versionimport React from "react@^18.0.0" - Installs compatible with 18.0.0import React from "react@~18.2.0" - Installs compatible within 18.2.ximport React from "react@18.2.0" - Installs exact version 18.2.0# Install any missing npm dependencies found in module load blocks
doh upgrade
# Or upgrade specific packages and install any missing npm deps
doh upgrade my_specific_package
Important: doh update only scans and reports dependencies - it does NOT install them. doh upgrade provides complete dependency management by running doh update before and after installations to ensure manifests are always current.
The Auto-Installer makes most Doh.Install() statements obsolete:
// OLD WAY (deprecated but still works)
Doh.Install('my_module', {
'npm:react': '^18.0.0',
'npm:axios': '',
'npm:date-fns': '~2.30.0'
});
// NEW WAY (recommended)
Doh.Module('my_module', [
'import React from "react@^18.0.0"',
'import axios from "axios"',
'import * as dateFns from "date-fns@~2.30.0"'
], function(React, axios, dateFns) {
// Dependencies auto-installed by doh upgrade
});
Doh.load() callsdoh upgrade is runDoh.Install remains supported for advanced cases (non-imported artifacts, scripts, or legacy flows), but module load blocks with Auto-Installer are the recommended approachJavaScript 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 use Doh's 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.
Scoped Parameter Resolution (SPR) is specifically for Doh.Module() callback parameters. It's a module-level convenience for dependency sharing in module suites, made possible by the Auto-Packager's build-time analysis.
When you have a group of related modules that all need the same imports, declare them once in a "producer" module, and descendant modules can reference them by parameter name in their callbacks instead of re-importing. That's it—SPR has nothing to do with regular function parameters or how the rest of your code works.
This is not a replacement for explicit imports—SPR is a narrow, pragmatic feature that complements standard ESM imports by reducing duplication in module suites.
The pattern is simple and powerful, but only applies to Doh.Module() callback parameters:
load arrays produce scope - Dependencies declared in module load arrays become available to descendant modulesparams consume that scope by name - Doh.Module() callbacks can request any dependency from their ancestor chain by parameter nameThis creates clean separation of concerns for module suites: declare dependencies where they logically belong, consume them in module callbacks where they're needed.
When resolving Doh.Module() callback parameters, the system searches only the scope tree created by your module load ancestry:
Resolution is deterministic and analyzable: The Auto-Packager's manifest visibility + diagnostics mean "mystery capture" is detectable and fixable.
If a name is not found in the module scope tree, the parameter receives undefined with a diagnostic (no silent failures).
Some Doh.Module() callback parameter forms are NOT SPR resolution—they're source declarations that pin names to shared scopes:
function(Store = {}) { … } — pins Store to Doh.Globals.Store (creates if missing)function(Registry = { globalThis: {} }) { … } — pins Registry to globalThis.Registry (creates if missing)These provide an undersurface for shorthand resolution in module callbacks, NOT a named-based system that absolves module maintainers of responsibility. When you use source declarations in module callbacks, you're explicitly managing shared state rather than relying on automatic resolution.
These parameter forms only work in Doh.Module() callbacks:
Resolution-based (search module scope tree):
function(core, auth) — resolved by name against the module scope treefunction({ format, parse }) — properties resolved independently from module scope, then re-packedfunction(db = 'Database') — resolve using the lookup name against the module scope treefunction(opts = { format: 'dateFnsFormat' }) — build object by resolving each key's lookup name from module scopeSource declarations (explicit shared containers):
function(Store = {}) — pins Store to Doh.Globals.Store (creates if missing)function(Registry = { globalThis: {} }) — pins Registry to globalThis.Registry (creates if missing)Special injections:
$ resolves to jQuery; DohPath is injected as a bound instance (not globalized).Use SPR (parameter resolution in Doh.Module() callbacks) when you want any of these benefits in your module suites:
browser?? / nodejs?? forks{} default containers in module callbacks (intentionally globalized)Otherwise, just use standard imports—that's the default, and it's great for direct dependencies. SPR is purely a convenience for module suites.
// 1) Producers: one place to pin and import
Doh.Module('suite.producers', [
'import { format, parse } from "date-fns@~2.30.0"'
], function(format, parse) {
// Nothing else; just producing names into scope
});
// 2) Common consumer: **reuse** the producers via params
Doh.Module('suite.common', ['suite.producers'], function(format, parse) {
// format/parse resolved by name from the suite's scope
});
// 3) Env branches declared declaratively; consumers keep params clean
Doh.Module('suite.ui', [
'suite.common',
'browser?? ui_lib'
], function(format) {
// 'format' still comes from suite.producers
});
Doh.Module('suite.server', [
'suite.common',
'nodejs?? server_lib'
], function(parse) {
// 'parse' still comes from suite.producers
});
// 4) Repack pattern: alias multiple lookups into one param
Doh.Module('suite.tools', [
'import path from "path"',
'suite.producers'
], function(utils = { format, parse, path }) {
// utils is a local object packed from independent name lookups
});
// A. Reuse the same ESM export in children (scope tree resolution)
Doh.Module('UsingESM_common', [
'import { format, parse } from "date-fns"'
], function(format, parse) { /* both resolved by name from scope tree */ });
Doh.Module('consumer', [
'UsingESM_common'
], function(format) { /* resolved by name from ancestor in scope tree */ });
// B. Pin to shared container (source declaration - NOT scope tree resolution)
Doh.Module('State', [
], function(Store = {}) {
// Store is pinned to Doh.Globals.Store (created if missing)
// This is a source declaration, not scope tree resolution
Store.count = (Store.count || 0) + 1;
});
// Another module can access the same container via source declaration:
Doh.Module('StateHelper', [
], function(Store = {}) {
// Store references the same Doh.Globals.Store
// Both modules explicitly manage this shared state
Store.count = (Store.count || 0) + 1;
});
// C. Alias a parameter name (scope tree resolution)
Doh.Module('AliasExample', [
'import Database from "sqlite"'
], function(db = 'Database') {
// Resolves using the lookup name "Database" from scope tree
});
// D. Repack by destructuring (scope tree resolution)
Doh.Module('Formats', [
'import { format, parse } from "date-fns"'
], function(utils = { format, parse }) {
// utils.format and utils.parse resolved from scope tree, then re-packed
});
Doh.Module() callback parameters—it has nothing to do with regular functionsThe 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 integrates with the Module system through AST analysis:
Doh.Module() calls and extract their structureThis 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
SPR (Module Callback Parameter Resolution) Confusion
Doh.Module() callback parameters, not regular functionsDoh.Globals is NOT automatically searched - only explicitly pinned objects via ={} in module callbacks are accessible from Doh.Globals