The Doh Load System is a dependency management solution that works seamlessly across all JavaScript environments (browser, Node.js, Deno, and Bun). It provides a unified approach to resource loading with a simple, declarative syntax that eliminates environment-specific code paths while giving precise control over execution flow.
This guide covers:
Note: For a higher-level view of how the Load System interacts with other core components like the Auto-Packager, Manifests, and HMR, see the Doh.js Core Architecture Overview.
Rather than dealing with multiple dependency systems across different environments (import, require, script tags, etc.), Doh.load() provides a single, consistent approach that allows code branching to happen naturally inline, reducing the need for complex shims and external environment awareness:
Doh.Module('MyFeature', [
'core-dependency', // Load another Doh module
'^/path/to/file.js', // Load a JavaScript file relative to current
'browser?? ^/styles.css', // Load CSS only in browsers
'nodejs?? import fs from "fs-extra"', // Load Node modules only in Node
'optional advanced-feature', // Continue even if this fails
'await critical-dependency', // Block subsequent loads until complete
], function(core) {
// Your code with dependencies as parameters
});
Key Benefits:
The foundation of the Doh Load System is the load statement - a simple string that declares what to load:
// Basic load statement examples
'module-name' // Load a Doh module or package
'^/path/to/file.js' // Load a JavaScript file relative to current
'/absolute/path/file.css' // Load a CSS file (absolute from Doh root)
'data.json' // Load and parse a JSON file
'import { render } from "preact"' // Import from an ES module
'browser?? mobile-ui' // Load conditionally (only in browsers)
'await auth-system' // Control execution flow
'optional analytics' // Handle failures gracefully
Doh automatically detects and handles different resource types based on file extension or syntax of load statement:
Type | Example | What Happens |
---|---|---|
doh |
'ui-component' |
Loads a Doh module with its dependencies |
js |
'utils.js' |
In browser: adds script tag In Node: loads as text |
css |
'styles.css' |
In browser: adds stylesheet link In Node: loads as text |
json |
'config.json' |
Fetches and parses as JSON |
yaml |
'settings.yaml' |
Fetches and parses as YAML |
import |
'import { x } from "y"' |
In browser: imports from local install or ESM.sh shim In Node: uses native import |
file |
'template.html' |
Loads as raw text |
You can also explicitly specify the type:
'^/data.php > json' // Parse response as JSON
'module-name > doh' // Force Doh module type
Load different resources based on runtime environment or configuration, which is essential when sharing module files across environments:
// Real-world example: A shared module file with environment-specific loading
Doh.Module('MyFeature', [
'common-dependency', // Loads in all environments
'browser?? browser-only-module', // Loads only in browsers
'nodejs?? server-only-module', // Loads only in Node.js
'deno?? deno-specific-module', // Loads only in Deno
'bun?? bun-specific-module', // Loads only in Bun
'config.darkTheme?? dark-theme', // Loads based on configuration
'!mobile?? desktop-enhancements' // Loads when condition is false (not mobile)
'browser&&DEBUG?? dev-tools' // Loads in browsers when DEBUG is true
], function() {
// Module code runs after dependencies load
});
This approach is particularly valuable for modules that share a file but need environment-specific dependencies. For example:
// A typical bifurcated module pattern - common pattern in Doh applications
Doh.Package('feature_name', {
load: [
// Common functionality for all environments
'feature_name_common',
// Environment-specific implementations
'browser?? feature_name_ui',
'nodejs?? feature_name_server'
]
});
// Common module with shared functionality
Doh.Module('feature_name_common', [], function(SharedObject) {
// Code that runs in all environments
SharedObject = SharedObject || {};
// ... shared implementation
return SharedObject;
});
// Browser-specific implementation
Doh.Module('feature_name_ui', [
'feature_name_common',
'html', // Browser-specific dependency
'^/styles.css' // Browser-only CSS
], function(SharedObject) {
// Extend the shared object with browser functionality
// ...
});
// Server-specific implementation
Doh.Module('feature_name_server', [
'feature_name_common',
'import fs from "fs"', // Node.js-specific dependency
'express_router' // Server-only routing
], function(SharedObject, fs, Router) {
// Extend the shared object with server functionality
// ...
});
await
and async
Control the loading sequence with simple decorators:
Doh.Module('SequencedFeature', [
'async large-dependency', // Group 1: Starts loading immediately
'async metrics', // Group 1: Also starts loading immediately
'await auth-system', // Group 2: First in await chain
'await data-loader', // Group 2: Second in await chain
'user-profile', // Group 3: Runs after all await deps complete
'ui-components' // Group 3: Runs in parallel with user-profile
], function(authSystem, dataLoader, userProfile, uiComponents) {
// Actual execution order:
// 1. 'async large-dependency' and 'async metrics' start immediately
// 2. 'await auth-system' executes completely
// 3. 'await data-loader' executes completely
// 4. 'user-profile' and 'ui-components' execute in parallel
// Only non-async dependencies are available as parameters in order
// Async dependencies must be accessed from Doh.Loaded
// You can safely await them again with dynamic Doh.load()
const [largeDep, metrics] = await Doh.load(['large-dependency', 'metrics']);
if (largeDep && metrics) {
// Use the async dependencies here
}
});
optional
Make your code resilient without complex try/catch blocks:
Doh.Module('ResilientFeature', [
'core-functionality', // Required dependency
'optional experimental-feature', // App continues loading if this fails
'browser?? optional legacy-polyfill' // Combines conditional and optional
], function() {
// Check if optional dependency loaded successfully (Loaded uses exact loadStatment as key)
if (Doh.Loaded['optional experimental-feature']) {
// Use the experimental feature
}
});
Use NPM modules seamlessly in any environment:
Doh.Module('DataVisualizer', [
// Auto-loaded from esm.sh in browsers, npm in Node
'import { format } from "date-fns"',
// Make available globally with the 'global' decorator
'global import * as _ from "lodash"',
// Specific version control
'import { marked } from "marked@4.3.0"',
// Combining features
'browser?? global import Chart from "chart.js"'
], function(format) {
// Access format as a parameter
const formatted = format(new Date(), 'yyyy-MM-dd');
// Access global imports directly
const doubled = _.map([1, 2, 3], n => n * 2);
});
Load dependencies on-demand when needed:
Doh.Module('DynamicFeature', [], async function() {
// Initial module code
if (userNeedsReports) {
// Load reporting features only when needed
const [ generateReport, jsPDF ] = await Doh.load([
'reporting-engine',
'import { jsPDF } from "jspdf"'
]);
// Use the dynamically loaded features
generateReport();
}
});
Reload dependencies without page refresh:
// Reload specific dependencies
await Doh.reload([
'updated-module',
'/styles.css',
'import { component } from "ui-library"'
]);
// Access updated elements and exports
const updatedStyleLink = Doh.Loaded['/styles.css'];
const newComponent = Doh.Globals.component;
A load statement can have multiple decorators that modify its behavior:
[condition??] [flow] [scope] [resource] [> type]
Where:
browser??
, config.feature??
)await
, async
, optional
)global
) > json
, > doh
)Decorators must follow this specific order:
browser??
, etc.)await
, async
, optional
) and scope (global
) decorators next > [type]
) last 'browser?? await global import { Chart } from "chart.js" > import'
// ^ condition ^ flow ^ scope ^ resource ^ type
Decorator | Description |
---|---|
browser?? |
Loads only in browser environments |
nodejs?? |
Loads only in Node.js environments |
deno?? |
Loads only in Deno environments |
bun?? |
Loads only in Bun environments |
config.property?? |
Loads when specified property is truthy |
!condition?? |
Negates a condition (loads when condition is false) |
condA&&condB?? |
Logical AND for multiple conditions |
nodejs&&!bun?? |
Multiple conditions with negation |
Decorator | Description |
---|---|
async |
Highest priority - starts loading immediately when the load block is parsed, runs in parallel without blocking. Not available as parameters in the callback function; can be accessed later by calling Doh.load() again without the async, which will return the already resolved loadable. |
await |
Second priority - dependencies are chained together sequentially, with each awaited dependency fully completing before the next one starts. All awaited dependencies complete before any regular dependencies begin. |
optional |
Continues loading even if this dependency fails |
Decorator | Description |
---|---|
global |
Makes ESM imports available globally |
Each load block (array of dependencies) operates as its own mini-scope with two processing phases:
Parsing phase: All entries in the block are collected and parsed into three distinct groups:
async
-decorated dependencies are identified firstawait
-decorated dependencies are collected nextExecution phase: Dependencies are processed based on their grouping:
async
dependencies start immediately when found and run in parallel - they are effectively "hoisted" since they start before anything else but don't blockawait
dependencies are chained together in sequence (each must complete before the next starts)This three-group prioritization ensures that:
async
dependencies begin loading immediately without entering the normal execution flowawait
-decorated plugins and core libraries can fully initialize before dependent code loadsIt's important to understand that Doh.Module doesn't enforce callback parameter ordering as you might expect from the load array. Instead, the Auto-Packager scans Module definitions and resolves parameters by name:
Doh.Globals
for a matching exportglobalThis
This flexible parameter resolution decouples your function signature from the dependency array order, making modules more maintainable.
We understand that this may not work for every use-case specifically, but our focus is on full-scale application development across multiple deployment targets. For this reason, prioritizing last-mile solutions like global export pollution makes sense because of the maintainability aspect during development time. ESM Imports already allow export remapping, and Doh modules are able to be "imported" in the same way as import() with Doh.load().
Doh.Module('ParameterExample', [
'await core-library', // Loads first (Group 2)
'await auth-system', // Loads second (Group 2)
'plugin-a', // Loads third (Group 3)
'async analytics' // Starts immediately (Group 1), but accessed differently
], function(plugin_a, auth, core) { // Parameter order doesn't need to match load order!
// Parameters are resolved by name from Doh.Globals, not by position
// So auth refers to auth-system, core to core-library, etc.
// Since Doh modules naturally export their imports to Doh.Globals
// the actual resolution doesn't depend on load array ordering
// Async dependencies are not resolvable by parameter name
// and must be accessed from awaiting a later non-async Doh.load()
const { analytics } = await Doh.load('analytics');
});
This system offers significant advantages:
Doh.Module('LoadExample', [
'async analytics', // Group 1: Starts loading immediately
'await core-library', // Group 2: First in await chain
'await auth-system', // Group 2: Second in await chain (after core-library)
'plugin-a', // Group 3: Runs in parallel after all awaits complete
'plugin-b', // Group 3: Runs in parallel after all awaits complete
], function(auth, core, pluginA, pluginB) { // Order is flexible, resolved by name
// Parameter resolution is by name matching to Doh.Globals, then globalThis
// Not by position in the dependency array
// Access async dependency from Doh.load dynamically
// uses module loaded earlier (we remove the async here,
// but only because we want to wait on it in this case,
// other decorators still work as normal)
const { analytics } = await Doh.load('analytics');
});
Doh provides flexible path resolution across environments:
Path Syntax | Description | Example |
---|---|---|
/path |
Absolute path from Doh root | '/components/Button.js' |
^/path |
Relative to current file | '^/utils/helpers.js' |
./path |
Relative to current file | './utils/helpers.js' |
http:// |
Remote URL | 'https://cdn.example.com/lib.js' |
For complex path operations:
// Create a context-aware path function
const DohPath = globalThis.DohPath.Overload(import.meta.url);
// Resolve paths relative to current file
const absolutePath = DohPath('^/utils/helpers.js');
// Convert to relative path
const relativePath = DohPath.Relative('/components/Button.js');
The Load System is deeply integrated with other parts of the Doh architecture:
A typical pattern is to bifurcate functionality into common, browser, and server modules:
// Main package with environment branching
Doh.Package('feature_name', {
load: [
// Common functionality
'feature_name_common',
// Environment-specific modules
'browser?? feature_name_ui',
'nodejs?? feature_name_server'
]
});
// Common functionality shared across environments
Doh.Module('feature_name_common', [], function(FeatureName) {
// Create shared interface
FeatureName = FeatureName || {};
// ... shared implementation
return FeatureName;
});
// Browser-specific implementation
Doh.Module('feature_name_ui', [
'feature_name_common',
'html' // Browser-only dependency
], function(FeatureName) {
// Add browser-specific functionality to shared object
});
// Server-specific implementation
Doh.Module('feature_name_server', [
'feature_name_common',
'express_router' // Server-only dependency
], function(FeatureName, Router) {
// Add server-specific functionality to shared object
// Register API routes
Router.AddRoute('/api/feature', function(data, req, res) {
// API implementation
});
});
Use pod configuration to enable/disable features:
// Define configuration in pod.yaml or via Doh.Pod()
Doh.Pod('feature_name', {
features: {
advanced_ui: true,
experimental: false
}
});
// Use configuration in load statements
Doh.Module('feature_name', [
// Base features
'core_component',
// Load based on feature flags
'Doh.pod.features.advanced_ui?? advanced_ui',
'Doh.pod.features.experimental?? experimental_feature',
// Combine with environment conditions
'browser&&Doh.pod.features.advanced_ui?? advanced_animations'
], function() {
// Module implementation
});
// Load dependencies
const [ dep1, dep2 ] = await Doh.load(['dependency1', 'dependency2']);
// Reload dependencies (force cache refresh)
const [ mod, link ] = await Doh.reload(['updated-module', '/styles.css']);
// Define a module with dependencies
Doh.Module('ModuleName', ['dep1', 'dep2'], function(dep1, dep2) {
// Module code
});
// Check load status with diagnostic info
Doh.loadstatus();
// View all loading attempts with parsed load statements
console.log(Doh.Loading);
// Check if a dependency loaded successfully and access its value
if (Doh.Loaded['dependency-name']) {
// Use the loaded resource (actual value or HTML element for scripts/styles)
const resource = Doh.Loaded['dependency-name'];
}
// Access module exports
const moduleExports = Doh.Loaded['module-name'];
// Access globalized imports
const globalImport = Doh.Globals.importedName;
Use Environment Branching - When modules share a file, use browser??
, nodejs??
, etc. to organize environment-specific code inline
Structure Shared Functionality - Create common modules with shared implementations that environment-specific modules extend
Control Execution Flow - Use await
for critical dependencies that others depend on, but use sparingly to maintain performance
Embrace Resilience - Mark experimental or non-critical features as optional
to prevent failures from breaking your application
Limit Global Scope - Use the global
decorator sparingly to avoid namespace conflicts
Specify Types Explicitly - When loading resources that might be ambiguous, use > type
to ensure correct handling
Be Mindful of Parameter Names - Since parameters are resolved by name from Doh.Globals, choose meaningful parameter names that match their exports
Module Not Found - Check if the module name is correct and that it's available in the current environment
Loading Order Problems - Use await
for dependencies that are required by others
Conditional Loading Issues - Verify that your condition matches the current environment
Path Resolution - Ensure you're using the appropriate path format (/
, ^/
, or ./
) for your context
Stuck Loads - Use Doh.loadstatus()
to identify dependencies that aren't resolving
Parameter Resolution Issues - Ensure parameter names match the exports in Doh.Globals or consider using Doh.load directly
// Check the status of all loaded dependencies
Doh.loadstatus();
// Examine all attempted load statements and their parsed details
console.log(Doh.Loading);
// Examine specific dependency value after loading
console.log(Doh.Loaded['dependency-name']);
// OR await the load during loading (or if you are unsure)
console.log(await Doh.load('dependency-name'))
// Inspect module exports
console.log(Doh.Loaded['module-name']);
// OR await the load during loading (or if you are unsure)
console.log(await Doh.load('module-name'))
// Check parameter resolution sources
console.log(Doh.Globals);