new·The score now tells you which way it movedA brain's exam only ever grows: its own material writes questions, and so does every question a real caller asked and did not get answered. The score is a percentage over that growing set, so a brain that learned more could post a smaller number — and this week three did. One of them answered two MORE questions than the week before and showed eighteen points less. Printed as a single percentage, that reads as decline to a reader and as punishment to anyone who contributes material.all news →
mozg.beta
Sign in

Bun · Bundler · all subjects

bytecode caching

38 notes, read out of this brain and free to use. Each one was extracted from a source and is re-checked against its exam.

Bytecode caching improves startup time

Bytecode caching is a build-time optimization that pre-compiles JavaScript to bytecode to improve startup time. For example, compiling TypeScript's tsc with bytecode enabled improves startup time by 2x.

Enable bytecode caching with --bytecode flag

Bytecode caching is enabled with the --bytecode flag during build. Without --format, the output format defaults to CommonJS.

Bytecode build outputs two files

When building with --bytecode, Bun writes two files: a .js file containing the bundled JavaScript, and a .js.jsc file containing the bytecode cache. At runtime, Bun automatically detects and uses the .jsc file when the .js file is executed.

ESM bytecode requires --compile

ESM bytecode requires the --compile flag because Bun embeds module metadata (import/export information) in the compiled binary. Without --compile, ESM bytecode would still require parsing the source to analyze module dependencies, which defeats the purpose of bytecode caching. With this metadata, the JavaScript engine skips parsing entirely at runtime.

Standalone executables with --compile embed bytecode

When creating an executable with --compile, Bun embeds the bytecode in the binary. Both ESM and CommonJS work with --compile. The resulting executable contains both the code and the bytecode.

Bytecode startup improvement scales with codebase size

Performance improvement from bytecode caching scales with codebase size: small CLI (< 100 KB) sees 1.5-2x faster startup; medium-large app (> 5 MB) sees 2-4x faster startup. Larger applications benefit more because they have more code to parse.

Best uses for bytecode caching

Bytecode caching is great for: CLI tools invoked frequently where startup time is the entire user experience (TypeScript compiler, Prettier, ESLint); build tools and task runners that run hundreds or thousands of times during development where milliseconds saved per run compound quickly; and standalone executables distributed to users where single-file distribution is convenient and startup time matters more than file size.

When to skip bytecode caching

Skip bytecode caching for: small scripts, code that runs once, development builds, and size-constrained environments.

Bytecode is not portable across Bun versions

Bytecode is not portable across Bun versions. The bytecode format is tied to JavaScriptCore's internal representation, which changes between versions. When you update Bun, you must regenerate bytecode. If bytecode doesn't match the current Bun version, Bun ignores it and falls back to parsing the JavaScript source. Best practice is to generate bytecode as part of your CI/CD build process, not commit .jsc files to git, and regenerate them whenever you update Bun.

Deploy both .js and .jsc files

You must deploy both the .js file (bundled source code) and the .jsc file (bytecode cache). The .jsc file is useless without its corresponding .js file. At runtime, Bun loads the .js file and sees a @bytecode pragma, then checks the .jsc file, loads it, validates the bytecode hash matches the source, and uses the bytecode if valid or falls back to parsing the source if invalid.

Bytecode does not obfuscate source code

Bytecode does not obscure your source code. It is an optimization, not a security measure.

Verify bytecode is being used with BUN_JSC_verboseDiskCache

To log whether Bun uses the bytecode, set BUN_JSC_verboseDiskCache=1 in the environment. On a cache hit, Bun logs '[Disk Cache] Cache hit for sourceCode'. On a cache miss, Bun logs '[Disk Cache] Cache miss for sourceCode'. Several cache-miss lines are normal because Bun doesn't bytecode-cache the JavaScript in its builtin modules.

Bytecode file size is typically 2-8x larger than source

Bytecode files are typically 2-8x larger than the source code due to verbose bytecode instructions, constant pools storing all literals, per-function metadata, profiling data structures, and pre-computed control flow. The .jsc file should be 2-8x larger than the .js file.

Bytecode compresses well with gzip/brotli

Bytecode compresses well with gzip/brotli compression, achieving 60-70% compression due to its repetitive structure and metadata.

Bytecode is architecture-independent

Bytecode is architecture-independent and portable across different architectures. You can build on macOS ARM64 and deploy to Linux x64, or build on Linux x64 and deploy to AWS Lambda ARM64. The bytecode contains abstract instructions that work on any architecture.

JavaScript execution steps: parsing, bytecode compilation, execution

When you run JavaScript, the JavaScript engine goes through several steps: (1) Parsing - the engine reads source code and converts it into an Abstract Syntax Tree (AST), (2) Bytecode compilation - the engine compiles the AST into bytecode, (3) Execution - the engine's interpreter or JIT compiler executes the bytecode. With bytecode caching, steps 1 and 2 are moved to the build step, and at runtime the engine loads the pre-compiled bytecode and jumps straight to execution.

Lazy parsing optimization in JavaScript engines

Modern JavaScript engines use lazy parsing - they don't parse all code upfront. Instead, they parse each function only when it's first called. With bytecode caching, Bun pre-compiles all functions, even the ones the engine would otherwise parse lazily, eliminating parsing overhead that happens throughout the application's lifetime.

.jsc file structure: header section

The header section of a .jsc file is validated on every load and contains: (1) Cache version - a hash tied to the JavaScriptCore framework version that ensures bytecode generated with one version of Bun only runs with that exact version, (2) Code block type tag - identifies whether this is a Program, Module, Eval, or Function code block.

.jsc file structure: SourceCodeKey

The SourceCodeKey section of a .jsc file validates that bytecode matches source and contains: (1) Source code hash - a hash of the original JavaScript source code that Bun verifies before using bytecode, (2) Source code length - the exact length of the source for additional validation, (3) Compilation flags - compilation context such as strict mode, script vs. module, and eval context type; the same source compiled with different flags produces different bytecode.

.jsc file structure: bytecode instructions

The bytecode instructions section of a .jsc file contains: (1) Instruction stream - the bytecode opcodes as a variable-length sequence of instructions, (2) Metadata table - associated metadata for each opcode such as profiling counters, type hints, and execution counts, (3) Jump targets - pre-computed addresses for control flow like if/else, loops, switch statements, (4) Switch tables - optimized lookup tables for switch statements.

.jsc file structure: constants and identifiers

The constants and identifiers section of a .jsc file contains: (1) Constant pool - all literal values in the code (numbers, strings, booleans, null, undefined) stored as JavaScript values so they don't need to be parsed from source at runtime, (2) Identifier table - all variable and function names used in the code, stored as deduplicated strings, (3) Source code representation markers - flags indicating how constants should be represented (as integers, doubles, big ints, etc.).

.jsc file structure: function metadata

For each function in code, a .jsc file contains function metadata: (1) Register allocation - how many registers (local variables) the function needs, including thisRegister, scopeRegister, numVars, numCalleeLocals, numParameters, (2) Code features - a bitmask of function characteristics: is it a constructor, arrow function, uses super, has tail calls, (3) Lexically scoped features - strict mode and other lexical context, (4) Parse mode - the mode in which the function was parsed (normal, async, generator, async generator).

.jsc file structure: nested structures

A .jsc file contains nested structures for: (1) Function declarations and expressions - each nested function gets its own bytecode block, recursively; a file with 100 functions has 100 separate bytecode blocks all nested in the structure, (2) Exception handlers - try/catch/finally blocks with their boundaries and handler addresses pre-computed, (3) Expression info - maps bytecode positions back to source code locations for error reporting and debugging.

Bytecode does not embed source code

Bytecode does not embed your source code. Instead, the JavaScript source is stored separately in the .js file, and the bytecode only stores a hash and length of the source. At load time, Bun validates the bytecode matches the current source code.

Unlinked vs linked bytecode distinction

JavaScriptCore distinguishes between unlinked and linked bytecode. Unlinked bytecode (what's cached in .jsc files) contains compiled bytecode instructions, structural information, constants, identifiers, and control flow information, but doesn't contain pointers to runtime objects, JIT-compiled machine code, profiling data, or call link information. Unlinked bytecode is immutable and shareable. Linked bytecode is created at runtime and adds call link information, profiling data, JIT compilation state, and pointers to runtime objects.

Bytecode caching enables JIT optimization and profiling

By separating unlinked bytecode caching from linked bytecode creation, Bun caches the expensive work of parsing and compilation to unlinked bytecode while still collecting runtime profiling data to guide optimizations and still applying JIT optimizations based on actual execution patterns. For production CLIs and serverless deployments, combining --bytecode --minify --sourcemap provides the best startup time while keeping errors mapped to original source.

Example bytecode build with CommonJS

Example command to enable bytecode caching with CommonJS output (default format): bun build ./index.ts --target=bun --bytecode --outdir=./dist This creates dist/index.js and dist/index.js.jsc. At runtime, execute with: bun ./dist/index.js (automatically uses index.js.jsc)

Example bytecode build with standalone executables (ESM)

Example command to create an ESM executable with bytecode: bun build ./cli.ts --compile --bytecode --format=esm --outfile=mycli ESM bytecode requires --compile because Bun embeds module metadata in the binary.

Example bytecode build with standalone executables (CommonJS)

Example command to create a CommonJS executable with bytecode: bun build ./cli.ts --compile --bytecode --outfile=mycli CommonJS works with or without --compile.

Example bytecode combined with minification and source maps

Example command combining bytecode with minification and source maps: bun build --compile --bytecode --minify --sourcemap ./cli.ts --outfile=mycli This combines: --minify reduces code size before bytecode generation (less code -> less bytecode); --sourcemap preserves error reporting (errors still point to original source); --bytecode eliminates parsing overhead.

Example Docker build with bytecode generation

Docker example showing bytecode generation in a multi-stage build: FROM oven/bun:1 AS builder WORKDIR /app COPY package.json bun.lock ./ RUN bun install --frozen-lockfile COPY . . RUN bun build --bytecode --minify --sourcemap --target=bun --compile ./src/server.ts --outfile=./dist/server FROM oven/bun:1 AS runner WORKDIR /app COPY --from=builder /app/dist/server /app/server CMD ["./server"] The bytecode is architecture-independent.

Example GitHub Actions workflow for bytecode generation

GitHub Actions example to generate bytecode during build pipeline: - name: Build with bytecode run: | bun install bun build --bytecode --minify --outdir=./dist --target=bun ./src/index.ts

Common bytecode issue: silently ignored bytecode

If bytecode is silently ignored, it's usually caused by a Bun version update. The cache version doesn't match, so Bun rejects the bytecode. Regenerate the bytecode to fix this issue.

Regenerate bytecode after updating Bun

When you update Bun, you must regenerate bytecode because the bytecode format is tied to JavaScriptCore's internal representation, which changes between versions. Example command: bun build --bytecode ./index.ts --outdir=./dist

--bytecode flag enables bytecode compilation

The --bytecode flag enables bytecode compilation, moving parsing overhead for large input files from runtime to build time. This makes startup approximately 2x faster for applications like tsc. Bytecode compilation supports both cjs and esm formats with --compile.

bytecode option for improved startup times

The bytecode option generates bytecode for JavaScript/TypeScript entrypoints, which can greatly improve startup times for large applications. Requires target: 'bun' and a matching version of Bun. Generates .jsc files for CommonJS or embeds bytecode in standalone executable for ESM.

bytecode CommonJS vs ESM requirements

CommonJS bytecode works with or without compile: true and generates .jsc files alongside each entrypoint. ESM bytecode requires compile: true and embeds the bytecode and module metadata in the standalone executable.

bytecode defaults to CommonJS without explicit format

Without an explicit format option, bytecode defaults to CommonJS.

Give your agent this brain