Skip to main content

rolldown_plugin_oxc_runtime/
lib.rs

1use std::borrow::Cow;
2
3use rolldown_common::ImportKind;
4use rolldown_plugin::{HookLoadReturn, HookUsage, Plugin};
5
6// Include the embedded helpers generated by build.rs
7mod generated;
8use generated::embedded_helpers::{
9  RUNTIME_HELPER_PREFIX, RUNTIME_HELPER_UNVERSIONED_PREFIX, get_helper_content, is_runtime_helper,
10  is_virtual_runtime_helper,
11};
12
13/// Plugin for handling @oxc-project/runtime helpers.
14///
15/// Embeds both the ESM and CJS variants of every helper directly so that bundling works in
16/// browser-like environments where module resolution is unavailable. Resolution dispatches by
17/// `ImportKind`: `import`/`dynamic-import` get the ESM variant (default-export-only), while
18/// `require()` gets the CJS variant (which sets `module.exports = fn` so the function is callable
19/// directly — matching the on-disk package).
20#[derive(Debug)]
21pub struct OxcRuntimePlugin;
22
23impl Plugin for OxcRuntimePlugin {
24  fn name(&self) -> Cow<'static, str> {
25    Cow::Borrowed("builtin:oxc-runtime")
26  }
27
28  fn register_hook_usage(&self) -> HookUsage {
29    HookUsage::ResolveId | HookUsage::Load
30  }
31
32  async fn resolve_id(
33    &self,
34    _ctx: &rolldown_plugin::PluginContext,
35    args: &rolldown_plugin::HookResolveIdArgs<'_>,
36  ) -> rolldown_plugin::HookResolveIdReturn {
37    if is_runtime_helper(args.specifier) {
38      // The portion after `@oxc-project/runtime/helpers/` — e.g. `defineProperty` or
39      // `esm/defineProperty` (the latter when the user opts into the ESM path explicitly).
40      let rest = &args.specifier[RUNTIME_HELPER_UNVERSIONED_PREFIX.len()..];
41      let path_in_helpers: Cow<'_, str> = if rest.ends_with(".js") {
42        Cow::Borrowed(rest)
43      } else {
44        Cow::Owned(rolldown_utils::concat_string!(rest, ".js"))
45      };
46
47      // If the caller wrote `helpers/esm/X` we honor that; otherwise pick CJS for `require()`
48      // and ESM for everything else. Picking CJS for `require()` is critical: oxc emits
49      // `var X = require("@oxc-project/runtime/helpers/X")` and uses `X` as a function, which
50      // only works when the resolved module sets `module.exports = X` (the CJS variant does).
51      let final_path: Cow<'_, str> =
52        if path_in_helpers.starts_with("esm/") || matches!(args.kind, ImportKind::Require) {
53          path_in_helpers
54        } else {
55          Cow::Owned(rolldown_utils::concat_string!("esm/", path_in_helpers))
56        };
57
58      let virtual_id = rolldown_utils::concat_string!("\0", RUNTIME_HELPER_PREFIX, final_path);
59      return Ok(Some(rolldown_plugin::HookResolveIdOutput {
60        id: virtual_id.into(),
61        ..Default::default()
62      }));
63    }
64
65    // Handle relative imports within the helpers, e.g., "./typeof.js" from a virtual helper.
66    // Preserve the importer's directory so an ESM helper's sibling stays ESM (and the CJS
67    // sibling stays CJS).
68    Ok(args.importer.and_then(|importer| {
69      let importer = importer.strip_prefix('\0')?;
70      if !is_virtual_runtime_helper(importer) || !args.specifier.starts_with("./") {
71        return None;
72      }
73      let dir_end = importer.rfind('/')?;
74      let dir = &importer[..dir_end];
75      let rel = args.specifier[2..].trim_start_matches("./");
76      let full_path = rolldown_utils::concat_string!("\0", dir, "/", rel);
77      Some(rolldown_plugin::HookResolveIdOutput { id: full_path.into(), ..Default::default() })
78    }))
79  }
80
81  fn resolve_id_meta(&self) -> Option<rolldown_plugin::PluginHookMeta> {
82    Some(rolldown_plugin::PluginHookMeta { order: Some(rolldown_plugin::PluginOrder::Pre) })
83  }
84
85  async fn load(
86    &self,
87    _ctx: rolldown_plugin::SharedLoadPluginContext,
88    args: &rolldown_plugin::HookLoadArgs<'_>,
89  ) -> HookLoadReturn {
90    Ok(args.id.strip_prefix('\0').and_then(|id| {
91      get_helper_content(id)
92        .map(|code| rolldown_plugin::HookLoadOutput { code, ..Default::default() })
93    }))
94  }
95
96  fn load_meta(&self) -> Option<rolldown_plugin::PluginHookMeta> {
97    Some(rolldown_plugin::PluginHookMeta { order: Some(rolldown_plugin::PluginOrder::Pre) })
98  }
99}