Skip to main content

rspack_core/loader/
rspack_loader.rs

1use std::sync::Arc;
2
3use rspack_error::{Diagnostic, Result};
4use rspack_fs::ReadableFileSystem;
5use rspack_loader_runner::{Content, LoaderContext, LoaderRunnerPlugin, ResourceData};
6use rspack_sources::SourceMap;
7use rustc_hash::FxHashSet as HashSet;
8
9use crate::{RunnerContext, SharedPluginDriver, utils::extract_source_map};
10
11pub struct RspackLoaderRunnerPlugin {
12  pub plugin_driver: SharedPluginDriver,
13  pub extract_source_map: Option<bool>,
14}
15
16#[async_trait::async_trait]
17impl LoaderRunnerPlugin for RspackLoaderRunnerPlugin {
18  type Context = RunnerContext;
19
20  fn name(&self) -> &'static str {
21    "rspack-loader-runner"
22  }
23
24  async fn before_all(&self, context: &mut LoaderContext<Self::Context>) -> Result<()> {
25    self
26      .plugin_driver
27      .normal_module_hooks
28      .loader
29      .call(context)
30      .await
31  }
32
33  async fn process_resource(
34    &self,
35    resource_data: &ResourceData,
36    fs: Arc<dyn ReadableFileSystem>,
37  ) -> Result<Option<(Content, Option<SourceMap>, HashSet<std::path::PathBuf>)>> {
38    // First try the plugin's read_resource hook
39    let result = self
40      .plugin_driver
41      .normal_module_hooks
42      .read_resource
43      .call(resource_data, &fs)
44      .await?;
45
46    if let Some(content) = result {
47      if let Some(true) = self.extract_source_map {
48        // Try to extract source map from the content
49        let extract_result = match &content {
50          Content::String(s) => extract_source_map(fs, s, resource_data.resource()).await,
51          Content::Buffer(b) => {
52            extract_source_map(fs, &String::from_utf8_lossy(b), resource_data.resource()).await
53          }
54        };
55
56        match extract_result {
57          Ok(extract_result) => {
58            // Convert file dependencies to FxHashSet for consistency
59            let file_deps = extract_result
60              .file_dependencies
61              .map(|deps| deps.into_iter().collect::<HashSet<_>>())
62              .unwrap_or_default();
63
64            // Return the content with source map extracted and file dependencies
65            return Ok(Some((
66              Content::String(extract_result.source),
67              extract_result.source_map,
68              file_deps,
69            )));
70          }
71          Err(e) => {
72            // If extraction fails, return original content with empty dependencies
73            // Log the error as a warning
74            self
75              .plugin_driver
76              .diagnostics
77              .lock()
78              .expect("should get lock")
79              .push(Diagnostic::warn("extractSourceMap".into(), e));
80            return Ok(Some((content, None, HashSet::default())));
81          }
82        }
83      }
84      // Return original content with empty dependencies when extract_source_map is disabled
85      return Ok(Some((content, None, HashSet::default())));
86    }
87
88    // If no plugin handled it, return None so the default logic can handle it
89    Ok(None)
90  }
91
92  async fn should_yield(&self, context: &LoaderContext<Self::Context>) -> Result<bool> {
93    let res = self
94      .plugin_driver
95      .normal_module_hooks
96      .loader_should_yield
97      .call(context)
98      .await?;
99
100    if let Some(res) = res {
101      return Ok(res);
102    }
103
104    Ok(false)
105  }
106
107  async fn start_yielding(&self, context: &mut LoaderContext<Self::Context>) -> Result<()> {
108    self
109      .plugin_driver
110      .normal_module_hooks
111      .loader_yield
112      .call(context)
113      .await
114  }
115}