rspack_plugin_runtime/runtime_module/
utils.rs

1use cow_utils::CowUtils;
2use itertools::Itertools;
3use rspack_collections::{UkeyIndexMap, UkeyIndexSet};
4use rspack_core::{
5  Chunk, ChunkLoading, ChunkUkey, Compilation, PathData, SourceType, chunk_graph_chunk::ChunkId,
6  get_js_chunk_filename_template, get_undo_path,
7};
8use rspack_util::test::{
9  HOT_TEST_ACCEPT, HOT_TEST_DISPOSE, HOT_TEST_OUTDATED, HOT_TEST_RUNTIME, HOT_TEST_UPDATED,
10};
11use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};
12
13pub fn get_initial_chunk_ids(
14  chunk: Option<ChunkUkey>,
15  compilation: &Compilation,
16  filter_fn: impl Fn(&ChunkUkey, &Compilation) -> bool,
17) -> HashSet<ChunkId> {
18  match chunk {
19    Some(chunk_ukey) => match compilation.chunk_by_ukey.get(&chunk_ukey) {
20      Some(chunk) => {
21        let mut js_chunks = chunk
22          .get_all_initial_chunks(&compilation.chunk_group_by_ukey)
23          .iter()
24          .filter(|key| !(chunk_ukey.eq(key) || filter_fn(key, compilation)))
25          .map(|chunk_ukey| {
26            let chunk = compilation.chunk_by_ukey.expect_get(chunk_ukey);
27            chunk.expect_id(&compilation.chunk_ids_artifact).clone()
28          })
29          .collect::<HashSet<_>>();
30        js_chunks.insert(chunk.expect_id(&compilation.chunk_ids_artifact).clone());
31        js_chunks
32      }
33      None => HashSet::default(),
34    },
35    None => HashSet::default(),
36  }
37}
38
39pub fn stringify_chunks(chunks: &HashSet<ChunkId>, value: u8) -> String {
40  let mut v = Vec::from_iter(chunks.iter());
41  v.sort_unstable();
42
43  format!(
44    r#"{{{}}}"#,
45    v.iter().fold(String::new(), |prev, cur| {
46      prev
47        + format!(
48          r#"{}: {value},"#,
49          serde_json::to_string(cur).expect("chunk to_string failed")
50        )
51        .as_str()
52    })
53  )
54}
55
56pub fn chunk_has_js(chunk_ukey: &ChunkUkey, compilation: &Compilation) -> bool {
57  if compilation
58    .chunk_graph
59    .get_number_of_entry_modules(chunk_ukey)
60    > 0
61  {
62    return true;
63  }
64
65  !compilation
66    .chunk_graph
67    .get_chunk_modules_by_source_type(
68      chunk_ukey,
69      SourceType::JavaScript,
70      &compilation.get_module_graph(),
71    )
72    .is_empty()
73}
74
75pub fn chunk_has_css(chunk: &ChunkUkey, compilation: &Compilation) -> bool {
76  !compilation
77    .chunk_graph
78    .get_chunk_modules_by_source_type(chunk, SourceType::Css, &compilation.get_module_graph())
79    .is_empty()
80}
81
82pub async fn get_output_dir(
83  chunk: &Chunk,
84  compilation: &Compilation,
85  enforce_relative: bool,
86) -> rspack_error::Result<String> {
87  let filename = get_js_chunk_filename_template(
88    chunk,
89    &compilation.options.output,
90    &compilation.chunk_group_by_ukey,
91  );
92  let output_dir = compilation
93    .get_path(
94      &filename,
95      PathData::default()
96        .chunk_id_optional(
97          chunk
98            .id(&compilation.chunk_ids_artifact)
99            .map(|id| id.as_str()),
100        )
101        .chunk_hash_optional(chunk.rendered_hash(
102          &compilation.chunk_hashes_artifact,
103          compilation.options.output.hash_digest_length,
104        ))
105        .chunk_name_optional(chunk.name_for_filename_template(&compilation.chunk_ids_artifact))
106        .content_hash_optional(chunk.rendered_content_hash_by_source_type(
107          &compilation.chunk_hashes_artifact,
108          &SourceType::JavaScript,
109          compilation.options.output.hash_digest_length,
110        )),
111    )
112    .await?;
113  Ok(get_undo_path(
114    output_dir.as_str(),
115    compilation.options.output.path.as_str().to_string(),
116    enforce_relative,
117  ))
118}
119
120pub fn is_enabled_for_chunk(
121  chunk_ukey: &ChunkUkey,
122  expected: &ChunkLoading,
123  compilation: &Compilation,
124) -> bool {
125  let chunk_loading = compilation
126    .chunk_by_ukey
127    .get(chunk_ukey)
128    .and_then(|chunk| chunk.get_entry_options(&compilation.chunk_group_by_ukey))
129    .and_then(|options| options.chunk_loading.as_ref())
130    .unwrap_or(&compilation.options.output.chunk_loading);
131  chunk_loading == expected
132}
133
134pub fn unquoted_stringify(chunk_id: Option<&ChunkId>, str: &str) -> String {
135  if let Some(chunk_id) = chunk_id
136    && str.len() >= 5
137    && str == chunk_id.as_str()
138  {
139    return "\" + chunkId + \"".to_string();
140  }
141  let result = serde_json::to_string(&str).expect("invalid json to_string");
142  result[1..result.len() - 1].to_string()
143}
144
145pub fn stringify_dynamic_chunk_map<F>(
146  f: F,
147  chunks: &UkeyIndexSet<ChunkUkey>,
148  chunk_map: &UkeyIndexMap<ChunkUkey, &Chunk>,
149  compilation: &Compilation,
150) -> String
151where
152  F: Fn(&Chunk) -> Option<String>,
153{
154  let mut result = HashMap::default();
155  let mut use_id = false;
156  let mut last_key = None;
157  let mut entries = 0;
158
159  for chunk_ukey in chunks.iter() {
160    if let Some(chunk) = chunk_map.get(chunk_ukey)
161      && let Some(chunk_id) = chunk.id(&compilation.chunk_ids_artifact)
162      && let Some(value) = f(chunk)
163    {
164      if value.as_str() == chunk_id.as_str() {
165        use_id = true;
166      } else {
167        result.insert(
168          chunk_id.as_str(),
169          serde_json::to_string(&value).expect("invalid json to_string"),
170        );
171        last_key = Some(chunk_id.as_str());
172        entries += 1;
173      }
174    }
175  }
176
177  let content = if entries == 0 {
178    "chunkId".to_string()
179  } else if entries == 1 {
180    if let Some(last_key) = last_key {
181      if use_id {
182        format!(
183          "(chunkId === {} ? {} : chunkId)",
184          serde_json::to_string(&last_key).expect("invalid json to_string"),
185          result.get(last_key).expect("cannot find last key value")
186        )
187      } else {
188        result
189          .get(last_key)
190          .expect("cannot find last key value")
191          .clone()
192      }
193    } else {
194      unreachable!();
195    }
196  } else if use_id {
197    format!("({}[chunkId] || chunkId)", stringify_map(&result))
198  } else {
199    format!("{}[chunkId]", stringify_map(&result))
200  };
201  format!("\" + {content} + \"")
202}
203
204pub fn stringify_static_chunk_map(filename: &String, chunk_ids: &[&str]) -> String {
205  let condition = if chunk_ids.len() == 1 {
206    format!(
207      "chunkId === {}",
208      serde_json::to_string(&chunk_ids.first()).expect("invalid json to_string")
209    )
210  } else {
211    let content = chunk_ids
212      .iter()
213      .sorted_unstable()
214      .map(|chunk_id| {
215        format!(
216          "{}:1",
217          serde_json::to_string(chunk_id).expect("invalid json to_string")
218        )
219      })
220      .join(",");
221    format!("{{ {content} }}[chunkId]")
222  };
223  format!("if ({condition}) return {filename};")
224}
225
226fn stringify_map<T: std::fmt::Display>(map: &HashMap<&str, T>) -> String {
227  format!(
228    r#"{{{}}}"#,
229    map
230      .keys()
231      .sorted_unstable()
232      .fold(String::new(), |prev, cur| {
233        prev
234          + format!(
235            r#"{}: {},"#,
236            serde_json::to_string(cur).expect("json stringify failed"),
237            map.get(cur).expect("get key from map")
238          )
239          .as_str()
240      })
241  )
242}
243
244pub fn generate_javascript_hmr_runtime(method: &str) -> String {
245  include_str!("runtime/javascript_hot_module_replacement.js")
246    .cow_replace("$key$", method)
247    .cow_replace("$HOT_TEST_OUTDATED$", &HOT_TEST_OUTDATED)
248    .cow_replace("$HOT_TEST_DISPOSE$", &HOT_TEST_DISPOSE)
249    .cow_replace("$HOT_TEST_UPDATED$", &HOT_TEST_UPDATED)
250    .cow_replace("$HOT_TEST_RUNTIME$", &HOT_TEST_RUNTIME)
251    .cow_replace("$HOT_TEST_ACCEPT$", &HOT_TEST_ACCEPT)
252    .into_owned()
253}