rolldown_plugin_oxc_runtime 1.0.3

Rolldown plugin for OXC runtime helpers
Documentation
use std::borrow::Cow;

use rolldown_common::ImportKind;
use rolldown_plugin::{HookLoadReturn, HookUsage, Plugin};

// Include the embedded helpers generated by build.rs
mod generated;
use generated::embedded_helpers::{
  RUNTIME_HELPER_PREFIX, RUNTIME_HELPER_UNVERSIONED_PREFIX, get_helper_content, is_runtime_helper,
  is_virtual_runtime_helper,
};

/// Plugin for handling @oxc-project/runtime helpers.
///
/// Embeds both the ESM and CJS variants of every helper directly so that bundling works in
/// browser-like environments where module resolution is unavailable. Resolution dispatches by
/// `ImportKind`: `import`/`dynamic-import` get the ESM variant (default-export-only), while
/// `require()` gets the CJS variant (which sets `module.exports = fn` so the function is callable
/// directly — matching the on-disk package).
#[derive(Debug)]
pub struct OxcRuntimePlugin;

impl Plugin for OxcRuntimePlugin {
  fn name(&self) -> Cow<'static, str> {
    Cow::Borrowed("builtin:oxc-runtime")
  }

  fn register_hook_usage(&self) -> HookUsage {
    HookUsage::ResolveId | HookUsage::Load
  }

  async fn resolve_id(
    &self,
    _ctx: &rolldown_plugin::PluginContext,
    args: &rolldown_plugin::HookResolveIdArgs<'_>,
  ) -> rolldown_plugin::HookResolveIdReturn {
    if is_runtime_helper(args.specifier) {
      // The portion after `@oxc-project/runtime/helpers/` — e.g. `defineProperty` or
      // `esm/defineProperty` (the latter when the user opts into the ESM path explicitly).
      let rest = &args.specifier[RUNTIME_HELPER_UNVERSIONED_PREFIX.len()..];
      let path_in_helpers: Cow<'_, str> = if rest.ends_with(".js") {
        Cow::Borrowed(rest)
      } else {
        Cow::Owned(rolldown_utils::concat_string!(rest, ".js"))
      };

      // If the caller wrote `helpers/esm/X` we honor that; otherwise pick CJS for `require()`
      // and ESM for everything else. Picking CJS for `require()` is critical: oxc emits
      // `var X = require("@oxc-project/runtime/helpers/X")` and uses `X` as a function, which
      // only works when the resolved module sets `module.exports = X` (the CJS variant does).
      let final_path: Cow<'_, str> =
        if path_in_helpers.starts_with("esm/") || matches!(args.kind, ImportKind::Require) {
          path_in_helpers
        } else {
          Cow::Owned(rolldown_utils::concat_string!("esm/", path_in_helpers))
        };

      let virtual_id = rolldown_utils::concat_string!("\0", RUNTIME_HELPER_PREFIX, final_path);
      return Ok(Some(rolldown_plugin::HookResolveIdOutput {
        id: virtual_id.into(),
        ..Default::default()
      }));
    }

    // Handle relative imports within the helpers, e.g., "./typeof.js" from a virtual helper.
    // Preserve the importer's directory so an ESM helper's sibling stays ESM (and the CJS
    // sibling stays CJS).
    Ok(args.importer.and_then(|importer| {
      let importer = importer.strip_prefix('\0')?;
      if !is_virtual_runtime_helper(importer) || !args.specifier.starts_with("./") {
        return None;
      }
      let dir_end = importer.rfind('/')?;
      let dir = &importer[..dir_end];
      let rel = args.specifier[2..].trim_start_matches("./");
      let full_path = rolldown_utils::concat_string!("\0", dir, "/", rel);
      Some(rolldown_plugin::HookResolveIdOutput { id: full_path.into(), ..Default::default() })
    }))
  }

  fn resolve_id_meta(&self) -> Option<rolldown_plugin::PluginHookMeta> {
    Some(rolldown_plugin::PluginHookMeta { order: Some(rolldown_plugin::PluginOrder::Pre) })
  }

  async fn load(
    &self,
    _ctx: rolldown_plugin::SharedLoadPluginContext,
    args: &rolldown_plugin::HookLoadArgs<'_>,
  ) -> HookLoadReturn {
    Ok(args.id.strip_prefix('\0').and_then(|id| {
      get_helper_content(id)
        .map(|code| rolldown_plugin::HookLoadOutput { code, ..Default::default() })
    }))
  }

  fn load_meta(&self) -> Option<rolldown_plugin::PluginHookMeta> {
    Some(rolldown_plugin::PluginHookMeta { order: Some(rolldown_plugin::PluginOrder::Pre) })
  }
}