rspack_plugin_javascript 0.101.0-rc.0

rspack javascript plugin
Documentation
use rspack_core::{
  ChunkCodeTemplate, ChunkKind, ChunkUkey, Compilation, RuntimeGlobals, RuntimeProxyMetadata,
  RuntimeVariable, property_access,
  rspack_sources::{BoxSource, ConcatSource, RawStringSource, SourceExt},
  runtime_mode::RuntimeMode,
};
use rspack_error::Result;

use crate::runtime::render_runtime_module_sources;

fn get_runtime_context_metadata<'a>(
  compilation: &'a Compilation,
  chunk_ukey: &ChunkUkey,
) -> Option<&'a RuntimeProxyMetadata> {
  if compilation.options.experiments.runtime_mode != RuntimeMode::Rspack {
    return None;
  }

  if let Some(metadata) = compilation.runtime_proxy_metadata_artifact.get(chunk_ukey) {
    return Some(metadata);
  }

  let chunk = compilation
    .build_chunk_graph_artifact
    .chunk_by_ukey
    .expect_get(chunk_ukey);
  compilation
    .runtime_proxy_metadata_artifact
    .iter()
    .find_map(|(runtime_chunk_ukey, metadata)| {
      let runtime_chunk = compilation
        .build_chunk_graph_artifact
        .chunk_by_ukey
        .expect_get(runtime_chunk_ukey);
      runtime_chunk
        .runtime()
        .iter()
        .any(|runtime| chunk.runtime().contains(runtime))
        .then_some(metadata)
    })
}

fn runtime_context_current_chunk_metadata(
  compilation: &Compilation,
  chunk_ukey: &ChunkUkey,
) -> Option<RuntimeProxyMetadata> {
  if compilation.options.experiments.runtime_mode != RuntimeMode::Rspack {
    return None;
  }

  let mut metadata = compilation
    .runtime_proxy_metadata_artifact
    .get(chunk_ukey)
    .cloned()
    .unwrap_or_default();
  for runtime_module_id in compilation
    .build_chunk_graph_artifact
    .chunk_graph
    .get_chunk_runtime_modules_iterable(chunk_ukey)
  {
    let runtime_module = compilation
      .runtime_modules
      .get(runtime_module_id)
      .expect("should have runtime module");
    let module_runtime_requirements = runtime_module.additional_runtime_requirements(compilation)
      | runtime_module.additional_write_runtime_requirements(compilation);
    metadata
      .tree_runtime_requirements
      .insert(module_runtime_requirements);
    metadata
      .runtime_module_requirements
      .insert(module_runtime_requirements);
  }

  (!metadata.tree_runtime_requirements.is_empty()).then_some(metadata)
}

pub fn render_runtime_context_declaration(runtime_template: &ChunkCodeTemplate) -> String {
  let runtime_context = runtime_template.render_runtime_variable(&RuntimeVariable::Context);
  format!("var {runtime_context}={{}};\n")
}

pub fn render_runtime_context_require_assignment(runtime_template: &ChunkCodeTemplate) -> String {
  format!(
    "{} = {};\n",
    runtime_template.render_runtime_globals(&RuntimeGlobals::REQUIRE),
    runtime_template.render_runtime_variable(&RuntimeVariable::Require)
  )
}

pub async fn render_rspack_runtime_modules(
  compilation: &Compilation,
  chunk_ukey: &ChunkUkey,
  runtime_template: &ChunkCodeTemplate,
) -> Result<BoxSource> {
  let runtime_module_sources =
    render_runtime_module_sources(compilation, chunk_ukey, runtime_template, true).await?;
  let mut sources = ConcatSource::default();
  let mut metadata = runtime_context_current_chunk_metadata(compilation, chunk_ukey);
  let script_nonce = RuntimeGlobals::SCRIPT_NONCE
    .to_lexical_name()
    .expect("script nonce should have lexical name");
  if runtime_module_sources
    .iter()
    .any(|(source, _)| source.source().into_string_lossy().contains(script_nonce))
  {
    metadata
      .get_or_insert_default()
      .tree_runtime_requirements
      .insert(RuntimeGlobals::SCRIPT_NONCE);
  }
  let base_metadata = get_runtime_context_metadata(compilation, chunk_ukey);
  let should_render_context = base_metadata.is_some_and(|metadata| {
    metadata
      .tree_runtime_requirements
      .contains(RuntimeGlobals::REQUIRE_SCOPE)
  });
  let runtime_context = runtime_template.render_runtime_variable(&RuntimeVariable::Context);
  let chunk = compilation
    .build_chunk_graph_artifact
    .chunk_by_ukey
    .expect_get(chunk_ukey);
  let owns_runtime = chunk.has_runtime(&compilation.build_chunk_graph_artifact.chunk_group_by_ukey);
  let is_hot_update = matches!(chunk.kind(), ChunkKind::HotUpdate);
  let runtime_requirements = compilation
    .cgc_runtime_requirements_artifact
    .get(chunk_ukey)
    .copied()
    .unwrap_or_default();
  let has_bootstrap_runtime_context = runtime_template.uses_runtime_context()
    && owns_runtime
    && runtime_requirements.needs_bootstrap_runtime_context();
  let has_runtime_context = is_hot_update
    || has_bootstrap_runtime_context
    || (!owns_runtime && runtime_template.uses_runtime_context());
  let mut current_chunk_generated_requirements = RuntimeGlobals::default();
  let mut hmr_state_keys = Vec::new();
  for runtime_module_id in compilation
    .build_chunk_graph_artifact
    .chunk_graph
    .get_chunk_runtime_modules_iterable(chunk_ukey)
  {
    let runtime_module = compilation
      .runtime_modules
      .get(runtime_module_id)
      .expect("should have runtime module");
    current_chunk_generated_requirements
      .insert(runtime_module.additional_write_runtime_requirements(compilation));
    let Some(key) = (match runtime_module.get_constructor_name().as_str() {
      "JsonpChunkLoadingRuntimeModule" => Some("jsonp"),
      "ModuleChunkLoadingRuntimeModule" => Some("module"),
      "ImportScriptsChunkLoadingRuntimeModule" => Some("importScripts"),
      "ReadFileChunkLoadingRuntimeModule" => Some("readFileVm"),
      "RequireChunkLoadingRuntimeModule" => Some("require"),
      _ => None,
    }) else {
      continue;
    };
    hmr_state_keys.push(key);
  }
  if is_hot_update && let Some(metadata) = &mut metadata {
    metadata.tree_runtime_requirements.insert(
      RuntimeGlobals::PUBLIC_PATH
        | RuntimeGlobals::GET_CHUNK_SCRIPT_FILENAME
        | RuntimeGlobals::GET_CHUNK_UPDATE_SCRIPT_FILENAME
        | RuntimeGlobals::GET_UPDATE_MANIFEST_FILENAME
        | RuntimeGlobals::GET_FULL_HASH
        | RuntimeGlobals::LOAD_SCRIPT
        | RuntimeGlobals::HAS_OWN_PROPERTY
        | RuntimeGlobals::MODULE_CACHE
        | RuntimeGlobals::CREATE_SCRIPT_URL,
    );
  }
  let isolate_runtime_context = runtime_template.uses_runtime_context()
    && runtime_template.uses_lexical_runtime_globals()
    && !compilation.options.output.module
    && should_render_context
    && base_metadata.is_some_and(|metadata| {
      !metadata.lexical_fields().is_empty() || !metadata.context_fields().is_empty()
    });

  if should_render_context {
    if isolate_runtime_context {
      sources.add(RawStringSource::from(format!(
        "(function({runtime_context}) {{\n"
      )));
    } else if !has_runtime_context {
      sources.add(RawStringSource::from(render_runtime_context_declaration(
        runtime_template,
      )));
    }

    if let Some(metadata) = &metadata {
      let render_context_field = |runtime_global: RuntimeGlobals| {
        runtime_global
          .rspack_context_property_name()
          .map(|property_name| {
            let value = format!("{runtime_context}{}", property_access([property_name], 0));
            if runtime_global.should_initialize_as_object() {
              format!("{value}||{{}}")
            } else if runtime_global.should_initialize_as_array() {
              format!("{value}||[]")
            } else {
              value
            }
          })
      };
      let render_runtime_global = |runtime_global: RuntimeGlobals| {
        if is_hot_update
          && runtime_global.intersects(
            RuntimeGlobals::HMR_DOWNLOAD_MANIFEST
              | RuntimeGlobals::HMR_DOWNLOAD_UPDATE_HANDLERS
              | RuntimeGlobals::HMR_INVALIDATE_MODULE_HANDLERS
              | RuntimeGlobals::HMR_MODULE_DATA,
          )
        {
          return None;
        }
        if is_hot_update {
          return render_context_field(runtime_global);
        }
        let should_render_context_field = owns_runtime
          && runtime_global
            .intersects(RuntimeGlobals::STARTUP | RuntimeGlobals::STARTUP_ENTRYPOINT)
          && metadata
            .bootstrap_proxy_requirements
            .contains(runtime_global)
          || !owns_runtime
            && (runtime_global.needs_bootstrap_runtime_context()
              || metadata
                .runtime_module_requirements
                .contains(runtime_global)
                && !current_chunk_generated_requirements.contains(runtime_global));
        if runtime_global == RuntimeGlobals::REQUIRE {
          Some(runtime_template.render_runtime_variable(&RuntimeVariable::Require))
        } else if runtime_global == RuntimeGlobals::MODULE_FACTORIES
          || runtime_global == RuntimeGlobals::MODULE_FACTORIES_ADD_ONLY
        {
          Some(runtime_template.render_runtime_variable(&RuntimeVariable::Modules))
        } else if runtime_global == RuntimeGlobals::MODULE_CACHE {
          Some(runtime_template.render_runtime_variable(&RuntimeVariable::ModuleCache))
        } else if should_render_context_field {
          render_context_field(runtime_global)
        } else {
          None
        }
      };
      sources.add(RawStringSource::from(
        metadata.render_lexical_declarations(Some(&render_runtime_global)),
      ));
      if metadata.lexical_fields().intersects(
        RuntimeGlobals::HMR_DOWNLOAD_UPDATE_HANDLERS
          | RuntimeGlobals::HMR_INVALIDATE_MODULE_HANDLERS
          | RuntimeGlobals::HMR_MODULE_DATA
          | RuntimeGlobals::HMR_RUNTIME_STATE_PREFIX,
      ) {
        for key in &hmr_state_keys {
          sources.add(RawStringSource::from(format!("var hmrS_{key};\n")));
        }
      }
    }
  }

  if !has_runtime_context
    && runtime_template.uses_lexical_runtime_globals()
    && let Some(metadata) = &metadata
  {
    for (_, runtime_global) in metadata.lexical_fields().iter_names() {
      let Some(key) = runtime_global.rspack_context_property_name() else {
        continue;
      };
      let Some(lexical_name) = runtime_global.to_lexical_name() else {
        continue;
      };
      sources.add(RawStringSource::from(format!(
        "{lexical_name} = {runtime_context}{};\n",
        property_access([key], 0)
      )));
      if runtime_global.should_initialize_as_object() {
        let value = if is_hot_update {
          format!("{lexical_name} || {{}}")
        } else {
          "{}".to_string()
        };
        sources.add(RawStringSource::from(format!(
          "{lexical_name} = {value};\n"
        )));
      } else if runtime_global.should_initialize_as_array() {
        let value = if is_hot_update {
          format!("{lexical_name} || []")
        } else {
          "[]".to_string()
        };
        sources.add(RawStringSource::from(format!(
          "{lexical_name} = {value};\n"
        )));
      }
    }
  }
  if is_hot_update && runtime_template.uses_runtime_context() {
    let require = runtime_template.render_runtime_variable(&RuntimeVariable::Require);
    let modules = runtime_template.render_runtime_variable(&RuntimeVariable::Modules);
    let module_cache = runtime_template.render_runtime_variable(&RuntimeVariable::ModuleCache);
    sources.add(RawStringSource::from(format!(
      "var {require}={runtime_context}.r,{modules}={runtime_context}.m,{module_cache}={runtime_context}.c;\n"
    )));
  }
  for (runtime_module_source, generated_requirements) in runtime_module_sources {
    sources.add(runtime_module_source);
    if !runtime_template.uses_lexical_runtime_globals() {
      continue;
    }
    let Some(metadata) = &metadata else {
      continue;
    };
    let mut context_fields = metadata
      .context_fields()
      .intersection(generated_requirements);
    context_fields.insert(generated_requirements.intersection(metadata.tree_runtime_requirements));
    let hmr_live_binding_fields = RuntimeGlobals::GET_FULL_HASH
      | RuntimeGlobals::GET_CHUNK_SCRIPT_FILENAME
      | RuntimeGlobals::GET_CHUNK_UPDATE_SCRIPT_FILENAME
      | RuntimeGlobals::GET_UPDATE_MANIFEST_FILENAME
      | RuntimeGlobals::PUBLIC_PATH;
    if !is_hot_update
      && metadata
        .tree_runtime_requirements
        .contains(RuntimeGlobals::HMR_DOWNLOAD_MANIFEST)
    {
      context_fields.insert(generated_requirements.intersection(hmr_live_binding_fields));
    }
    if is_hot_update {
      context_fields.insert(generated_requirements);
      context_fields.remove(RuntimeGlobals::REQUIRE | RuntimeGlobals::REQUIRE_SCOPE);
    }
    if context_fields.is_empty() {
      continue;
    }
    for (_, runtime_global) in context_fields.iter_names() {
      let (Some(key), Some(lexical_name)) = (
        runtime_global.rspack_context_property_name(),
        runtime_global.to_lexical_name(),
      ) else {
        continue;
      };
      let needs_live_binding = !is_hot_update
        && (metadata.context_setter_fields().contains(runtime_global)
          || hmr_live_binding_fields.contains(runtime_global)
            && metadata
              .tree_runtime_requirements
              .contains(RuntimeGlobals::HMR_DOWNLOAD_MANIFEST));
      if needs_live_binding {
        sources.add(RawStringSource::from(format!(
          "Object.defineProperty({}, {}, {{ configurable: true, get: function() {{ return {}; }}, set: function(value) {{ {} = value; }} }});\n",
          runtime_context,
          rspack_util::json_stringify(key),
          lexical_name,
          lexical_name
        )));
      } else {
        sources.add(RawStringSource::from(format!(
          "{}{} = {};\n",
          runtime_context,
          property_access([key], 0),
          lexical_name
        )));
      }
    }
  }
  if isolate_runtime_context {
    sources.add(RawStringSource::from(format!(
      "\n}}).call(this, {runtime_context});\n"
    )));
  }

  Ok(sources.boxed())
}