Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Bun plugin #16295

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
zackradisic wants to merge 2 commits into tailwindlabs:main
base: main
Choose a base branch
Loading
from zackradisic:bun-plugin
Open

Bun plugin #16295

zackradisic wants to merge 2 commits into tailwindlabs:main from zackradisic:bun-plugin

Conversation

Copy link

@zackradisic zackradisic commented Feb 6, 2025
edited
Loading

(Sorry for creating this PR without opening an issue first)

This PR implements a Bun plugin that makes TailwindCSS fast in Bun.

c612d2faa37f2cff03c8620a08bb2def7268851bb183c2fa8e2763246b9243e5

(bundling 2048 html + react + tailwind files)

Overview

This plugins is comprised of two parts:

  • A regular Bun bundler plugin which is the main driver of everything and invokes the native bundler plugin
  • A native bundler plugin which parallelizes the module graph scanning of candidates. Tailwind was part of the motivation for implementing this API.

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). The bun-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:

# original size
❯ ls -lhS dist/tailwindcss-oxide-darwin-arm64.tgz
-rw-r--r--@ 1 zackradisic staff 2.1M Feb 4 20:42 dist/tailwindcss-oxide-darwin-arm64.tgz
# new size
❯ ls -lhS dist/tailwindcss-oxide-darwin-arm64.tgz
-rw-r--r--@ 1 zackradisic staff 2.2M Feb 4 18:42 dist/tailwindcss-oxide-darwin-arm64.tgz

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:

/// Create the TailwindContextExternal and return it to JS wrapped in a Napi External.
///
/// Napi has an `External<T>` type which allows us to wrap it in an
/// external easily.
#[no_mangle]
#[napi]
pub fn twctx_create() -> External<TailwindContextExternal> {
 let external = External::new(TailwindContextExternal {
 module_graph_candidates: Default::default(),
 dirty: AtomicBool::new(false),
 });
 external
}

And the JS side:

// import napi functions which let us manipulate the external
import { twctxCreate, twctxIsDirty, twctxToJs } from '@tailwindcss/oxide'
// create the state, the returned value
// is a Napi External
const external = twctxCreate()
/* ... other code ... */
let moduleGraphCandidates = new Map<string, Set<string>>()
function getSharedCandidates() {
 // check if there are changes
 if (twctxIsDirty(external)) {
 // convert the state into js values
 let rawCandidates: Array<{ id: string; candidates: string[] }> = twctxToJs(external)
 for (let { id, candidates } of rawCandidates) {
 moduleGraphCandidates.set(id, new Set(candidates))
 }
 }
 return moduleGraphCandidates
}

napi-rs version bump

The napi-rs crate was updated to version 2.16.15 so we can use the External::inner_from_raw() function to turn an External's *mut c_void pointer back into TailwindContextExternal.

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:

// Called on every file which matches the filter
// before it is parsed by Bun
build.onBeforeParse(
 // filter which files the native plugin apply to
 { filter: NON_CSS_ROOT_FILE_RE },
 // pass the napi module, the symbol which points to the plugin main function,
 // and the external which holds the tailwind state
 { napiModule: addon, symbol: 'tw_on_before_parse', external },
)

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

penspinner, productdevbook, kravetsone, saadeghi, brielov, jlucaso1, oronautus, shinzui, DoKoB0512, deadcoder0904, and 21 more reacted with thumbs up emoji samyok, alexanbj, penspinner, productdevbook, assimelha, RiskyMH, kravetsone, nikhilsnayak, saadeghi, brielov, and 14 more reacted with hooray emoji saadeghi, brielov, jakeboone02, cirospaciari, lin72h, Nanome203, sadeghbarati, sudhakar, viniciusnevescosta, AFCMS, and 4 more reacted with heart emoji MartinCura, ahhcash, FutureExcited, lin72h, sudhakar, viniciusnevescosta, AFCMS, pvds, auwtch, harryqt, and flip111 reacted with rocket emoji
Modify integration tests
Make `ChangedContent` possibly borrow its contents to avoid unnecessary allocations
Revert "Make `ChangedContent` possibly borrow its contents to avoid unnecessary allocations"
This reverts commit 10e2b0c.
Did not result in significant performance gain.
Modify workflow to test
delete that for now
make it work
udpate
update
messed up lock file
nice
lol
lmao
sanity test
blah
asdf
asdflkjasfd
man wut
wtf
???
cmon
LOOOOOOOOOOOOOOOOOL
clean tht bad boi up
stuff
bring it back
bring it back
change tests
bye bye
@zackradisic zackradisic requested a review from a team as a code owner February 6, 2025 04:16
Copy link
Member

Hey! Really excited for an official Bun plugin! We’re happy to collaborate and work on this together but to set the expectations right it will realistically take a few weeks before someone from our team can focus on this.

One reason for that is that we’re currently working on some changes that will break the existing Oxide <> Node APIs that are necessary fix some critical bugs. These changes will then impact the API design for the updated Oxide bindings necessary for the Bun plugin as well.

I’m sorry that we can’t hop onto this right away but we have to prioritize some other important fixes for the v4 release right now, I hope you understand. We’re pretty stoked about this feature though and hope to get to it soon!

190n, RiskyMH, lin72h, Nanome203, kravetsone, cirospaciari, vjpr, oronautus, tony13tv, SpiZeak, and 10 more reacted with heart emoji

Copy link

Any update on this?

lin72h reacted with eyes emoji

Copy link

would love to see this make it through! using tailwind with bun is a much better experience than vite, yet the former is not even listed in the installation docs, and the latter is the default suggestion 😭

lin72h reacted with eyes emoji

Copy link

bump

lin72h, flip111, and OlegLustenko reacted with thumbs up emoji

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

No reviews

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

AltStyle によって変換されたページ (->オリジナル) /