#![cfg_attr(docsrs, feature(doc_cfg))]
pub use fob_graph::*;
pub mod builders;
pub mod builtins;
pub mod cache;
pub mod config;
pub mod output;
pub mod plugins;
pub mod runtime;
pub mod target;
pub mod from_rolldown;
pub mod module_collection_plugin;
pub mod analysis;
pub mod diagnostics;
pub use rolldown::{
BundleOutput, Bundler, BundlerBuilder, BundlerOptions, GlobalsOutputOption, InputItem,
IsExternal, OutputFormat, Platform, RawMinifyOptions, ResolveOptions, SourceMapType,
};
pub use rolldown_common::{
BundlerTransformOptions, DecoratorOptions, EmittedAsset, IsolatedDeclarationsOptions,
LogWithoutPlugin, ModuleType, Output, OutputAsset, OutputChunk, TypeScriptOptions,
};
pub use rolldown_plugin::{
__inner::SharedPluginable, HookGenerateBundleArgs, HookLoadArgs, HookLoadOutput,
HookLoadReturn, HookNoopReturn, HookResolveIdArgs, HookResolveIdOutput, HookResolveIdReturn,
HookTransformArgs, HookTransformOutput, HookTransformReturn, HookUsage, Plugin, PluginContext,
SharedTransformPluginContext,
};
pub use builders::{
BuildOptions, BuildOutput, BuildResult, CodeSplittingConfig, EntryMode, EntryPoints,
ExternalConfig, IncrementalConfig, MinifyLevel, build,
};
pub use builtins::CssPlugin;
pub use cache::{CacheConfig, CacheKey};
pub use config::{
BuildConfig, ExternalPattern, OptimizationConfig, OutputConfig, ResolutionConfig,
};
#[cfg(feature = "dts-generation")]
pub use builders::DtsOptions;
#[cfg(feature = "logging")]
#[cfg_attr(docsrs, doc(cfg(feature = "logging")))]
pub mod logging;
#[cfg(feature = "logging")]
#[cfg_attr(docsrs, doc(cfg(feature = "logging")))]
pub use logging::{LogLevel, init_logging, init_logging_from_env};
pub use output::{AppBuild, Bundle as JoyBundle, ComponentBuild, ImportMap};
pub use target::{DeploymentTarget, ExportConditions, NodeBuiltins, RuntimeEnvironment};
pub use analysis::AnalyzedBundle;
#[cfg(all(any(test, doctest), not(target_family = "wasm")))]
pub use fob_graph::test_utils;
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("Rolldown bundler error: {}", format_bundler_error(.0))]
Bundler(Vec<diagnostics::ExtractedDiagnostic>),
#[error("Invalid configuration: {0}")]
InvalidConfig(String),
#[error("I/O error: {0}")]
Io(#[from] std::io::Error),
#[error("Invalid output path: {0}")]
InvalidOutputPath(String),
#[error("Write failure: {0}")]
WriteFailure(String),
#[error("Output exists: {0}")]
OutputExists(String),
#[error("Asset not found: {specifier} (searched from: {searched_from})")]
AssetNotFound {
specifier: String,
searched_from: String,
},
#[error("Asset security violation: {path} - {reason}")]
AssetSecurityViolation { path: String, reason: String },
#[error("Asset too large: {path} ({size} bytes exceeds limit of {max_size} bytes)")]
AssetTooLarge {
path: String,
size: u64,
max_size: u64,
},
#[error("{message}")]
IoError {
message: String,
#[source]
source: std::io::Error,
},
#[error("Foundation error: {0}")]
Foundation(#[from] fob_graph::Error),
}
pub type Result<T> = std::result::Result<T, Error>;
impl Error {
pub fn from_rolldown_batch(error: &dyn std::fmt::Debug) -> Self {
Error::Bundler(diagnostics::extract_from_rolldown_error(error))
}
}
fn format_bundler_error(diagnostics: &[diagnostics::ExtractedDiagnostic]) -> String {
if diagnostics.is_empty() {
return "Unknown bundler error".to_string();
}
if diagnostics.len() == 1 {
let diag = &diagnostics[0];
format!("{}: {}", diag.kind, diag.message)
} else {
format!(
"{} errors: {}",
diagnostics.len(),
diagnostics
.iter()
.map(|d| format!("{}: {}", d.kind, d.message))
.collect::<Vec<_>>()
.join("; ")
)
}
}
impl std::fmt::Display for diagnostics::DiagnosticKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
diagnostics::DiagnosticKind::MissingExport => write!(f, "MissingExport"),
diagnostics::DiagnosticKind::ParseError => write!(f, "ParseError"),
diagnostics::DiagnosticKind::CircularDependency => write!(f, "CircularDependency"),
diagnostics::DiagnosticKind::UnresolvedEntry => write!(f, "UnresolvedEntry"),
diagnostics::DiagnosticKind::UnresolvedImport => write!(f, "UnresolvedImport"),
diagnostics::DiagnosticKind::InvalidOption => write!(f, "InvalidOption"),
diagnostics::DiagnosticKind::Plugin => write!(f, "Plugin"),
diagnostics::DiagnosticKind::Transform => write!(f, "Transform"),
diagnostics::DiagnosticKind::Other(s) => write!(f, "{}", s),
}
}
}
impl miette::Diagnostic for Error {
fn code(&self) -> Option<Box<dyn std::fmt::Display + '_>> {
Some(Box::new(match self {
Error::Bundler(_) => "BUNDLER_ERROR",
Error::InvalidConfig(_) => "INVALID_CONFIG",
Error::Io(_) => "IO_ERROR",
Error::InvalidOutputPath(_) => "INVALID_OUTPUT_PATH",
Error::WriteFailure(_) => "WRITE_FAILURE",
Error::OutputExists(_) => "OUTPUT_EXISTS",
Error::AssetNotFound { .. } => "ASSET_NOT_FOUND",
Error::AssetSecurityViolation { .. } => "ASSET_SECURITY_VIOLATION",
Error::AssetTooLarge { .. } => "ASSET_TOO_LARGE",
Error::IoError { .. } => "IO_ERROR",
Error::Foundation(_) => "FOUNDATION_ERROR",
}))
}
fn severity(&self) -> Option<miette::Severity> {
Some(miette::Severity::Error)
}
fn help(&self) -> Option<Box<dyn std::fmt::Display + '_>> {
match self {
Error::InvalidConfig(msg) => Some(Box::new(format!(
"Check your configuration file for syntax errors.\nError: {}",
msg
))),
Error::InvalidOutputPath(path) => Some(Box::new(format!(
"The output path '{}' is invalid. Ensure it's within the project directory and doesn't contain '..' components.",
path
))),
Error::WriteFailure(msg) => Some(Box::new(format!(
"Failed to write file. Check disk space and permissions.\nError: {}",
msg
))),
Error::OutputExists(msg) => Some(Box::new(format!(
"Output file already exists: {}\nUse --overwrite flag to replace existing files.",
msg
))),
Error::AssetNotFound {
specifier,
searched_from,
} => Some(Box::new(format!(
"Could not find asset '{}'.\nSearched from: {}\nCheck that the file exists and the path is correct.",
specifier, searched_from
))),
Error::AssetSecurityViolation { path, reason } => Some(Box::new(format!(
"Security violation detected for path '{}': {}\nPaths must be within the project directory.",
path, reason
))),
Error::AssetTooLarge {
path,
size,
max_size,
} => Some(Box::new(format!(
"Asset '{}' is too large: {} bytes (max: {} bytes).\nConsider splitting large assets or increasing the size limit.",
path, size, max_size
))),
Error::Bundler(diagnostics) => {
if diagnostics.len() == 1 {
diagnostics[0]
.help
.as_ref()
.map(|h| Box::new(h.clone()) as Box<dyn std::fmt::Display>)
} else {
Some(Box::new(
"Multiple bundler errors occurred. See details below.".to_string(),
))
}
}
_ => None,
}
}
fn related(&self) -> Option<Box<dyn Iterator<Item = &dyn miette::Diagnostic> + '_>> {
match self {
Error::Bundler(diagnostics) if diagnostics.len() > 1 => {
None
}
_ => None,
}
}
}