1#![cfg_attr(docsrs, feature(doc_cfg))]
2
3pub use fob_graph::*;
68
69pub mod builders;
71pub mod builtins;
72pub mod cache;
73pub mod config;
74pub mod output;
75pub mod plugins;
76pub mod runtime;
77pub mod target;
78
79pub mod from_rolldown;
81pub mod module_collection_plugin;
82
83pub mod analysis;
85
86pub mod diagnostics;
88
89pub use rolldown::{
91 BundleOutput, Bundler, BundlerBuilder, BundlerOptions, GlobalsOutputOption, InputItem,
92 IsExternal, OutputFormat, Platform, RawMinifyOptions, ResolveOptions, SourceMapType,
93};
94
95pub use rolldown_common::{
97 BundlerTransformOptions, DecoratorOptions, EmittedAsset, IsolatedDeclarationsOptions,
98 LogWithoutPlugin, ModuleType, Output, OutputAsset, OutputChunk, TypeScriptOptions,
99};
100
101pub use rolldown_plugin::{
103 __inner::SharedPluginable, HookGenerateBundleArgs, HookLoadArgs, HookLoadOutput,
104 HookLoadReturn, HookNoopReturn, HookResolveIdArgs, HookResolveIdOutput, HookResolveIdReturn,
105 HookTransformArgs, HookTransformOutput, HookTransformReturn, HookUsage, Plugin, PluginContext,
106 SharedTransformPluginContext,
107};
108
109pub use builders::{
111 BuildOptions, BuildOutput, BuildResult, CodeSplittingConfig, EntryMode, EntryPoints,
112 ExternalConfig, IncrementalConfig, MinifyLevel, build,
113};
114pub use builtins::CssPlugin;
115pub use cache::{CacheConfig, CacheKey};
116pub use config::{
117 BuildConfig, ExternalPattern, OptimizationConfig, OutputConfig, ResolutionConfig,
118};
119
120#[cfg(feature = "dts-generation")]
121pub use builders::DtsOptions;
122
123#[cfg(feature = "logging")]
125#[cfg_attr(docsrs, doc(cfg(feature = "logging")))]
126pub mod logging;
127
128#[cfg(feature = "logging")]
129#[cfg_attr(docsrs, doc(cfg(feature = "logging")))]
130pub use logging::{LogLevel, init_logging, init_logging_from_env};
131
132pub use output::{AppBuild, Bundle as JoyBundle, ComponentBuild, ImportMap};
133pub use target::{DeploymentTarget, ExportConditions, NodeBuiltins, RuntimeEnvironment};
134
135pub use analysis::AnalyzedBundle;
137
138#[cfg(all(any(test, doctest), not(target_family = "wasm")))]
141pub use fob_graph::test_utils;
142
143#[derive(Debug, thiserror::Error)]
145pub enum Error {
146 #[error("Rolldown bundler error: {}", format_bundler_error(.0))]
148 Bundler(Vec<diagnostics::ExtractedDiagnostic>),
149
150 #[error("Invalid configuration: {0}")]
152 InvalidConfig(String),
153
154 #[error("I/O error: {0}")]
156 Io(#[from] std::io::Error),
157
158 #[error("Invalid output path: {0}")]
160 InvalidOutputPath(String),
161
162 #[error("Write failure: {0}")]
164 WriteFailure(String),
165
166 #[error("Output exists: {0}")]
168 OutputExists(String),
169
170 #[error("Asset not found: {specifier} (searched from: {searched_from})")]
172 AssetNotFound {
173 specifier: String,
174 searched_from: String,
175 },
176
177 #[error("Asset security violation: {path} - {reason}")]
179 AssetSecurityViolation { path: String, reason: String },
180
181 #[error("Asset too large: {path} ({size} bytes exceeds limit of {max_size} bytes)")]
183 AssetTooLarge {
184 path: String,
185 size: u64,
186 max_size: u64,
187 },
188
189 #[error("{message}")]
191 IoError {
192 message: String,
193 #[source]
194 source: std::io::Error,
195 },
196
197 #[error("Foundation error: {0}")]
199 Foundation(#[from] fob_graph::Error),
200}
201
202pub type Result<T> = std::result::Result<T, Error>;
204
205impl Error {
206 pub fn from_rolldown_batch(error: &dyn std::fmt::Debug) -> Self {
210 Error::Bundler(diagnostics::extract_from_rolldown_error(error))
211 }
212}
213
214fn format_bundler_error(diagnostics: &[diagnostics::ExtractedDiagnostic]) -> String {
216 if diagnostics.is_empty() {
217 return "Unknown bundler error".to_string();
218 }
219
220 if diagnostics.len() == 1 {
221 let diag = &diagnostics[0];
222 format!("{}: {}", diag.kind, diag.message)
223 } else {
224 format!(
225 "{} errors: {}",
226 diagnostics.len(),
227 diagnostics
228 .iter()
229 .map(|d| format!("{}: {}", d.kind, d.message))
230 .collect::<Vec<_>>()
231 .join("; ")
232 )
233 }
234}
235
236impl std::fmt::Display for diagnostics::DiagnosticKind {
237 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
238 match self {
239 diagnostics::DiagnosticKind::MissingExport => write!(f, "MissingExport"),
240 diagnostics::DiagnosticKind::ParseError => write!(f, "ParseError"),
241 diagnostics::DiagnosticKind::CircularDependency => write!(f, "CircularDependency"),
242 diagnostics::DiagnosticKind::UnresolvedEntry => write!(f, "UnresolvedEntry"),
243 diagnostics::DiagnosticKind::UnresolvedImport => write!(f, "UnresolvedImport"),
244 diagnostics::DiagnosticKind::InvalidOption => write!(f, "InvalidOption"),
245 diagnostics::DiagnosticKind::Plugin => write!(f, "Plugin"),
246 diagnostics::DiagnosticKind::Transform => write!(f, "Transform"),
247 diagnostics::DiagnosticKind::Other(s) => write!(f, "{}", s),
248 }
249 }
250}
251
252impl miette::Diagnostic for Error {
253 fn code(&self) -> Option<Box<dyn std::fmt::Display + '_>> {
254 Some(Box::new(match self {
255 Error::Bundler(_) => "BUNDLER_ERROR",
256 Error::InvalidConfig(_) => "INVALID_CONFIG",
257 Error::Io(_) => "IO_ERROR",
258 Error::InvalidOutputPath(_) => "INVALID_OUTPUT_PATH",
259 Error::WriteFailure(_) => "WRITE_FAILURE",
260 Error::OutputExists(_) => "OUTPUT_EXISTS",
261 Error::AssetNotFound { .. } => "ASSET_NOT_FOUND",
262 Error::AssetSecurityViolation { .. } => "ASSET_SECURITY_VIOLATION",
263 Error::AssetTooLarge { .. } => "ASSET_TOO_LARGE",
264 Error::IoError { .. } => "IO_ERROR",
265 Error::Foundation(_) => "FOUNDATION_ERROR",
266 }))
267 }
268
269 fn severity(&self) -> Option<miette::Severity> {
270 Some(miette::Severity::Error)
271 }
272
273 fn help(&self) -> Option<Box<dyn std::fmt::Display + '_>> {
274 match self {
275 Error::InvalidConfig(msg) => Some(Box::new(format!(
276 "Check your configuration file for syntax errors.\nError: {}",
277 msg
278 ))),
279 Error::InvalidOutputPath(path) => Some(Box::new(format!(
280 "The output path '{}' is invalid. Ensure it's within the project directory and doesn't contain '..' components.",
281 path
282 ))),
283 Error::WriteFailure(msg) => Some(Box::new(format!(
284 "Failed to write file. Check disk space and permissions.\nError: {}",
285 msg
286 ))),
287 Error::OutputExists(msg) => Some(Box::new(format!(
288 "Output file already exists: {}\nUse --overwrite flag to replace existing files.",
289 msg
290 ))),
291 Error::AssetNotFound {
292 specifier,
293 searched_from,
294 } => Some(Box::new(format!(
295 "Could not find asset '{}'.\nSearched from: {}\nCheck that the file exists and the path is correct.",
296 specifier, searched_from
297 ))),
298 Error::AssetSecurityViolation { path, reason } => Some(Box::new(format!(
299 "Security violation detected for path '{}': {}\nPaths must be within the project directory.",
300 path, reason
301 ))),
302 Error::AssetTooLarge {
303 path,
304 size,
305 max_size,
306 } => Some(Box::new(format!(
307 "Asset '{}' is too large: {} bytes (max: {} bytes).\nConsider splitting large assets or increasing the size limit.",
308 path, size, max_size
309 ))),
310 Error::Bundler(diagnostics) => {
311 if diagnostics.len() == 1 {
312 diagnostics[0]
313 .help
314 .as_ref()
315 .map(|h| Box::new(h.clone()) as Box<dyn std::fmt::Display>)
316 } else {
317 Some(Box::new(
318 "Multiple bundler errors occurred. See details below.".to_string(),
319 ))
320 }
321 }
322 _ => None,
323 }
324 }
325
326 fn related(&self) -> Option<Box<dyn Iterator<Item = &dyn miette::Diagnostic> + '_>> {
327 match self {
328 Error::Bundler(diagnostics) if diagnostics.len() > 1 => {
329 None
333 }
334 _ => None,
335 }
336 }
337}