Compare commits

...

8 Commits

Author SHA1 Message Date
Claude Bot
46ee6da96d test(s3): add symmetric test for static list() with requestPayer: false
Adds a test to verify the x-amz-request-payer header is NOT included
when calling S3Client.list() statically with requestPayer: false.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-06 14:37:07 +00:00
Claude Bot
91a22fa674 fix(s3): add requestPayer support to list() method
`S3Client.list()` did not respect the `requestPayer` option, causing
`AccessDenied` errors on Requester Pays buckets. This was because the
`listObjects()` function in the S3 client was missing the `request_payer`
parameter that other operations (like `delete()`) already had.

Changes:
- Add `request_payer` parameter to `listObjects()` in `src/s3/client.zig`
- Pass `request_payer` to `signRequest()` to include the
  `x-amz-request-payer: requester` header
- Update Store.zig to pass `request_payer` when calling `listObjects()`
- Add `requestPayer` to the TypeScript types for `list()` options

Fixes #26778

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-06 14:24:00 +00:00
robobun
ddefa11070 fix(fs): handle '.' path normalization on Windows (#26634)
## Summary
- Fix path normalization for "." on Windows where `normalizeStringBuf`
was incorrectly stripping it to an empty string
- This caused `existsSync('.')`, `statSync('.')`, and other fs
operations to fail on Windows

## Test plan
- Added regression test `test/regression/issue/26631.test.ts` that tests
`existsSync`, `exists`, `statSync`, and `stat` for both `.` and `..`
paths
- All tests pass locally with `bun bd test
test/regression/issue/26631.test.ts`
- Verified code compiles on all platforms with `bun run zig:check-all`

Fixes #26631

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-01 00:33:59 -08:00
Dylan Conway
35f8154319 bump versions 2026-01-31 17:38:23 -08:00
robobun
9d68ec882a require --compile for ESM bytecode (#26624)
## Summary
- Add validation to require `--compile` when using ESM bytecode
- Update documentation to clarify ESM bytecode requirements

## Why
ESM module resolution is two-phase: (1) analyze imports/exports, (2)
evaluate. Without `--compile`, there's no `module_info` embedded, so JSC
must still parse the file for module analysis even with bytecode -
causing a double-parse deopt.

## Changes
- **CLI**: Error when `--bytecode --format=esm` is used without
`--compile`
- **JS API**: Error when `bytecode: true, format: 'esm'` is used without
`compile: true`
- **Docs**: Update bytecode.mdx, executables.mdx, index.mdx to clarify
requirements
- **Types**: Update JSDoc for bytecode option in bun.d.ts

## Test plan
```bash
# Should error
bun build ./test.js --bytecode --format=esm --outdir=./out
# error: ESM bytecode requires --compile. Use --format=cjs for bytecode without --compile.

# Should work
bun build ./test.js --bytecode --format=esm --compile --outfile=./mytest
bun build ./test.js --bytecode --format=cjs --outdir=./out
```

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-01-31 17:35:03 -08:00
Dylan Conway
1337f5dba4 add --cpu-prof-interval flag (#26620)
Adds `--cpu-prof-interval` to configure the CPU profiler sampling
interval in microseconds (default: 1000), matching Node.js's
`--cpu-prof-interval` flag.

```sh
bun --cpu-prof --cpu-prof-interval 500 index.js
```

- Parsed as `u32`, truncated to `c_int` when passed to JSC's
`SamplingProfiler::setTimingInterval`
- Invalid values silently fall back to the default (1000μs)
- Warns if used without `--cpu-prof` or `--cpu-prof-md`

---------

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 16:59:03 -08:00
robobun
56b5be4ba4 fix(shell): prevent double-free during GC finalization (#26626)
## Summary

Fixes #26625

This fixes a segmentation fault that occurred on Windows x64 when the GC
finalizer tried to free shell interpreter resources that were already
partially freed during normal shell completion.

- Added explicit `cleanup_state` enum to track resource ownership state
- `needs_full_cleanup`: Nothing cleaned up yet, finalizer must clean
everything
- `runtime_cleaned`: `finish()` already cleaned IO/shell, finalizer
skips those
- Early return in `#derefRootShellAndIOIfNeeded()` when already cleaned
- Explicit state-based cleanup in `deinitFromFinalizer()`

The vulnerability existed on all platforms but was most reliably
triggered on Windows with high GC pressure (many concurrent shell
commands).

## Test plan

- [x] Build passes (`bun bd`)
- [x] New regression test added (`test/regression/issue/26625.test.ts`)
- [x] Existing shell tests pass (same 4 pre-existing failures, no new
failures)


🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
2026-01-31 16:57:59 -08:00
Dylan Conway
6c119d608e Simplify bun run build:local to auto-build JSC (#26645)
## Summary

- `bun run build:local` now handles everything: configuring JSC,
building JSC, and building Bun in a single command on all platforms
(macOS, Linux, Windows). Previously required manually running `bun run
jsc:build:debug`, deleting a duplicate `InspectorProtocolObjects.h`
header, and then running the Bun build separately.
- Incremental JSC rebuilds: JSC is built via `add_custom_target` that
delegates to JSC's inner Ninja, which tracks WebKit source file changes
and only rebuilds what changed. `ninja -Cbuild/debug-local` also works
after the first build.
- Cross-platform support:
  - macOS: Uses system ICU automatically
- Linux: Uses system ICU via find_package instead of requiring bundled
static libs
- Windows: Builds ICU from source automatically (only when libs don't
already exist), sets up static CRT and ICU naming conventions

### Changes
- cmake/tools/SetupWebKit.cmake: Replace the old WEBKIT_LOCAL block
(which just set include paths and assumed JSC was pre-built) with full
JSC configure + build integration for all platforms
- cmake/targets/BuildBun.cmake: Add jsc as a build dependency, use
system ICU on Linux for local builds, handle bmalloc linking for local
builds
- CONTRIBUTING.md / docs/project/contributing.mdx: Simplify "Building
WebKit locally" docs from ~15 lines of manual steps to 3 lines

## Test plan

- [x] macOS arm64: clean build, incremental rebuild, WebKit source
change rebuild
- [x] Windows x64: clean build with ICU, incremental rebuild with ICU
skip
- [x] Linux x64: build with system ICU via find_package
- [x] No duplicate InspectorProtocolObjects.h errors
- [x] build/debug-local/bun-debug --version works

Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-31 16:52:51 -08:00
24 changed files with 479 additions and 100 deletions

View File

@@ -259,18 +259,13 @@ $ git clone https://github.com/oven-sh/WebKit vendor/WebKit
# Check out the commit hash specified in `set(WEBKIT_VERSION <commit_hash>)` in cmake/tools/SetupWebKit.cmake
$ git -C vendor/WebKit checkout <commit_hash>
# Make a debug build of JSC. This will output build artifacts in ./vendor/WebKit/WebKitBuild/Debug
# Optionally, you can use `bun run jsc:build` for a release build
$ bun run jsc:build:debug && rm vendor/WebKit/WebKitBuild/Debug/JavaScriptCore/DerivedSources/inspector/InspectorProtocolObjects.h
# After an initial run of `make jsc-debug`, you can rebuild JSC with:
$ cmake --build vendor/WebKit/WebKitBuild/Debug --target jsc && rm vendor/WebKit/WebKitBuild/Debug/JavaScriptCore/DerivedSources/inspector/InspectorProtocolObjects.h
# Build bun with the local JSC build
# Build bun with the local JSC build — this automatically configures and builds JSC
$ bun run build:local
```
Using `bun run build:local` will build Bun in the `./build/debug-local` directory (instead of `./build/debug`), you'll have to change a couple of places to use this new directory:
`bun run build:local` handles everything: configuring JSC, building JSC, and building Bun. On subsequent runs, JSC will incrementally rebuild if any WebKit sources changed. `ninja -Cbuild/debug-local` also works after the first build, and will build Bun+JSC.
The build output goes to `./build/debug-local` (instead of `./build/debug`), so you'll need to update a couple of places:
- The first line in [`src/js/builtins.d.ts`](/src/js/builtins.d.ts)
- The `CompilationDatabase` line in [`.clangd` config](/.clangd) should be `CompilationDatabase: build/debug-local`
@@ -281,7 +276,7 @@ Note that the WebKit folder, including build artifacts, is 8GB+ in size.
If you are using a JSC debug build and using VScode, make sure to run the `C/C++: Select a Configuration` command to configure intellisense to find the debug headers.
Note that if you change make changes to our [WebKit fork](https://github.com/oven-sh/WebKit), you will also have to change [`SetupWebKit.cmake`](/cmake/tools/SetupWebKit.cmake) to point to the commit hash.
Note that if you make changes to our [WebKit fork](https://github.com/oven-sh/WebKit), you will also have to change [`SetupWebKit.cmake`](/cmake/tools/SetupWebKit.cmake) to point to the commit hash.
## Troubleshooting

2
LATEST
View File

@@ -1 +1 @@
1.3.7
1.3.8

View File

@@ -1273,13 +1273,18 @@ else()
${WEBKIT_LIB_PATH}/libWTF.a
${WEBKIT_LIB_PATH}/libJavaScriptCore.a
)
if(NOT APPLE OR EXISTS ${WEBKIT_LIB_PATH}/libbmalloc.a)
if(WEBKIT_LOCAL OR NOT APPLE OR EXISTS ${WEBKIT_LIB_PATH}/libbmalloc.a)
target_link_libraries(${bun} PRIVATE ${WEBKIT_LIB_PATH}/libbmalloc.a)
endif()
endif()
include_directories(${WEBKIT_INCLUDE_PATH})
# When building with a local WebKit, ensure JSC is built before compiling Bun's C++ sources.
if(WEBKIT_LOCAL AND TARGET jsc)
add_dependencies(${bun} jsc)
endif()
# Include the generated dependency versions header
include_directories(${CMAKE_BINARY_DIR})
@@ -1324,9 +1329,14 @@ if(LINUX)
target_link_libraries(${bun} PUBLIC libatomic.so)
endif()
target_link_libraries(${bun} PRIVATE ${WEBKIT_LIB_PATH}/libicudata.a)
target_link_libraries(${bun} PRIVATE ${WEBKIT_LIB_PATH}/libicui18n.a)
target_link_libraries(${bun} PRIVATE ${WEBKIT_LIB_PATH}/libicuuc.a)
if(WEBKIT_LOCAL)
find_package(ICU REQUIRED COMPONENTS data i18n uc)
target_link_libraries(${bun} PRIVATE ICU::data ICU::i18n ICU::uc)
else()
target_link_libraries(${bun} PRIVATE ${WEBKIT_LIB_PATH}/libicudata.a)
target_link_libraries(${bun} PRIVATE ${WEBKIT_LIB_PATH}/libicui18n.a)
target_link_libraries(${bun} PRIVATE ${WEBKIT_LIB_PATH}/libicuuc.a)
endif()
endif()
if(WIN32)

View File

@@ -3,6 +3,7 @@
option(WEBKIT_VERSION "The version of WebKit to use")
option(WEBKIT_LOCAL "If a local version of WebKit should be used instead of downloading")
option(WEBKIT_BUILD_TYPE "The build type for local WebKit (defaults to CMAKE_BUILD_TYPE)")
if(NOT WEBKIT_VERSION)
set(WEBKIT_VERSION 515344bc5d65aa2d4f9ff277b5fb944f0e051dcd)
@@ -15,7 +16,10 @@ string(SUBSTRING ${WEBKIT_VERSION} 0 16 WEBKIT_VERSION_PREFIX)
string(SUBSTRING ${WEBKIT_VERSION} 0 8 WEBKIT_VERSION_SHORT)
if(WEBKIT_LOCAL)
set(DEFAULT_WEBKIT_PATH ${VENDOR_PATH}/WebKit/WebKitBuild/${CMAKE_BUILD_TYPE})
if(NOT WEBKIT_BUILD_TYPE)
set(WEBKIT_BUILD_TYPE ${CMAKE_BUILD_TYPE})
endif()
set(DEFAULT_WEBKIT_PATH ${VENDOR_PATH}/WebKit/WebKitBuild/${WEBKIT_BUILD_TYPE})
else()
set(DEFAULT_WEBKIT_PATH ${CACHE_PATH}/webkit-${WEBKIT_VERSION_PREFIX})
endif()
@@ -30,35 +34,153 @@ set(WEBKIT_INCLUDE_PATH ${WEBKIT_PATH}/include)
set(WEBKIT_LIB_PATH ${WEBKIT_PATH}/lib)
if(WEBKIT_LOCAL)
if(EXISTS ${WEBKIT_PATH}/cmakeconfig.h)
# You may need to run:
# make jsc-compile-debug jsc-copy-headers
include_directories(
${WEBKIT_PATH}
${WEBKIT_PATH}/JavaScriptCore/Headers
${WEBKIT_PATH}/JavaScriptCore/Headers/JavaScriptCore
${WEBKIT_PATH}/JavaScriptCore/PrivateHeaders
${WEBKIT_PATH}/bmalloc/Headers
${WEBKIT_PATH}/WTF/Headers
${WEBKIT_PATH}/JavaScriptCore/PrivateHeaders/JavaScriptCore
${WEBKIT_PATH}/JavaScriptCore/DerivedSources/inspector
)
set(WEBKIT_SOURCE_DIR ${VENDOR_PATH}/WebKit)
# On Windows, add ICU include path from vcpkg
if(WIN32)
# Auto-detect vcpkg triplet
set(VCPKG_ARM64_PATH ${VENDOR_PATH}/WebKit/vcpkg_installed/arm64-windows-static)
set(VCPKG_X64_PATH ${VENDOR_PATH}/WebKit/vcpkg_installed/x64-windows-static)
if(EXISTS ${VCPKG_ARM64_PATH})
set(VCPKG_ICU_PATH ${VCPKG_ARM64_PATH})
if(WIN32)
# --- Build ICU from source (Windows only) ---
# On macOS, ICU is found automatically (Homebrew icu4c for headers, system for libs).
# On Linux, ICU is found automatically from system packages (e.g. libicu-dev).
# On Windows, there is no system ICU, so we build it from source.
set(ICU_LOCAL_ROOT ${VENDOR_PATH}/WebKit/WebKitBuild/icu)
if(NOT EXISTS ${ICU_LOCAL_ROOT}/lib/sicudt.lib)
message(STATUS "Building ICU from source...")
if(CMAKE_SYSTEM_PROCESSOR MATCHES "arm64|ARM64|aarch64|AARCH64")
set(ICU_PLATFORM "ARM64")
else()
set(VCPKG_ICU_PATH ${VCPKG_X64_PATH})
set(ICU_PLATFORM "x64")
endif()
if(EXISTS ${VCPKG_ICU_PATH}/include)
include_directories(${VCPKG_ICU_PATH}/include)
message(STATUS "Using ICU from vcpkg: ${VCPKG_ICU_PATH}/include")
execute_process(
COMMAND powershell -ExecutionPolicy Bypass -File
${WEBKIT_SOURCE_DIR}/build-icu.ps1
-Platform ${ICU_PLATFORM}
-BuildType ${WEBKIT_BUILD_TYPE}
-OutputDir ${ICU_LOCAL_ROOT}
RESULT_VARIABLE ICU_BUILD_RESULT
)
if(NOT ICU_BUILD_RESULT EQUAL 0)
message(FATAL_ERROR "Failed to build ICU (exit code: ${ICU_BUILD_RESULT}).")
endif()
endif()
# Copy ICU libs to WEBKIT_LIB_PATH with the names BuildBun.cmake expects.
# Prebuilt WebKit uses 's' prefix (static) and 'd' suffix (debug).
file(MAKE_DIRECTORY ${WEBKIT_LIB_PATH})
if(WEBKIT_BUILD_TYPE STREQUAL "Debug")
set(ICU_SUFFIX "d")
else()
set(ICU_SUFFIX "")
endif()
file(COPY_FILE ${ICU_LOCAL_ROOT}/lib/sicudt.lib ${WEBKIT_LIB_PATH}/sicudt${ICU_SUFFIX}.lib ONLY_IF_DIFFERENT)
file(COPY_FILE ${ICU_LOCAL_ROOT}/lib/icuin.lib ${WEBKIT_LIB_PATH}/sicuin${ICU_SUFFIX}.lib ONLY_IF_DIFFERENT)
file(COPY_FILE ${ICU_LOCAL_ROOT}/lib/icuuc.lib ${WEBKIT_LIB_PATH}/sicuuc${ICU_SUFFIX}.lib ONLY_IF_DIFFERENT)
endif()
# --- Configure JSC ---
message(STATUS "Configuring JSC from local WebKit source at ${WEBKIT_SOURCE_DIR}...")
set(JSC_CMAKE_ARGS
-S ${WEBKIT_SOURCE_DIR}
-B ${WEBKIT_PATH}
-G ${CMAKE_GENERATOR}
-DPORT=JSCOnly
-DENABLE_STATIC_JSC=ON
-DUSE_THIN_ARCHIVES=OFF
-DENABLE_FTL_JIT=ON
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON
-DUSE_BUN_JSC_ADDITIONS=ON
-DUSE_BUN_EVENT_LOOP=ON
-DENABLE_BUN_SKIP_FAILING_ASSERTIONS=ON
-DALLOW_LINE_AND_COLUMN_NUMBER_IN_BUILTINS=ON
-DCMAKE_BUILD_TYPE=${WEBKIT_BUILD_TYPE}
-DCMAKE_C_COMPILER=${CMAKE_C_COMPILER}
-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}
-DENABLE_REMOTE_INSPECTOR=ON
)
if(WIN32)
# ICU paths and Windows-specific compiler/linker settings
list(APPEND JSC_CMAKE_ARGS
-DICU_ROOT=${ICU_LOCAL_ROOT}
-DICU_LIBRARY=${ICU_LOCAL_ROOT}/lib
-DICU_INCLUDE_DIR=${ICU_LOCAL_ROOT}/include
-DCMAKE_LINKER=lld-link
)
# Static CRT and U_STATIC_IMPLEMENTATION
if(WEBKIT_BUILD_TYPE STREQUAL "Debug")
set(JSC_MSVC_RUNTIME "MultiThreadedDebug")
else()
set(JSC_MSVC_RUNTIME "MultiThreaded")
endif()
list(APPEND JSC_CMAKE_ARGS
-DCMAKE_MSVC_RUNTIME_LIBRARY=${JSC_MSVC_RUNTIME}
"-DCMAKE_C_FLAGS=/DU_STATIC_IMPLEMENTATION"
"-DCMAKE_CXX_FLAGS=/DU_STATIC_IMPLEMENTATION /clang:-fno-c++-static-destructors"
)
endif()
if(ENABLE_ASAN)
list(APPEND JSC_CMAKE_ARGS -DENABLE_SANITIZERS=address)
endif()
# Pass through ccache if available
if(CMAKE_C_COMPILER_LAUNCHER)
list(APPEND JSC_CMAKE_ARGS -DCMAKE_C_COMPILER_LAUNCHER=${CMAKE_C_COMPILER_LAUNCHER})
endif()
if(CMAKE_CXX_COMPILER_LAUNCHER)
list(APPEND JSC_CMAKE_ARGS -DCMAKE_CXX_COMPILER_LAUNCHER=${CMAKE_CXX_COMPILER_LAUNCHER})
endif()
execute_process(
COMMAND ${CMAKE_COMMAND} ${JSC_CMAKE_ARGS}
RESULT_VARIABLE JSC_CONFIGURE_RESULT
)
if(NOT JSC_CONFIGURE_RESULT EQUAL 0)
message(FATAL_ERROR "Failed to configure JSC (exit code: ${JSC_CONFIGURE_RESULT}). "
"Check the output above for errors.")
endif()
if(WIN32)
set(JSC_BYPRODUCTS
${WEBKIT_LIB_PATH}/JavaScriptCore.lib
${WEBKIT_LIB_PATH}/WTF.lib
${WEBKIT_LIB_PATH}/bmalloc.lib
)
else()
set(JSC_BYPRODUCTS
${WEBKIT_LIB_PATH}/libJavaScriptCore.a
${WEBKIT_LIB_PATH}/libWTF.a
${WEBKIT_LIB_PATH}/libbmalloc.a
)
endif()
if(WIN32)
add_custom_target(jsc ALL
COMMAND ${CMAKE_COMMAND} --build ${WEBKIT_PATH} --config ${WEBKIT_BUILD_TYPE} --target jsc
BYPRODUCTS ${JSC_BYPRODUCTS}
COMMENT "Building JSC (${WEBKIT_PATH})"
)
else()
add_custom_target(jsc ALL
COMMAND ${CMAKE_COMMAND} --build ${WEBKIT_PATH} --config ${WEBKIT_BUILD_TYPE} --target jsc
BYPRODUCTS ${JSC_BYPRODUCTS}
COMMENT "Building JSC (${WEBKIT_PATH})"
USES_TERMINAL
)
endif()
include_directories(
${WEBKIT_PATH}
${WEBKIT_PATH}/JavaScriptCore/Headers
${WEBKIT_PATH}/JavaScriptCore/Headers/JavaScriptCore
${WEBKIT_PATH}/JavaScriptCore/PrivateHeaders
${WEBKIT_PATH}/bmalloc/Headers
${WEBKIT_PATH}/WTF/Headers
${WEBKIT_PATH}/JavaScriptCore/PrivateHeaders/JavaScriptCore
)
# On Windows, add ICU headers from the local ICU build
if(WIN32)
include_directories(${ICU_LOCAL_ROOT}/include)
endif()
# After this point, only prebuilt WebKit is supported

View File

@@ -7,9 +7,9 @@ Bytecode caching is a build-time optimization that dramatically improves applica
## Usage
### Basic usage
### Basic usage (CommonJS)
Enable bytecode caching with the `--bytecode` flag:
Enable bytecode caching with the `--bytecode` flag. Without `--format`, this defaults to CommonJS:
```bash terminal icon="terminal"
bun build ./index.ts --target=bun --bytecode --outdir=./dist
@@ -17,7 +17,7 @@ bun build ./index.ts --target=bun --bytecode --outdir=./dist
This generates two files:
- `dist/index.js` - Your bundled JavaScript
- `dist/index.js` - Your bundled JavaScript (CommonJS)
- `dist/index.jsc` - The bytecode cache file
At runtime, Bun automatically detects and uses the `.jsc` file:
@@ -28,14 +28,24 @@ bun ./dist/index.js # Automatically uses index.jsc
### With standalone executables
When creating executables with `--compile`, bytecode is embedded into the binary:
When creating executables with `--compile`, bytecode is embedded into the binary. Both ESM and CommonJS formats are supported:
```bash terminal icon="terminal"
# ESM (requires --compile)
bun build ./cli.ts --compile --bytecode --format=esm --outfile=mycli
# CommonJS (works with or without --compile)
bun build ./cli.ts --compile --bytecode --outfile=mycli
```
The resulting executable contains both the code and bytecode, giving you maximum performance in a single file.
### ESM bytecode
ESM bytecode requires `--compile` because Bun embeds module metadata (import/export information) in the compiled binary. This metadata allows the JavaScript engine to skip parsing entirely at runtime.
Without `--compile`, ESM bytecode would still require parsing the source to analyze module dependencies—defeating the purpose of bytecode caching.
### Combining with other optimizations
Bytecode works great with minification and source maps:
@@ -90,35 +100,9 @@ Larger applications benefit more because they have more code to parse.
- ❌ **Code that runs once**
- ❌ **Development builds**
- ❌ **Size-constrained environments**
- ❌ **Code with top-level await** (not supported)
## Limitations
### CommonJS only
Bytecode caching currently works with CommonJS output format. Bun's bundler automatically converts most ESM code to CommonJS, but **top-level await** is the exception:
```js
// This prevents bytecode caching
const data = await fetch("https://api.example.com");
export default data;
```
**Why**: Top-level await requires async module evaluation, which can't be represented in CommonJS. The module graph becomes asynchronous, and the CommonJS wrapper function model breaks down.
**Workaround**: Move async initialization into a function:
```js
async function init() {
const data = await fetch("https://api.example.com");
return data;
}
export default init;
```
Now the module exports a function that the consumer can await when needed.
### Version compatibility
Bytecode is **not portable across Bun versions**. The bytecode format is tied to JavaScriptCore's internal representation, which changes between versions.
@@ -236,8 +220,6 @@ It's normal for it it to log a cache miss multiple times since Bun doesn't curre
- Compressing `.jsc` files for network transfer (gzip/brotli)
- Evaluating if the startup performance gain is worth the size increase
**Top-level await**: Not supported. Refactor to use async initialization functions.
## What is bytecode?
When you run JavaScript, the JavaScript engine doesn't execute your source code directly. Instead, it goes through several steps:

View File

@@ -322,10 +322,7 @@ Using bytecode compilation, `tsc` starts 2x faster:
Bytecode compilation moves parsing overhead for large input files from runtime to bundle time. Your app starts faster, in exchange for making the `bun build` command a little slower. It doesn't obscure source code.
<Warning>
**Experimental:** Bytecode compilation is an experimental feature. Only `cjs` format is supported (which means no
top-level-await). Let us know if you run into any issues!
</Warning>
<Note>Bytecode compilation supports both `cjs` and `esm` formats when used with `--compile`.</Note>
### What do these flags do?

View File

@@ -1508,22 +1508,43 @@ BuildArtifact (entry-point) {
## Bytecode
The `bytecode: boolean` option can be used to generate bytecode for any JavaScript/TypeScript entrypoints. This can greatly improve startup times for large applications. Only supported for `"cjs"` format, only supports `"target": "bun"` and dependent on a matching version of Bun. This adds a corresponding `.jsc` file for each entrypoint.
The `bytecode: boolean` option can be used to generate bytecode for any JavaScript/TypeScript entrypoints. This can greatly improve startup times for large applications. Requires `"target": "bun"` and is dependent on a matching version of Bun.
- **CommonJS**: Works with or without `compile: true`. Generates a `.jsc` file alongside each entrypoint.
- **ESM**: Requires `compile: true`. Bytecode and module metadata are embedded in the standalone executable.
Without an explicit `format`, bytecode defaults to CommonJS.
<Tabs>
<Tab title="JavaScript">
```ts title="build.ts" icon="/icons/typescript.svg"
// CommonJS bytecode (generates .jsc files)
await Bun.build({
entrypoints: ["./index.tsx"],
outdir: "./out",
bytecode: true,
})
// ESM bytecode (requires compile)
await Bun.build({
entrypoints: ["./index.tsx"],
outfile: "./mycli",
bytecode: true,
format: "esm",
compile: true,
})
```
</Tab>
<Tab title="CLI">
```bash terminal icon="terminal"
# CommonJS bytecode
bun build ./index.tsx --outdir ./out --bytecode
# ESM bytecode (requires --compile)
bun build ./index.tsx --outfile ./mycli --bytecode --format=esm --compile
```
</Tab>
</Tabs>
@@ -1690,7 +1711,10 @@ interface BuildConfig {
* start times, but will make the final output larger and slightly increase
* memory usage.
*
* Bytecode is currently only supported for CommonJS (`format: "cjs"`).
* - CommonJS: works with or without `compile: true`
* - ESM: requires `compile: true`
*
* Without an explicit `format`, defaults to CommonJS.
*
* Must be `target: "bun"`
* @default false

View File

@@ -266,18 +266,13 @@ git clone https://github.com/oven-sh/WebKit vendor/WebKit
# Check out the commit hash specified in `set(WEBKIT_VERSION <commit_hash>)` in cmake/tools/SetupWebKit.cmake
git -C vendor/WebKit checkout <commit_hash>
# Make a debug build of JSC. This will output build artifacts in ./vendor/WebKit/WebKitBuild/Debug
# Optionally, you can use `bun run jsc:build` for a release build
bun run jsc:build:debug && rm vendor/WebKit/WebKitBuild/Debug/JavaScriptCore/DerivedSources/inspector/InspectorProtocolObjects.h
# After an initial run of `make jsc-debug`, you can rebuild JSC with:
cmake --build vendor/WebKit/WebKitBuild/Debug --target jsc && rm vendor/WebKit/WebKitBuild/Debug/JavaScriptCore/DerivedSources/inspector/InspectorProtocolObjects.h
# Build bun with the local JSC build
# Build bun with the local JSC build — this automatically configures and builds JSC
bun run build:local
```
Using `bun run build:local` will build Bun in the `./build/debug-local` directory (instead of `./build/debug`), you'll have to change a couple of places to use this new directory:
`bun run build:local` handles everything: configuring JSC, building JSC, and building Bun. On subsequent runs, JSC will incrementally rebuild if any WebKit sources changed. `ninja -Cbuild/debug-local` also works after the first build, and will build Bun+JSC.
The build output goes to `./build/debug-local` (instead of `./build/debug`), so you'll need to update a couple of places:
- The first line in `src/js/builtins.d.ts`
- The `CompilationDatabase` line in `.clangd` config should be `CompilationDatabase: build/debug-local`
@@ -288,7 +283,7 @@ Note that the WebKit folder, including build artifacts, is 8GB+ in size.
If you are using a JSC debug build and using VScode, make sure to run the `C/C++: Select a Configuration` command to configure intellisense to find the debug headers.
Note that if you change make changes to our [WebKit fork](https://github.com/oven-sh/WebKit), you will also have to change `SetupWebKit.cmake` to point to the commit hash.
Note that if you make changes to our [WebKit fork](https://github.com/oven-sh/WebKit), you will also have to change `SetupWebKit.cmake` to point to the commit hash.
## Troubleshooting

View File

@@ -50,7 +50,8 @@ bun build <entry points>
</ParamField>
<ParamField path="--format" type="string" default="esm">
Module format of the output bundle. One of <code>esm</code>, <code>cjs</code>, or <code>iife</code>
Module format of the output bundle. One of <code>esm</code>, <code>cjs</code>, or <code>iife</code>. Defaults to{" "}
<code>cjs</code> when <code>--bytecode</code> is used.
</ParamField>
### File Naming

View File

@@ -1,7 +1,7 @@
{
"private": true,
"name": "bun",
"version": "1.3.8",
"version": "1.3.9",
"workspaces": [
"./packages/bun-types",
"./packages/@types/bun"

View File

@@ -2594,7 +2594,10 @@ declare module "bun" {
* start times, but will make the final output larger and slightly increase
* memory usage.
*
* Bytecode is currently only supported for CommonJS (`format: "cjs"`).
* - CommonJS: works with or without `compile: true`
* - ESM: requires `compile: true`
*
* Without an explicit `format`, defaults to CommonJS.
*
* Must be `target: "bun"`
* @default false

View File

@@ -1284,7 +1284,10 @@ declare module "bun" {
*/
list(
input?: S3ListObjectsOptions | null,
options?: Pick<S3Options, "accessKeyId" | "secretAccessKey" | "sessionToken" | "region" | "bucket" | "endpoint">,
options?: Pick<
S3Options,
"accessKeyId" | "secretAccessKey" | "sessionToken" | "region" | "bucket" | "endpoint" | "requestPayer"
>,
): Promise<S3ListObjectsResponse>;
/**
@@ -1320,7 +1323,10 @@ declare module "bun" {
*/
static list(
input?: S3ListObjectsOptions | null,
options?: Pick<S3Options, "accessKeyId" | "secretAccessKey" | "sessionToken" | "region" | "bucket" | "endpoint">,
options?: Pick<
S3Options,
"accessKeyId" | "secretAccessKey" | "sessionToken" | "region" | "bucket" | "endpoint" | "requestPayer"
>,
): Promise<S3ListObjectsResponse>;
}

View File

@@ -285,7 +285,9 @@ pub const Run = struct {
.dir = cpu_prof_opts.dir,
.md_format = cpu_prof_opts.md_format,
.json_format = cpu_prof_opts.json_format,
.interval = cpu_prof_opts.interval,
};
CPUProfiler.setSamplingInterval(cpu_prof_opts.interval);
CPUProfiler.startCPUProfiler(vm.jsc_vm);
bun.analytics.Features.cpu_profile += 1;
}

View File

@@ -1019,6 +1019,13 @@ pub const JSBundler = struct {
}
}
// ESM bytecode requires compile because module_info (import/export metadata)
// is only available in compiled binaries. Without it, JSC must parse the file
// twice (once for module analysis, once for bytecode), which is a deopt.
if (this.bytecode and this.format == .esm and this.compile == null) {
return globalThis.throwInvalidArguments("ESM bytecode requires compile: true. Use format: 'cjs' for bytecode without compile.", .{});
}
return this;
}

View File

@@ -19,6 +19,12 @@
extern "C" void Bun__startCPUProfiler(JSC::VM* vm);
extern "C" void Bun__stopCPUProfiler(JSC::VM* vm, BunString* outJSON, BunString* outText);
extern "C" void Bun__setSamplingInterval(int intervalMicroseconds);
void Bun__setSamplingInterval(int intervalMicroseconds)
{
Bun::setSamplingInterval(intervalMicroseconds);
}
namespace Bun {

View File

@@ -3,11 +3,17 @@ pub const CPUProfilerConfig = struct {
dir: []const u8,
md_format: bool = false,
json_format: bool = false,
interval: u32 = 1000,
};
// C++ function declarations
extern fn Bun__startCPUProfiler(vm: *jsc.VM) void;
extern fn Bun__stopCPUProfiler(vm: *jsc.VM, outJSON: ?*bun.String, outText: ?*bun.String) void;
extern fn Bun__setSamplingInterval(intervalMicroseconds: c_int) void;
pub fn setSamplingInterval(interval: u32) void {
Bun__setSamplingInterval(@intCast(interval));
}
pub fn startCPUProfiler(vm: *jsc.VM) void {
Bun__startCPUProfiler(vm);

View File

@@ -649,6 +649,10 @@ pub const PathLike = union(enum) {
const normal = path_handler.normalizeBuf(resolve, b, .windows);
return strings.toKernel32Path(@alignCast(std.mem.bytesAsSlice(u16, buf)), normal);
}
// Handle "." specially since normalizeStringBuf strips it to an empty string
if (s.len == 1 and s[0] == '.') {
return strings.toKernel32Path(@alignCast(std.mem.bytesAsSlice(u16, buf)), ".");
}
const normal = path_handler.normalizeStringBuf(s, b, true, .windows, false);
return strings.toKernel32Path(@alignCast(std.mem.bytesAsSlice(u16, buf)), normal);
}

View File

@@ -427,7 +427,7 @@ pub const S3 = struct {
.store = store, // store is needed in case of not found error
.resolvedlistOptions = options,
.global = globalThis,
}), proxy);
}), proxy, aws_options.request_payer);
return value;
}

View File

@@ -392,6 +392,7 @@ pub const Command = struct {
enabled: bool = false,
name: []const u8 = "",
dir: []const u8 = "",
interval: u32 = 1000,
md_format: bool = false,
json_format: bool = false,
} = .{},

View File

@@ -91,6 +91,7 @@ pub const runtime_params_ = [_]ParamType{
clap.parseParam("--cpu-prof-name <STR> Specify the name of the CPU profile file") catch unreachable,
clap.parseParam("--cpu-prof-dir <STR> Specify the directory where the CPU profile will be saved") catch unreachable,
clap.parseParam("--cpu-prof-md Output CPU profile in markdown format (grep-friendly, designed for LLM analysis)") catch unreachable,
clap.parseParam("--cpu-prof-interval <STR> Specify the sampling interval in microseconds for CPU profiling (default: 1000)") catch unreachable,
clap.parseParam("--heap-prof Generate V8 heap snapshot on exit (.heapsnapshot)") catch unreachable,
clap.parseParam("--heap-prof-name <STR> Specify the name of the heap profile file") catch unreachable,
clap.parseParam("--heap-prof-dir <STR> Specify the directory where the heap profile will be saved") catch unreachable,
@@ -178,7 +179,7 @@ pub const build_only_params = [_]ParamType{
clap.parseParam("--sourcemap <STR>? Build with sourcemaps - 'linked', 'inline', 'external', or 'none'") catch unreachable,
clap.parseParam("--banner <STR> Add a banner to the bundled output such as \"use client\"; for a bundle being used with RSCs") catch unreachable,
clap.parseParam("--footer <STR> Add a footer to the bundled output such as // built with bun!") catch unreachable,
clap.parseParam("--format <STR> Specifies the module format to build to. \"esm\", \"cjs\" and \"iife\" are supported. Defaults to \"esm\".") catch unreachable,
clap.parseParam("--format <STR> Specifies the module format to build to. \"esm\", \"cjs\" and \"iife\" are supported. Defaults to \"esm\", or \"cjs\" with --bytecode.") catch unreachable,
clap.parseParam("--root <STR> Root directory used for multiple entry points") catch unreachable,
clap.parseParam("--splitting Enable code splitting") catch unreachable,
clap.parseParam("--public-path <STR> A prefix to be appended to any import paths in bundled code") catch unreachable,
@@ -864,6 +865,9 @@ pub fn parse(allocator: std.mem.Allocator, ctx: Command.Context, comptime cmd: C
ctx.runtime_options.cpu_prof.md_format = cpu_prof_md_flag;
// json_format is true if --cpu-prof is passed (regardless of --cpu-prof-md)
ctx.runtime_options.cpu_prof.json_format = cpu_prof_flag;
if (args.option("--cpu-prof-interval")) |interval_str| {
ctx.runtime_options.cpu_prof.interval = std.fmt.parseInt(u32, interval_str, 10) catch 1000;
}
} else {
// Warn if --cpu-prof-name or --cpu-prof-dir is used without a profiler flag
if (args.option("--cpu-prof-name")) |_| {
@@ -872,6 +876,9 @@ pub fn parse(allocator: std.mem.Allocator, ctx: Command.Context, comptime cmd: C
if (args.option("--cpu-prof-dir")) |_| {
Output.warn("--cpu-prof-dir requires --cpu-prof or --cpu-prof-md to be enabled", .{});
}
if (args.option("--cpu-prof-interval")) |_| {
Output.warn("--cpu-prof-interval requires --cpu-prof or --cpu-prof-md to be enabled", .{});
}
}
const heap_prof_v8 = args.flag("--heap-prof");
@@ -1339,10 +1346,18 @@ pub fn parse(allocator: std.mem.Allocator, ctx: Command.Context, comptime cmd: C
}
ctx.bundler_options.output_format = format;
// ESM bytecode is supported for --compile builds (module_info is embedded in binary)
if (format != .cjs and format != .esm and ctx.bundler_options.bytecode) {
Output.errGeneric("format must be 'cjs' or 'esm' when bytecode is true.", .{});
Global.exit(1);
if (ctx.bundler_options.bytecode) {
if (format != .cjs and format != .esm) {
Output.errGeneric("format must be 'cjs' or 'esm' when bytecode is true.", .{});
Global.exit(1);
}
// ESM bytecode requires --compile because module_info (import/export metadata)
// is only available in compiled binaries. Without it, JSC must parse the file
// twice (once for module analysis, once for bytecode), which is a deopt.
if (format == .esm and !ctx.bundler_options.compile) {
Output.errGeneric("ESM bytecode requires --compile. Use --format=cjs for bytecode without --compile.", .{});
Global.exit(1);
}
}
}

View File

@@ -109,6 +109,7 @@ pub fn listObjects(
callback: *const fn (S3ListObjectsResult, *anyopaque) bun.JSTerminated!void,
callback_context: *anyopaque,
proxy_url: ?[]const u8,
request_payer: bool,
) bun.JSTerminated!void {
var search_params: bun.ByteList = .{};
@@ -177,6 +178,7 @@ pub fn listObjects(
.path = "",
.method = .GET,
.search_params = search_params.slice(),
.request_payer = request_payer,
}, true, null) catch |sign_err| {
search_params.deinit(bun.default_allocator);

View File

@@ -280,6 +280,17 @@ pub const Interpreter = struct {
exit_code: ?ExitCode = 0,
this_jsvalue: JSValue = .zero,
/// Tracks which resources have been cleaned up to avoid double-free.
/// When the interpreter finishes normally via finish(), it cleans up
/// the runtime resources (IO, shell env) and sets this to .runtime_cleaned.
/// The GC finalizer then only cleans up what remains (args, interpreter itself).
cleanup_state: enum(u8) {
/// Nothing has been cleaned up yet - need full cleanup
needs_full_cleanup,
/// Runtime resources (IO, shell env) have been cleaned up via finish()
runtime_cleaned,
} = .needs_full_cleanup,
__alloc_scope: if (bun.Environment.enableAllocScopes) bun.AllocationScope else void,
estimated_size_for_gc: usize = 0,
@@ -1222,6 +1233,11 @@ pub const Interpreter = struct {
}
fn #derefRootShellAndIOIfNeeded(this: *ThisInterpreter, free_buffered_io: bool) void {
// Check if already cleaned up to prevent double-free
if (this.cleanup_state == .runtime_cleaned) {
return;
}
if (free_buffered_io) {
// Can safely be called multiple times.
if (this.root_shell._buffered_stderr == .owned) {
@@ -1240,10 +1256,26 @@ pub const Interpreter = struct {
}
this.this_jsvalue = .zero;
// Mark that runtime resources have been cleaned up
this.cleanup_state = .runtime_cleaned;
}
fn deinitFromFinalizer(this: *ThisInterpreter) void {
this.#derefRootShellAndIOIfNeeded(true);
log("Interpreter(0x{x}) deinitFromFinalizer (cleanup_state={s})", .{ @intFromPtr(this), @tagName(this.cleanup_state) });
switch (this.cleanup_state) {
.needs_full_cleanup => {
// The interpreter never finished normally (e.g., early error or never started),
// so we need to clean up IO and shell env here
this.root_io.deref();
this.root_shell.deinitImpl(false, true);
},
.runtime_cleaned => {
// finish() already cleaned up IO and shell env via #derefRootShellAndIOIfNeeded,
// nothing more to do for those resources
},
}
this.keep_alive.disable();
this.args.deinit();
this.allocator.destroy(this);

View File

@@ -248,4 +248,131 @@ describe("s3 - Requester Pays", () => {
expect(url.searchParams.get("x-amz-request-payer")).toBeNull();
});
it("should include x-amz-request-payer header in list() requests when requestPayer is true", async () => {
let reqHeaders: Headers | undefined = undefined;
let reqMethod: string | undefined = undefined;
using server = Bun.serve({
port: 0,
async fetch(req) {
reqHeaders = req.headers;
reqMethod = req.method;
return new Response(
`<?xml version="1.0" encoding="UTF-8"?><ListBucketResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<Name>my_bucket</Name>
</ListBucketResult>`,
{
headers: {
"Content-Type": "application/xml",
},
status: 200,
},
);
},
});
const client = new S3Client({
...s3Options,
endpoint: server.url.href,
requestPayer: true,
});
await client.list({ prefix: "test/" });
expect(reqMethod).toBe("GET");
expect(reqHeaders!.get("authorization")).toInclude("x-amz-request-payer");
expect(reqHeaders!.get("x-amz-request-payer")).toBe("requester");
});
it("should NOT include x-amz-request-payer header in list() requests when requestPayer is false", async () => {
let reqHeaders: Headers | undefined = undefined;
using server = Bun.serve({
port: 0,
async fetch(req) {
reqHeaders = req.headers;
return new Response(
`<?xml version="1.0" encoding="UTF-8"?><ListBucketResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<Name>my_bucket</Name>
</ListBucketResult>`,
{
headers: {
"Content-Type": "application/xml",
},
status: 200,
},
);
},
});
const client = new S3Client({
...s3Options,
endpoint: server.url.href,
requestPayer: false,
});
await client.list();
expect(reqHeaders!.get("authorization")).not.toInclude("x-amz-request-payer");
expect(reqHeaders!.get("x-amz-request-payer")).toBeNull();
});
it("should include x-amz-request-payer header in static list() when requestPayer is true", async () => {
let reqHeaders: Headers | undefined = undefined;
using server = Bun.serve({
port: 0,
async fetch(req) {
reqHeaders = req.headers;
return new Response(
`<?xml version="1.0" encoding="UTF-8"?><ListBucketResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<Name>my_bucket</Name>
</ListBucketResult>`,
{
headers: {
"Content-Type": "application/xml",
},
status: 200,
},
);
},
});
await S3Client.list(null, {
...s3Options,
endpoint: server.url.href,
requestPayer: true,
});
expect(reqHeaders!.get("authorization")).toInclude("x-amz-request-payer");
expect(reqHeaders!.get("x-amz-request-payer")).toBe("requester");
});
it("should NOT include x-amz-request-payer header in static list() when requestPayer is false", async () => {
let reqHeaders: Headers | undefined = undefined;
using server = Bun.serve({
port: 0,
async fetch(req) {
reqHeaders = req.headers;
return new Response(
`<?xml version="1.0" encoding="UTF-8"?><ListBucketResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<Name>my_bucket</Name>
</ListBucketResult>`,
{
headers: {
"Content-Type": "application/xml",
},
status: 200,
},
);
},
});
await S3Client.list(null, {
...s3Options,
endpoint: server.url.href,
requestPayer: false,
});
expect(reqHeaders!.get("authorization")).not.toInclude("x-amz-request-payer");
expect(reqHeaders!.get("x-amz-request-payer")).toBeNull();
});
});

View File

@@ -0,0 +1,42 @@
import { expect, test } from "bun:test";
import { existsSync, statSync } from "node:fs";
import { exists, stat } from "node:fs/promises";
// https://github.com/oven-sh/bun/issues/26631
// Path resolution fails for current directory '.' on Windows
test("existsSync('.') should return true", () => {
expect(existsSync(".")).toBe(true);
});
test("exists('.') should return true", async () => {
expect(await exists(".")).toBe(true);
});
test("statSync('.') should return directory stats", () => {
const stats = statSync(".");
expect(stats.isDirectory()).toBe(true);
});
test("stat('.') should return directory stats", async () => {
const stats = await stat(".");
expect(stats.isDirectory()).toBe(true);
});
test("existsSync('..') should return true", () => {
expect(existsSync("..")).toBe(true);
});
test("exists('..') should return true", async () => {
expect(await exists("..")).toBe(true);
});
test("statSync('..') should return directory stats", () => {
const stats = statSync("..");
expect(stats.isDirectory()).toBe(true);
});
test("stat('..') should return directory stats", async () => {
const stats = await stat("..");
expect(stats.isDirectory()).toBe(true);
});