rspack_loader_swc 0.102.2

rspack builtin swc loader
use indoc::formatdoc;
use rspack_core::{Module as RspackCoreModule, NormalModule, RscModuleType};
use rspack_error::Result;
use rspack_util::json_stringify_str;
use swc::atoms::Wtf8Atom;

fn to_cjs_server_entry(resource: &str, server_refs: &[Wtf8Atom]) -> Result<String> {
  let mut cjs_source =
    "const { createServerEntry } = require(\"react-server-dom-rspack/server\");\n".to_string();

  for server_ref in server_refs {
    match server_ref.as_str() {
      Some("default") => {
        cjs_source.push_str(&formatdoc! {
          r#"
            module.exports = createServerEntry(
              require({request}),
              {resource}
            );
          "#,
          request = json_stringify_str(&format!("{resource}?rsc-server-entry-proxy=true")),
          resource = json_stringify_str(resource)
        });
      }
      Some(ident) => {
        cjs_source.push_str(&formatdoc! {
          r#"
            exports.{ident} = createServerEntry(
              require({request}).{ident},
              {resource}
            );
          "#,
          ident = ident,
          request = json_stringify_str(&format!("{resource}?rsc-server-entry-proxy=true")),
          resource = json_stringify_str(resource)
        });
      }
      _ => {}
    }
  }

  Ok(cjs_source)
}

fn to_esm_server_entry(resource: &str, server_refs: &[Wtf8Atom]) -> Result<String> {
  let mut esm_source =
    "import { createServerEntry } from \"react-server-dom-rspack/server\";\n".to_string();

  for server_ref in server_refs {
    match server_ref.as_str() {
      Some("default") => {
        esm_source.push_str(&formatdoc! {
          r#"
            import _default from {request};
            export default createServerEntry(
              _default,
              {resource}
            );
          "#,
          request = json_stringify_str(&format!("{resource}?rsc-server-entry-proxy=true")),
          resource = json_stringify_str(resource)
        });
      }
      Some(ident) => {
        esm_source.push_str(&formatdoc! {
          r#"
            import {{ {ident} as _original_{ident} }} from {request};
            export const {ident} = createServerEntry(
              _original_{ident},
              {resource}
            );
          "#,
          ident = ident,
          request = json_stringify_str(&format!("{resource}?rsc-server-entry-proxy=true")),
          resource = json_stringify_str(resource)
        });
      }
      _ => {}
    }
  }

  Ok(esm_source)
}

pub fn to_server_entry(module: &NormalModule) -> Result<Option<String>> {
  if module
    .get_layer()
    .is_none_or(|layer| layer != "react-server-components")
  {
    return Ok(None);
  }

  let Some(rsc) = module.build_info().rsc.as_ref() else {
    return Ok(None);
  };

  match rsc.module_type {
    RscModuleType::ServerEntry => {
      if rsc
        .server_refs
        .iter()
        .any(|server_ref| server_ref.as_str() == Some("*"))
      {
        return Err(rspack_error::error!(
          r#"It's currently unsupported to use "export *" in a server entry. Please use named exports instead."#
        ));
      }

      Ok(Some(if rsc.is_cjs {
        to_cjs_server_entry(module.resource_resolved_data().resource(), &rsc.server_refs)?
      } else {
        to_esm_server_entry(module.resource_resolved_data().resource(), &rsc.server_refs)?
      }))
    }
    RscModuleType::Client => {
      if rsc
        .client_refs
        .iter()
        .any(|client_ref| client_ref.as_str() == Some("*"))
      {
        Err(rspack_error::error!(
          r#"It's currently unsupported to use "export *" in a client boundary. Please use named exports instead."#
        ))
      } else {
        Ok(None)
      }
    }
    _ => Ok(None),
  }
}