rolldown / Bindings (rust/js)
Before reading this chapter, take a look at:
The topic of this chapter is how does the napi bindings declared in the rolldown_binding crate (and the code generated from it) are finally consumed.
createBundler
packages/rolldown/src/utils/create-bundler.ts
options
normalize
- There are multiple 
normalize-*.tsfiles exporting differentnomalize*()functions - Those functions compose into 
normalizeInputOptionsandnormalizeOutputOptions - They may accept some zod schemas - examples:
 
bindingify
Once normalized, those options are instanciated with objects following the interfaces packages/rolldown/src/binding.d.ts which was generated based on rolldown_binding.
- There are multiple 
bindingify-*.tsfiles exporting differentbindingify*()functions - Those functions accept a the normalized TS types and return objects that will be able to interact with napi
 
result
It returns a Bundler as describe in packages/rolldown/src/binding.d.ts:
export declare class Bundler {
  constructor(inputOptions: BindingInputOptions, outputOptions: BindingOutputOptions, parallelPluginsRegistry?: ParallelJsPluginRegistry | undefined | null)
  write(): Promise<BindingOutputs>
  generate(): Promise<BindingOutputs>
  scan(): Promise<void>
  close(): Promise<void>
  watch(): Promise<BindingWatcher>
}
This means the calls to the methods write, generate, scan, close, watch on the NodeJS side will be routed via napi to rolldown_binding::bundler. Where we can see the following implementations in rust:
#![allow(unused)] fn main() { impl Bundler { #[napi(constructor)] #[cfg_attr(target_family = "wasm", allow(unused))] pub fn new( env: Env, mut input_options: BindingInputOptions, output_options: BindingOutputOptions, parallel_plugins_registry: Option<ParallelJsPluginRegistry>, ) -> napi::Result<Self> { // ... } #[napi] #[tracing::instrument(level = "debug", skip_all)] pub async fn write(&self) -> napi::Result<BindingOutputs> { self.write_impl().await } #[napi] #[tracing::instrument(level = "debug", skip_all)] pub async fn write(&self) -> napi::Result<BindingOutputs> { self.write_impl().await } #[napi] #[tracing::instrument(level = "debug", skip_all)] pub async fn generate(&self) -> napi::Result<BindingOutputs> { self.generate_impl().await } #[napi] #[tracing::instrument(level = "debug", skip_all)] pub async fn scan(&self) -> napi::Result<()> { self.scan_impl().await } #[napi] #[tracing::instrument(level = "debug", skip_all)] pub async fn close(&self) -> napi::Result<()> { self.close_impl().await } #[napi] #[tracing::instrument(level = "debug", skip_all)] pub async fn watch(&self) -> napi::Result<BindingWatcher> { self.watch_impl().await } } }