rspack_plugin_runtime 0.100.0

rspack runtime plugin
Documentation
use std::hash::Hash;

use rspack_core::{
  ChunkGraph, ChunkKind, ChunkUkey, Compilation, CompilationAdditionalChunkRuntimeRequirements,
  CompilationParams, CompilerCompilation, Plugin, RuntimeCodeTemplate, RuntimeGlobals,
  RuntimeModule, RuntimeVariable,
  rspack_sources::{ConcatSource, RawStringSource, SourceExt},
};
use rspack_error::Result;
use rspack_hash::RspackHash;
use rspack_hook::{plugin, plugin_hook};
use rspack_plugin_javascript::{
  JavascriptModulesChunkHash, JavascriptModulesRenderChunk, JsPlugin, RenderSource,
  runtime::{render_chunk_runtime_modules, render_runtime_modules},
};

use super::{generate_entry_startup, update_hash_for_entry_startup};

const PLUGIN_NAME: &str = "rspack.ArrayPushCallbackChunkFormatPlugin";

#[plugin]
#[derive(Debug, Default)]
pub struct ArrayPushCallbackChunkFormatPlugin;

#[plugin_hook(CompilerCompilation for ArrayPushCallbackChunkFormatPlugin)]
async fn compilation(
  &self,
  compilation: &mut Compilation,
  _params: &mut CompilationParams,
) -> Result<()> {
  let hooks = JsPlugin::get_compilation_hooks_mut(compilation.id());
  let mut hooks = hooks.write().await;
  hooks.chunk_hash.tap(js_chunk_hash::new(self));
  hooks.render_chunk.tap(render_chunk::new(self));
  Ok(())
}

#[plugin_hook(CompilationAdditionalChunkRuntimeRequirements for ArrayPushCallbackChunkFormatPlugin)]
async fn additional_chunk_runtime_requirements(
  &self,
  compilation: &Compilation,
  chunk_ukey: &ChunkUkey,
  runtime_requirements: &mut RuntimeGlobals,
  _runtime_modules: &mut Vec<Box<dyn RuntimeModule>>,
) -> Result<()> {
  let chunk = compilation
    .build_chunk_graph_artifact
    .chunk_by_ukey
    .expect_get(chunk_ukey);

  if chunk.has_runtime(&compilation.build_chunk_graph_artifact.chunk_group_by_ukey) {
    return Ok(());
  }

  if compilation
    .build_chunk_graph_artifact
    .chunk_graph
    .get_number_of_entry_modules(chunk_ukey)
    > 0
  {
    runtime_requirements.insert(RuntimeGlobals::ON_CHUNKS_LOADED);
    runtime_requirements.insert(RuntimeGlobals::EXPORTS);
    runtime_requirements.insert(RuntimeGlobals::REQUIRE);
  }
  runtime_requirements.insert(RuntimeGlobals::CHUNK_CALLBACK);

  Ok(())
}

#[plugin_hook(JavascriptModulesChunkHash for ArrayPushCallbackChunkFormatPlugin)]
async fn js_chunk_hash(
  &self,
  compilation: &Compilation,
  chunk_ukey: &ChunkUkey,
  hasher: &mut RspackHash,
) -> Result<()> {
  let chunk = compilation
    .build_chunk_graph_artifact
    .chunk_by_ukey
    .expect_get(chunk_ukey);
  if chunk.has_runtime(&compilation.build_chunk_graph_artifact.chunk_group_by_ukey) {
    return Ok(());
  }

  PLUGIN_NAME.hash(hasher);
  let output = &compilation.options.output;
  output.global_object.hash(hasher);
  output.chunk_loading_global.hash(hasher);
  output.hot_update_global.hash(hasher);

  update_hash_for_entry_startup(
    hasher,
    compilation,
    compilation
      .build_chunk_graph_artifact
      .chunk_graph
      .get_chunk_entry_modules_with_chunk_group_iterable(chunk_ukey),
    chunk_ukey,
  );

  Ok(())
}

#[plugin_hook(JavascriptModulesRenderChunk for ArrayPushCallbackChunkFormatPlugin)]
async fn render_chunk(
  &self,
  compilation: &Compilation,
  chunk_ukey: &ChunkUkey,
  render_source: &mut RenderSource,
  runtime_template: &RuntimeCodeTemplate<'_>,
) -> Result<()> {
  let hooks = JsPlugin::get_compilation_hooks(compilation.id());
  let chunk = compilation
    .build_chunk_graph_artifact
    .chunk_by_ukey
    .expect_get(chunk_ukey);
  let has_runtime_modules = compilation
    .build_chunk_graph_artifact
    .chunk_graph
    .has_chunk_runtime_modules(chunk_ukey);
  let global_object = &compilation.options.output.global_object;
  let hot_update_global = &compilation.options.output.hot_update_global;
  let mut source = ConcatSource::default();

  if matches!(chunk.kind(), ChunkKind::HotUpdate) {
    source.add(RawStringSource::from(format!(
      "{}[{}]({}, ",
      global_object,
      rspack_util::json_stringify_str(hot_update_global),
      rspack_util::json_stringify(chunk.expect_id())
    )));
    source.add(render_source.source.clone());
    if has_runtime_modules {
      source.add(RawStringSource::from_static(","));
      source.add(render_chunk_runtime_modules(compilation, chunk_ukey, runtime_template).await?);
    }
    source.add(RawStringSource::from_static(")"));
  } else {
    let chunk_loading_global = &compilation.options.output.chunk_loading_global;

    source.add(RawStringSource::from(format!(
      r#"({}["{}"] = {}["{}"] || []).push([[{}], "#,
      global_object,
      chunk_loading_global,
      global_object,
      chunk_loading_global,
      rspack_util::json_stringify(chunk.expect_id()),
    )));
    source.add(render_source.source.clone());
    let has_entry = chunk.has_entry_module(&compilation.build_chunk_graph_artifact.chunk_graph);
    if has_entry || has_runtime_modules {
      source.add(RawStringSource::from_static(","));
      source.add(RawStringSource::from(format!(
        "function({}) {{\n",
        runtime_template.render_runtime_globals(&RuntimeGlobals::REQUIRE)
      )));
      if has_runtime_modules {
        source.add(render_runtime_modules(compilation, chunk_ukey, runtime_template).await?);
      }
      if has_entry {
        let entries = compilation
          .build_chunk_graph_artifact
          .chunk_graph
          .get_chunk_entry_modules_with_chunk_group_iterable(chunk_ukey);
        let runtime_requirements =
          ChunkGraph::get_tree_runtime_requirements(compilation, chunk_ukey);
        let passive = !runtime_requirements.contains(RuntimeGlobals::STARTUP_ENTRYPOINT);
        let start_up_source =
          generate_entry_startup(compilation, chunk_ukey, entries, passive, runtime_template);
        let last_entry_module = entries
          .keys()
          .next_back()
          .expect("should have last entry module");
        let mut render_source = RenderSource {
          source: start_up_source,
        };
        hooks
          .try_read()
          .expect("should have js plugin drive")
          .render_startup
          .call(
            compilation,
            chunk_ukey,
            last_entry_module,
            &mut render_source,
            runtime_template,
          )
          .await?;
        source.add(render_source.source);
        if runtime_requirements.contains(RuntimeGlobals::RETURN_EXPORTS_FROM_RUNTIME) {
          source.add(RawStringSource::from(format!(
            "return {};\n",
            runtime_template.render_runtime_variable(&RuntimeVariable::Exports)
          )));
        }
      }
      source.add(RawStringSource::from_static("\n}\n"));
    }
    source.add(RawStringSource::from_static("])"));
  }
  render_source.source = source.boxed();
  Ok(())
}

impl Plugin for ArrayPushCallbackChunkFormatPlugin {
  fn name(&self) -> &'static str {
    PLUGIN_NAME
  }

  fn apply(&self, ctx: &mut rspack_core::ApplyContext<'_>) -> Result<()> {
    ctx.compiler_hooks.compilation.tap(compilation::new(self));
    ctx
      .compilation_hooks
      .additional_chunk_runtime_requirements
      .tap(additional_chunk_runtime_requirements::new(self));
    Ok(())
  }
}