Packages are the building blocks of Doh's architectural organization—pure structure without behavior. They encode structural relationships, dependencies, and metadata into reusable units that Modules can depend on by name.
A Package is simply a Module without a callback: same registry (Doh.Packages
), same dependency system, same analysis by the Auto-Packager. This unified treatment means that organizational structure and behavioral implementation follow the same rules.
The system provides:
Doh.Packages
registry (a Module is simply a Package with a callback)This architectural separation allows you to encode organizational structure independently of behavior, creating reusable relationship patterns that Modules can extend with execution logic.
Packages contribute to the layered scope used by Scoped Parameter Resolution (SPR)—enabling module callbacks to consume names that earlier modules produced without re-importing.
load
arrays; Modules consume those layers via callback parameters.Resolution is deterministic (current module's producers → ancestors), with no implicit global search. The Auto-Packager's manifest visibility + diagnostics mean "mystery capture" is detectable and fixable.
See Modules → Scoped Parameter Resolution (SPR) for full details and examples.
Packages serve as the structural foundation of Doh applications by encoding organizational relationships into reusable primitives:
The most declarative way to define packages is through YAML configuration files. The Auto-Packager discovers these files through filesystem scanning and uses YAML parsing to extract package definitions. Key features:
.doh.yaml
extension will be recognizedFor packages with simple dependencies, you can use a shorthand notation in YAML:
simple_package: dependency_package
A powerful use of this is to make a
packageName.doh.yaml
file withpackageName: doh_js
in it, and nothing else. This depends on the Doh core, and is all that is needed to make a folder into a Dohball, ready to be baked and upgraded in other instances, even if there is no JS in the folder at all.
This is equivalent to:
simple_package:
load:
- dependency_package
For programmatic package definition, use the Doh.Package()
function (defines a Package without a callback):
Doh.Package('package_name', {
load: ['dependency1', 'dependency2'],
install: ['npm:some-module']
});
Doh.Package()
is a build-time declaration analyzed by the Auto-Packager's AST parser when scanning .js
files. The Auto-Packager uses Abstract Syntax Tree analysis to extract complete structural information from the declaration, including complex object literals, computed values, and environment conditions. This call contributes rich metadata to manifests through static analysis; it does not execute code. Use Doh.Module()
when you need to add a runtime callback to the same structural concept.
In Doh, modules and packages share the same global registry (Doh.Packages
). A module is a package with a callback, defined atomically via Doh.Module('Name', [...], callback)
. Do not define a Doh.Package('Name', ...)
and then add a callback later with Doh.Module('Name', ...)
. Because the Auto-Packager treats each declaration as a distinct definition, overlapping names from separate Doh.Package()
and Doh.Module()
calls are considered conflicts.
Other metafunctions like Doh.Pod(package_name, pod)
, Doh.CLI(package_name, cli_settings)
, and Doh.Install(package_name, dep_list)
can be used alongside Doh.Package()
OR Doh.Module()
to contribute metadata.
The core component of every package is its load
block, which defines that package’s dependencies. It is processed by the Load System exactly like a Module’s load
block:
load: [
'dependency1', // Basic dependency
'await dependency2', // Awaited dependency (loads before subsequent deps)
'async dependency3', // Asynchronous loading (starts immediately)
'browser?? browser-only-dep', // Conditional dependency (browser environments only)
'nodejs?? node-only-dep', // Environment-specific dependency
'import { func } from "es-module"', // ES module import
'path/to/script.js', // Direct file loading
'styles/main.css' // CSS loading
]
The load block supports a rich set of decorators and syntax for fine-grained control over dependency loading:
await
and async
control the loading sequencebrowser??
and nodejs??
config.debug&&!production??
for configuration-based loadingfile.php > css
for explicit resource type declarationFor complete details, see the Load System documentation.
Environment branching is a key capability of the Doh Package system. It allows you to define different dependencies for different environments within a single package definition:
// Define a package with environment-specific dependencies
Doh.Package('cross_platform_feature', {
load: [
// Common dependencies loaded in all environments
'core_utilities',
'data_models',
// Browser-specific dependencies
'browser?? ui_components',
'browser?? styles/main.css',
// Node.js-specific dependencies
'nodejs?? server_api',
'nodejs?? import fs from "fs-extra"',
// Configuration-based loading
'Doh.pod.analytics?? analytics_module',
// Combined conditions
'browser&&Doh.pod.debug?? debug_tools'
]
});
A common pattern for cross-platform features is to create a "bifurcated" package structure that branches into environment-specific implementations:
// Main package that branches by environment
Doh.Package('feature_name', {
load: [
// Common functionality shared across environments
'feature_name_common',
// Environment-specific implementations
'browser?? feature_name_ui', // Only loads in browsers
'nodejs?? feature_name_server' // Only loads in Node.js
]
});
This pattern allows you to:
Preferred approach:
import x from 'pkg@^1.2.0'
).Optional (advanced) installation block:
install: {
'npm:package-name': '^1.2.3', // Explicit requirement when needed
}
The Auto-Packager merges installs derived from pinned imports with any explicit install
entries. Use explicit install
when you need to declare non-imported dependencies or specialized constraints.
Packages can include pod configuration data:
pod: {
key: 'value',
nested: {
config: true
}
}
This data is merged into the global pod configuration and can be accessed via Doh.pod.key
. Placing your keys in a container is preferred.
Packages can register CLI commands:
cli: {
'command-name': {
file: 'path/to/script.js',
help: 'description of command'
}
}
These commands become available through the Doh CLI system.
A Module in Doh is an extension of a Package that adds a callback function:
Doh.Module('module_name', ['dependency1', 'dependency2'], function() {
// Module code here
});
Modules inherit all the capabilities of Packages but add execution logic:
Doh.Packages
namespace as plain packagesFor complete details, see the Module documentation.
The Auto-Packager provides AST-based analysis:
Doh.Package()
declarationsThis integration enables:
For complete details, see the Auto-Packager documentation.
When a package is requested (either directly or as a dependency):
Doh.Module()
), that callback is executedThis process is handled by the Doh runtime, making dependency management seamless.
// Main package with environment branching
Doh.Package('user_management', {
load: [
// Common functionality
'user_management_common',
// Environment-specific implementations
'browser?? user_management_ui',
'nodejs?? user_management_server'
]
});
This pattern creates a unified namespace for a feature while keeping environment-specific implementations separate.
// Define configuration in pod.yaml or via Doh.Pod()
Doh.Pod('feature_system', {
features: {
advanced: true,
experimental: false
}
});
// Package that uses configuration-based loading
Doh.Package('feature_system', {
load: [
// Core functionality
'feature_core',
// Conditional features based on configuration
'config.features.advanced?? advanced_features',
'config.features.experimental?? experimental_features',
// Combine with environment conditions
'browser&&config.features.advanced?? advanced_ui'
]
});
This approach allows features to be enabled or disabled through configuration without code changes.
// Package that wraps an NPM module for use in Doh
Doh.Package('chart_library', {
// Installation requirements
install: {
'npm:chart.js': '^3.0.0'
},
// Load the module with environment branching
load: [
// Browser: Load from ESM.sh
'browser?? global import Chart from "chart.js"',
// Node.js: Direct import
'nodejs?? import Chart from "chart.js"'
]
});
This pattern makes external NPM packages available as Doh packages with proper environment handling.
It's important to distinguish between Packages and Dohballs:
Doh.Packages
registry)A Dohball might contain multiple Package definitions, but the Package itself is not the collection of files—it's the abstract definition that is bundled into Dohballs.
Doh encourages defining multiple packages in the same file:
// Package definition
Doh.Package('Package1', {
load: ['dep1', 'dep2']
});
// Module definition (a package with a function)
Doh.Module('Module1', ['Package1'], function() {
// Module code
});
// Another package definition
Doh.Package('Package2', {
load: ['dep4', 'dep5', 'Module1']
});
This approach allows for organized, modular code without necessitating separate files for each package or module.
When the Auto-Packager processes package definitions:
.doh.yaml
files, Doh.Package()
calls, and Doh.Module()
callsDuring runtime loading, the Doh Load System handles:
Doh.load()
or as a dependency in Doh.Module() / Doh.Package()
Package Not Found
Doh.Package()
or in a .doh.yaml
filedoh update
to refresh manifestsDependency Not Loading
Doh.loadstatus()
to see what's loaded and what's missingEnvironment-Specific Issues
browser??
, nodejs??
) are correctly appliedIsBrowser()
, IsNode()
) to handle edge casesUnlike traditional package systems (npm, yarn, etc.), Doh Packages: