rspack_plugin_runtime/runtime_module/
utils.rs1use std::fmt::Write as _;
2
3use rspack_core::{
4 Chunk, ChunkLoading, ChunkUkey, Compilation, PathData, RuntimeCodeTemplate, SourceType,
5 chunk_graph_chunk::{ChunkId, ChunkIdSet},
6 get_js_chunk_filename_template, get_undo_path,
7};
8use rspack_error::Result;
9use rspack_util::{
10 fx_hash::{FxIndexMap, FxIndexSet},
11 test::is_hot_test,
12};
13
14pub fn get_initial_chunk_ids(
15 chunk: Option<ChunkUkey>,
16 compilation: &Compilation,
17 filter_fn: impl Fn(&ChunkUkey, &Compilation) -> bool,
18) -> ChunkIdSet {
19 match chunk {
20 Some(chunk_ukey) => match compilation
21 .build_chunk_graph_artifact
22 .chunk_by_ukey
23 .get(&chunk_ukey)
24 {
25 Some(chunk) => {
26 let mut js_chunks = chunk
27 .get_all_initial_chunks(&compilation.build_chunk_graph_artifact.chunk_group_by_ukey)
28 .iter()
29 .filter(|key| !(chunk_ukey.eq(key) || filter_fn(key, compilation)))
30 .map(|chunk_ukey| {
31 let chunk = compilation
32 .build_chunk_graph_artifact
33 .chunk_by_ukey
34 .expect_get(chunk_ukey);
35 chunk.expect_id().clone()
36 })
37 .collect::<ChunkIdSet>();
38 js_chunks.insert(chunk.expect_id().clone());
39 js_chunks
40 }
41 None => ChunkIdSet::default(),
42 },
43 None => ChunkIdSet::default(),
44 }
45}
46
47pub fn stringify_chunks(chunks: &ChunkIdSet, value: u8) -> String {
48 let mut v = chunks.iter().collect::<Vec<_>>();
49 v.sort_unstable();
50
51 let mut result = String::with_capacity(v.len() * 8 + 2);
52 result.push('{');
53 for chunk_id in v {
54 let key = rspack_util::json_stringify(chunk_id);
55 result.reserve(key.len() + 4);
56 result.push_str(&key);
57 result.push_str(": ");
58 write!(result, "{value},").expect("infallible write to String");
59 }
60 result.push('}');
61 result
62}
63
64pub fn chunk_has_css(chunk: &ChunkUkey, compilation: &Compilation) -> bool {
65 compilation
66 .build_chunk_graph_artifact
67 .chunk_graph
68 .has_chunk_module_by_source_type(chunk, SourceType::Css, compilation.get_module_graph())
69}
70
71pub async fn get_output_dir(
72 chunk: &Chunk,
73 compilation: &Compilation,
74 enforce_relative: bool,
75) -> rspack_error::Result<String> {
76 let filename = get_js_chunk_filename_template(
77 chunk,
78 &compilation.options.output,
79 &compilation.build_chunk_graph_artifact.chunk_group_by_ukey,
80 );
81 let output_dir = compilation
82 .get_path(
83 &filename,
84 PathData::default()
85 .chunk_id_optional(chunk.id().map(|id| id.as_str()))
86 .chunk_hash_optional(chunk.rendered_hash(
87 &compilation.chunk_hashes_artifact,
88 compilation.options.output.hash_digest_length,
89 ))
90 .chunk_name_optional(chunk.name_for_filename_template())
91 .content_hash_optional(chunk.rendered_content_hash_by_source_type(
92 &compilation.chunk_hashes_artifact,
93 &SourceType::JavaScript,
94 compilation.options.output.hash_digest_length,
95 )),
96 )
97 .await?;
98 Ok(get_undo_path(
99 output_dir.as_str(),
100 compilation.options.output.path.as_str().to_string(),
101 enforce_relative,
102 ))
103}
104
105pub fn is_enabled_for_chunk(
106 chunk_ukey: &ChunkUkey,
107 expected: &ChunkLoading,
108 compilation: &Compilation,
109) -> bool {
110 let chunk_loading = compilation
111 .build_chunk_graph_artifact
112 .chunk_by_ukey
113 .get(chunk_ukey)
114 .and_then(|chunk| {
115 chunk.get_entry_options(&compilation.build_chunk_graph_artifact.chunk_group_by_ukey)
116 })
117 .and_then(|options| options.chunk_loading.as_ref())
118 .unwrap_or(&compilation.options.output.chunk_loading);
119 chunk_loading == expected
120}
121
122pub fn unquoted_stringify(chunk_id: Option<&ChunkId>, str: &str) -> String {
123 if let Some(chunk_id) = chunk_id
124 && str.len() >= 5
125 && str == chunk_id.as_str()
126 {
127 return "\" + chunkId + \"".to_string();
128 }
129 let result = rspack_util::json_stringify_str(str);
130 result[1..result.len() - 1].to_string()
131}
132
133pub fn stringify_dynamic_chunk_map<F>(
134 f: F,
135 chunks: &FxIndexSet<ChunkUkey>,
136 chunk_map: &FxIndexMap<ChunkUkey, &Chunk>,
137) -> String
138where
139 F: Fn(&Chunk) -> Option<String>,
140{
141 let mut entries = Vec::with_capacity(chunks.len());
142 let mut use_id = false;
143
144 for chunk_ukey in chunks.iter() {
145 if let Some(chunk) = chunk_map.get(chunk_ukey)
146 && let Some(chunk_id) = chunk.id()
147 && let Some(value) = f(chunk)
148 {
149 if value.as_str() == chunk_id.as_str() {
150 use_id = true;
151 } else {
152 entries.push((chunk_id, rspack_util::json_stringify_str(&value)));
153 }
154 }
155 }
156
157 let content = match entries.as_mut_slice() {
158 [] => "chunkId".to_string(),
159 [(chunk_id, value)] => {
160 if use_id {
161 format!(
162 "(chunkId === {} ? {} : chunkId)",
163 rspack_util::json_stringify(*chunk_id),
164 value
165 )
166 } else {
167 value.clone()
168 }
169 }
170 entries => {
171 let map = stringify_map(entries);
172 if use_id {
173 format!("({map}[chunkId] || chunkId)")
174 } else {
175 format!("{map}[chunkId]")
176 }
177 }
178 };
179 format!("\" + {content} + \"")
180}
181
182pub fn stringify_static_chunk_map(filename: &str, chunk_ids: &[&ChunkId]) -> String {
183 let condition = if chunk_ids.len() == 1 {
184 let mut condition = String::from("chunkId === ");
185 let chunk_id = chunk_ids.first().expect("should have one chunk id");
186 condition.push_str(&rspack_util::json_stringify(*chunk_id));
187 condition
188 } else {
189 let mut sorted_chunk_ids = chunk_ids.to_vec();
190 sorted_chunk_ids.sort_unstable();
191
192 let mut condition = String::with_capacity(sorted_chunk_ids.len() * 8 + 14);
193 condition.push('{');
194 condition.push(' ');
195 for (idx, chunk_id) in sorted_chunk_ids.iter().enumerate() {
196 if idx != 0 {
197 condition.push(',');
198 }
199 let key = rspack_util::json_stringify(*chunk_id);
200 condition.reserve(key.len() + 2);
201 condition.push_str(&key);
202 condition.push_str(":1");
203 }
204 condition.push_str(" }[chunkId]");
205 condition
206 };
207 let mut result = String::with_capacity(condition.len() + filename.len() + 14);
208 result.push_str("if (");
209 result.push_str(&condition);
210 result.push_str(") return ");
211 result.push_str(filename);
212 result.push(';');
213 result
214}
215
216fn stringify_map<T: std::fmt::Display>(entries: &mut [(&ChunkId, T)]) -> String {
217 entries.sort_unstable_by_key(|(left, _)| *left);
218
219 let mut result = String::with_capacity(entries.len() * 8 + 2);
220 result.push('{');
221 for (chunk_id, value) in entries.iter() {
222 let key = rspack_util::json_stringify(*chunk_id);
223 result.reserve(key.len() + 4);
224 result.push_str(&key);
225 result.push_str(": ");
226 write!(result, "{value},").expect("infallible write to String");
227 }
228 result.push('}');
229 result
230}
231
232pub fn generate_javascript_hmr_runtime(
233 key: &str,
234 method: &str,
235 runtime_template: &RuntimeCodeTemplate,
236) -> Result<String> {
237 runtime_template.render(
238 key,
239 Some(serde_json::json!({
240 "_loading_method": method,
241 "_is_hot_test": is_hot_test(),
242 })),
243 )
244}
245
246#[cfg(test)]
247mod tests {
248 use rspack_core::chunk_graph_chunk::{ChunkId, ChunkIdSet};
249
250 use super::{stringify_chunks, stringify_map, stringify_static_chunk_map};
251
252 #[test]
253 fn stringify_chunks_keeps_sorted_numeric_ids() {
254 let mut chunks = ChunkIdSet::default();
255 chunks.insert(ChunkId::from("2"));
256 chunks.insert(ChunkId::from("10"));
257
258 assert_eq!(stringify_chunks(&chunks, 1), "{10: 1,2: 1,}");
259 }
260
261 #[test]
262 fn stringify_map_keeps_sorted_and_quoted_values() {
263 let chunk_a = ChunkId::from("a");
264 let chunk_b = ChunkId::from("b");
265 let mut entries = vec![
266 (&chunk_b, rspack_util::json_stringify_str("beta")),
267 (&chunk_a, rspack_util::json_stringify_str("alpha")),
268 ];
269
270 assert_eq!(
271 stringify_map(&mut entries),
272 r#"{"a": "alpha","b": "beta",}"#
273 );
274 }
275
276 #[test]
277 fn stringify_static_chunk_map_single_chunk_keeps_condition_shape() {
278 let filename = "\"style.css\"".to_string();
279 let chunk_id = ChunkId::from("main");
280
281 assert_eq!(
282 stringify_static_chunk_map(&filename, &[&chunk_id]),
283 r#"if (chunkId === "main") return "style.css";"#
284 );
285 }
286
287 #[test]
288 fn stringify_static_chunk_map_multiple_chunks_keeps_sorted_object_shape() {
289 let filename = "\"style.css\"".to_string();
290 let chunk_a = ChunkId::from("b");
291 let chunk_b = ChunkId::from("a");
292
293 assert_eq!(
294 stringify_static_chunk_map(&filename, &[&chunk_a, &chunk_b]),
295 r#"if ({ "a":1,"b":1 }[chunkId]) return "style.css";"#
296 );
297 }
298}