⚠️ Note: This is an advanced and experimental API recommended only for plugin developers who are familiar with systems programming and the C ABI. Use with caution.
Bun Native Plugins
This crate provides a Rustified wrapper over the Bun's native bundler plugin C API.
Some advantages to native bundler plugins as opposed to regular ones implemented in JS are:
- Native plugins take full advantage of Bun's parallelized bundler pipeline and run on multiple threads at the same time
- Unlike JS, native plugins don't need to do the UTF-8 <-> UTF-16 source code string conversions
What are native bundler plugins exactly? Precisely, they are NAPI modules which expose a C ABI function which implement a plugin lifecycle hook.
The currently supported lifecycle hooks are:
onBeforeParse(called immediately before a file is parsed, allows you to modify the source code of the file)
Getting started
Since native bundler plugins are NAPI modules, the easiest way to get started is to create a new napi-rs project:
Then install this crate:
Now, inside the lib.rs file, we'll use the bun_native_plugin::bun proc macro to define a function which
will implement our native plugin.
Here's an example implementing the onBeforeParse hook:
use ;
use napi;
/// Define the plugin and its name
define_bun_plugin!;
/// Here we'll implement `onBeforeParse` with code that replaces all occurrences of
/// `foo` with `bar`.
///
/// We use the #[bun] macro to generate some of the boilerplate code.
///
/// The argument of the function (`handle: &mut OnBeforeParse`) tells
/// the macro that this function implements the `onBeforeParse` hook.
Internally, the #[bun] macro wraps your code and declares a C ABI function which implements
the function signature of onBeforeParse plugins in Bun's C API for bundler plugins.
Then it calls your code. The wrapper looks roughly like this:
pub extern "C"
Now, let's compile this NAPI module. If you're using napi-rs, the package.json should have a build script you can run:
This will produce a .node file in the project directory.
With the compiled NAPI module, you can now register the plugin from JS:
const result = await ;
Very important information
Error handling and panics
In the case that the value of the Result your plugin function returns is an Err(...), the error will be logged to Bun's bundler.
It is highly advised that you return all errors and avoid .unwrap()'ing or .expecting()'ing results.
The #[bun] wrapper macro actually runs your code inside of a panic::catch_unwind,
which may catch some panics but not guaranteed to catch all panics.
Therefore, it is recommended to avoid panics at all costs.
Passing state to and from JS: External
One way to communicate data from your plugin and JS and vice versa is through the NAPI's External type.
An External in NAPI is like an opaque pointer to data that can be passed to and from JS. Inside your NAPI module, you can retrieve the pointer and modify the data.
As an example that extends our getting started example above, let's say you wanted to count the number of foo's that the native plugin encounters.
You would expose a NAPI module function which creates this state. Recall that state in native plugins must be threadsafe. This usually means
that your state must be Sync:
When you register your plugin from Javascript, you call the napi module function to create the external and then pass it:
const napiModule = require;
const pluginState = napiModule.;
const result = await ;
console.log;
Finally, from the native implementation of your plugin, you can extract the external:
Concurrency
Your plugin function can be called on any thread at any time and possibly multiple times at once.
Therefore, you must design any state management to be threadsafe.