use crate::builders::unified::{
EntryPoints, MinifyLevel,
primitives::{CodeSplittingConfig, EntryMode, ExternalConfig},
};
use crate::target::{ExportConditions, NodeBuiltins};
use crate::{OutputFormat, SourceMapType};
use rustc_hash::FxHashMap;
use std::path::PathBuf;
use std::sync::Arc;
#[derive(Debug, Clone)]
pub enum ExternalPattern {
Exact(String),
Pattern(String),
}
impl ExternalPattern {
pub fn exact(name: impl Into<String>) -> Self {
Self::Exact(name.into())
}
pub fn pattern(pattern: impl Into<String>) -> Self {
Self::Pattern(pattern.into())
}
}
#[derive(Debug, Clone)]
pub struct OutputConfig {
pub dir: PathBuf,
pub format: OutputFormat,
pub sourcemap: Option<SourceMapType>,
pub splitting: bool,
}
#[derive(Debug, Clone)]
pub struct ResolutionConfig {
pub conditions: ExportConditions,
pub aliases: FxHashMap<String, String>,
pub external: Vec<ExternalPattern>,
pub node_builtins: NodeBuiltins,
}
#[derive(Debug, Clone)]
pub struct OptimizationConfig {
pub minify: MinifyLevel,
pub tree_shake: bool,
pub bundle: bool,
}
pub struct BuildConfig {
pub entries: EntryPoints,
pub output: OutputConfig,
pub resolution: ResolutionConfig,
pub optimization: OptimizationConfig,
pub cwd: Option<PathBuf>,
pub runtime: Option<Arc<dyn crate::Runtime>>,
pub virtual_files: FxHashMap<String, String>,
}
impl BuildConfig {
pub fn new(entry: impl Into<EntryPoints>) -> Self {
let entries = entry.into();
Self {
entries,
output: OutputConfig {
dir: PathBuf::from("dist"),
format: OutputFormat::Esm,
sourcemap: Some(SourceMapType::File),
splitting: false,
},
resolution: ResolutionConfig {
conditions: ExportConditions::browser(),
aliases: FxHashMap::default(),
external: Vec::new(),
node_builtins: NodeBuiltins::Error,
},
optimization: OptimizationConfig {
minify: MinifyLevel::None,
tree_shake: true,
bundle: true,
},
cwd: None,
runtime: None,
virtual_files: FxHashMap::default(),
}
}
pub fn for_target(mut self, target: &dyn crate::DeploymentTarget) -> Self {
self.resolution.conditions = target.conditions();
self.resolution.node_builtins = target.node_builtins();
self.resolution.external.extend(
target
.external_packages()
.into_iter()
.map(ExternalPattern::exact),
);
self
}
pub fn output_dir(mut self, dir: impl Into<PathBuf>) -> Self {
self.output.dir = dir.into();
self
}
pub fn format(mut self, format: OutputFormat) -> Self {
self.output.format = format;
self
}
pub fn sourcemap(mut self, enabled: bool) -> Self {
self.output.sourcemap = if enabled {
Some(SourceMapType::File)
} else {
None
};
self
}
pub fn sourcemap_type(mut self, sourcemap: Option<SourceMapType>) -> Self {
self.output.sourcemap = sourcemap;
self
}
pub fn minify(mut self, level: MinifyLevel) -> Self {
self.optimization.minify = level;
self
}
pub fn bundle(mut self, enabled: bool) -> Self {
self.optimization.bundle = enabled;
self
}
pub fn splitting(mut self, enabled: bool) -> Self {
self.output.splitting = enabled;
self
}
pub fn external(mut self, packages: impl IntoIterator<Item = impl Into<String>>) -> Self {
self.resolution.external.extend(
packages
.into_iter()
.map(|p| ExternalPattern::exact(p.into())),
);
self
}
pub fn path_alias(mut self, alias: impl Into<String>, target: impl Into<String>) -> Self {
self.resolution.aliases.insert(alias.into(), target.into());
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 crate::Runtime>) -> Self {
self.runtime = Some(runtime);
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 validate(&self) -> crate::Result<()> {
if self.output.splitting {
if matches!(self.entries, EntryPoints::Single(_)) {
return Err(crate::Error::InvalidConfig(
"code splitting requires multiple entry points".into(),
));
}
if !self.optimization.bundle {
return Err(crate::Error::InvalidConfig(
"code splitting requires bundle: true".into(),
));
}
}
const MAX_ENTRY_POINTS: usize = 1000;
let entry_count = match &self.entries {
EntryPoints::Single(_) => 1,
EntryPoints::Multiple(v) => v.len(),
EntryPoints::Named(m) => m.len(),
};
if entry_count > MAX_ENTRY_POINTS {
return Err(crate::Error::InvalidConfig(format!(
"Too many entry points: {} (max {})",
entry_count, MAX_ENTRY_POINTS
)));
}
if entry_count == 0 {
return Err(crate::Error::InvalidConfig(
"At least one entry point is required".into(),
));
}
Ok(())
}
pub async fn build(self) -> crate::Result<crate::BuildResult> {
self.validate()?;
let options = self.into_build_options();
crate::builders::build(options).await
}
fn into_build_options(self) -> crate::BuildOptions {
use crate::Platform;
let platform = match self.resolution.conditions {
ExportConditions::Node => Platform::Node,
ExportConditions::Edge | ExportConditions::Browser => Platform::Browser,
};
let external = if self.optimization.bundle {
ExternalConfig::None
} else {
ExternalConfig::List(vec![])
};
let (entry_mode, code_splitting) = match &self.entries {
EntryPoints::Single(_) => (EntryMode::Shared, None),
EntryPoints::Multiple(_) | EntryPoints::Named(_) => {
if self.output.splitting {
(EntryMode::Shared, Some(CodeSplittingConfig::default()))
} else {
(EntryMode::Isolated, None)
}
}
};
let external_config = if self.resolution.external.is_empty() {
external
} else {
let packages: Vec<String> = self
.resolution
.external
.into_iter()
.map(|p| match p {
ExternalPattern::Exact(s) => s,
ExternalPattern::Pattern(s) => s,
})
.collect();
ExternalConfig::List(packages)
};
crate::BuildOptions {
entry: self.entries,
entry_mode,
code_splitting,
external: external_config,
outdir: Some(self.output.dir),
outfile: None,
platform,
format: self.output.format,
sourcemap: self.output.sourcemap,
minify_level: match self.optimization.minify {
MinifyLevel::None => None,
MinifyLevel::Whitespace => Some("whitespace".to_string()),
MinifyLevel::Syntax => Some("syntax".to_string()),
MinifyLevel::Identifiers => Some("identifiers".to_string()),
},
globals: FxHashMap::default(),
virtual_files: self.virtual_files,
path_aliases: self.resolution.aliases,
cwd: self.cwd,
runtime: self.runtime,
decorator: None,
#[cfg(feature = "dts-generation")]
dts: None,
cache: None,
incremental: None,
max_parallel_builds: None,
}
}
}