In Doh, Packages are first-class loadable units that are identical in structure to Modules but without a callback. Practically, a Package is a Module without executable code: it declares a name, a load
array (its dependencies), and optional metadata (install, pod, cli). Because there’s no callback, a Package can be defined in JSON/YAML (.doh.yaml
) or via Doh.Package('name', { ... })
.
Key points:
load
array is its dependency list; it is processed by the Load System exactly like a Module’s.Doh.Packages
registry and share the same namespace as Modules; a Module is simply a Package with a callback.This separation lets you model structure and relationships declaratively, while Modules add behavior on top of that structure when needed.
Packages contribute to the layered scope used by Scoped Parameter Resolution (SPR):
load
arrays; Modules consume those layers via callback parameters.Resolver (rule of four), summarized for Packages context:
Doh.Globals[name]
(shared state collection)globalThis[name]
See Modules → Scoped Parameter Resolution (SPR) for full details and examples.
Packages form the backbone of Doh’s architecture:
load
arrays are processed identically to Modules, including decorators and conditionsThe most declarative way to define packages is through YAML configuration files. The Auto-Packager discovers these files and converts them into package entries (no callback). 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 compile-time declaration consumed by the Auto-Packager when scanning .js
files. Conceptually, packages are just JSON (name, load, install, pod, cli, etc.). This call contributes that metadata to manifests; 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 automatically:
This 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 transparently 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: