1use crate::builders::unified::{
10 EntryPoints, MinifyLevel,
11 primitives::{CodeSplittingConfig, EntryMode, ExternalConfig},
12};
13use crate::target::{ExportConditions, NodeBuiltins};
14use crate::{OutputFormat, SourceMapType};
15use rustc_hash::FxHashMap;
16use std::path::PathBuf;
17use std::sync::Arc;
18
19#[derive(Debug, Clone)]
21pub enum ExternalPattern {
22 Exact(String),
24 Pattern(String),
26}
27
28impl ExternalPattern {
29 pub fn exact(name: impl Into<String>) -> Self {
30 Self::Exact(name.into())
31 }
32
33 pub fn pattern(pattern: impl Into<String>) -> Self {
34 Self::Pattern(pattern.into())
35 }
36}
37
38#[derive(Debug, Clone)]
40pub struct OutputConfig {
41 pub dir: PathBuf,
42 pub format: OutputFormat,
43 pub sourcemap: Option<SourceMapType>,
44 pub splitting: bool,
45}
46
47#[derive(Debug, Clone)]
49pub struct ResolutionConfig {
50 pub conditions: ExportConditions,
52
53 pub aliases: FxHashMap<String, String>,
55
56 pub external: Vec<ExternalPattern>,
58
59 pub node_builtins: NodeBuiltins,
61}
62
63#[derive(Debug, Clone)]
65pub struct OptimizationConfig {
66 pub minify: MinifyLevel,
67 pub tree_shake: bool,
68 pub bundle: bool,
69}
70
71pub struct BuildConfig {
76 pub entries: EntryPoints,
78
79 pub output: OutputConfig,
81
82 pub resolution: ResolutionConfig,
84
85 pub optimization: OptimizationConfig,
87
88 pub cwd: Option<PathBuf>,
90
91 pub runtime: Option<Arc<dyn crate::Runtime>>,
93
94 pub virtual_files: FxHashMap<String, String>,
96}
97
98impl BuildConfig {
99 pub fn new(entry: impl Into<EntryPoints>) -> Self {
101 let entries = entry.into();
102 Self {
103 entries,
104 output: OutputConfig {
105 dir: PathBuf::from("dist"),
106 format: OutputFormat::Esm,
107 sourcemap: Some(SourceMapType::File),
108 splitting: false,
109 },
110 resolution: ResolutionConfig {
111 conditions: ExportConditions::browser(),
112 aliases: FxHashMap::default(),
113 external: Vec::new(),
114 node_builtins: NodeBuiltins::Error,
115 },
116 optimization: OptimizationConfig {
117 minify: MinifyLevel::None,
118 tree_shake: true,
119 bundle: true,
120 },
121 cwd: None,
122 runtime: None,
123 virtual_files: FxHashMap::default(),
124 }
125 }
126
127 pub fn for_target(mut self, target: &dyn crate::DeploymentTarget) -> Self {
129 self.resolution.conditions = target.conditions();
130 self.resolution.node_builtins = target.node_builtins();
131 self.resolution.external.extend(
132 target
133 .external_packages()
134 .into_iter()
135 .map(ExternalPattern::exact),
136 );
137 self
138 }
139
140 pub fn output_dir(mut self, dir: impl Into<PathBuf>) -> Self {
142 self.output.dir = dir.into();
143 self
144 }
145
146 pub fn format(mut self, format: OutputFormat) -> Self {
148 self.output.format = format;
149 self
150 }
151
152 pub fn sourcemap(mut self, enabled: bool) -> Self {
154 self.output.sourcemap = if enabled {
155 Some(SourceMapType::File)
156 } else {
157 None
158 };
159 self
160 }
161
162 pub fn sourcemap_type(mut self, sourcemap: Option<SourceMapType>) -> Self {
164 self.output.sourcemap = sourcemap;
165 self
166 }
167
168 pub fn minify(mut self, level: MinifyLevel) -> Self {
170 self.optimization.minify = level;
171 self
172 }
173
174 pub fn bundle(mut self, enabled: bool) -> Self {
176 self.optimization.bundle = enabled;
177 self
178 }
179
180 pub fn splitting(mut self, enabled: bool) -> Self {
182 self.output.splitting = enabled;
183 self
184 }
185
186 pub fn external(mut self, packages: impl IntoIterator<Item = impl Into<String>>) -> Self {
188 self.resolution.external.extend(
189 packages
190 .into_iter()
191 .map(|p| ExternalPattern::exact(p.into())),
192 );
193 self
194 }
195
196 pub fn path_alias(mut self, alias: impl Into<String>, target: impl Into<String>) -> Self {
198 self.resolution.aliases.insert(alias.into(), target.into());
199 self
200 }
201
202 pub fn cwd(mut self, cwd: impl Into<PathBuf>) -> Self {
204 self.cwd = Some(cwd.into());
205 self
206 }
207
208 pub fn runtime(mut self, runtime: Arc<dyn crate::Runtime>) -> Self {
210 self.runtime = Some(runtime);
211 self
212 }
213
214 pub fn virtual_file(mut self, path: impl Into<String>, content: impl Into<String>) -> Self {
216 self.virtual_files.insert(path.into(), content.into());
217 self
218 }
219
220 pub fn validate(&self) -> crate::Result<()> {
224 if self.output.splitting {
226 if matches!(self.entries, EntryPoints::Single(_)) {
227 return Err(crate::Error::InvalidConfig(
228 "code splitting requires multiple entry points".into(),
229 ));
230 }
231 if !self.optimization.bundle {
232 return Err(crate::Error::InvalidConfig(
233 "code splitting requires bundle: true".into(),
234 ));
235 }
236 }
237
238 const MAX_ENTRY_POINTS: usize = 1000;
240 let entry_count = match &self.entries {
241 EntryPoints::Single(_) => 1,
242 EntryPoints::Multiple(v) => v.len(),
243 EntryPoints::Named(m) => m.len(),
244 };
245
246 if entry_count > MAX_ENTRY_POINTS {
247 return Err(crate::Error::InvalidConfig(format!(
248 "Too many entry points: {} (max {})",
249 entry_count, MAX_ENTRY_POINTS
250 )));
251 }
252
253 if entry_count == 0 {
254 return Err(crate::Error::InvalidConfig(
255 "At least one entry point is required".into(),
256 ));
257 }
258
259 Ok(())
260 }
261
262 pub async fn build(self) -> crate::Result<crate::BuildResult> {
266 self.validate()?;
267 let options = self.into_build_options();
268 crate::builders::build(options).await
269 }
270
271 fn into_build_options(self) -> crate::BuildOptions {
273 use crate::Platform;
274
275 let platform = match self.resolution.conditions {
277 ExportConditions::Node => Platform::Node,
278 ExportConditions::Edge | ExportConditions::Browser => Platform::Browser,
279 };
280
281 let external = if self.optimization.bundle {
283 ExternalConfig::None
284 } else {
285 ExternalConfig::List(vec![])
286 };
287
288 let (entry_mode, code_splitting) = match &self.entries {
290 EntryPoints::Single(_) => (EntryMode::Shared, None),
291 EntryPoints::Multiple(_) | EntryPoints::Named(_) => {
292 if self.output.splitting {
293 (EntryMode::Shared, Some(CodeSplittingConfig::default()))
294 } else {
295 (EntryMode::Isolated, None)
296 }
297 }
298 };
299
300 let external_config = if self.resolution.external.is_empty() {
302 external
303 } else {
304 let packages: Vec<String> = self
305 .resolution
306 .external
307 .into_iter()
308 .map(|p| match p {
309 ExternalPattern::Exact(s) => s,
310 ExternalPattern::Pattern(s) => s,
311 })
312 .collect();
313 ExternalConfig::List(packages)
314 };
315
316 crate::BuildOptions {
317 entry: self.entries,
318 entry_mode,
319 code_splitting,
320 external: external_config,
321 outdir: Some(self.output.dir),
322 outfile: None,
323 platform,
324 format: self.output.format,
325 sourcemap: self.output.sourcemap,
326 minify_level: match self.optimization.minify {
327 MinifyLevel::None => None,
328 MinifyLevel::Whitespace => Some("whitespace".to_string()),
329 MinifyLevel::Syntax => Some("syntax".to_string()),
330 MinifyLevel::Identifiers => Some("identifiers".to_string()),
331 },
332 globals: FxHashMap::default(),
333 virtual_files: self.virtual_files,
334 path_aliases: self.resolution.aliases,
335 cwd: self.cwd,
336 runtime: self.runtime,
337 decorator: None,
338 #[cfg(feature = "dts-generation")]
339 dts: None,
340 cache: None,
341 incremental: None,
342 max_parallel_builds: None,
343 }
344 }
345}