use crate::{Error, OutputFormat, Platform, Result, Runtime};
use rustc_hash::FxHashMap;
use std::path::{Path, PathBuf};
use std::sync::Arc;
#[cfg(feature = "dts-generation")]
use super::dts::DtsOptions;
use super::entry::EntryPoints;
use super::primitives::{CodeSplittingConfig, EntryMode, ExternalConfig, IncrementalConfig};
#[derive(Debug, Clone)]
pub struct BuildOptions {
pub entry: EntryPoints,
pub entry_mode: EntryMode,
pub code_splitting: Option<CodeSplittingConfig>,
pub external: ExternalConfig,
pub outdir: Option<PathBuf>,
pub outfile: Option<PathBuf>,
pub platform: Platform,
pub format: OutputFormat,
pub sourcemap: Option<crate::SourceMapType>,
pub minify_level: Option<String>,
pub globals: FxHashMap<String, String>,
pub virtual_files: FxHashMap<String, String>,
pub path_aliases: FxHashMap<String, String>,
pub cwd: Option<PathBuf>,
pub runtime: Option<Arc<dyn Runtime>>,
pub decorator: Option<crate::DecoratorOptions>,
#[cfg(feature = "dts-generation")]
pub dts: Option<DtsOptions>,
pub cache: Option<crate::cache::CacheConfig>,
pub incremental: Option<IncrementalConfig>,
pub max_parallel_builds: Option<usize>,
}
impl BuildOptions {
pub fn new(entry: impl AsRef<Path>) -> Self {
Self {
entry: EntryPoints::Single(normalize_entry_path(entry)),
entry_mode: EntryMode::Shared,
code_splitting: None,
external: ExternalConfig::None,
outdir: None,
outfile: None,
platform: Platform::Browser,
format: OutputFormat::Esm,
sourcemap: Some(crate::SourceMapType::File),
minify_level: None,
globals: FxHashMap::default(),
virtual_files: FxHashMap::default(),
path_aliases: FxHashMap::default(),
cwd: None,
runtime: None,
decorator: None,
#[cfg(feature = "dts-generation")]
dts: None,
cache: None,
incremental: None,
max_parallel_builds: None,
}
}
pub fn new_multiple<P, I>(entries: I) -> Self
where
P: AsRef<Path>,
I: IntoIterator<Item = P>,
{
let normalized = entries.into_iter().map(normalize_entry_path).collect();
Self {
entry: EntryPoints::Multiple(normalized),
entry_mode: EntryMode::Isolated,
code_splitting: None,
external: ExternalConfig::None,
outdir: None,
outfile: None,
platform: Platform::Browser,
format: OutputFormat::Esm,
sourcemap: Some(crate::SourceMapType::File),
minify_level: None,
globals: FxHashMap::default(),
virtual_files: FxHashMap::default(),
path_aliases: FxHashMap::default(),
cwd: None,
runtime: None,
decorator: None,
#[cfg(feature = "dts-generation")]
dts: None,
cache: None,
incremental: None,
max_parallel_builds: None,
}
}
pub fn bundle_together(mut self) -> Self {
self.entry_mode = EntryMode::Shared;
self
}
pub fn bundle_separately(mut self) -> Self {
self.entry_mode = EntryMode::Isolated;
self.code_splitting = None; self
}
pub fn with_code_splitting(mut self) -> Self {
self.code_splitting = Some(CodeSplittingConfig::default());
self
}
pub fn with_code_splitting_config(mut self, config: CodeSplittingConfig) -> Self {
self.code_splitting = Some(config);
self
}
pub fn externalize<I, S>(mut self, packages: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
let packages_vec: Vec<String> = packages.into_iter().map(|s| s.into()).collect();
self.external = ExternalConfig::List(packages_vec);
self
}
pub fn externalize_from(mut self, manifest_path: impl Into<std::path::PathBuf>) -> Self {
self.external = ExternalConfig::FromManifest(manifest_path.into());
self
}
pub fn outdir(mut self, dir: impl Into<PathBuf>) -> Self {
self.outdir = Some(dir.into());
self
}
pub fn outfile(mut self, file: impl Into<PathBuf>) -> Self {
self.outfile = Some(file.into());
self
}
pub fn platform(mut self, platform: Platform) -> Self {
self.platform = platform;
self
}
pub fn format(mut self, format: OutputFormat) -> Self {
self.format = format;
self
}
pub fn sourcemap(mut self, enabled: bool) -> Self {
self.sourcemap = if enabled {
Some(crate::SourceMapType::File)
} else {
None
};
self
}
pub fn sourcemap_inline(mut self) -> Self {
self.sourcemap = Some(crate::SourceMapType::Inline);
self
}
pub fn sourcemap_hidden(mut self) -> Self {
self.sourcemap = Some(crate::SourceMapType::Hidden);
self
}
pub fn minify_level(mut self, level: impl Into<String>) -> Self {
self.minify_level = Some(level.into());
self
}
pub fn globals_map<I, K, V>(mut self, entries: I) -> Self
where
I: IntoIterator<Item = (K, V)>,
K: Into<String>,
V: Into<String>,
{
for (k, v) in entries {
self.globals.insert(k.into(), v.into());
}
self
}
pub fn virtual_file(mut self, path: impl Into<String>, content: impl Into<String>) -> Self {
self.virtual_files.insert(path.into(), content.into());
self
}
pub fn path_alias(mut self, alias: impl Into<String>, target: impl Into<String>) -> Self {
self.path_aliases.insert(alias.into(), target.into());
self
}
pub fn path_aliases(mut self, aliases: FxHashMap<String, String>) -> Self {
self.path_aliases = aliases;
self
}
pub fn cwd(mut self, cwd: impl Into<PathBuf>) -> Self {
self.cwd = Some(cwd.into());
self
}
pub fn runtime(mut self, runtime: Arc<dyn Runtime>) -> Self {
self.runtime = Some(runtime);
self
}
pub fn decorators(mut self, enabled: bool) -> Self {
if enabled {
self.decorator = Some(crate::DecoratorOptions {
legacy: Some(false),
emit_decorator_metadata: None,
});
} else {
self.decorator = None;
}
self
}
#[cfg(feature = "dts-generation")]
pub fn emit_dts(mut self, enabled: bool) -> Self {
let mut dts = self.dts.unwrap_or_default();
dts.emit = Some(enabled);
self.dts = Some(dts);
self
}
#[cfg(feature = "dts-generation")]
pub fn dts_outdir(mut self, dir: impl Into<PathBuf>) -> Self {
let mut dts = self.dts.unwrap_or_default();
dts.outdir = Some(dir.into());
self.dts = Some(dts);
self
}
#[cfg(feature = "dts-generation")]
pub fn strip_internal(mut self, enabled: bool) -> Self {
let mut dts = self.dts.unwrap_or_default();
dts.strip_internal = enabled;
self.dts = Some(dts);
self
}
#[cfg(feature = "dts-generation")]
pub fn declaration_map(mut self, enabled: bool) -> Self {
let mut dts = self.dts.unwrap_or_default();
dts.sourcemap = enabled;
self.dts = Some(dts);
self
}
pub fn cache_dir(mut self, dir: impl Into<std::path::PathBuf>) -> Self {
self.cache = Some(crate::cache::CacheConfig::new(dir));
self
}
pub fn cache(mut self, config: crate::cache::CacheConfig) -> Self {
self.cache = Some(config);
self
}
pub fn force_rebuild(mut self) -> Self {
if let Some(cache) = &mut self.cache {
cache.force_rebuild = true;
}
self
}
pub fn incremental(mut self) -> Self {
self.incremental = Some(IncrementalConfig::default());
self
}
pub fn incremental_dir(mut self, dir: impl AsRef<std::path::Path>) -> Self {
self.incremental = Some(IncrementalConfig::new(dir.as_ref()));
self
}
pub fn max_parallel_builds(mut self, max: usize) -> Self {
self.max_parallel_builds = Some(max);
self
}
pub fn validate(&self) -> Result<()> {
const MAX_ENTRY_POINTS: usize = 1000;
let entry_count = match &self.entry {
EntryPoints::Single(_) => 1,
EntryPoints::Multiple(v) => v.len(),
EntryPoints::Named(m) => m.len(),
};
if entry_count > MAX_ENTRY_POINTS {
return Err(Error::InvalidConfig(format!(
"Too many entry points: {} (max {})",
entry_count, MAX_ENTRY_POINTS
)));
}
if entry_count == 0 {
return Err(Error::InvalidConfig(
"At least one entry point is required".into(),
));
}
match self.entry_mode {
EntryMode::Shared => {
}
EntryMode::Isolated => {
if matches!(self.entry, EntryPoints::Single(_)) {
return Err(Error::InvalidConfig(
"EntryMode::Isolated requires multiple entry points. \
Use EntryMode::Shared for single entry builds."
.into(),
));
}
if self.code_splitting.is_some() {
return Err(Error::InvalidConfig(
"EntryMode::Isolated cannot use code splitting (bundles are independent). \
Use EntryMode::Shared for code splitting."
.into(),
));
}
}
}
if let Some(config) = &self.code_splitting {
if matches!(self.entry, EntryPoints::Single(_)) {
return Err(Error::InvalidConfig(
"Code splitting requires multiple entry points.".into(),
));
}
if config.min_size == 0 {
return Err(Error::InvalidConfig(
"min_size must be greater than 0".into(),
));
}
if config.min_imports < 2 {
return Err(Error::InvalidConfig(
"min_imports must be at least 2 for shared chunks".into(),
));
}
}
if self.outfile.is_some() {
if !matches!(self.entry, EntryPoints::Single(_)) {
return Err(Error::InvalidConfig(
"outfile can only be used with a single entry point. Use outdir for multiple entries.".into(),
));
}
if self.code_splitting.is_some() {
return Err(Error::InvalidConfig(
"outfile cannot be used with code splitting. Use outdir instead.".into(),
));
}
}
if self.outdir.is_some() && self.outfile.is_some() {
return Err(Error::InvalidConfig(
"outdir and outfile cannot both be specified".into(),
));
}
Ok(())
}
pub async fn build(self) -> Result<super::output::BuildResult> {
super::build(self).await
}
}
impl Default for BuildOptions {
fn default() -> Self {
Self::new(".")
}
}
fn normalize_entry_path(entry: impl AsRef<Path>) -> String {
crate::builders::common::normalize_entry_path(entry)
}