rspack_plugin_runtime/
common_js_chunk_format.rs

1use std::hash::Hash;
2
3use rspack_core::{
4  ChunkUkey, Compilation, CompilationAdditionalChunkRuntimeRequirements,
5  CompilationDependentFullHash, CompilationParams, CompilerCompilation, Plugin, RuntimeGlobals,
6  rspack_sources::{ConcatSource, RawStringSource, SourceExt},
7};
8use rspack_error::Result;
9use rspack_hash::RspackHash;
10use rspack_hook::{plugin, plugin_hook};
11use rspack_plugin_javascript::{
12  JavascriptModulesChunkHash, JavascriptModulesRenderChunk, JsPlugin, RenderSource,
13  runtime::render_chunk_runtime_modules,
14};
15use rspack_util::json_stringify;
16
17use crate::{
18  generate_entry_startup, get_chunk_output_name, get_relative_path, get_runtime_chunk_output_name,
19  runtime_chunk_has_hash, update_hash_for_entry_startup,
20};
21
22const PLUGIN_NAME: &str = "rspack.CommonJsChunkFormatPlugin";
23
24#[plugin]
25#[derive(Debug, Default)]
26pub struct CommonJsChunkFormatPlugin;
27
28#[plugin_hook(CompilerCompilation for CommonJsChunkFormatPlugin)]
29async fn compilation(
30  &self,
31  compilation: &mut Compilation,
32  _params: &mut CompilationParams,
33) -> Result<()> {
34  let hooks = JsPlugin::get_compilation_hooks_mut(compilation.id());
35  let mut hooks = hooks.write().await;
36  hooks.chunk_hash.tap(js_chunk_hash::new(self));
37  hooks.render_chunk.tap(render_chunk::new(self));
38  Ok(())
39}
40
41#[plugin_hook(CompilationAdditionalChunkRuntimeRequirements for CommonJsChunkFormatPlugin)]
42async fn additional_chunk_runtime_requirements(
43  &self,
44  compilation: &mut Compilation,
45  chunk_ukey: &ChunkUkey,
46  runtime_requirements: &mut RuntimeGlobals,
47) -> Result<()> {
48  let chunk = compilation.chunk_by_ukey.expect_get(chunk_ukey);
49
50  if chunk.has_runtime(&compilation.chunk_group_by_ukey) {
51    return Ok(());
52  }
53
54  if compilation
55    .chunk_graph
56    .get_number_of_entry_modules(chunk_ukey)
57    > 0
58  {
59    runtime_requirements.insert(RuntimeGlobals::REQUIRE);
60    runtime_requirements.insert(RuntimeGlobals::STARTUP_ENTRYPOINT);
61    runtime_requirements.insert(RuntimeGlobals::EXTERNAL_INSTALL_CHUNK);
62  }
63
64  Ok(())
65}
66
67#[plugin_hook(JavascriptModulesChunkHash for CommonJsChunkFormatPlugin)]
68async fn js_chunk_hash(
69  &self,
70  compilation: &Compilation,
71  chunk_ukey: &ChunkUkey,
72  hasher: &mut RspackHash,
73) -> Result<()> {
74  let chunk = compilation.chunk_by_ukey.expect_get(chunk_ukey);
75  if chunk.has_runtime(&compilation.chunk_group_by_ukey) {
76    return Ok(());
77  }
78
79  PLUGIN_NAME.hash(hasher);
80
81  update_hash_for_entry_startup(
82    hasher,
83    compilation,
84    compilation
85      .chunk_graph
86      .get_chunk_entry_modules_with_chunk_group_iterable(chunk_ukey),
87    chunk_ukey,
88  );
89
90  Ok(())
91}
92
93#[plugin_hook(CompilationDependentFullHash for CommonJsChunkFormatPlugin)]
94async fn compilation_dependent_full_hash(
95  &self,
96  compilation: &Compilation,
97  chunk_ukey: &ChunkUkey,
98) -> Result<Option<bool>> {
99  let chunk = compilation.chunk_by_ukey.expect_get(chunk_ukey);
100  if chunk.has_entry_module(&compilation.chunk_graph)
101    && runtime_chunk_has_hash(compilation, chunk_ukey).await?
102  {
103    return Ok(Some(true));
104  }
105  Ok(None)
106}
107
108#[plugin_hook(JavascriptModulesRenderChunk for CommonJsChunkFormatPlugin)]
109async fn render_chunk(
110  &self,
111  compilation: &Compilation,
112  chunk_ukey: &ChunkUkey,
113  render_source: &mut RenderSource,
114) -> Result<()> {
115  let hooks = JsPlugin::get_compilation_hooks(compilation.id());
116  let chunk = compilation.chunk_by_ukey.expect_get(chunk_ukey);
117  let base_chunk_output_name = get_chunk_output_name(chunk, compilation).await?;
118  let mut sources = ConcatSource::default();
119  sources.add(RawStringSource::from(format!(
120    "exports.ids = [{}];\n",
121    json_stringify(chunk.expect_id(&compilation.chunk_ids_artifact))
122  )));
123  sources.add(RawStringSource::from_static("exports.modules = "));
124  sources.add(render_source.source.clone());
125  sources.add(RawStringSource::from_static(";\n"));
126  if compilation
127    .chunk_graph
128    .has_chunk_runtime_modules(chunk_ukey)
129  {
130    sources.add(RawStringSource::from_static("exports.runtime = "));
131    sources.add(render_chunk_runtime_modules(compilation, chunk_ukey).await?);
132    sources.add(RawStringSource::from_static(";\n"));
133  }
134
135  if chunk.has_entry_module(&compilation.chunk_graph) {
136    let runtime_chunk_output_name = get_runtime_chunk_output_name(compilation, chunk_ukey).await?;
137    sources.add(RawStringSource::from(format!(
138      "// load runtime\nvar {} = require({});\n",
139      RuntimeGlobals::REQUIRE,
140      json_stringify(&get_relative_path(
141        base_chunk_output_name
142          .trim_start_matches("/")
143          .trim_start_matches("\\"),
144        &runtime_chunk_output_name
145      ))
146    )));
147    sources.add(RawStringSource::from(format!(
148      "{}(exports)\n",
149      RuntimeGlobals::EXTERNAL_INSTALL_CHUNK,
150    )));
151
152    let entries = compilation
153      .chunk_graph
154      .get_chunk_entry_modules_with_chunk_group_iterable(chunk_ukey);
155    let start_up_source = generate_entry_startup(compilation, chunk_ukey, entries, false);
156    let last_entry_module = entries
157      .keys()
158      .next_back()
159      .expect("should have last entry module");
160    let mut startup_render_source = RenderSource {
161      source: start_up_source,
162    };
163    hooks
164      .try_read()
165      .expect("should have js plugin drive")
166      .render_startup
167      .call(
168        compilation,
169        chunk_ukey,
170        last_entry_module,
171        &mut startup_render_source,
172      )
173      .await?;
174    sources.add(startup_render_source.source);
175    render_source.source = ConcatSource::new([
176      RawStringSource::from_static("(function() {\n").boxed(),
177      sources.boxed(),
178      RawStringSource::from_static("\n})()").boxed(),
179    ])
180    .boxed();
181    return Ok(());
182  }
183  render_source.source = sources.boxed();
184  Ok(())
185}
186
187impl Plugin for CommonJsChunkFormatPlugin {
188  fn name(&self) -> &'static str {
189    PLUGIN_NAME
190  }
191
192  fn apply(&self, ctx: &mut rspack_core::ApplyContext<'_>) -> Result<()> {
193    ctx.compiler_hooks.compilation.tap(compilation::new(self));
194    ctx
195      .compilation_hooks
196      .additional_chunk_runtime_requirements
197      .tap(additional_chunk_runtime_requirements::new(self));
198    ctx
199      .compilation_hooks
200      .dependent_full_hash
201      .tap(compilation_dependent_full_hash::new(self));
202    Ok(())
203  }
204}