1mod rebuild;
2use std::sync::{Arc, atomic::AtomicU32};
3
4use futures::future::join_all;
5use rspack_cacheable::cacheable;
6use rspack_error::Result;
7use rspack_fs::{IntermediateFileSystem, NativeFileSystem, ReadableFileSystem, WritableFileSystem};
8use rspack_hook::define_hook;
9use rspack_paths::{Utf8Path, Utf8PathBuf};
10use rspack_sources::BoxSource;
11use rspack_tasks::{CompilerContext, within_compiler_context};
12use rspack_util::{node_path::NodePath, tracing_preset::TRACING_BENCH_TARGET};
13use rustc_hash::FxHashMap as HashMap;
14use tracing::instrument;
15
16pub use self::rebuild::CompilationRecords;
17use crate::{
18 BoxPlugin, CleanOptions, Compilation, CompilationAsset, CompilationLogging, CompilerOptions,
19 CompilerPlatform, ContextModuleFactory, Filename, KeepPattern, NormalModuleFactory, PluginDriver,
20 ResolverFactory, SharedPluginDriver,
21 cache::{Cache, new_cache},
22 compilation::build_module_graph::ModuleExecutor,
23 fast_set, include_hash,
24 incremental::{Incremental, IncrementalPasses},
25 logger::Logger,
26 trim_dir,
27};
28
29define_hook!(CompilerThisCompilation: Series(compilation: &mut Compilation, params: &mut CompilationParams));
31define_hook!(CompilerCompilation: Series(compilation: &mut Compilation, params: &mut CompilationParams));
33define_hook!(CompilerMake: Series(compilation: &mut Compilation));
35define_hook!(CompilerFinishMake: Series(compilation: &mut Compilation));
36define_hook!(CompilerShouldEmit: SeriesBail(compilation: &mut Compilation) -> bool);
38define_hook!(CompilerShouldRecord: SeriesBail(compilation: &mut Compilation) -> bool);
39define_hook!(CompilerEmit: Series(compilation: &mut Compilation));
40define_hook!(CompilerAfterEmit: Series(compilation: &mut Compilation));
41define_hook!(CompilerAssetEmitted: Series(compilation: &Compilation, filename: &str, info: &AssetEmittedInfo));
42define_hook!(CompilerClose: Series(compilation: &Compilation));
43define_hook!(CompilerDone: Series(compilation: &Compilation));
44define_hook!(CompilerFailed: Series(compilation: &Compilation));
45
46#[derive(Debug, Default)]
47pub struct CompilerHooks {
48 pub this_compilation: CompilerThisCompilationHook,
49 pub compilation: CompilerCompilationHook,
50 pub make: CompilerMakeHook,
51 pub finish_make: CompilerFinishMakeHook,
52 pub should_emit: CompilerShouldEmitHook,
53 pub should_record: CompilerShouldRecordHook,
54 pub emit: CompilerEmitHook,
55 pub after_emit: CompilerAfterEmitHook,
56 pub asset_emitted: CompilerAssetEmittedHook,
57 pub close: CompilerCloseHook,
58 pub done: CompilerDoneHook,
59 pub failed: CompilerFailedHook,
60}
61
62static COMPILER_ID: AtomicU32 = AtomicU32::new(0);
63
64#[cacheable]
65#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, Ord, PartialOrd)]
66pub struct CompilerId(u32);
67
68impl CompilerId {
69 pub fn new() -> Self {
70 Self(COMPILER_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed))
71 }
72}
73
74impl Default for CompilerId {
75 fn default() -> Self {
76 Self::new()
77 }
78}
79impl CompilerId {
80 pub fn as_u32(&self) -> u32 {
81 self.0
82 }
83}
84
85#[derive(Debug)]
86pub struct Compiler {
87 id: CompilerId,
88 pub compiler_path: String,
89 pub options: Arc<CompilerOptions>,
90 pub output_filesystem: Arc<dyn WritableFileSystem>,
91 pub intermediate_filesystem: Arc<dyn IntermediateFileSystem>,
92 pub input_filesystem: Arc<dyn ReadableFileSystem>,
93 pub compilation: Compilation,
94 pub plugin_driver: SharedPluginDriver,
95 pub buildtime_plugin_driver: SharedPluginDriver,
96 pub resolver_factory: Arc<ResolverFactory>,
97 pub loader_resolver_factory: Arc<ResolverFactory>,
98 pub cache: Box<dyn Cache>,
99 pub emitted_asset_versions: HashMap<String, String>,
102 pub platform: Arc<CompilerPlatform>,
103 compiler_context: Arc<CompilerContext>,
104 last_records: Option<Arc<CompilationRecords>>,
105}
106
107impl Compiler {
108 #[allow(clippy::too_many_arguments)]
109 pub fn new(
110 compiler_path: String,
111 options: CompilerOptions,
112 plugins: Vec<BoxPlugin>,
113 buildtime_plugins: Vec<BoxPlugin>,
114 output_filesystem: Option<Arc<dyn WritableFileSystem>>,
115 intermediate_filesystem: Option<Arc<dyn IntermediateFileSystem>>,
116 input_filesystem: Option<Arc<dyn ReadableFileSystem>>,
118 resolver_factory: Option<Arc<ResolverFactory>>,
120 loader_resolver_factory: Option<Arc<ResolverFactory>>,
121 compiler_context: Option<Arc<CompilerContext>>,
122 platform: Arc<CompilerPlatform>,
123 ) -> Self {
124 #[cfg(debug_assertions)]
125 {
126 if let Ok(mut debug_info) = crate::debug_info::DEBUG_INFO.lock() {
127 debug_info.with_context(options.context.to_string());
128 }
129 }
130 let pnp = options.resolve.pnp.unwrap_or(false);
131 let input_filesystem = input_filesystem.unwrap_or_else(|| Arc::new(NativeFileSystem::new(pnp)));
133
134 let output_filesystem =
135 output_filesystem.unwrap_or_else(|| Arc::new(NativeFileSystem::new(false)));
136 let intermediate_filesystem =
137 intermediate_filesystem.unwrap_or_else(|| Arc::new(NativeFileSystem::new(false)));
138
139 let resolver_factory = resolver_factory.unwrap_or_else(|| {
140 Arc::new(ResolverFactory::new(
141 options.resolve.clone(),
142 input_filesystem.clone(),
143 ))
144 });
145 let loader_resolver_factory = loader_resolver_factory.unwrap_or_else(|| {
146 Arc::new(ResolverFactory::new(
147 options.resolve_loader.clone(),
148 input_filesystem.clone(),
149 ))
150 });
151
152 let options = Arc::new(options);
153 let compilation_logging: CompilationLogging = Default::default();
154 let plugin_driver = PluginDriver::new(options.clone(), plugins, resolver_factory.clone());
155 let buildtime_plugin_driver =
156 PluginDriver::new(options.clone(), buildtime_plugins, resolver_factory.clone());
157 let cache = new_cache(
158 &compiler_path,
159 options.clone(),
160 input_filesystem.clone(),
161 intermediate_filesystem.clone(),
162 compilation_logging.clone(),
163 );
164 let incremental = Incremental::new_cold(options.incremental);
165 let module_executor = ModuleExecutor::default();
166
167 let id = CompilerId::new();
168 let compiler_context = compiler_context.unwrap_or_else(|| Arc::new(CompilerContext::new()));
169 Self {
170 id,
171 compiler_path,
172 options: options.clone(),
173 compilation: Compilation::new(
174 id,
175 options,
176 platform.clone(),
177 plugin_driver.clone(),
178 buildtime_plugin_driver.clone(),
179 resolver_factory.clone(),
180 loader_resolver_factory.clone(),
181 None,
182 incremental,
183 Some(module_executor),
184 compilation_logging,
185 Default::default(),
186 Default::default(),
187 input_filesystem.clone(),
188 intermediate_filesystem.clone(),
189 output_filesystem.clone(),
190 false,
191 compiler_context.clone(),
192 ),
193 output_filesystem,
194 intermediate_filesystem,
195 plugin_driver,
196 buildtime_plugin_driver,
197 resolver_factory,
198 loader_resolver_factory,
199 cache,
200 emitted_asset_versions: Default::default(),
201 input_filesystem,
202 platform,
203 compiler_context,
204 last_records: None,
205 }
206 }
207
208 pub fn id(&self) -> CompilerId {
209 self.id
210 }
211
212 pub async fn run(&mut self) -> Result<()> {
213 self.build().await?;
214 Ok(())
215 }
216
217 pub async fn build(&mut self) -> Result<()> {
218 let compiler_context = self.compiler_context.clone();
219 match within_compiler_context(compiler_context, self.build_inner()).await {
220 Ok(_) => {
221 self
222 .plugin_driver
223 .compiler_hooks
224 .done
225 .call(&self.compilation)
226 .await?;
227 Ok(())
228 }
229 Err(e) => {
230 self
231 .plugin_driver
232 .compiler_hooks
233 .failed
234 .call(&self.compilation)
235 .await?;
236 Err(e)
237 }
238 }
239 }
240
241 #[instrument("Compiler:build",target=TRACING_BENCH_TARGET, skip_all)]
242 async fn build_inner(&mut self) -> Result<()> {
243 let plugin_driver_clone = self.plugin_driver.clone();
246 let compilation_id = self.compilation.id();
247 let _guard = scopeguard::guard((), move |_| plugin_driver_clone.clear_cache(compilation_id));
248 let compilation_logging = self.compilation.get_logging().clone();
249 compilation_logging.clear();
250
251 fast_set(
252 &mut self.compilation,
253 Compilation::new(
254 self.id,
255 self.options.clone(),
256 self.platform.clone(),
257 self.plugin_driver.clone(),
258 self.buildtime_plugin_driver.clone(),
259 self.resolver_factory.clone(),
260 self.loader_resolver_factory.clone(),
261 None,
262 Incremental::new_cold(self.options.incremental),
263 Some(Default::default()),
264 compilation_logging,
265 Default::default(),
266 Default::default(),
267 self.input_filesystem.clone(),
268 self.intermediate_filesystem.clone(),
269 self.output_filesystem.clone(),
270 false,
271 self.compiler_context.clone(),
272 ),
273 );
274 let _is_hot = self.cache.before_compile(&mut self.compilation).await;
275 self.compile().await?;
282 self.compile_done().await?;
283 self.cache.after_compile(&self.compilation).await;
284 #[cfg(allocative)]
285 crate::utils::snapshot_allocative("build");
286
287 Ok(())
288 }
289 #[instrument("Compiler:compile", target=TRACING_BENCH_TARGET,skip_all)]
290 async fn compile(&mut self) -> Result<()> {
291 let mut compilation_params = self.new_compilation_params();
292 self
294 .plugin_driver
295 .compiler_hooks
296 .this_compilation
297 .call(&mut self.compilation, &mut compilation_params)
298 .await?;
299 self
300 .plugin_driver
301 .compiler_hooks
302 .compilation
303 .call(&mut self.compilation, &mut compilation_params)
304 .await?;
305
306 let logger = self.compilation.get_logger("rspack.Compiler");
307 let start = logger.time("seal compilation");
308 self
309 .compilation
310 .run_passes(self.plugin_driver.clone(), &mut *self.cache)
311 .await?;
312 logger.time_end(start);
313
314 let plugin_driver_diagnostics = self.plugin_driver.take_diagnostic();
316 self
317 .compilation
318 .extend_diagnostics(plugin_driver_diagnostics);
319
320 Ok(())
321 }
322
323 #[instrument("Compile:done", skip_all)]
324 async fn compile_done(&mut self) -> Result<()> {
325 let logger = self.compilation.get_logger("rspack.Compiler");
326
327 let should_record = !matches!(
328 self
329 .plugin_driver
330 .compiler_hooks
331 .should_record
332 .call(&mut self.compilation)
333 .await?,
334 Some(false)
335 );
336
337 if should_record {
338 self.last_records = Some(Arc::new(CompilationRecords::record(&self.compilation)));
339 }
340
341 if matches!(
342 self
343 .plugin_driver
344 .compiler_hooks
345 .should_emit
346 .call(&mut self.compilation)
347 .await?,
348 Some(false)
349 ) {
350 return Ok(());
351 }
352
353 let start = logger.time("emitAssets");
354 self.emit_assets().await?;
355 logger.time_end(start);
356
357 Ok(())
358 }
359
360 #[instrument("emit_assets", skip_all)]
361 pub async fn emit_assets(&mut self) -> Result<()> {
362 let output_path_str = self
363 .compilation
364 .get_path(
365 &Filename::from(&self.options.output.path),
366 Default::default(),
367 )
368 .await?;
369 let output_path = Utf8Path::new(&output_path_str);
370 self.run_clean_options(output_path).await?;
371
372 self
373 .plugin_driver
374 .compiler_hooks
375 .emit
376 .call(&mut self.compilation)
377 .await?;
378
379 let mut new_emitted_asset_versions = HashMap::default();
380 let emit_assets_incremental = self
381 .compilation
382 .incremental
383 .passes_enabled(IncrementalPasses::EMIT_ASSETS);
384
385 rspack_parallel::scope(|token| {
386 self
387 .compilation
388 .assets()
389 .iter()
390 .for_each(|(filename, asset)| {
391 if emit_assets_incremental {
393 new_emitted_asset_versions.insert(filename.clone(), asset.info.version.clone());
394 }
395
396 if emit_assets_incremental
397 && let Some(old_version) = self.emitted_asset_versions.get(filename)
398 && old_version.as_str() == asset.info.version
399 && !old_version.is_empty()
400 {
401 return;
402 }
403
404 let s = unsafe { token.used((&self, filename, asset, output_path)) };
406
407 s.spawn(|(this, filename, asset, output_path)| {
408 this.emit_asset(output_path, filename, asset)
409 });
410 })
411 })
412 .await;
413
414 self.emitted_asset_versions = new_emitted_asset_versions;
415
416 self
417 .plugin_driver
418 .compiler_hooks
419 .after_emit
420 .call(&mut self.compilation)
421 .await
422 }
423 async fn emit_asset(
424 &self,
425 output_path: &Utf8Path,
426 filename: &str,
427 asset: &CompilationAsset,
428 ) -> Result<()> {
429 if let Some(source) = asset.get_source() {
430 let (target_file, query) = filename.split_once('?').unwrap_or((filename, ""));
431 let file_path = output_path.node_join(target_file);
432 self
433 .output_filesystem
434 .create_dir_all(
435 file_path
436 .parent()
437 .unwrap_or_else(|| panic!("The parent of {file_path} can't found")),
438 )
439 .await?;
440
441 let content = source.buffer();
442
443 let mut immutable = asset.info.immutable.unwrap_or(false);
444 if !query.is_empty() {
445 immutable = immutable
446 && (include_hash(target_file, &asset.info.content_hash)
447 || include_hash(target_file, &asset.info.chunk_hash)
448 || include_hash(target_file, &asset.info.full_hash));
449 }
450
451 let stat = self
452 .output_filesystem
453 .stat(file_path.as_path().as_ref())
454 .await
455 .ok();
456
457 let need_write = if !self.options.output.compare_before_emit {
458 true
460 } else if !stat.as_ref().is_some_and(|stat| stat.is_file) {
461 true
463 } else if immutable {
464 false
466 } else if (content.len() as u64) == stat.as_ref().unwrap_or_else(|| unreachable!()).size {
467 match self
468 .output_filesystem
469 .read_file(file_path.as_path().as_ref())
470 .await
471 {
472 Ok(c) => content != c,
474 Err(_) => true,
476 }
477 } else {
478 true
480 };
481
482 if need_write {
483 self.output_filesystem.write(&file_path, &content).await?;
484 self.compilation.emitted_assets.insert(filename.to_string());
485 }
486
487 let info = AssetEmittedInfo {
488 output_path: output_path.to_owned(),
489 source: source.clone(),
490 target_path: file_path,
491 };
492 self
493 .plugin_driver
494 .compiler_hooks
495 .asset_emitted
496 .call(&self.compilation, filename, &info)
497 .await?;
498 }
499 Ok(())
500 }
501
502 async fn run_clean_options(&mut self, output_path: &Utf8Path) -> Result<()> {
503 let clean_options = &self.options.output.clean;
504
505 if let CleanOptions::CleanAll(false) = clean_options {
507 return Ok(());
508 }
509
510 if self.emitted_asset_versions.is_empty() {
511 match clean_options {
512 CleanOptions::CleanAll(true) => {
513 self.output_filesystem.remove_dir_all(output_path).await?;
514 }
515 CleanOptions::KeepPath(p) => {
516 let path = output_path.join(p);
517 trim_dir(
518 &*self.output_filesystem,
519 output_path,
520 KeepPattern::Path(&path),
521 )
522 .await?;
523 }
524 CleanOptions::KeepRegex(r) => {
525 let keep_pattern = KeepPattern::Regex(r);
526 trim_dir(&*self.output_filesystem, output_path, keep_pattern).await?;
527 }
528 CleanOptions::KeepFunc(f) => {
529 let keep_pattern = KeepPattern::Func(f);
530 trim_dir(&*self.output_filesystem, output_path, keep_pattern).await?;
531 }
532 _ => {}
533 }
534
535 return Ok(());
536 }
537
538 let assets = self.compilation.assets();
539 join_all(self.emitted_asset_versions.keys().filter_map(|filename| {
540 if !assets.contains_key(filename) {
541 let filename = filename.to_owned();
542 Some(async {
543 if !clean_options.keep(&filename).await {
544 let filename = output_path.join(filename);
545 let _ = self.output_filesystem.remove_file(&filename).await;
546 }
547 })
548 } else {
549 None
550 }
551 }))
552 .await;
553
554 Ok(())
555 }
556
557 pub fn new_compilation_params(&self) -> CompilationParams {
558 CompilationParams {
559 normal_module_factory: Arc::new(NormalModuleFactory::new(
560 self.options.clone(),
561 self.loader_resolver_factory.clone(),
562 self.plugin_driver.clone(),
563 )),
564 context_module_factory: Arc::new(ContextModuleFactory::new(
565 self.resolver_factory.clone(),
566 self.loader_resolver_factory.clone(),
567 self.plugin_driver.clone(),
568 )),
569 }
570 }
571
572 pub async fn close(&self) -> Result<()> {
573 self
574 .plugin_driver
575 .compiler_hooks
576 .close
577 .call(&self.compilation)
578 .await?;
579
580 self.cache.close().await;
581
582 Ok(())
583 }
584}
585
586#[derive(Debug)]
587pub struct CompilationParams {
588 pub normal_module_factory: Arc<NormalModuleFactory>,
589 pub context_module_factory: Arc<ContextModuleFactory>,
590}
591
592#[derive(Debug)]
593pub struct AssetEmittedInfo {
594 pub source: BoxSource,
595 pub output_path: Utf8PathBuf,
596 pub target_path: Utf8PathBuf,
597}