Home Doh Ref
Guides β–Ό
Dohballs β–Ό
  • πŸ“ doh_modules
    • πŸ“¦ dataforge
    • πŸ“¦ express
    • πŸ“ sso
    • πŸ“ user

The Load System: Dependency Management

Doh.load()

We found that dependency management gets simpler when you use the same syntax across all JavaScript environments. The Load System handles different environments, syntax, timing concerns, and installation requirements through declarative statements that work the same way everywhere.

Decorators like ?? and await let you express dependency relationships and loading behavior in the dependency declarations themselves.

The system provides:

  • Declarative resource specifications that handle environment differences
  • Universal load statements that work across all JavaScript environments
  • Conditional loading that branches automatically based on context
  • Flow control for dependency orchestration
  • Error resilience and graceful degradation patterns
  • NPM integration with automatic installation
  • Hot reloading capabilities for development workflow

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.

Two Access Patterns: Declarative and Dynamic Loading

The loading system is accessible through two interfaces, each solving different classes of dependency problems:

  1. Declarative Dependencies: Resource requirements are specified in Doh.Module() or Doh.Package() load arrays. This approach enables build-time analysis by the Auto-Packager, which extracts dependency graphs and environmental conditions for runtime orchestration. You declare what is needed; the system handles how and when.

  2. Dynamic Loading: The Doh.load() function provides imperative access to the same primitives for runtime dependency resolution. This enables on-demand loading scenarios while maintaining the same declarative syntax and cross-environment consistency.

Both interfaces use identical load statement syntaxβ€”the same declarative primitives that handle environment differences, timing concerns, and installation requirements. The choice between them depends on whether dependencies are known at build-time (declarative) or must be resolved dynamically (imperative).

Universal Loading Primitives

The loading system handles multiple dependency systems across environments (import, require, <script> tags). A single, declarative syntax handles all resource types and environments, with environmental logic built directly into the load statements rather than requiring external shims or configuration.

Declarative Dependencies: Build-Time Analysis

Declaring dependencies within Modules and Packages enables the Auto-Packager to perform build-time analysis. This transforms declarative statements into runtime execution plans, extracting dependency graphs, parameter patterns, and environmental conditions that eliminate discovery overhead.

Module Dependency Declaration

Executable components declare their resource requirements through declarative load arrays. The system handles environmental branching, timing, and installation automatically:

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
});

In a Doh.Package

When defining a package, which is a unit of organization, you also use a load array to specify its dependencies. This is often used to group related modules or to wrap external libraries. Remember that Doh.Package() contributes metadata only; a Doh.Module() defines both the package metadata and its callback in one place.

Doh.Package('my-ui-library', {
  load: [
    'ui-component-a',
    'ui-component-b',
    'browser?? global import Chart from "chartjs"' // Make ChartJS available
  ]
});

Benefits:

  • Declarative Clarity: Component requirements specified in a single, readable location
  • Build-Time Analysis: AST analysis extracts expressions, signatures, and environmental conditions
  • Optimization: Dependency graphs optimized for parallel loading, branching, and parameter resolution
  • Structural Metadata: Manifests enable runtime execution
  • Transparency: Application structure visible through both source declarations and generated information

Primitive Components

Load Statements: The Syntax

The foundation of the system is the load statementβ€”a simple string that encodes loading logic into declarative syntax:

// Declarative statements that handle environment complexity
'module-name'                            // Doh module/package resolution
'^/path/to/file.js'                      // File-relative path resolution
'/absolute/path/file.css'                // Project-relative resource loading
'data.json'                              // Automatic parsing and format detection
'import { render } from "preact"'        // Cross-environment ESM integration
'browser?? mobile-ui'                    // Load conditionally (only in browsers)
'await auth-system'                      // Control execution flow
'optional analytics'                     // Handle failures gracefully

Resource Types

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

Key Features

1. Environment-Aware Loading with Conditionals

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
  // ...
});

2. Flow Control with 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
    }
});

3. Error Resilience with 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
    }
});

4. NPM Integration with Auto-Installer

Use NPM modules seamlessly in any environment with automatic dependency management:

Doh.Module('DataVisualizer', [
    // Auto-installed via Auto-Installer (triggered by doh upgrade)
    'import { format } from "date-fns"',

    // Make available globally with the 'global' decorator
    'global import * as _ from "lodash"',

    // Pin a specific version in the import specifier
    'import { marked } from "marked@4.3.0"',

    // Combining features with version pinning
    'browser?? global import Chart from "chart.js@^4.0.0"'
], function(format, marked) {
    // 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);
});

Auto-Installer: Automatic NPM Dependency Management

Doh includes an Auto-Installer that automatically manages npm dependencies based on import statements in your Module and Package load blocks. This eliminates the need for manual Doh.Install() declarations in most cases.

Notes on installation behavior:

  • Auto-Installer only works with declarative load blocks (Doh.Module() and Doh.Package()) - NOT with Doh.load()
  • All imports in declarative load blocks are auto-installed when doh upgrade is run
  • Version pinning only works in declarative load blocks - Doh.load() accepts semver syntax but does not use it for installation
  • Prefer pinning versions directly in declarative import specifiers to capture version intent alongside usage
  • Doh.Install remains supported for advanced cases (non-imported artifacts, scripts, or legacy flows), but declarative load blocks with Auto-Installer are the recommended approach

5. Dynamic Loading with Doh.load()

Load dependencies on-demand when needed. Important: Doh.load() does NOT trigger the Auto-Installer. If npm packages are not already installed, Doh.load() will fail rather than installing them.

Doh.Module('DynamicFeature', [
  // to Auto-Install dynamic features, use async:
  // (also, start loading immediately, don't bind exports or wait for completion)
  'async reporting-engine',
  // to attempt failure-tolerant optional install use async optional:
  // (additionally don't report or worry about errors)
  'async optional import { jsPDF } from "jspdf"'
], async function(/* nothing here because the async load statements above don't bind */) {
    // Initial module code

    if (userNeedsReports) {
        // Load reporting features only when needed
        // Note: This assumes 'reporting-engine' and 'jsPDF' are already installed
        // Missing npm packages will NOT be auto-installed by Doh.load()
        const [ generateReport, { jsPDF } ] = await Doh.load([
            'reporting-engine',
            'import { jsPDF } from "jspdf"'
        ]);

        // Use the dynamically loaded features
        generateReport();
    }
});

Key Difference: Unlike Doh.Module()/Doh.Package() load blocks which auto-install missing npm packages via the Auto-Installer, Doh.load() is for runtime dynamic loading where dependencies should already be available. Use declarative load blocks if you want automatic npm dependency management.

6. Hot Reloading with Doh.reload()

Reload dependencies without page refresh:

// Reload specific dependencies
const [ , , { component } ] = 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;

Load Statement Syntax

A load statement can have multiple decorators that modify its behavior:

[condition??] [flow] [scope] [resource] [> type]

Where:

  • [condition??] - Optional environment/config condition (browser??, config.feature??)
  • [flow] - Optional flow control (await, async, optional)
  • [scope] - Optional scope decorator (global)
  • [resource] - Required resource identifier (module name, path, import statement)
  • [> type] - Optional explicit type declaration ( > json, > doh)

Decorator Order

Decorators must follow this specific order:

  1. Conditional decorator first (browser??, etc.)
  2. Flow control (await, async, optional) and scope (global) decorators next
  3. The resource identifier
  4. Type specifier ( > [type]) last

Examples

  'browser?? await global import { Chart } from "chart.js" > import'
// ^ condition ^ flow ^ scope ^ resource                   ^ type

Decorator Reference

Conditional Decorators

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

Flow Control Decorators

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

Scope Decorators

Decorator Description
global Makes ESM imports available globally

Understanding Load Blocks and Decorator Scoping

Each load block (array of dependencies) operates as its own mini-scope with two processing phases:

  1. Parsing phase: All entries in the block are collected and parsed into three distinct groups:

    • Group 1: async-decorated dependencies are identified first
    • Group 2: await-decorated dependencies are collected next
    • Group 3: Regular dependencies (no flow control decorators) are gathered last
  2. Execution phase: Dependencies are processed based on their grouping:

    • Group 1: async dependencies start immediately when found and run in parallel - they are effectively "hoisted" since they start before anything else but don't block
    • Group 2: await dependencies are chained together in sequence (each must complete before the next starts)
    • Group 3: Regular dependencies are executed using Promise.all (parallel execution) after all awaited dependencies complete

This three-group prioritization ensures that:

  1. async dependencies begin loading immediately without entering the normal execution flow
  2. await-decorated plugins and core libraries can fully initialize before dependent code loads
  3. Non-critical dependencies run in parallel for optimal performance when possible

Scoped Parameter Resolution (SPR) in callbacks

Scoped Parameter Resolution (SPR) enables module callbacks to consume names that earlier modules producedβ€”without re-importing. The Auto‑Packager uses AST analysis to extract function signatures, including destructuring patterns, default values, and parameter forms, recording them in manifests so runtime resolution is fast, consistent, and analyzable.

Resolution is deterministic and scoped: The system searches only the scope tree created by your load ancestry (current module's producers β†’ ancestors). No implicit global searchβ€”Doh.Globals[name] and globalThis[name] require explicit source declarations.

Supported Parameter Forms:

  1. Simple names: function(core, auth) β€” resolved by name against the scope tree
  2. Destructuring: function({ format, parse }) β€” properties resolved independently, then re-packed
  3. String aliases: function(util = 'underscore') β€” resolve using the lookup name against the scope tree
  4. Object repack: function(opts = { format: 'dateFnsFormat' }) β€” build object by resolving each key's lookup name
  5. Source declarations: function(Store = {}) β€” pins to Doh.Globals.Store (explicit shared containers)
  6. Global declarations: function(Registry = { globalThis: {} }) β€” pins to globalThis.Registry (explicit globalThis containers)

If a name is not found: Resolution yields undefined with a diagnostic (no silent failures). The Auto-Packager's manifest visibility makes "mystery capture" detectable and fixable.

This enables suite patterns: Import once high up; descendants ask by name via normal scope tree resolution. Load arrays build scope; callback parameters consume scope.

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 against the scope tree (not position)
    // Resolution searches this module β†’ ancestor modules; no automatic global scope searching
    
    // 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 has advantages:

  • Reordering dependencies in the load array doesn't break your code
  • Module names can differ from parameter names for readability
  • Export handling becomes more consistent through Doh.Globals namespaces
  • Modules can be more easily refactored without updating all dependents
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
    // Parameters resolved by name against the scope tree (module β†’ ancestors)
    // NOT by position in the dependency array, and NO automatic global scope searching
    
    // 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');
});

Path Resolution

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'

DohPath Utilities

For complex path operations:

// Create a context-aware path function
const DohPath = 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('/absolute/path/to/components/Button.js');

Integration with Doh Architecture

The Load System is integrated with other parts of the Doh architecture:

  1. Packages: Define dependency relationships using load statements
  2. Modules: Extend packages with runtime functions and parameter resolution
  3. Auto-Packager: Analyzes dependencies and generates manifests
  4. Patterns: Created and loaded within modules for component reuse
  5. Dohballs: Packaging and distribution of bundles containing modules

Real-World Usage Patterns

1. Common Environment Branching Pattern

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
  });
});

2. Feature Flags and Configuration-Based Loading

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
});

API Reference

Core Declarations and Functions

// Declaratively define a module with its dependencies
Doh.Module('ModuleName', ['dep1', 'dep2'], function(dep1, dep2) {
    // Module code
});

// Imperatively load dependencies on-demand
const [ dep1, dep2 ] = await Doh.load(['dependency1', 'dependency2']);

// Imperatively reload dependencies (force cache refresh)
const [ mod, link ] = await Doh.reload(['updated-module', '/styles.css']);

// Check load status with diagnostic info
Doh.loadstatus();

Accessing Loaded Resources

// 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;

Best Practices

  1. Use Environment Branching - When modules share a file, use browser??, nodejs??, etc. to organize environment-specific code inline

  2. Structure Shared Functionality - Create common modules with shared implementations that environment-specific modules extend

  3. Control Execution Flow - Use await for critical dependencies that others depend on, but use sparingly to maintain performance

  4. Embrace Resilience - Mark experimental or non-critical features as optional to prevent failures from breaking your application

  5. Limit Global Scope - Use the global decorator sparingly to avoid namespace conflicts

  6. Specify Types Explicitly - When loading resources that might be ambiguous, use > type to ensure correct handling

  7. Be Mindful of Parameter Names - Since parameters are resolved by name from Doh.Globals, choose meaningful parameter names that match their exports

Troubleshooting

Common Issues

  1. Module Not Found - Check if the module name is correct and that it's available in the current environment

  2. Loading Order Problems - Use await for dependencies that are required by others

  3. Conditional Loading Issues - Verify that your condition matches the current environment

  4. Path Resolution - Ensure you're using the appropriate path format (/, ^/, or ./) for your context

  5. Stuck Loads - Use Doh.load_status() to identify dependencies that aren't resolving

  6. Parameter Resolution Issues - Ensure parameter names match the exports in Doh.Globals or consider using Doh.load directly

Diagnosing Load Problems

// Check the status of all loaded dependencies
Doh.load_status();

// 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);

Related Documentation

  • Modules - Learn about the executable units built on top of the Load System
  • Packages - Understand how packages organize code and dependencies
  • Auto-Packager - Explore how dependencies are analyzed and managed
  • Hot Module Replacement - Discover how loaded resources can update in real-time
  • DohPath - Master path resolution across different environments
  • Dohballs - Learn about Doh's unique approach to code distribution
Last updated: 10/22/2025