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 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 a pattern for writing clean, reusable module suites, made possible by the Auto-Packager's build-time analysis. It allows modules to consume dependencies provided by their ancestors without re-importing them, enabling suites to declare dependencies once at a high level and consume them by name further down.
This is not a replacement for explicit imports—SPR is a complementary pattern that emerges from manifest-driven architecture. Standard ESM imports remain the foundation; SPR provides an elegant way to share those imports across module hierarchies without repetition.
The pattern is simple and powerful:
load
arrays produce scope - Dependencies declared in load arrays become available to descendant modulesparams
consume that scope by name - Module callbacks can request any dependency from their ancestor chain by parameter nameThis creates clean separation of concerns: declare dependencies where they logically belong, consume them where they're needed.
The system searches only the scope tree created by your 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, the parameter receives undefined
with a diagnostic (no silent failures).
Some parameter forms are NOT 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, NOT a named-based system that absolves module maintainers of responsibility. When you use source declarations, you're explicitly managing shared state rather than relying on automatic resolution.
Resolution-based (search scope tree):
function(core, auth)
— resolved by name against the scope treefunction({ format, parse })
— properties resolved independently, then re-packedfunction(db = 'Database')
— resolve using the lookup name against the scope treefunction(opts = { format: 'dateFnsFormat' })
— build object by resolving each key's lookup nameSource 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 when you want any of these benefits:
browser??
/ nodejs??
forks{}
default containers (intentionally globalized)Otherwise, just import—that's the default, and it's great for direct dependencies.
// 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
});
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 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 (Scope Tree Resolution) Confusion
Doh.Globals
is NOT automatically searched - only explicitly pinned objects via ={}
are accessible from Doh.Globals