Bun.js is the drop-in replacement for Node that now comes with a blazingly-fast bundler. Here's a first look at bundling with Bun.js.
Bun.js is an exciting new project in the JavaScript ecosystem that offers a comprehensive runtime and toolchain.ย Iโve previously written a general introduction to the project as a drop-in replacement for Node.js.ย This article focuses on Bunโs new bundling feature. While itโs still in early releases, the Bun bundler is quite capable and flaunts Bunโs speed. It could easily replace tools like WebPack, Vite, and Snowpack.
Letโs check out the new bundling support in Bun.
Why another bundler?
The first question for many developers is why we need another bundler.ย We already have a few of them, after all.
So whatโs special about the Bun bundler? One advantage is that having a bundler in the Bun toolchain brings everything under one roof. Rather than building a toolkit from parts, youโve got everything you need in one toolchain. The other advantage is that Bun is built for speed, which matters for bundling. Bun is built on Zig and JavaScript to support faster built times. When magnified across development and devops (CI/CD) workflows, Bunโs performance advantage can have a significant impact on productivity.
State of the Bun bundler
The Bun.js bundler is still in beta, so we can expect a few rough edges.ย But one thing you canโt miss is the speed of the engine.ย Shane OโSullivan in the SOS blog wrote that building with Bun seemed to โfinish as the Enter key was still traveling back upwards from executing the command.โย In short, itโs quick. So are the other parts of Bun, like the server and package manager.ย Again, having all the tools in one place and working fast yields an evolved developer experience, which many developers will welcome.
Take the bundler for a test drive
Letโs take the Bun.js bundler for a test drive with a create-react-app.ย For this test, youโll need Node and/or NPM installed.ย You can install the create-react-app with the command: npm i -g create-react-app, or just use npx, as weโll do below.
We of course also need Bun.js, and weโll need the latest version that includes the bundler (0.6.0+).ย ย There are several ways to install bun.ย You can use: npm: $ npm i -g bun. Or, if you have it already installed with an older version, make sure to upgrade with $ bun upgrade.
Letโs create a new application by typing: $ npx create-react-app bun-react.ย Then, switch to the new directory with $ cd bun-react.ย Note that create-react-app will run npm install for you automatically.
If we were just using NPM, we could run the app in dev mode with $ npm run start. Instead, letโs bundle it together and serve it with Bun.ย
To start, bundle the application with: $ bun build ./src/index.js --outdir ./build.ย That will create a bundle of the application (with a default target of the browser) in the /build directory.ย
Now, we need to create an index.html page like the one shown here.
Listing 1. Index.html with JavaScript and CSS included
<html>
<body>
<link rel="stylesheet" href="App-f6cf171344c59401.css">
<div id="root"></div>
<script type="module" src="/index.js"></script>
</body>
</html>
Note that your CSS file will be different from mine depending on the build output.ย Once you have the index in place, you can serve the application with: $ bunx serve build.
Visiting localhost:3000 will now give you the familiar create-react-app landing page.
Execute the build with a script
You can also use a script file to execute build commands with the Bun API. The one shown in Listing 2 is the equivalent of the command-line call we just executed.
Listing 2. Build script
await Bun.build({
entrypoints: ['./src/index.js'],
outdir: './build',
})
Bundling a server
As the Bun documentation notes, you can often execute server-side code directly; however, bundling such code can reduce startup times. Moreover, the benefits of doing so will compound in CI/CD scenarios.
To bundle a server, you change the target type to โbunโ or โnode,โ depending on the runtime you are using.ย Letโs create a simple Express server and run it with Bun.js.
Weโll use theย Hello World serverย shown in Listing 3.
Listing 3. The Express server
const express = require('express')
const app = express()
const port = 3000
app.get('/', (req, res) => {
res.send('Hello World!')
})
app.listen(port, () => {
console.log(`Example app listening on port ${port}`)
})
Next, we use the following script to bundle the server.
Listing 4. Bundling the server
await Bun.build({
entrypoints: ['./index.js'],
outdir: './out',
target: 'bun'
})
This code outputs a complete server to ./out/index.js.ย You can run it with bun out/index.js and get the anticipated behavior (โHello World!โ printed to the browser at localhost:3000).
Build a standalone executable
The bundler includes a very nice power, to โcompileโ an application down to an executable target.
We can take our Express server and make an executable with it by using the --compile switch, as shown in Listing 5.
Listing 5. Compile a standalone server
await Bun.build({
entrypoints: ['./index.js'],
outdir: './out',
target: 'bun'
})
This will drop a server file into the current directory which we can run with: ./server.ย When you do this, youโll get the same server behavior.ย Pretty slick!
Do more with Bun
Letโs look at a few more of Bunโs bundling features.ย To start, we can have our bundled server (from Listing 3) output a text file to the browser.ย Create a file in the working directory, named โquote.txt,โ and add some text to it. (I used โAfter silence that which comes nearest to expressing the inexpressible is music.โ)ย
We can reference the file in the server as shown in Listing 6.
Listing 6. Outputting a text file to the browser
bun build ./index.js --compile --outfile server
Now we can bundle the text file into our application with the command: $ bun build ./index.js --compile --outfile server.ย
When you run the server now, youโll see the contents of the quote.txt file output to the browser.
Splitting
Another important feature the Bun.js bundler supports is splitting.ย In essence, whenever you have two entry points in the application that both rely on the same third bundle, you can instruct the bundler to break this out into a separate chunk.ย You do this by setting the splitting property to true.
We can see this in action by duplicating the index.js file: $ cp index.js index2.js.ย Now, we have two files that both reference the quote.txt file.
Next, create a build.js file like the one in Listing 7.
Listing 7. A build file with splitting
const express = require('express')
const app = express()
const port = 3000
import contents from "./quote.txt";
app.get('/', (req, res) => {
res.send(contents)
})
app.listen(port, () => {
console.log(`Example app listening on port ${port}`)
})
This build file sets splitting to true and adds both index.js and index2.js to the entrypoints.ย Now, we can run the build with: $ bun build.js.ย
The application will be output to /build and youโll see three files: index.js, index2.js, and chunk-8e53e696087e358a.js.
This is a contrived example but in many situations, especially on the front end, splitting is an important feature for reducing code bundle sizes.ย The browser can cache one version of the bundle and all other code that depends on it will avoid having it included, and also avoid that trip to the server. When conscientiously used, code splitting is key to performance. The Bun bundler makes it dead simple to use.
Plugins
Bun uses a generic plugin format for both the runtime engine and the bundler. You can learn about plugins here.ย
For the bundler, you can add plugins as shown in Listing 8, where we include a hypothetical YAML plugin.ย
Listing 8. Plugin syntax
await Bun.build({
entrypoints: ['./index.js','index2.js'],
outdir: './build',
target: 'bun',
splitting: true
})
The bundler also has controls for sourcemap generation, minification, and โexternalโ paths (resources not included in final bundle).ย You can also customize naming conventions with the naming property and fine-tune the root directory with the root property.ย The publicPath property allows you to define a root path for URLs, to handle things like images being hosted at a CDN.
You can also define global variables that the bundler will replace with the value given by the define property, which works something like a macro.
Conclusion
Using the Bun.js bundler just feels easy. It takes an overall cleaner approach to bundling than earlier tools, and it applies the lessons learned from them. No doubt, there are edge cases, gotchas, and wrinkles to be ironed outโboth in the tool and for complex or very custom build chains.ย But the Bun.js team is working furiously on the codebase, and we can expect improvements over time. The toolโs high points are its simplicity and the fact that itโs part of an all-in-one toolset (bundler, packager, runtime), and the palpable speed of the engine. Altogether, you get a distinct feeling that you are using a next-generation tool. Itโs probably a good indicator ofย what we can expect to see from server-side JavaScript going forward: comprehensive, seamless, and fast.


