1use std::hash::Hash;
2
3use rspack_core::{
4 ChunkGraph, ChunkKind, ChunkUkey, Compilation, CompilationAdditionalChunkRuntimeRequirements,
5 CompilationDependentFullHash, CompilationParams, CompilerCompilation, ModuleIdentifier, Plugin,
6 RuntimeGlobals, SourceType,
7 rspack_sources::{ConcatSource, RawStringSource, Source, SourceExt},
8};
9use rspack_error::Result;
10use rspack_hash::RspackHash;
11use rspack_hook::{plugin, plugin_hook};
12use rspack_plugin_javascript::{
13 JavascriptModulesChunkHash, JavascriptModulesRenderChunk, JavascriptModulesRenderStartup,
14 JsPlugin, RenderSource, runtime::render_chunk_runtime_modules,
15};
16use rspack_util::{itoa, json_stringify};
17use rustc_hash::FxHashSet as HashSet;
18
19use super::update_hash_for_entry_startup;
20use crate::{
21 chunk_has_js, get_all_chunks, get_chunk_output_name, get_relative_path,
22 get_runtime_chunk_output_name, runtime_chunk_has_hash,
23};
24
25const PLUGIN_NAME: &str = "rspack.ModuleChunkFormatPlugin";
26
27#[plugin]
28#[derive(Debug, Default)]
29pub struct ModuleChunkFormatPlugin;
30
31#[plugin_hook(CompilerCompilation for ModuleChunkFormatPlugin)]
32async fn compilation(
33 &self,
34 compilation: &mut Compilation,
35 _params: &mut CompilationParams,
36) -> Result<()> {
37 let hooks = JsPlugin::get_compilation_hooks_mut(compilation.id());
38 let mut hooks = hooks.write().await;
39 hooks.render_chunk.tap(render_chunk::new(self));
40 hooks.render_startup.tap(render_startup::new(self));
41 hooks.chunk_hash.tap(js_chunk_hash::new(self));
42 Ok(())
43}
44
45#[plugin_hook(CompilationAdditionalChunkRuntimeRequirements for ModuleChunkFormatPlugin)]
46async fn additional_chunk_runtime_requirements(
47 &self,
48 compilation: &mut Compilation,
49 chunk_ukey: &ChunkUkey,
50 runtime_requirements: &mut RuntimeGlobals,
51) -> Result<()> {
52 let chunk = compilation.chunk_by_ukey.expect_get(chunk_ukey);
53
54 if chunk.has_runtime(&compilation.chunk_group_by_ukey) {
55 return Ok(());
56 }
57
58 if compilation
59 .chunk_graph
60 .get_number_of_entry_modules(chunk_ukey)
61 > 0
62 {
63 runtime_requirements.insert(RuntimeGlobals::REQUIRE);
64 runtime_requirements.insert(RuntimeGlobals::STARTUP_ENTRYPOINT);
65 runtime_requirements.insert(RuntimeGlobals::EXTERNAL_INSTALL_CHUNK);
66 }
67
68 Ok(())
69}
70
71#[plugin_hook(JavascriptModulesChunkHash for ModuleChunkFormatPlugin)]
72async fn js_chunk_hash(
73 &self,
74 compilation: &Compilation,
75 chunk_ukey: &ChunkUkey,
76 hasher: &mut RspackHash,
77) -> Result<()> {
78 let chunk = compilation.chunk_by_ukey.expect_get(chunk_ukey);
79 if chunk.has_runtime(&compilation.chunk_group_by_ukey) {
80 return Ok(());
81 }
82
83 PLUGIN_NAME.hash(hasher);
84
85 update_hash_for_entry_startup(
86 hasher,
87 compilation,
88 compilation
89 .chunk_graph
90 .get_chunk_entry_modules_with_chunk_group_iterable(chunk_ukey),
91 chunk_ukey,
92 );
93
94 Ok(())
95}
96
97#[plugin_hook(CompilationDependentFullHash for ModuleChunkFormatPlugin)]
98async fn compilation_dependent_full_hash(
99 &self,
100 compilation: &Compilation,
101 chunk_ukey: &ChunkUkey,
102) -> Result<Option<bool>> {
103 if !chunk_has_js(chunk_ukey, compilation) {
104 return Ok(None);
105 }
106
107 let chunk = compilation.chunk_by_ukey.expect_get(chunk_ukey);
108
109 if !chunk.has_entry_module(&compilation.chunk_graph) {
110 return Ok(None);
111 }
112
113 if runtime_chunk_has_hash(compilation, chunk_ukey).await? {
114 return Ok(Some(true));
115 }
116
117 Ok(None)
118}
119
120#[plugin_hook(JavascriptModulesRenderChunk for ModuleChunkFormatPlugin)]
121async fn render_chunk(
122 &self,
123 compilation: &Compilation,
124 chunk_ukey: &ChunkUkey,
125 render_source: &mut RenderSource,
126) -> Result<()> {
127 let hooks = JsPlugin::get_compilation_hooks(compilation.id());
128 let chunk = compilation.chunk_by_ukey.expect_get(chunk_ukey);
129 let base_chunk_output_name = get_chunk_output_name(chunk, compilation).await?;
130
131 let chunk_id_json_string = json_stringify(chunk.expect_id(&compilation.chunk_ids_artifact));
132
133 let mut sources = ConcatSource::default();
134 sources.add(RawStringSource::from(format!(
135 "export const __webpack_id__ = {chunk_id_json_string} ;\n",
136 )));
137 sources.add(RawStringSource::from(format!(
138 "export const __webpack_ids__ = [{chunk_id_json_string}];\n",
139 )));
140 sources.add(RawStringSource::from_static(
141 "export const __webpack_modules__ = ",
142 ));
143 sources.add(render_source.source.clone());
144 sources.add(RawStringSource::from_static(";\n"));
145
146 if compilation
147 .chunk_graph
148 .has_chunk_runtime_modules(chunk_ukey)
149 {
150 sources.add(RawStringSource::from_static(
151 "export const __webpack_runtime__ = ",
152 ));
153 sources.add(render_chunk_runtime_modules(compilation, chunk_ukey).await?);
154 sources.add(RawStringSource::from_static(";\n"));
155 }
156
157 if matches!(chunk.kind(), ChunkKind::HotUpdate) {
158 render_source.source = sources.boxed();
159 return Ok(());
160 }
161
162 if chunk.has_entry_module(&compilation.chunk_graph) {
163 let runtime_chunk_output_name = get_runtime_chunk_output_name(compilation, chunk_ukey).await?;
164 sources.add(RawStringSource::from(format!(
165 "import __webpack_require__ from '{}';\n",
166 get_relative_path(
167 base_chunk_output_name
168 .trim_start_matches("/")
169 .trim_start_matches("\\"),
170 &runtime_chunk_output_name
171 )
172 )));
173
174 let entries = compilation
175 .chunk_graph
176 .get_chunk_entry_modules_with_chunk_group_iterable(chunk_ukey);
177
178 let mut startup_source = vec![];
179
180 startup_source.push(format!(
181 "var __webpack_exec__ = function(moduleId) {{ return __webpack_require__({} = moduleId); }}",
182 RuntimeGlobals::ENTRY_MODULE_ID
183 ));
184
185 let module_graph = compilation.get_module_graph();
186 let mut loaded_chunks = HashSet::default();
187 for (i, (module, entry)) in entries.iter().enumerate() {
188 if !module_graph
189 .module_by_identifier(module)
190 .is_some_and(|module| {
191 module
192 .source_types(&module_graph)
193 .contains(&SourceType::JavaScript)
194 })
195 {
196 continue;
197 }
198 let module_id = ChunkGraph::get_module_id(&compilation.module_ids_artifact, *module)
199 .expect("should have module id");
200 let runtime_chunk = compilation
201 .chunk_group_by_ukey
202 .expect_get(entry)
203 .get_runtime_chunk(&compilation.chunk_group_by_ukey);
204 let chunks = get_all_chunks(
205 entry,
206 &runtime_chunk,
207 None,
208 &compilation.chunk_group_by_ukey,
209 );
210
211 for chunk_ukey in chunks.iter() {
212 if !chunk_has_js(chunk_ukey, compilation) {
214 continue;
215 }
216 if loaded_chunks.contains(chunk_ukey) {
217 continue;
218 }
219 loaded_chunks.insert(*chunk_ukey);
220 let index = loaded_chunks.len();
221 let chunk = compilation.chunk_by_ukey.expect_get(chunk_ukey);
222 let other_chunk_output_name = get_chunk_output_name(chunk, compilation).await?;
223 let mut index_buffer = itoa::Buffer::new();
224 let index_str = index_buffer.format(index);
225 startup_source.push(format!(
226 "import * as __webpack_chunk_${}__ from '{}';",
227 index_str,
228 get_relative_path(&base_chunk_output_name, &other_chunk_output_name)
229 ));
230 let mut index_buffer2 = itoa::Buffer::new();
231 let index_str2 = index_buffer2.format(index);
232 startup_source.push(format!(
233 "{}(__webpack_chunk_${}__);",
234 RuntimeGlobals::EXTERNAL_INSTALL_CHUNK,
235 index_str2
236 ));
237 }
238
239 let module_id_expr = serde_json::to_string(module_id).expect("invalid module_id");
240
241 startup_source.push(format!(
242 "{}__webpack_exec__({module_id_expr});",
243 if i + 1 == entries.len() {
244 "var __webpack_exports__ = "
245 } else {
246 ""
247 }
248 ));
249 }
250
251 let last_entry_module = entries
252 .keys()
253 .next_back()
254 .expect("should have last entry module");
255 let mut render_source = RenderSource {
256 source: RawStringSource::from(startup_source.join("\n")).boxed(),
257 };
258 hooks
259 .try_read()
260 .expect("should have js plugin drive")
261 .render_startup
262 .call(
263 compilation,
264 chunk_ukey,
265 last_entry_module,
266 &mut render_source,
267 )
268 .await?;
269 sources.add(render_source.source);
270 }
271 render_source.source = sources.boxed();
272 Ok(())
273}
274
275fn render_chunk_import(named_import: &str, import_source: &str) -> String {
276 format!("import * as {} from '{}';\n", named_import, import_source)
277}
278#[plugin_hook(JavascriptModulesRenderStartup for ModuleChunkFormatPlugin)]
279async fn render_startup(
280 &self,
281 compilation: &Compilation,
282 chunk_ukey: &ChunkUkey,
283 _module: &ModuleIdentifier,
284 render_source: &mut RenderSource,
285) -> Result<()> {
286 let chunk = compilation.chunk_by_ukey.expect_get(chunk_ukey);
287 let entries_count = compilation
288 .chunk_graph
289 .get_number_of_entry_modules(chunk_ukey);
290 let has_runtime = chunk.has_runtime(&compilation.chunk_group_by_ukey);
291
292 if entries_count > 0 && has_runtime {
293 let dependent_chunks = compilation
294 .chunk_graph
295 .get_chunk_entry_dependent_chunks_iterable(
296 chunk_ukey,
297 &compilation.chunk_by_ukey,
298 &compilation.chunk_group_by_ukey,
299 );
300 let base_chunk_output_name = get_chunk_output_name(chunk, compilation).await?;
301
302 let mut dependent_load = ConcatSource::default();
303 for (index, ck) in dependent_chunks.enumerate() {
304 if !chunk_has_js(&ck, compilation) {
305 continue;
306 }
307
308 let dependant_chunk = compilation.chunk_by_ukey.expect_get(&ck);
309
310 let named_import = format!("__webpack_imports__{}", index);
311
312 let dependant_chunk_name = get_chunk_output_name(dependant_chunk, compilation).await?;
313
314 let imported = get_relative_path(&base_chunk_output_name, &dependant_chunk_name);
315
316 dependent_load.add(RawStringSource::from(render_chunk_import(
317 &named_import,
318 &imported,
319 )));
320 dependent_load.add(RawStringSource::from(format!(
321 "{}({});\n",
322 RuntimeGlobals::EXTERNAL_INSTALL_CHUNK,
323 named_import
324 )));
325 }
326
327 if !dependent_load.source().is_empty() {
328 let mut sources = ConcatSource::default();
329 sources.add(dependent_load);
330 sources.add(render_source.source.clone());
331 render_source.source = sources.boxed();
332 }
333 }
334 Ok(())
335}
336
337impl Plugin for ModuleChunkFormatPlugin {
338 fn name(&self) -> &'static str {
339 PLUGIN_NAME
340 }
341
342 fn apply(&self, ctx: &mut rspack_core::ApplyContext<'_>) -> Result<()> {
343 ctx.compiler_hooks.compilation.tap(compilation::new(self));
344 ctx
345 .compilation_hooks
346 .additional_chunk_runtime_requirements
347 .tap(additional_chunk_runtime_requirements::new(self));
348 ctx
349 .compilation_hooks
350 .dependent_full_hash
351 .tap(compilation_dependent_full_hash::new(self));
352 Ok(())
353 }
354}