Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
This PR implements a Bun plugin that makes TailwindCSS fast in Bun.
(bundling 2048 html + react + tailwind files)
Overview
This plugins is comprised of two parts:
Native bundler plugin
The native bundler plugin is used to scan the module graph in parallel with the
Scanner
struct from@tailwindcss/oxide
.The main logic for this code is in the
tw_on_before_parse
function.Native bundler plugins run in parallel on Bun's bundler threads and do not need to do UTF-16 <-> UTF-8 string conversions. This speeds up the plugin a lot.
Native bundler plugins are NAPI modules which export additional symbols (since NAPI modules themselves are dynamically loaded libraries which can be
dlopen()
'd). Thebun-native-plugin
crate handles the boilerplate for creating one.I placed the Bun plugin inside the existing
crates/node/lib.rs
(the@tailwindcss/oxide
package). This reduces the need to create more compiled artifacts at the cost of a relatively small binary size change:Please let me know if you would like me to split it out into its own separate package if you don't like the binary size change.
Sharing state between the native plugin and JS
The scanned candidates and other state are held inside a NAPI External. The struct in the code that does
this is called
TailwindContextExternal
.A NAPI External is a NAPI value which can be given to JS and which holds a
void*
data pointer. This data is inaccessible to JS, but a NAPI module can dereference the data and convert it to NAPI values.This looks a bit like this on the Rust side:
And the JS side:
napi-rs
version bumpThe
napi-rs
crate was updated to version2.16.15
so we can use theExternal::inner_from_raw()
function to turn anExternal
's*mut c_void
pointer back intoTailwindContextExternal
.JS plugin
The JS plugin
@tailwindcss-bun/src/index.ts
uses logic copied over from the vite plugin implementation but modified to work with Bun's plugin API.It invokes the native bundler plugin using the
.onBeforeParse
plugin API function:One thing to note is that Bun's bundler currently does not have an API that is analogous to
.addWatchedFile()
, so there is currently no way to add additional files to the module graph.Testing
I added a
integrations/bun/index.test.ts
file, please let me know if you would like more tests