Description
I’d be interested in potentially implementing support for outputting Web Bundles in a new bundler & transpiler called Bun.
The specific motivation for Bun’s usecase today is to improve page load time in development for large apps while keeping hot module reloading incredibly fast.
Currently, there’s an unfortunate tradeoff for hot module reloading <> bundling. Bundling improves page load time, but means the browser must reload lots of code for small changes. There are various workarounds, but a better solution is if browsers exposed an interface for remapping import paths to byte ranges within a bundled file (like with web bundles!)
From there, the initial request could be bundled and the bundler-specific implementation of hot module reloading would be able to use consistent import paths in transpiled code. Consistent import paths help prevent reloading code unnecessarily. The alternative is implementing a custom module loader on top of either ESM or <script>
tags, which adds runtime & build-time performance overhead.
Incase it's helpful, here's a little about the metadata Bun's current bundling format stores.
For apps with ~15 MB of JavaScript dependencies (3,185 modules & 295 npm packages), the metadata loads in ~0.15ms on my laptop
The file starts with #!/usr/bin/env bun\n
, followed by a byte offset to the start of the metadata, then the bundled code for each file, and at the end, the metadata.
// This is the top-level type loaded from disk
message JavascriptBundleContainer {
uint32 bundle_format_version = 1;
// These fields are specific to Bun and probably not other bundlers
LoadedRouteConfig routes = 3;
LoadedFramework framework = 2;
JavascriptBundle bundle = 4;
// Don't technically need to store this, but it may be helpful as a sanity check
uint32 code_length = 5;
}
struct StringPointer {
uint32 offset;
uint32 length;
}
struct JavascriptBundledModule {
// package-relative path including file extension
StringPointer path;
// Source code
StringPointer code;
// index into JavascriptBundle.packages
uint32 package_id;
// The ESM export is this id ("$" + number.toString(16))
uint32 id;
// This lets us efficiently compare strings ignoring the extension
byte path_extname_length;
}
struct JavascriptBundledPackage {
StringPointer name;
StringPointer version;
uint32 hash;
uint32 modules_offset;
uint32 modules_length;
}
struct JavascriptBundle {
// These are sorted alphabetically so you can do binary search
JavascriptBundledModule[] modules;
JavascriptBundledPackage[] packages;
byte[] etag;
uint32 generated_at;
// generated by hashing all ${name}@${version} in sorted order
byte[] app_package_json_dependencies_hash;
byte[] import_from_name;
// This is what StringPointer refers to
byte[] manifest_string;
}
Eventually, this will include exports & imports of each module.
Is the recommended way to try out web bundles in a browser today using the <link>
based api & an origin trial with Chromium?