@remotex-labs/xbuild
Version:
A versatile JavaScript and TypeScript toolchain build system
1,444 lines (1,442 loc) • 212 kB
TypeScript
/**
* This file was automatically generated by xBuild.
* DO NOT EDIT MANUALLY.
*/
import { BuildOptions, BuildResult, Loader, Message, OnEndResult, OnLoadArgs, OnLoadResult, OnResolveArgs, OnResolveResult, OnStartResult, PartialMessage, Platform, PluginBuild } from 'esbuild';
import ts, { CompilerOptions, DiagnosticCategory, IScriptSnapshot, LanguageService, ParsedCommandLine, ResolvedModuleWithFailedLookupLocations, SourceFile } from 'typescript';
import { IncomingMessage, ServerResponse } from 'http';
import { ResolveMetadataInterface as xMapResolveMetadataInterface, ResolveOptionsInterface } from '@remotex-labs/xmap';
import { Options } from 'yargs';
/**
* Orchestrates the build process across multiple variants with lifecycle management and configuration.
*
* @remarks
* The `BuildService` is the primary service for managing multi-variant builds in xBuild.
* It handles configuration changes, variant lifecycle, type checking, and build execution
* with support for hot reloading and file watching.
*
* **Key responsibilities**:
* - Manages multiple build variants (e.g., production, development, testing)
* - Provides reactive configuration updates through subscription system
* - Coordinates lifecycle hooks (onStart, onEnd) across all variants
* - Handles macro transformation and directive processing
* - Supports incremental builds and file touch notifications
* - Aggregates build results and type checking diagnostics
*
* **Architecture**:
* Each variant is managed by a {@link VariantService} instance with its own:
* - esbuild configuration and context
* - TypeScript language service
* - Lifecycle provider for hooks and plugins
* - Build state and watch mode support
*
* The service uses a subscription pattern to react to configuration changes,
* automatically creating new variants or disposing removed ones.
*
* @example Basic usage
* ```ts
* const buildService = new BuildService({
* variants: {
* production: {
* esbuild: { minify: true, sourcemap: false }
* },
* development: {
* esbuild: { minify: false, sourcemap: true }
* }
* }
* });
*
* // Build all variants
* const results = await buildService.build();
* console.log(results.production.errors);
* ```
*
* @example With lifecycle hooks
* ```ts
* const buildService = new BuildService(config);
*
* buildService.onStart = (context) => {
* console.log(`Building variant: ${context.variantName}`);
* };
*
* buildService.onEnd = (context) => {
* console.log(`Completed ${context.variantName}: ${context.result.errors.length} errors`);
* };
*
* await buildService.build();
* ```
*
* @example Configuration reload
* ```ts
* const buildService = new BuildService(initialConfig);
*
* // Reload with new configuration
* buildService.reload({
* variants: {
* production: { esbuild: { target: 'es2020' } },
* staging: { esbuild: { minify: true } }
* }
* });
* // Old variants disposed, new ones created
* ```
*
* @example Type checking
* ```ts
* const buildService = new BuildService(config);
* const diagnostics = await buildService.typeChack();
*
* for (const [variant, errors] of Object.entries(diagnostics)) {
* console.log(`${variant}: ${errors.length} type errors`);
* }
* ```
*
* @see {@link VariantService} for individual variant management
* @see {@link ConfigurationService} for configuration handling
* @see {@link LifecycleProvider} for hook management
*
* @since 2.0.0
*/
declare class BuildService {
private argv;
/**
* Callback invoked when a build completes for any variant.
*
* @remarks
* Set via the `onEnd` setter. Called after each variant's build finishes,
* providing access to build results, errors, warnings, and metadata.
*
* @since 2.0.0
*/
private onEndCallback?;
/**
* Callback invoked when a build starts for any variant.
*
* @remarks
* Set via the `onStart` setter. Called before each variant's build begins,
* after macro metadata analysis completes.
*
* @since 2.0.0
*/
private onStartCallback?;
/**
* Map of variant names to their service instances.
*
* @remarks
* Contains all active build variants. Variants are created during construction
* and updated when configuration changes via {@link reload} or {@link setConfiguration}.
*
* @since 2.0.0
*/
private variants;
/**
* Configuration service managing build settings and variant definitions.
*
* @remarks
* Injected singleton that provides reactive configuration updates through
* its subscription system. Changes trigger automatic variant recreation.
*
* @since 2.0.0
*/
private readonly configuration;
/**
* Creates a new BuildService instance with optional configuration and command-line arguments.
*
* @param argv - Command-line arguments passed to variant services (default: empty object)
*
* @remarks
* The constructor:
* 1. Accepts optional initial configuration
* 2. Stores command-line arguments for variant initialization
* 3. Subscribes to configuration changes via {@link parseVariants}
* 4. Automatically creates variants defined in the configuration
*
* Configuration can be provided later via {@link reload} or {@link setConfiguration}
* if not supplied during construction.
*
* @since 2.0.0
*/
constructor(argv?: Record<string, unknown>);
/**
* Gets the current complete build configuration.
*
* @returns The active build configuration including all variants and common settings
*
* @remarks
* Retrieves the immutable snapshot of the current configuration from the
* configuration service. Changes to the returned object do not affect
* the actual configuration - use {@link setConfiguration} or {@link reload} instead.
*
* @since 2.0.0
*/
get config(): BuildConfigInterface;
/**
* Sets the callback to invoke when any variant build completes.
*
* @param callback - Function receiving the result context with build output and metadata
*
* @remarks
* The callback receives a {@link ResultContextInterface} containing:
* - Variant name
* - Build result (errors, warnings, outputs)
* - Metadata files and outputs
* - Timestamp and duration
*
* Called after the build finishes but before promises resolve.
*
* @example
* ```ts
* buildService.onEnd = (context) => {
* const { variantName, result } = context;
* console.log(`✓ ${variantName}: ${result.errors.length} errors`);
* };
* ```
*
* @since 2.0.0
*/
set onEnd(callback: OnEndType);
/**
* Sets the callback to invoke when any variant build starts.
*
* @param callback - Function receiving the build context with file and variant information
*
* @remarks
* The callback receives a {@link BuildContextInterface} containing:
* - Variant name
* - File path being processed
* - Build stage and metadata
* - Loader type
*
* Called after macro analysis but before transformation begins.
*
* @example
* ```ts
* buildService.onStart = (context) => {
* console.log(`Building ${context.args.path} for ${context.variantName}`);
* };
* ```
*
* @since 2.0.0
*/
set onStart(callback: OnStartType);
/**
* Reloads the build configuration and updates variants accordingly.
*
* @param config - Optional new configuration to replace the current one
* @param clearCache - Whether to clear cached files and TypeScript language service state before reloading
*
* @remarks
* The reload process:
* 1. Optionally clears cached file state and TypeScript language service data
* 2. Replaces configuration if provided
* 3. Compares new variant names with existing ones
* 4. Disposes variants no longer in configuration
* 5. Creates new variants from the updated configuration
* 6. Existing variants with matching names continue unchanged
*
* This is useful for hot-reloading configuration files without restarting the build process.
*
* @example
* ```ts
* // Reload with a new staging variant
* buildService.reload({
* config: {
* variants: {
* ...buildService.config.variants,
* staging: { esbuild: { minify: true } }
* }
* }
* });
* ```
*
* @example
* ```ts
* // Reload and clear cached file/type-checking state first
* buildService.reload({
* clearCache: true
* });
* ```
*
* @since 2.3.0
*/
reload({ config, clearCache }?: ReloadOptionsInterface): void;
/**
* Notifies all variants that specific files have been modified.
*
* @param files - Array of file paths that have changed
*
* @remarks
* Propagates file change notifications to all variant services, triggering
* incremental rebuilds in watch mode. Each variant's watch service handles
* the actual rebuild logic.
*
* Typically used by file watchers or development servers to trigger hot reloads.
*
* @example
* ```ts
* // File watcher integration
* watcher.on('change', (changedFiles) => {
* buildService.touchFiles(changedFiles);
* });
* ```
*
* @see {@link VariantService.touchFiles}
*
* @since 2.0.0
*/
touchFiles(files: Array<string>): void;
/**
* Partially updates the build configuration without replacing it entirely.
*
* @param config - Partial configuration to merge with the current configuration
*
* @remarks
* Performs a shallow merge of the provided configuration with the current one.
* Use {@link reload} for deep configuration replacement or variant restructuring.
*
* Common use cases:
* - Toggling minification
* - Updating define constants
* - Modifying common build options
*
* @example
* ```ts
* // Enable minification for all variants
* buildService.setConfiguration({
* common: { esbuild: { minify: true } }
* });
* ```
*
* @see {@link reload} for full configuration replacement
*
* @since 2.0.0
*/
setConfiguration(config: Partial<BuildConfigInterface>): void;
/**
* Performs TypeScript type checking across all variants.
*
* @returns Promise resolving to a map of variant names to their diagnostic results
*
* @remarks
* Runs the TypeScript compiler's diagnostic checker for each variant in parallel.
* Returns all type errors, warnings, and suggestions without failing the build.
*
* **Note**: Method name has a typo - should be `typeCheck` but kept for backward compatibility.
*
* Useful for:
* - Pre-build validation
* - CI/CD type checking pipelines
* - IDE integration and diagnostics display
*
* @example
* ```ts
* const diagnostics = await buildService.typeChack();
*
* for (const [variant, errors] of Object.entries(diagnostics)) {
* if (errors.length > 0) {
* console.error(`${variant} has ${errors.length} type errors`);
* errors.forEach(err => console.error(err.messageText));
* }
* }
* ```
*
* @see {@link DiagnosticInterface}
* @see {@link VariantService.check}
*
* @since 2.0.0
*/
typeChack(): Promise<Record<string, DiagnosticInterface[]>>;
/**
* Executes the build process for all or specific variants,
* respecting `dependOn` ordering and running independent variants in parallel.
*
* @param names - Optional array of variant names to build (builds all if omitted)
*
* @returns Promise resolving to a map of variant names to their enhanced build results
*
* @throws xBuildError - When a circular dependency is detected before any build starts
* @throws AggregateError - When any variant build fails, containing all error details
*
* @remarks
* The build process:
* 1. Validates the dependency graph — throws immediately on circular deps
* 2. Launches all requested variants concurrently
* 3. Each variant awaits its `dependOn` dependencies before running
* 4. Collects results and errors from each variant without stopping others
* 5. Enhances build results with additional metadata
* 6. Throws AggregateError if any builds failed
*
* **Dependency resolution**:
* - `dependOn` variants always finish before the dependent variant starts
* - A shared dependency (e.g. two variants both depending on `types`) builds
* only once — subsequent dependents await the same running promise
* - Independent variants run fully in parallel
*
* **Error handling**:
* - Build failures don't stop other variants from building
* - All errors are collected into {@link BuildTreeInterface.errors} and thrown
* together after all builds complete
* - Supports both esbuild-specific errors and generic JavaScript errors
*
* **Result enhancement**:
* Build results are processed by {@link enhancedBuildResult} to provide
* structured error and warning information.
*
* @example Build all variants
* ```ts
* try {
* const results = await buildService.build();
* console.log(`Built ${Object.keys(results).length} variants`);
* } catch (error) {
* if (error instanceof AggregateError) {
* error.errors.forEach(err => console.error(err.message));
* }
* }
* ```
*
* @example Build specific variants
* ```ts
* const results = await buildService.build(['production', 'staging']);
* // Only production and staging variants are built
* ```
*
* @example With dependency ordering
* ```ts
* // Given: main dependOn shared, shared dependOn types
* // Build order: types → shared → main (dependencies first)
* const results = await buildService.build();
* ```
*
* @see {@link buildVariant} for per-variant execution and caching logic
* @see {@link BuildTreeInterface}
* @see {@link enhancedBuildResult}
* @see {@link BuildResultInterface}
* @see {@link VariantService.build}
* @see {@link validateDependencies} for circular dependency detection
*
* @since 2.4.0
*/
build(names?: Array<string>): Promise<Record<string, BuildResultInterface>>;
/**
* Triggers the onEnd callback when a variant build completes.
*
* @param context - The result context containing build output and metadata
*
* @remarks
* Internal handler that safely invokes the user-provided onEnd callback if set.
* Called by variant lifecycle providers after each build finishes.
*
* @since 2.0.0
*/
private onEndTrigger;
/**
* Triggers the onStart callback and performs macro analysis before a variant build starts.
*
* @param context - The build context containing file and variant information
*
* @returns Promise resolving to the load result after macro metadata analysis
*
* @throws Error - Propagates errors from macro analysis that aren't AggregateErrors
*
* @remarks
* Internal handler that:
* 1. Analyzes macro metadata for the file being built
* 2. Invokes the user-provided onStart callback if set
* 3. Returns the analysis result to the build pipeline
* 4. Converts AggregateErrors to esbuild-compatible error format
*
* The macro analysis prepares directive information ($$ifdef, $$inline, etc.)
* that will be used during the transformation phase.
*
* @see {@link analyzeMacroMetadata}
*
* @since 2.0.0
*/
private onStartTrigger;
/**
* Disposes and removes variants by name.
*
* @param dispose - Array of variant names to dispose
*
* @remarks
* Cleanly shuts down variant services and removes them from the internal map.
* Called during configuration reload to remove variants no longer in config.
*
* Each variant's dispose method:
* - Stops watch mode if active
* - Cleans up esbuild contexts
* - Releases TypeScript language service resources
*
* @since 2.0.0
*/
private disposeVariants;
/**
* Compares two objects and returns keys present in the second but not the first.
*
* @param obj1 - Reference object (usually new configuration)
* @param obj2 - Comparison object (usually existing variants)
*
* @returns Array of keys present in obj2 but missing in obj1
*
* @remarks
* Used to identify variants that should be disposed during configuration reload.
* If a variant exists in the service but not in the new configuration, it's removed.
*
* @since 2.0.0
*/
private compareKeys;
/**
* Creates variant service instances from the current configuration.
*
* @throws xBuildError - When no variants are defined in the configuration
*
* @remarks
* Invoked by the configuration subscription whenever configuration changes.
* For each variant in the configuration:
* 1. Skips if the variant already exists (prevents recreation)
* 2. Creates a new LifecycleProvider with hooks
* 3. Attaches onStart and onEnd listeners
* 4. Creates VariantService with configuration
* 5. Registers macro transformer directive
*
* The lifecycle hooks enable:
* - Build start/end notifications
* - Macro analysis and transformation
* - Custom plugin integration
*
* @see {@link VariantService}
* @see {@link LifecycleProvider}
* @see {@link transformerDirective}
*
* @since 2.0.0
*/
private parseVariants;
/**
* Returns the normalized `dependOn` list for a variant.
*
* @param variantName - The variant to look up
*
* @returns Array of dependency variant names, empty if none defined
*
* @remarks
* Normalizes the `dependOn` field from the variant configuration into
* a consistent array form, since the field accepts either a single
* string or an array of strings.
*
* @see {@link buildVariant}
* @see {@link validateDependencies}
*
* @since 2.4.0
*/
private getDependOn;
/**
* Validates the dependency graph for all or specific variants before building starts.
*
* @param names - Optional subset of variant names to validate (validates all if omitted)
*
* @throws xBuildError - When a circular dependency is detected, with the full cycle
* path included in the message (e.g. `Circular dependency detected: main → shared → main`)
*
* @remarks
* Performs a depth-first traversal of the dependency graph using two sets:
* - `visited` — variants fully processed, skipped on revisit
* - `inStack` — variants in the current traversal path, used to detect cycles
*
* Dependencies that exist in `dependOn` but have no matching variant instance
* in {@link variants} are silently skipped.
*
* Called by {@link build} before any variant starts, ensuring the entire
* graph is valid before any work begins.
*
* @see {@link build}
* @see {@link getDependOn}
*
* @since 2.4.0
*/
private validateDependencies;
/**
* Executes the build for a single variant after all its dependencies resolve.
*
* @param name - Variant name to build
* @param ctx - Isolated build context for this {@link build} invocation
*
* @remarks
* Called exclusively by {@link buildVariant} after the promise is registered
* in {@link BuildTreeInterface.cache}, preventing re-entry.
*
* Awaits all `dependOn` dependencies concurrently via `Promise.all` before
* running the variant. Dependencies missing from {@link variants} are silently skipped.
*
* Errors are pushed into {@link BuildTreeInterface.errors} rather than thrown,
* so all variants attempt to build even if a sibling fails. Handles both
* esbuild-specific errors via {@link isBuildResultError} and generic JavaScript errors.
*
* @see {@link buildVariant}
* @see {@link getDependOn}
* @see {@link isBuildResultError}
* @see {@link BuildTreeInterface}
* @see {@link enhancedBuildResult}
*
* @since 2.4.0
*/
private executeBuild;
/**
* Builds a single variant, first awaiting any `dependOn` dependencies.
*
* @param name - Variant name to build
* @param ctx - Isolated build context for this {@link build} invocation,
* carrying the promise cache, error list, and results map
*
* @returns Promise that resolves when the variant and all its dependencies finish
*
* @remarks
* Stores its promise in {@link BuildTreeInterface.cache} on first call so any
* subsequent caller depending on the same variant awaits the already-running
* promise rather than triggering a duplicate build.
*
* Delegates actual execution to {@link executeBuild} after registering the promise,
* ensuring the cache is populated before any async work begins.
*
* @see {@link build}
* @see {@link executeBuild}
* @see {@link BuildTreeInterface}
*
* @since 2.4.0
*/
private buildVariant;
}
/**
* Options used to reload the build service configuration.
*
* @remarks
* These options control how configuration reload behaves:
* - `config` replaces the current build configuration
* - `clearCache` clears cached file and TypeScript language service state before reloading
*
* @since 2.3.0
*/
interface ReloadOptionsInterface {
/**
* Optional new configuration to replace the current one.
*
* @remarks
* When provided, the build service reloads using this configuration
* before recalculating variants.
*
* @since 2.3.0
*/
config?: PartialBuildConfigType;
/**
* Whether to clear cached files and TypeScript language service state before reloading.
*
* @remarks
* When enabled, cached file tracking and language service state are reset
* before the configuration is reloaded.
*
* @since 2.3.0
*/
clearCache?: boolean;
}
/**
* Isolated state container for a single {@link BuildService.build} invocation.
*
* @remarks
* Created fresh on every `build()` call to ensure concurrent watch-mode
* rebuilds cannot share or overwrite each other's state.
*
* Passed through {@link BuildService.buildVariant} to carry the promise
* cache, accumulated errors, and collected results across the full
* dependency graph traversal.
*
* @see {@link BuildService.build}
* @see {@link BuildService.buildVariant}
*
* @since 2.4.0
*/
interface BuildTreeInterface {
/**
* Promise cache keyed by variant name.
*
* @remarks
* Ensures each variant builds exactly once per `build()` call.
* Subsequent callers depending on the same variant await the
* already-running promise instead of triggering a duplicate build.
*
* @since 2.4.0
*/
cache: Map<string, Promise<void>>;
/**
* Accumulated build errors across all variants.
*
* @remarks
* Errors are pushed here rather than thrown immediately, so all
* variants attempt to build even if a sibling fails. Thrown together
* as an `AggregateError` after all builds complete.
*
* @since 2.4.0
*/
errors: Array<Error>;
/**
* Collected build results keyed by variant name.
*
* @remarks
* Populated by {@link BuildService.buildVariant} as each variant
* finishes. Only contains results for variants that built successfully.
*
* @since 2.4.0
*/
results: Record<string, BuildResultInterface>;
}
/**
* Extended build result interface with normalized error and warning arrays.
*
* @remarks
* This interface extends esbuild's {@link BuildResult} while replacing the `errors` and `warnings`
* properties with normalized Error instances instead of esbuild's Message objects. This normalization
* provides consistent error handling throughout the xBuild system with proper stack traces, formatting,
* and error classification.
*
* **Key differences from esbuild's BuildResult**:
* - `errors`: Changed from `Message[]` to `Error[]` with normalized error types
* - `warnings`: Changed from `Message[]` to `Error[]` with normalized error types
* - All other properties (metafile, outputFiles, mangleCache) are preserved unchanged
*
* **Benefits of normalization**:
* - Consistent error handling across different error sources (esbuild, TypeScript, VM runtime)
* - Proper error inheritance and type checking
* - Rich stack trace information with source mapping
* - Formatted error output with syntax highlighting
* - Integration with xBuild's custom error classes
*
* The normalized errors may include:
* - {@link TypesError} for TypeScript type checking failures
* - {@link xBuildError} for text errors during build hooks
* - {@link esBuildError} for esbuild compilation errors with location information
* - {@link VMRuntimeError} for runtime errors during build hooks
* - {@link xBuildBaseError} for custom build system errors
*
* @example
* ```ts
* const result: BuildResultInterface = {
* errors: [
* new esBuildError(esbuildMessage),
* new TypesError('Type checking failed', diagnostics)
* ],
* warnings: [
* new xBuildError('Deprecation warning')
* ],
* metafile: { ... },
* outputFiles: [ ... ],
* mangleCache: { ... }
* };
* ```
*
* @see {@link BuildResult} from esbuild for the base interface
*
* @since 2.0.0
*/
interface BuildResultInterface extends Omit<BuildResult, 'errors' | 'warnings'> {
/**
* Array of normalized error instances encountered during the build.
*
* @remarks
* Contains Error instances converted from esbuild messages and other error sources.
* Unlike esbuild's native error array which contains Message objects, this array
* contains fully normalized Error instances with proper stack traces and formatting.
*
* Errors in this array may originate from:
* - Compilation errors (syntax, resolution failures)
* - Type checking failures
* - Build hook execution errors
* - Plugin errors
*
* @example
* ```ts
* if (result.errors.length > 0) {
* console.error(`Build failed with ${result.errors.length} errors`);
* result.errors.forEach(err => console.error(err.stack));
* }
* ```
*
* @since 2.0.0
*/
errors: Array<Error>;
/**
* Array of normalized warning instances encountered during the build.
*
* @remarks
* Contains Error instances converted from esbuild warning messages and other warning sources.
* Unlike esbuild's native warning array which contains Message objects, this array
* contains fully normalized Error instances with proper stack traces and formatting.
*
* Warnings indicate non-fatal issues that don't prevent build completion but may
* require attention, such as:
* - Deprecated API usage
* - Type checking warnings
* - Performance concerns
* - Potential runtime issues
*
* @example
* ```ts
* if (result.warnings.length > 0) {
* console.warn(`Build completed with ${result.warnings.length} warnings`);
* result.warnings.forEach(warn => console.warn(warn.message));
* }
* ```
*
* @since 2.0.0
*/
warnings: Array<Error>;
}
/**
* Recursively makes all properties of a type optional.
*
* @remarks
* This utility type behaves like TypeScript’s built-in {@link Partial} type,
* but applies recursively to all nested object properties.
*
* It is commonly used for:
* - Partial configuration overrides
* - Patch / update objects
* - Programmatic configuration merging
* - Build variant and preset definitions
*
* This type only affects compile-time type checking and has no runtime impact.
*
* ⚠️ **Important limitations**:
* - Arrays and functions are treated as objects and will also be recursively
* transformed. If this is undesirable, a more specialized deep-partial
* implementation should be used.
* - Intended for configuration and data-shaping use cases, not strict domain models.
*
* @example
* ```ts
* interface Config {
* server: {
* host: string;
* port: number;
* };
* features: {
* experimental: boolean;
* };
* }
*
* const override: DeepPartialType<Config> = {
* server: {
* port: 8080
* }
* };
* ```
*
* @template T - The type to recursively make optional.
*
* @since 2.0.0
*/
type DeepPartialType<T> = {
[K in keyof T]?: T[K] extends object ? DeepPartialType<T[K]> : T[K];
};
/**
* Represents code that can be injected into build output as a banner or footer.
* Can be a static string or a function that generates code dynamically based on build context.
*
* @remarks
* This type provides flexibility for injecting code at the top (banner) or bottom (footer) of
* bundled output files. The function form receives the plugin name and command-line arguments,
* allowing for context-aware code generation.
*
* Common use cases:
* - Static banners: Copyright notices, license headers, version information
* - Dynamic banners: Build timestamps, environment-specific code, conditional imports
* - Footer code: Analytics snippets, polyfills, initialization scripts
*
* When using the function form, the generated string is cached per build variant to avoid
* regenerating the same code multiple times.
*
* @example
* ```ts
* // Static banner
* const banner: InjectableCodeType = '\/* Copyright 2024 *\/';
*
* // Dynamic banner
* const banner: InjectableCodeType = (name, argv) => {
* const version = argv.version || '1.0.0';
* return `\/* Built by ${name} v${version} at ${new Date().toISOString()} *\/`;
* };
* ```
*
* @see {@link BaseBuildDefinitionInterface.banner}
* @see {@link BaseBuildDefinitionInterface.footer}
*
* @since 2.0.0
*/
type InjectableCodeType = string | ((name: string, argv: Record<string, unknown>) => string);
/**
* Defines lifecycle hook handlers for build process stages.
* Allows registration of custom logic during resolution, loading, build start, build end, and success.
*
* @remarks
* This interface groups all available lifecycle hooks in a single configuration object.
* All hooks are optional, allowing selective registration of only necessary handlers.
*
* Hook execution order during a build:
* 1. `onStart` - Before any file processing
* 2. `onResolve` - During import path resolution
* 3. `onLoad` - When loading file contents
* 4. `onEnd` - After build completes (success or failure)
* 5. `onSuccess` - After a build completes successfully
*
* Each hook receives a specialized context object appropriate for its lifecycle stage, providing
* access to build configuration, variant information, and cross-hook communication through the
* shared stage object.
*
* @example
* ```ts
* const hooks: LifecycleHooksInterface = {
* onStart: async (context) => {
* console.log(`${context.variantName} build starting...`);
* },
* onLoad: async (context) => {
* if (context.args.path.endsWith('.custom')) {
* return { contents: transform(context.contents), loader: 'ts' };
* }
* },
* onSuccess: async (context) => {
* console.log(`Build succeeded in ${context.duration}ms!`);
* }
* };
* ```
*
* @see {@link OnEndType}
* @see {@link OnLoadType}
* @see {@link OnStartType}
* @see {@link OnResolveType}
*
* @since 2.0.0
*/
interface LifecycleHooksInterface {
/**
* Hook handler executed when the build completes, regardless of success or failure.
*
* @remarks
* Called after all build operations finish with a result context containing the build result,
* calculated duration, variant name, arguments, and stage state. Useful for cleanup, logging,
* reporting, and post-processing.
*
* The handler receives `ResultContextInterface` providing access to:
* - `buildResult`: Final build outcome with errors and warnings
* - `duration`: Build duration in milliseconds
* - `variantName`: Build variant identifier
* - `argv`: Command-line arguments and configuration
* - `stage`: Shared state object for cross-hook communication
*
* @example
* ```ts
* onEnd: async (context) => {
* const { buildResult, duration, variantName } = context;
* console.log(`${variantName} completed in ${duration}ms`);
* if (buildResult.errors.length > 0) {
* // Handle errors
* }
* }
* ```
*
* @see {@link ResultContextInterface}
*
* @since 2.0.0
*/
onEnd?: OnEndType;
/**
* Hook handler executed when loading file contents during module processing.
*
* @remarks
* Called for each file being processed with a load context containing the current file contents
* (potentially transformed by previous hooks), loader type, load arguments, variant name, and
* stage state. Can transform contents and change the loader type. Multiple handlers execute in
* a pipeline pattern where each receives the output of previous hooks.
*
* The handler receives `LoadContextInterface` providing access to:
* - `contents`: Current file contents (string or binary)
* - `loader`: Current loader type (e.g., 'ts', 'js', 'json')
* - `args`: Load arguments including file path and namespace
* - `variantName`: Build variant identifier
* - `argv`: Command-line arguments and configuration
* - `stage`: Shared state object for cross-hook communication
*
* @example
* ```ts
* onLoad: async (context) => {
* const { contents, args, variantName } = context;
* if (args.path.endsWith('.custom')) {
* return {
* contents: transform(contents.toString()),
* loader: 'ts'
* };
* }
* }
* ```
*
* @see {@link LoadContextInterface}
*
* @since 2.0.0
*/
onLoad?: OnLoadType;
/**
* Hook handler executed when the build process begins.
*
* @remarks
* Called before any file processing starts with a build context containing the esbuild build object,
* variant name, arguments, and stage state. Useful for initialization, validation, and setup tasks.
*
* The handler receives `BuildContextInterface` providing access to:
* - `build`: esbuild plugin build object with configuration and utilities
* - `variantName`: Build variant identifier
* - `argv`: Command-line arguments and configuration
* - `stage`: Shared state object for cross-hook communication
*
* @example
* ```ts
* onStart: async (context) => {
* const { build, variantName, stage } = context;
* console.log(`Starting ${variantName} build`);
* stage.startTime = new Date();
*
* // Validate configuration
* if (!build.initialOptions.outdir) {
* return { errors: [{ text: 'Output directory required' }] };
* }
* }
* ```
*
* @see {@link BuildContextInterface}
*
* @since 2.0.0
*/
onStart?: OnStartType;
/**
* Hook handler executed when the build completes successfully without errors.
*
* @remarks
* Only called when `buildResult.errors.length === 0`, after all regular end hooks have completed.
* Receives the same result context as end hooks, containing build result, duration, variant name,
* arguments, and stage state. Useful for deployment, success notifications, and success-only operations.
*
* The handler receives `ResultContextInterface` providing access to:
* - `buildResult`: Final build outcome (guaranteed to have zero errors)
* - `duration`: Build duration in milliseconds
* - `variantName`: Build variant identifier
* - `argv`: Command-line arguments and configuration
* - `stage`: Shared state object for cross-hook communication
*
* @example
* ```ts
* onSuccess: async (context) => {
* const { buildResult, duration, variantName } = context;
* console.log(`${variantName} succeeded in ${duration}ms!`);
* await deploy(buildResult.metafile);
* }
* ```
*
* @see {@link ResultContextInterface}
*
* @since 2.0.0
*/
onSuccess?: OnEndType;
/**
* Hook handler executed during module path resolution.
*
* @remarks
* Called when resolving import paths to file system locations with a resolve context containing
* the resolution arguments, variant name, and stage state. Can redirect imports, mark modules as
* external, or implement custom resolution logic. Multiple handlers execute, and their results are
* merged, with later hooks able to override earlier ones.
*
* The handler receives `ResolveContextInterface` providing access to:
* - `args`: Resolution arguments including import path and importer info
* - `variantName`: Build variant identifier
* - `argv`: Command-line arguments and configuration
* - `stage`: Shared state object for cross-hook communication
*
* @example
* ```ts
* onResolve: async (context) => {
* const { args, variantName } = context;
*
* // Redirect '@/' imports to 'src/'
* if (args.path.startsWith('@/')) {
* return {
* path: resolve('src', args.path.slice(2)),
* namespace: 'file'
* };
* }
*
* // Mark as external in production
* if (variantName === 'production' && args.path.includes('node_modules')) {
* return { path: args.path, external: true };
* }
* }
* ```
*
* @see {@link ResolveContextInterface}
*
* @since 2.0.0
*/
onResolve?: OnResolveType;
}
/**
* Configuration options for TypeScript declaration file generation.
*
* @remarks
* Controls how and where TypeScript declaration files (`.d.ts`) are generated during the build.
* These options work in conjunction with the TypeScript compiler to produce type definitions
* for bundled code.
*
* When `bundle` is true, declarations from multiple source files are combined into a single
* declaration file per entry point. When false, individual declaration files are generated
* for each source file.
*
* @example
* ```ts
* // Generate bundled declarations in custom directory
* const options: DeclarationOptionsInterface = {
* outDir: 'types',
* bundle: true
* };
* ```
*
* @see {@link BaseBuildDefinitionInterface.declaration}
*
* @since 2.0.0
*/
interface DeclarationOptionsInterface {
/**
* Output directory for generated declaration files.
*
* @remarks
* Specifies where `.d.ts` files should be written. If not provided, uses the TypeScript
* compiler's `declarationDir` or `outDir` from `tsconfig.json`.
*
* @example
* ```ts
* outDir: 'dist/types'
* ```
*
* @since 2.0.0
*/
outDir?: string;
/**
* Whether to bundle declarations into a single file per entry point.
*
* @remarks
* When true, combines all declarations from imported modules into a single `.d.ts` file.
* When false, generates individual declaration files mirroring the source structure.
*
* Bundling is useful for library distribution as it provides a single type definition file
* that consumers can reference.
*
* @example
* ```ts
* bundle: true // Produces single bundled .d.ts
* bundle: false // Produces multiple .d.ts files
* ```
*
* @since 2.0.0
*/
bundle?: boolean;
}
/**
* Configuration options for TypeScript type checking during builds.
*
* @remarks
* Controls how TypeScript type checking is performed and whether type errors should fail the build.
* Type checking runs in parallel with the esbuild compilation process for better performance.
*
* @example
* ```ts
* // Fail build on type errors
* const options: TypeCheckOptionsInterface = {
* failOnError: true
* };
* ```
*
* @see {@link BaseBuildDefinitionInterface.types}
*
* @since 2.0.0
*/
interface TypeCheckOptionsInterface {
/**
* Whether to fail the build when TypeScript errors are detected.
*
* @remarks
* When true, any TypeScript errors will cause the build to fail with a non-zero exit code.
* When false, errors are logged, but the build continues and succeeds.
*
* Useful in CI/CD pipelines where type safety must be enforced before deployment.
*
* @example
* ```ts
* failOnError: true // Build fails on type errors
* failOnError: false // Type errors logged, but build continues
* ```
*
* @since 2.0.0
*/
failOnError?: boolean;
}
/**
* Base configuration shared across all build definitions, including common and variant builds.
* Provides common settings for hooks, type checking, code injection, and declaration generation.
*
* @remarks
* This interface defines the foundation for build configuration that applies to both common
* settings and individual build variants. Properties defined here can be overridden at the
* variant level for customization.
*
* Configuration inheritance:
* - Common build settings apply to all variants
* - Variant settings override common settings
* - Objects like `define` are merged (variant takes precedence)
* - Arrays and primitives replace common values
*
* @example
* ```ts
* const base: BaseBuildDefinitionInterface = {
* types: { failOnError: true },
* declaration: { bundle: true, outDir: 'types' },
* define: { 'process.env.NODE_ENV': '"production"' },
* banner: 'const x = "test"',
* hooks: {
* onSuccess: async () => console.log('Build complete!')
* }
* };
* ```
*
* @see {@link CommonBuildInterface}
* @see {@link VariantBuildInterface}
* @see {@link BuildConfigInterface}
*
* @since 2.0.0
*/
interface BaseBuildDefinitionInterface {
/**
* Lifecycle hook handlers for build process stages.
*
* @remarks
* Registers custom handlers for various build lifecycle events including start, resolve,
* load, end, and success stages. All hooks are optional.
*
* @see {@link LifecycleHooksInterface}
*
* @since 2.0.0
*/
lifecycle?: LifecycleHooksInterface;
/**
* TypeScript type checking configuration.
*
* @remarks
* Controls whether and how TypeScript type checking is performed during builds.
* - `true`: Enable type checking with default options
* - `false` or omitted: Disable type checking
* - Object: Enable with specific options like `failOnError`
*
* @example
* ```ts
* types: true // Enable with default
* types: { failOnError: true } // Enable and fail on errors
* types: false // Disable
* ```
*
* @see {@link TypeCheckOptionsInterface}
*
* @since 2.0.0
*/
types?: boolean | TypeCheckOptionsInterface;
/**
* Global constants to replace during bundling.
*
* @remarks
* Defines key-value pairs for constant replacement during the build. Keys are identifiers
* or property access expressions, values are JSON-stringified replacements.
*
* Commonly used for environment variables, feature flags, and build-time constants.
*
* @example
* ```ts
* define: {
* 'process.env.NODE_ENV': '"production"',
* 'DEBUG': 'false',
* 'VERSION': '"1.2.3"'
* }
* ```
*
* @since 2.0.0
*/
define?: Record<string, unknown>;
/**
* Code to inject at the beginning of each output file.
*
* @remarks
* Can be a static string or a function that generates code based on build context.
* Commonly used for copyright notices, license headers, or polyfill imports.
*
* @example
* ```ts
* banner: 'const x = "test"'
* banner: (name, argv) => `const x = "Built: ${new Date().toISOString()}"`
* ```
*
* @see {@link InjectableCodeType}
*
* @since 2.0.0
*/
banner?: {
[key: string]: InjectableCodeType;
};
/**
* Code to inject at the end of each output file.
*
* @remarks
* Can be a static string or a function that generates code based on build context.
* Commonly used for initialization code, analytics, or polyfills.
*
* @example
* ```ts
* footer: '// End of bundle'
* footer: (name, argv) => `console.log('Loaded ${name}');`
* ```
*
* @see {@link InjectableCodeType}
*
* @since 2.0.0
*/
footer?: {
[key: string]: InjectableCodeType;
};
/**
* TypeScript declaration file generation configuration.
*
* @remarks
* Controls whether and how TypeScript declaration files are generated.
* - `true`: Generate declarations with default options
* - `false` or omitted: Do not generate declarations
* - Object: Generate with specific options like `outDir` and `bundle`
*
* @example
* ```ts
* declaration: true // Generate with default
* declaration: { outDir: 'types', bundle: true } // Generate bundled in custom dir
* declaration: false // Disable
* ```
*
* @see {@link DeclarationOptionsInterface}
*
* @since 2.0.0
*/
declaration?: boolean | DeclarationOptionsInterface;
}
/**
* Build configuration for a specific build variant including esbuild settings and entry points.
* Extends base configuration with variant-specific esbuild options and required entry points.
*
* @remarks
* A variant represents a distinct build target with its own entry points and esbuild configuration.
* Variants inherit settings from the common configuration but can override any property.
*
* The `esbuild` property excludes fields that are managed by the build system:
* - `plugins`: Managed by the hook provider
* - `define`, `banner`, `footer`: Managed by base configuration
* - `entryPoints`: Required at variant level (non-nullable)
*
* Multiple variants enable building different outputs from the same codebase, such as
* - Different module formats (ESM, CJS)
* - Different targets (Node.js, browser)
* - Different bundles (main, worker, tests)
*
* @example
* ```ts
* const variant: VariantBuildInterface = {
* esbuild: {
* entryPoints: ['src/index.ts'],
* outdir: 'dist/esm',
* format: 'esm',
* target: 'es2020'
* },
* types: true,
* declaration: { bundle: true, outDir: 'dist/types' }
* };
* ```
*
* @see {@link VariantsType}
* @see {@link CommonBuildInterface}
* @see {@link BaseBuildDefinitionInterface}
*
* @since 2.0.0
*/
interface VariantBuildInterface extends BaseBuildDefinitionInterface {
/**
* Esbuild-specific configuration for this variant including entry points.
*
* @remarks
* Contains all esbuild options except those managed by the build system.
* The `entryPoints` field is required and must be non-empty to define what to build.
*
* Common options include:
* - `format`: Output format (esm, cjs, iife)
* - `outdir` or `outfile`: Output location
* - `target`: ECMAScript target version
* - `platform`: Target platform (browser, node, neutral)
* - `minify`: Whether to minify output
* - `sourcemap`: Whether to generate source maps
*
* @example
* ```ts
* esbuild: {
* entryPoints: ['src/index.ts', 'src/worker.ts'],
* outdir: 'dist',
* format: 'esm',
* target: 'es2020',
* minify: true,
* sourcemap: true
* }
* ```
*
* @since 2.0.0
*/
esbuild: Omit<BuildOptions, 'plugins' | 'define' | 'banner' | 'footer'>;
/**
* Variants that must finish building before this variant starts.
*
* @remarks
* Use this field to express build ordering between variants when one output
* depends on another being completed first.
*
* You can provide:
* - a single variant name
* - an array of variant names
*
* The build system should resolve these dependencies before running the current
* variant and should also detect circular dependencies to avoid infinite loops.
*
* @example
* ```ts
* dependOn: 'types'
* ```
*
* @example
* ```ts
* dependOn: ['types', 'shared']
* ```
*
* @see {@link VariantsType}
* @since 2.4.0
*/
dependOn?: string | Array<string>;
}
/**
* Shared configuration applied to all build variants.
* Extends base configuration with esbuild settings but without entry points.
*
* @remarks
* Common configuration provides default settings that apply to all variants unless overridden.
* This reduces duplication when multiple variants share similar settings.
*
* The `esbuild` property excludes managed fields and `entryPoints` (which must be variant-specific).
* Settings defined here are merged with variant-specific settings, with variants taking preced