In Doh, Packages serve as the fundamental organizational unit for code and dependencies. Unlike traditional JavaScript packages, Doh Packages are not physical collections of files, but rather abstract definitions that combine:
This approach separates the logical organization of code (Packages) from the physical organization (files and directories), providing greater flexibility and modularity.
Packages form the backbone of Doh's architecture:
The most declarative way to define packages is through YAML configuration files:
package_name:
load:
- dependency1
- dependency2
install:
- npm:some-module
version: 1.0.0
pod:
key: value
another_package:
load:
- another-dependency
version: 0.5.0
The Auto-Packager automatically discovers these files and processes them. Key features:
.doh.yaml
extension will be recognizedFor programmatic package definition, use the Doh.Package()
function:
Doh.Package('package_name', {
load: ['dependency1', 'dependency2'],
install: ['npm:some-module']
});
This function serves as an abstraction layer that makes it possible to define packages in js files and modules. Other metafunctions like Doh.Pod(package_name, pod)
, Doh.CLI(package_name, cli_settings)
, and Doh.Install(package_name, dep_list)
, can also be used alongside Doh.Package()
definitions.
For packages with simple dependencies, you can use a shorthand notation in YAML:
simple_package: 'dependency_package'
This is equivalent to:
simple_package:
load:
- dependency_package
The core component of every package is its load block, which defines dependencies:
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:
Packages can define installation requirements:
install: {
'npm:package-name': 'version', // NPM package dependency
}
These instructions are processed by the Auto-Packager to ensure all requirements are met when the package is installed.
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:
For 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):
This 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:
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 dependencyPackage 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: