diff --git a/.buildkite/Dockerfile b/.buildkite/Dockerfile index e2747b3f85..033aec633d 100644 --- a/.buildkite/Dockerfile +++ b/.buildkite/Dockerfile @@ -48,6 +48,14 @@ RUN --mount=type=tmpfs,target=/tmp \ wget -O /tmp/cmake.sh "$cmake_url" && \ sh /tmp/cmake.sh --skip-license --prefix=/usr +RUN --mount=type=tmpfs,target=/tmp \ + sccache_version="0.12.0" && \ + arch=$(uname -m) && \ + sccache_url="https://github.com/mozilla/sccache/releases/download/v${sccache_version}/sccache-v${sccache_version}-${arch}-unknown-linux-musl.tar.gz" && \ + wget -O /tmp/sccache.tar.gz "$sccache_url" && \ + tar -xzf /tmp/sccache.tar.gz -C /tmp && \ + install -m755 /tmp/sccache-v${sccache_version}-${arch}-unknown-linux-musl/sccache /usr/local/bin + RUN update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-13 130 \ --slave /usr/bin/g++ g++ /usr/bin/g++-13 \ --slave /usr/bin/gcc-ar gcc-ar /usr/bin/gcc-ar-13 \ @@ -110,14 +118,14 @@ ARG BUILDKITE_AGENT_TAGS # Install Rust nightly -RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y \ +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y \ && export PATH=$HOME/.cargo/bin:$PATH \ && rustup install nightly \ && rustup default nightly RUN ARCH=$(if [ "$TARGETARCH" = "arm64" ]; then echo "arm64"; else echo "amd64"; fi) && \ - echo "Downloading buildkite" && \ + echo "Downloading buildkite" && \ curl -fsSL "https://github.com/buildkite/agent/releases/download/v3.87.0/buildkite-agent-linux-${ARCH}-3.87.0.tar.gz" -o /tmp/buildkite-agent.tar.gz && \ mkdir -p /tmp/buildkite-agent && \ tar -xzf /tmp/buildkite-agent.tar.gz -C /tmp/buildkite-agent && \ @@ -125,6 +133,20 @@ RUN ARCH=$(if [ "$TARGETARCH" = "arm64" ]; then echo "arm64"; else echo "amd64"; RUN mkdir -p /var/cache/buildkite-agent /var/log/buildkite-agent /var/run/buildkite-agent /etc/buildkite-agent /var/lib/buildkite-agent/cache/bun +# The following is necessary to configure buildkite to use a stable +# checkout directory. sccache hashes absolute paths into its cache keys, +# so if buildkite uses a different checkout path each time (which it does +# by default), sccache will be useless. +RUN mkdir -p -m 755 /var/lib/buildkite-agent/hooks && \ + cat <<'EOF' > /var/lib/buildkite-agent/hooks/environment +#!/bin/sh +set -efu + +export BUILDKITE_BUILD_CHECKOUT_PATH=/var/lib/buildkite-agent/build +EOF + +RUN chmod 744 /var/lib/buildkite-agent/hooks/environment + COPY ../*/agent.mjs /var/bun/scripts/ ENV BUN_INSTALL_CACHE=/var/lib/buildkite-agent/cache/bun @@ -147,7 +169,7 @@ COPY . /workspace/bun # Install Rust nightly -RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y \ +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y \ && export PATH=$HOME/.cargo/bin:$PATH \ && rustup install nightly \ && rustup default nightly @@ -161,4 +183,4 @@ RUN --mount=type=tmpfs,target=/workspace/bun/build \ ls -la \ && bun run build:release \ && mkdir -p /target \ - && cp -r /workspace/bun/build/release/bun /target/bun \ No newline at end of file + && cp -r /workspace/bun/build/release/bun /target/bun diff --git a/.buildkite/ci.mjs b/.buildkite/ci.mjs index 6d27bb7e65..bf346e6c69 100755 --- a/.buildkite/ci.mjs +++ b/.buildkite/ci.mjs @@ -16,6 +16,7 @@ import { getEmoji, getEnv, getLastSuccessfulBuild, + getSecret, isBuildkite, isBuildManual, isFork, @@ -108,9 +109,9 @@ const buildPlatforms = [ { os: "linux", arch: "x64", distro: "amazonlinux", release: "2023", features: ["docker"] }, { os: "linux", arch: "x64", baseline: true, distro: "amazonlinux", release: "2023", features: ["docker"] }, { os: "linux", arch: "x64", profile: "asan", distro: "amazonlinux", release: "2023", features: ["docker"] }, - { os: "linux", arch: "aarch64", abi: "musl", distro: "alpine", release: "3.21" }, - { os: "linux", arch: "x64", abi: "musl", distro: "alpine", release: "3.21" }, - { os: "linux", arch: "x64", abi: "musl", baseline: true, distro: "alpine", release: "3.21" }, + { os: "linux", arch: "aarch64", abi: "musl", distro: "alpine", release: "3.22" }, + { os: "linux", arch: "x64", abi: "musl", distro: "alpine", release: "3.22" }, + { os: "linux", arch: "x64", abi: "musl", baseline: true, distro: "alpine", release: "3.22" }, { os: "windows", arch: "x64", release: "2019" }, { os: "windows", arch: "x64", baseline: true, release: "2019" }, ]; @@ -133,9 +134,9 @@ const testPlatforms = [ { os: "linux", arch: "x64", distro: "ubuntu", release: "24.04", tier: "latest" }, { os: "linux", arch: "x64", baseline: true, distro: "ubuntu", release: "25.04", tier: "latest" }, { os: "linux", arch: "x64", baseline: true, distro: "ubuntu", release: "24.04", tier: "latest" }, - { os: "linux", arch: "aarch64", abi: "musl", distro: "alpine", release: "3.21", tier: "latest" }, - { os: "linux", arch: "x64", abi: "musl", distro: "alpine", release: "3.21", tier: "latest" }, - { os: "linux", arch: "x64", abi: "musl", baseline: true, distro: "alpine", release: "3.21", tier: "latest" }, + { os: "linux", arch: "aarch64", abi: "musl", distro: "alpine", release: "3.22", tier: "latest" }, + { os: "linux", arch: "x64", abi: "musl", distro: "alpine", release: "3.22", tier: "latest" }, + { os: "linux", arch: "x64", abi: "musl", baseline: true, distro: "alpine", release: "3.22", tier: "latest" }, { os: "windows", arch: "x64", release: "2019", tier: "oldest" }, { os: "windows", arch: "x64", release: "2019", baseline: true, tier: "oldest" }, ]; @@ -223,7 +224,7 @@ function getImageName(platform, options) { * @param {number} [limit] * @link https://buildkite.com/docs/pipelines/command-step#retry-attributes */ -function getRetry(limit = 0) { +function getRetry() { return { manual: { permit_on_passed: true, @@ -292,7 +293,7 @@ function getEc2Agent(platform, options, ec2Options) { * @returns {string} */ function getCppAgent(platform, options) { - const { os, arch, distro } = platform; + const { os, arch } = platform; if (os === "darwin") { return { @@ -313,7 +314,7 @@ function getCppAgent(platform, options) { * @returns {string} */ function getLinkBunAgent(platform, options) { - const { os, arch, distro } = platform; + const { os, arch } = platform; if (os === "darwin") { return { @@ -343,7 +344,7 @@ function getZigPlatform() { arch: "aarch64", abi: "musl", distro: "alpine", - release: "3.21", + release: "3.22", }; } @@ -352,14 +353,7 @@ function getZigPlatform() { * @param {PipelineOptions} options * @returns {Agent} */ -function getZigAgent(platform, options) { - const { arch } = platform; - - // Uncomment to restore to using macOS on-prem for Zig. - // return { - // queue: "build-zig", - // }; - +function getZigAgent(_platform, options) { return getEc2Agent(getZigPlatform(), options, { instanceType: "r8g.large", }); @@ -461,23 +455,6 @@ function getBuildCommand(target, options, label) { return `bun run build:${buildProfile}`; } -/** - * @param {Platform} platform - * @param {PipelineOptions} options - * @returns {Step} - */ -function getBuildVendorStep(platform, options) { - return { - key: `${getTargetKey(platform)}-build-vendor`, - label: `${getTargetLabel(platform)} - build-vendor`, - agents: getCppAgent(platform, options), - retry: getRetry(), - cancel_on_build_failing: isMergeQueue(), - env: getBuildEnv(platform, options), - command: `${getBuildCommand(platform, options)} --target dependencies`, - }; -} - /** * @param {Platform} platform * @param {PipelineOptions} options @@ -527,9 +504,9 @@ function getBuildZigStep(platform, options) { const toolchain = getBuildToolchain(platform); return { key: `${getTargetKey(platform)}-build-zig`, + retry: getRetry(), label: `${getTargetLabel(platform)} - build-zig`, agents: getZigAgent(platform, options), - retry: getRetry(), cancel_on_build_failing: isMergeQueue(), env: getBuildEnv(platform, options), command: `${getBuildCommand(platform, options)} --target bun-zig --toolchain ${toolchain}`, @@ -579,7 +556,6 @@ function getBuildBunStep(platform, options) { /** * @typedef {Object} TestOptions * @property {string} [buildId] - * @property {boolean} [unifiedTests] * @property {string[]} [testFiles] * @property {boolean} [dryRun] */ @@ -592,7 +568,7 @@ function getBuildBunStep(platform, options) { */ function getTestBunStep(platform, options, testOptions = {}) { const { os, profile } = platform; - const { buildId, unifiedTests, testFiles } = testOptions; + const { buildId, testFiles } = testOptions; const args = [`--step=${getTargetKey(platform)}-build-bun`]; if (buildId) { @@ -614,7 +590,7 @@ function getTestBunStep(platform, options, testOptions = {}) { agents: getTestAgent(platform, options), retry: getRetry(), cancel_on_build_failing: isMergeQueue(), - parallelism: unifiedTests ? undefined : os === "darwin" ? 2 : 10, + parallelism: os === "darwin" ? 2 : 10, timeout_in_minutes: profile === "asan" || os === "windows" ? 45 : 30, env: { ASAN_OPTIONS: "allow_user_segv_handler=1:disable_coredump=0:detect_leaks=0", @@ -796,8 +772,6 @@ function getBenchmarkStep() { * @property {Platform[]} [buildPlatforms] * @property {Platform[]} [testPlatforms] * @property {string[]} [testFiles] - * @property {boolean} [unifiedBuilds] - * @property {boolean} [unifiedTests] */ /** @@ -968,22 +942,6 @@ function getOptionsStep() { default: "false", options: booleanOptions, }, - { - key: "unified-builds", - select: "Do you want to build each platform in a single step?", - hint: "If true, builds will not be split into separate steps (this will likely slow down the build)", - required: false, - default: "false", - options: booleanOptions, - }, - { - key: "unified-tests", - select: "Do you want to run tests in a single step?", - hint: "If true, tests will not be split into separate steps (this will be very slow)", - required: false, - default: "false", - options: booleanOptions, - }, ], }; } @@ -1049,8 +1007,6 @@ async function getPipelineOptions() { buildImages: parseBoolean(options["build-images"]), publishImages: parseBoolean(options["publish-images"]), testFiles: parseArray(options["test-files"]), - unifiedBuilds: parseBoolean(options["unified-builds"]), - unifiedTests: parseBoolean(options["unified-tests"]), buildPlatforms: buildPlatformKeys?.length ? buildPlatformKeys.flatMap(key => buildProfiles.map(profile => ({ ...buildPlatformsMap.get(key), profile }))) : Array.from(buildPlatformsMap.values()), @@ -1132,7 +1088,7 @@ async function getPipeline(options = {}) { }); } - let { skipBuilds, forceBuilds, unifiedBuilds, dryRun } = options; + let { skipBuilds, forceBuilds, dryRun } = options; dryRun = dryRun || !!buildImages; /** @type {string | undefined} */ @@ -1163,13 +1119,16 @@ async function getPipeline(options = {}) { dependsOn.push(`${imageKey}-build-image`); } + const steps = []; + steps.push(getBuildCppStep(target, options)); + steps.push(getBuildZigStep(target, options)); + steps.push(getLinkBunStep(target, options)); + return getStepWithDependsOn( { key: getTargetKey(target), group: getTargetLabel(target), - steps: unifiedBuilds - ? [getBuildBunStep(target, options)] - : [getBuildCppStep(target, options), getBuildZigStep(target, options), getLinkBunStep(target, options)], + steps, }, ...dependsOn, ); @@ -1178,13 +1137,13 @@ async function getPipeline(options = {}) { } if (!isMainBranch()) { - const { skipTests, forceTests, unifiedTests, testFiles } = options; + const { skipTests, forceTests, testFiles } = options; if (!skipTests || forceTests) { steps.push( ...testPlatforms.map(target => ({ key: getTargetKey(target), group: getTargetLabel(target), - steps: [getTestBunStep(target, options, { unifiedTests, testFiles, buildId })], + steps: [getTestBunStep(target, options, { testFiles, buildId })], })), ); } @@ -1227,6 +1186,43 @@ async function main() { console.log("Generated options:", options); } + startGroup("Querying GitHub for files..."); + if (options && isBuildkite && !isMainBranch()) { + /** @type {string[]} */ + let allFiles = []; + /** @type {string[]} */ + let newFiles = []; + let prFileCount = 0; + try { + console.log("on buildkite: collecting new files from PR"); + const per_page = 50; + const { BUILDKITE_PULL_REQUEST } = process.env; + for (let i = 1; i <= 10; i++) { + const res = await fetch( + `https://api.github.com/repos/oven-sh/bun/pulls/${BUILDKITE_PULL_REQUEST}/files?per_page=${per_page}&page=${i}`, + { headers: { Authorization: `Bearer ${getSecret("GITHUB_TOKEN")}` } }, + ); + const doc = await res.json(); + console.log(`-> page ${i}, found ${doc.length} items`); + if (doc.length === 0) break; + for (const { filename, status } of doc) { + prFileCount += 1; + allFiles.push(filename); + if (status !== "added") continue; + newFiles.push(filename); + } + if (doc.length < per_page) break; + } + console.log(`- PR ${BUILDKITE_PULL_REQUEST}, ${prFileCount} files, ${newFiles.length} new files`); + } catch (e) { + console.error(e); + } + if (allFiles.every(filename => filename.startsWith("docs/"))) { + console.log(`- PR is only docs, skipping tests!`); + return; + } + } + startGroup("Generating pipeline..."); const pipeline = await getPipeline(options); if (!pipeline) { diff --git a/.claude/commands/dedupe.md b/.claude/commands/dedupe.md new file mode 100644 index 0000000000..3f48f3a02a --- /dev/null +++ b/.claude/commands/dedupe.md @@ -0,0 +1,43 @@ +--- +allowed-tools: Bash(gh issue view:*), Bash(gh search:*), Bash(gh issue list:*), Bash(gh api:*), Bash(gh issue comment:*) +description: Find duplicate GitHub issues +--- + +# Issue deduplication command + +Find up to 3 likely duplicate issues for a given GitHub issue. + +To do this, follow these steps precisely: + +1. Use an agent to check if the GitHub issue (a) is closed, (b) does not need to be deduped (eg. because it is broad product feedback without a specific solution, or positive feedback), or (c) already has a duplicate detection comment (check for the exact HTML marker `` in the issue comments - ignore other bot comments). If so, do not proceed. +2. Use an agent to view a GitHub issue, and ask the agent to return a summary of the issue +3. Then, launch 5 parallel agents to search GitHub for duplicates of this issue, using diverse keywords and search approaches, using the summary from Step 2. **IMPORTANT**: Always scope searches with `repo:owner/repo` to constrain results to the current repository only. +4. Next, feed the results from Steps 2 and 3 into another agent, so that it can filter out false positives, that are likely not actually duplicates of the original issue. If there are no duplicates remaining, do not proceed. +5. Finally, comment back on the issue with a list of up to three duplicate issues (or zero, if there are no likely duplicates) + +Notes (be sure to tell this to your agents, too): + +- Use `gh` to interact with GitHub, rather than web fetch +- Do not use other tools, beyond `gh` (eg. don't use other MCP servers, file edit, etc.) +- Make a todo list first +- Always scope searches with `repo:owner/repo` to prevent cross-repo false positives +- For your comment, follow the following format precisely (assuming for this example that you found 3 suspected duplicates): + +--- + +Found 3 possible duplicate issues: + +1. +2. +3. + +This issue will be automatically closed as a duplicate in 3 days. + +- If your issue is a duplicate, please close it and 👍 the existing issue instead +- To prevent auto-closure, add a comment or 👎 this comment + +🤖 Generated with [Claude Code](https://claude.ai/code) + + + +--- diff --git a/.claude/hooks/post-edit-zig-format.js b/.claude/hooks/post-edit-zig-format.js new file mode 100755 index 0000000000..e70d5eb99e --- /dev/null +++ b/.claude/hooks/post-edit-zig-format.js @@ -0,0 +1,88 @@ +#!/usr/bin/env bun +import { extname } from "path"; +import { spawnSync } from "child_process"; + +const input = await Bun.stdin.json(); + +const toolName = input.tool_name; +const toolInput = input.tool_input || {}; +const filePath = toolInput.file_path; + +// Only process Write, Edit, and MultiEdit tools +if (!["Write", "Edit", "MultiEdit"].includes(toolName)) { + process.exit(0); +} + +const ext = extname(filePath); + +// Only format known files +if (!filePath) { + process.exit(0); +} + +function formatZigFile() { + try { + // Format the Zig file + const result = spawnSync("vendor/zig/zig.exe", ["fmt", filePath], { + cwd: process.env.CLAUDE_PROJECT_DIR || process.cwd(), + encoding: "utf-8", + }); + + if (result.error) { + console.error(`Failed to format ${filePath}: ${result.error.message}`); + process.exit(0); + } + + if (result.status !== 0) { + console.error(`zig fmt failed for ${filePath}:`); + if (result.stderr) { + console.error(result.stderr); + } + process.exit(0); + } + } catch (error) {} +} + +function formatTypeScriptFile() { + try { + // Format the TypeScript file + const result = spawnSync( + "./node_modules/.bin/prettier", + ["--plugin=prettier-plugin-organize-imports", "--config", ".prettierrc", "--write", filePath], + { + cwd: process.env.CLAUDE_PROJECT_DIR || process.cwd(), + encoding: "utf-8", + }, + ); + } catch (error) {} +} + +if (ext === ".zig") { + formatZigFile(); +} else if ( + [ + ".cjs", + ".css", + ".html", + ".js", + ".json", + ".jsonc", + ".jsx", + ".less", + ".mjs", + ".pcss", + ".postcss", + ".sass", + ".scss", + ".styl", + ".stylus", + ".toml", + ".ts", + ".tsx", + ".yaml", + ].includes(ext) +) { + formatTypeScriptFile(); +} + +process.exit(0); diff --git a/.claude/hooks/pre-bash-zig-build.js b/.claude/hooks/pre-bash-zig-build.js new file mode 100755 index 0000000000..d65b8b43d7 --- /dev/null +++ b/.claude/hooks/pre-bash-zig-build.js @@ -0,0 +1,207 @@ +#!/usr/bin/env bun +import { basename, extname } from "path"; + +const input = await Bun.stdin.json(); + +const toolName = input.tool_name; +const toolInput = input.tool_input || {}; +const command = toolInput.command || ""; +const timeout = toolInput.timeout; +const cwd = input.cwd || ""; + +// Get environment variables from the hook context +// Note: We check process.env directly as env vars are inherited +let useSystemBun = process.env.USE_SYSTEM_BUN; + +if (toolName !== "Bash" || !command) { + process.exit(0); +} + +function denyWithReason(reason) { + const output = { + hookSpecificOutput: { + hookEventName: "PreToolUse", + permissionDecision: "deny", + permissionDecisionReason: reason, + }, + }; + console.log(JSON.stringify(output)); + process.exit(0); +} + +// Parse the command to extract argv0 and positional args +let tokens; +try { + // Simple shell parsing - split on spaces but respect quotes (both single and double) + tokens = command.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g)?.map(t => t.replace(/^['"]|['"]$/g, "")) || []; +} catch { + process.exit(0); +} + +if (tokens.length === 0) { + process.exit(0); +} + +// Strip inline environment variable assignments (e.g., FOO=1 bun test) +const inlineEnv = new Map(); +let commandStart = 0; +while ( + commandStart < tokens.length && + /^[A-Za-z_][A-Za-z0-9_]*=/.test(tokens[commandStart]) && + !tokens[commandStart].includes("/") +) { + const [name, value = ""] = tokens[commandStart].split("=", 2); + inlineEnv.set(name, value); + commandStart++; +} +if (commandStart >= tokens.length) { + process.exit(0); +} +tokens = tokens.slice(commandStart); +useSystemBun = inlineEnv.get("USE_SYSTEM_BUN") ?? useSystemBun; + +// Get the executable name (argv0) +const argv0 = basename(tokens[0], extname(tokens[0])); + +// Check if it's zig or zig.exe +if (argv0 === "zig") { + // Filter out flags (starting with -) to get positional arguments + const positionalArgs = tokens.slice(1).filter(arg => !arg.startsWith("-")); + + // Check if the positional args contain "build" followed by "obj" + if (positionalArgs.length >= 2 && positionalArgs[0] === "build" && positionalArgs[1] === "obj") { + denyWithReason("error: Use `bun bd` to build Bun and wait patiently"); + } +} + +// Check if argv0 is timeout and the command is "bun bd" +if (argv0 === "timeout") { + // Find the actual command after timeout and its arguments + const timeoutArgEndIndex = tokens.slice(1).findIndex(t => !t.startsWith("-") && !/^\d/.test(t)); + if (timeoutArgEndIndex === -1) { + process.exit(0); + } + + const actualCommandIndex = timeoutArgEndIndex + 1; + if (actualCommandIndex >= tokens.length) { + process.exit(0); + } + + const actualCommand = basename(tokens[actualCommandIndex]); + const restArgs = tokens.slice(actualCommandIndex + 1); + + // Check if it's "bun bd" or "bun-debug bd" without other positional args + if (actualCommand === "bun" || actualCommand.includes("bun-debug")) { + // Claude is a sneaky fucker + let positionalArgs = restArgs.filter(arg => !arg.startsWith("-")); + const redirectStderrToStdoutIndex = positionalArgs.findIndex(arg => arg === "2>&1"); + if (redirectStderrToStdoutIndex !== -1) { + positionalArgs.splice(redirectStderrToStdoutIndex, 1); + } + const redirectStdoutToStderrIndex = positionalArgs.findIndex(arg => arg === "1>&2"); + if (redirectStdoutToStderrIndex !== -1) { + positionalArgs.splice(redirectStdoutToStderrIndex, 1); + } + + const redirectToFileIndex = positionalArgs.findIndex(arg => arg === ">"); + if (redirectToFileIndex !== -1) { + positionalArgs.splice(redirectToFileIndex, 2); + } + + const redirectToFileAppendIndex = positionalArgs.findIndex(arg => arg === ">>"); + if (redirectToFileAppendIndex !== -1) { + positionalArgs.splice(redirectToFileAppendIndex, 2); + } + + const redirectTOFileInlineIndex = positionalArgs.findIndex(arg => arg.startsWith(">")); + if (redirectTOFileInlineIndex !== -1) { + positionalArgs.splice(redirectTOFileInlineIndex, 1); + } + + const pipeIndex = positionalArgs.findIndex(arg => arg === "|"); + if (pipeIndex !== -1) { + positionalArgs = positionalArgs.slice(0, pipeIndex); + } + + positionalArgs = positionalArgs.map(arg => arg.trim()).filter(Boolean); + + if (positionalArgs.length === 1 && positionalArgs[0] === "bd") { + denyWithReason("error: Run `bun bd` without a timeout"); + } + } +} + +// Check if command is "bun .* test" or "bun-debug test" with -u/--update-snapshots AND -t/--test-name-pattern +if (argv0 === "bun" || argv0.includes("bun-debug")) { + const allArgs = tokens.slice(1); + + // Check if "test" is in positional args or "bd" followed by "test" + const positionalArgs = allArgs.filter(arg => !arg.startsWith("-")); + const hasTest = positionalArgs.includes("test") || (positionalArgs[0] === "bd" && positionalArgs[1] === "test"); + + if (hasTest) { + const hasUpdateSnapshots = allArgs.some(arg => arg === "-u" || arg === "--update-snapshots"); + const hasTestNamePattern = allArgs.some(arg => arg === "-t" || arg === "--test-name-pattern"); + + if (hasUpdateSnapshots && hasTestNamePattern) { + denyWithReason("error: Cannot use -u/--update-snapshots with -t/--test-name-pattern"); + } + } +} + +// Check if timeout option is set for "bun bd" command +if (timeout !== undefined && (argv0 === "bun" || argv0.includes("bun-debug"))) { + const positionalArgs = tokens.slice(1).filter(arg => !arg.startsWith("-")); + if (positionalArgs.length === 1 && positionalArgs[0] === "bd") { + denyWithReason("error: Run `bun bd` without a timeout"); + } +} + +// Check if running "bun test " without USE_SYSTEM_BUN=1 +if ((argv0 === "bun" || argv0.includes("bun-debug")) && useSystemBun !== "1") { + const allArgs = tokens.slice(1); + const positionalArgs = allArgs.filter(arg => !arg.startsWith("-")); + + // Check if it's "test" (not "bd test") + if (positionalArgs.length >= 1 && positionalArgs[0] === "test" && positionalArgs[0] !== "bd") { + denyWithReason( + "error: In development, use `bun bd test ` to test your changes. If you meant to use a release version, set USE_SYSTEM_BUN=1", + ); + } +} + +// Check if running "bun bd test" from bun repo root or test folder without a file path +if (argv0 === "bun" || argv0.includes("bun-debug")) { + const allArgs = tokens.slice(1); + const positionalArgs = allArgs.filter(arg => !arg.startsWith("-")); + + // Check if it's "bd test" + if (positionalArgs.length >= 2 && positionalArgs[0] === "bd" && positionalArgs[1] === "test") { + // Check if cwd is the bun repo root or test folder + const isBunRepoRoot = cwd === "/workspace/bun" || cwd.endsWith("/bun"); + const isTestFolder = cwd.endsWith("/bun/test"); + + if (isBunRepoRoot || isTestFolder) { + // Check if there's a file path argument (looks like a path: contains / or has test extension) + const hasFilePath = positionalArgs + .slice(2) + .some( + arg => + arg.includes("/") || + arg.endsWith(".test.ts") || + arg.endsWith(".test.js") || + arg.endsWith(".test.tsx") || + arg.endsWith(".test.jsx"), + ); + + if (!hasFilePath) { + denyWithReason( + "error: `bun bd test` from repo root or test folder will run all tests. Use `bun bd test ` with a specific test file.", + ); + } + } + } +} + +// Allow the command to proceed +process.exit(0); diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000000..387633d5fd --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,26 @@ +{ + "hooks": { + "PreToolUse": [ + { + "matcher": "Bash", + "hooks": [ + { + "type": "command", + "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/pre-bash-zig-build.js" + } + ] + } + ], + "PostToolUse": [ + { + "matcher": "Write|Edit|MultiEdit", + "hooks": [ + { + "type": "command", + "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/post-edit-zig-format.js" + } + ] + } + ] + } +} diff --git a/.coderabbit.yaml b/.coderabbit.yaml new file mode 100644 index 0000000000..5a0c7d0aad --- /dev/null +++ b/.coderabbit.yaml @@ -0,0 +1,143 @@ +language: en-US + +reviews: + profile: assertive + request_changes_workflow: false + high_level_summary: false + high_level_summary_placeholder: "@coderabbitai summary" + high_level_summary_in_walkthrough: true + auto_title_placeholder: "@coderabbitai" + review_status: false + commit_status: false + fail_commit_status: false + collapse_walkthrough: false + changed_files_summary: true + sequence_diagrams: false + estimate_code_review_effort: false + assess_linked_issues: true + related_issues: true + related_prs: true + suggested_labels: false + suggested_reviewers: true + in_progress_fortune: false + poem: false + abort_on_close: true + + path_filters: + - "!test/js/node/test/" + + auto_review: + enabled: true + auto_incremental_review: true + drafts: false + + finishing_touches: + docstrings: + enabled: false + unit_tests: + enabled: false + + pre_merge_checks: + docstrings: + mode: off + title: + mode: warning + description: + mode: warning + issue_assessment: + mode: warning + + tools: + shellcheck: + enabled: true + ruff: + enabled: true + markdownlint: + enabled: true + github-checks: + enabled: true + timeout_ms: 90000 + languagetool: + enabled: true + enabled_only: false + level: default + biome: + enabled: true + hadolint: + enabled: true + swiftlint: + enabled: true + phpstan: + enabled: true + level: default + phpmd: + enabled: true + phpcs: + enabled: true + golangci-lint: + enabled: true + yamllint: + enabled: true + gitleaks: + enabled: true + checkov: + enabled: true + detekt: + enabled: true + eslint: + enabled: true + flake8: + enabled: true + rubocop: + enabled: true + buf: + enabled: true + regal: + enabled: true + actionlint: + enabled: true + pmd: + enabled: true + clang: + enabled: true + cppcheck: + enabled: true + semgrep: + enabled: true + circleci: + enabled: true + clippy: + enabled: true + sqlfluff: + enabled: true + prismaLint: + enabled: true + pylint: + enabled: true + oxc: + enabled: true + shopifyThemeCheck: + enabled: true + luacheck: + enabled: true + brakeman: + enabled: true + dotenvLint: + enabled: true + htmlhint: + enabled: true + checkmake: + enabled: true + osvScanner: + enabled: true + +chat: + auto_reply: true + +knowledge_base: + opt_out: false + code_guidelines: + enabled: true + filePatterns: + - "**/.cursor/rules/*.mdc" + - "**/CLAUDE.md" diff --git a/.cursor/rules/building-bun.mdc b/.cursor/rules/building-bun.mdc index 0a5fa27f2c..2fef59b551 100644 --- a/.cursor/rules/building-bun.mdc +++ b/.cursor/rules/building-bun.mdc @@ -30,7 +30,7 @@ bun bd <...args> Debug logs look like this: ```zig -const log = bun.Output.scoped(.${SCOPE}, false); +const log = bun.Output.scoped(.${SCOPE}, .hidden); // ...later log("MY DEBUG LOG", .{}) diff --git a/.gitattributes b/.gitattributes index 50f21bf12f..e9d49f71d1 100644 --- a/.gitattributes +++ b/.gitattributes @@ -16,6 +16,7 @@ *.map text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2 *.md text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2 *.mdc text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2 +*.mdx text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2 *.mjs text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2 *.mts text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2 diff --git a/.github/workflows/auto-close-duplicates.yml b/.github/workflows/auto-close-duplicates.yml new file mode 100644 index 0000000000..886976bf6a --- /dev/null +++ b/.github/workflows/auto-close-duplicates.yml @@ -0,0 +1,29 @@ +name: Auto-close duplicate issues +on: + schedule: + - cron: "0 9 * * *" + workflow_dispatch: + +jobs: + auto-close-duplicates: + runs-on: ubuntu-latest + timeout-minutes: 10 + concurrency: + group: auto-close-duplicates-${{ github.repository }} + cancel-in-progress: true + permissions: + contents: read + issues: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Bun + uses: ./.github/actions/setup-bun + + - name: Auto-close duplicate issues + run: bun run scripts/auto-close-duplicates.ts + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_REPOSITORY: ${{ github.repository }} diff --git a/.github/workflows/claude-dedupe-issues.yml b/.github/workflows/claude-dedupe-issues.yml new file mode 100644 index 0000000000..3677f61352 --- /dev/null +++ b/.github/workflows/claude-dedupe-issues.yml @@ -0,0 +1,34 @@ +name: Claude Issue Dedupe +on: + issues: + types: [opened] + workflow_dispatch: + inputs: + issue_number: + description: 'Issue number to process for duplicate detection' + required: true + type: string + +jobs: + claude-dedupe-issues: + runs-on: ubuntu-latest + timeout-minutes: 10 + concurrency: + group: claude-dedupe-issues-${{ github.event.issue.number || inputs.issue_number }} + cancel-in-progress: true + permissions: + contents: read + issues: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Run Claude Code slash command + uses: anthropics/claude-code-base-action@beta + with: + prompt: "/dedupe ${{ github.repository }}/issues/${{ github.event.issue.number || inputs.issue_number }}" + anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} + claude_args: "--model claude-sonnet-4-5-20250929" + claude_env: | + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml index 77b5551f9d..3ab51a4309 100644 --- a/.github/workflows/claude.yml +++ b/.github/workflows/claude.yml @@ -57,8 +57,7 @@ jobs: git reset --hard origin/${{ github.event.pull_request.head.ref }} - name: Run Claude Code id: claude - # TODO: switch this out once they merge their v1 - uses: km-anthropic/claude-code-action@v1-dev + uses: anthropics/claude-code-action@v1 with: timeout_minutes: "180" claude_args: | diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml deleted file mode 100644 index a09f546fec..0000000000 --- a/.github/workflows/docs.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: Docs - -on: - push: - paths: - - "docs/**" - - "packages/bun-types/**.d.ts" - - "CONTRIBUTING.md" - - "src/cli/install.sh" - - "src/cli/install.ps1" - branches: - - main - -jobs: - deploy: - name: Deploy - runs-on: ubuntu-latest - if: ${{ github.repository_owner == 'oven-sh' }} - steps: - # redeploy Vercel site when a file in `docs` changes - # using VERCEL_DEPLOY_HOOK environment variable - - name: Trigger Webhook - run: | - curl -v ${{ secrets.VERCEL_DEPLOY_HOOK }} diff --git a/.github/workflows/labeled.yml b/.github/workflows/labeled.yml index 8b965d2f07..08c09c3741 100644 --- a/.github/workflows/labeled.yml +++ b/.github/workflows/labeled.yml @@ -142,8 +142,8 @@ jobs: uses: actions/github-script@v7 with: script: | - const closeAction = JSON.parse('${{ steps.add-labels.outputs.close-action }}'); - + const closeAction = ${{ fromJson(steps.add-labels.outputs.close-action) }}; + // Comment with the reason await github.rest.issues.createComment({ owner: context.repo.owner, @@ -151,7 +151,7 @@ jobs: issue_number: context.issue.number, body: closeAction.comment }); - + // Close the issue await github.rest.issues.update({ owner: context.repo.owner, diff --git a/.github/workflows/typos.yml b/.github/workflows/typos.yml deleted file mode 100644 index 74c2aaec89..0000000000 --- a/.github/workflows/typos.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: Typos - -on: - push: - branches: - - main - pull_request: - branches: - - main - -jobs: - docs: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Spellcheck - uses: crate-ci/typos@v1.29.4 - with: - files: docs/**/* diff --git a/.github/workflows/update-sqlite3.yml b/.github/workflows/update-sqlite3.yml index 6ee8115f7c..65321f466a 100644 --- a/.github/workflows/update-sqlite3.yml +++ b/.github/workflows/update-sqlite3.yml @@ -70,24 +70,7 @@ jobs: - name: Update SQLite if needed if: success() && steps.check-version.outputs.current_num < steps.check-version.outputs.latest_num run: | - set -euo pipefail - - TEMP_DIR=$(mktemp -d) - cd $TEMP_DIR - - echo "Downloading from: https://sqlite.org/${{ steps.check-version.outputs.latest_year }}/sqlite-amalgamation-${{ steps.check-version.outputs.latest_num }}.zip" - - # Download and extract latest version - wget "https://sqlite.org/${{ steps.check-version.outputs.latest_year }}/sqlite-amalgamation-${{ steps.check-version.outputs.latest_num }}.zip" - unzip "sqlite-amalgamation-${{ steps.check-version.outputs.latest_num }}.zip" - cd "sqlite-amalgamation-${{ steps.check-version.outputs.latest_num }}" - - # Add header comment and copy files - echo "// clang-format off" > $GITHUB_WORKSPACE/src/bun.js/bindings/sqlite/sqlite3.c - cat sqlite3.c >> $GITHUB_WORKSPACE/src/bun.js/bindings/sqlite/sqlite3.c - - echo "// clang-format off" > $GITHUB_WORKSPACE/src/bun.js/bindings/sqlite/sqlite3_local.h - cat sqlite3.h >> $GITHUB_WORKSPACE/src/bun.js/bindings/sqlite/sqlite3_local.h + ./scripts/update-sqlite-amalgamation.sh ${{ steps.check-version.outputs.latest_num }} ${{ steps.check-version.outputs.latest_year }} - name: Create Pull Request if: success() && steps.check-version.outputs.current_num < steps.check-version.outputs.latest_num diff --git a/.gitignore b/.gitignore index 3f71c2acc9..4b95245f9c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,9 @@ +.claude/settings.local.json .DS_Store .env .envrc .eslintcache +.gdb_history .idea .next .ninja_deps @@ -189,4 +191,4 @@ scratch*.{js,ts,tsx,cjs,mjs} scripts/lldb-inline # We regenerate these in all the build scripts -cmake/sources/*.txt \ No newline at end of file +cmake/sources/*.txt diff --git a/.prettierignore b/.prettierignore index 5344afdbd8..548265d59b 100644 --- a/.prettierignore +++ b/.prettierignore @@ -7,4 +7,8 @@ src/react-refresh.js *.min.js test/snippets test/js/node/test +test/napi/node-napi-tests bun.lock + +# the output codeblocks need to stay minified +docs/bundler/minifier.mdx diff --git a/.prettierrc b/.prettierrc index c9da1bd439..14285ca704 100644 --- a/.prettierrc +++ b/.prettierrc @@ -19,6 +19,12 @@ "options": { "printWidth": 80 } + }, + { + "files": ["src/codegen/bindgenv2/**/*.ts", "*.bindv2.ts"], + "options": { + "printWidth": 100 + } } ] } diff --git a/CLAUDE.md b/CLAUDE.md index 09a8499345..526996c187 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -23,28 +23,51 @@ Tip: Bun is already installed and in $PATH. The `bd` subcommand is a package.jso ### Test Organization +If a test is for a specific numbered GitHub Issue, it should be placed in `test/regression/issue/${issueNumber}.test.ts`. Ensure the issue number is **REAL** and not a placeholder! + +If no valid issue number is provided, find the best existing file to modify instead, such as; + - `test/js/bun/` - Bun-specific API tests (http, crypto, ffi, shell, etc.) - `test/js/node/` - Node.js compatibility tests - `test/js/web/` - Web API tests (fetch, WebSocket, streams, etc.) - `test/cli/` - CLI command tests (install, run, test, etc.) -- `test/regression/issue/` - Regression tests (create one per bug fix) -- `test/bundler/` - Bundler and transpiler tests +- `test/bundler/` - Bundler and transpiler tests. Use `itBundled` helper. - `test/integration/` - End-to-end integration tests - `test/napi/` - N-API compatibility tests - `test/v8/` - V8 C++ API compatibility tests ### Writing Tests -Tests use Bun's Jest-compatible test runner with proper test fixtures: +Tests use Bun's Jest-compatible test runner with proper test fixtures. + +- For **single-file tests**, prefer `-e` over `tempDir`. +- For **multi-file tests**, prefer `tempDir` and `Bun.spawn`. ```typescript import { test, expect } from "bun:test"; import { bunEnv, bunExe, normalizeBunSnapshot, tempDir } from "harness"; -test("my feature", async () => { +test("(single-file test) my feature", async () => { + await using proc = Bun.spawn({ + cmd: [bunExe(), "-e", "console.log('Hello, world!')"], + env: bunEnv, + }); + + const [stdout, stderr, exitCode] = await Promise.all([ + proc.stdout.text(), + proc.stderr.text(), + proc.exited, + ]); + + expect(normalizeBunSnapshot(stdout)).toMatchInlineSnapshot(`"Hello, world!"`); + expect(exitCode).toBe(0); +}); + +test("(multi-file test) my feature", async () => { // Create temp directory with test files using dir = tempDir("test-prefix", { - "index.js": `console.log("hello");`, + "index.js": `import { foo } from "./foo.ts"; foo();`, + "foo.ts": `export function foo() { console.log("foo"); }`, }); // Spawn Bun process @@ -61,15 +84,21 @@ test("my feature", async () => { proc.exited, ]); - expect(exitCode).toBe(0); // Prefer snapshot tests over expect(stdout).toBe("hello\n"); expect(normalizeBunSnapshot(stdout, dir)).toMatchInlineSnapshot(`"hello"`); + + // Assert the exit code last. This gives you a more useful error message on test failure. + expect(exitCode).toBe(0); }); ``` - Always use `port: 0`. Do not hardcode ports. Do not use your own random port number function. - Use `normalizeBunSnapshot` to normalize snapshot output of the test. - NEVER write tests that check for no "panic" or "uncaught exception" or similar in the test output. That is NOT a valid test. +- Use `tempDir` from `"harness"` to create a temporary directory. **Do not** use `tmpdirSync` or `fs.mkdtempSync` to create temporary directories. +- When spawning processes, tests should expect(stdout).toBe(...) BEFORE expect(exitCode).toBe(0). This gives you a more useful error message on test failure. +- **CRITICAL**: Do not write flaky tests. Do not use `setTimeout` in tests. Instead, `await` the condition to be met. You are not testing the TIME PASSING, you are testing the CONDITION. +- **CRITICAL**: Verify your test fails with `USE_SYSTEM_BUN=1 bun test ` and passes with `bun bd test `. Your test is NOT VALID if it passes with `USE_SYSTEM_BUN=1`. ## Code Architecture @@ -78,7 +107,7 @@ test("my feature", async () => { - **Zig code** (`src/*.zig`): Core runtime, JavaScript bindings, package manager - **C++ code** (`src/bun.js/bindings/*.cpp`): JavaScriptCore bindings, Web APIs - **TypeScript** (`src/js/`): Built-in JavaScript modules with special syntax (see JavaScript Modules section) -- **Generated code**: Many files are auto-generated from `.classes.ts` and other sources +- **Generated code**: Many files are auto-generated from `.classes.ts` and other sources. Bun will automatically rebuild these files when you make changes to them. ### Core Source Organization @@ -143,19 +172,6 @@ When implementing JavaScript classes in C++: 3. Add iso subspaces for classes with C++ fields 4. Cache structures in ZigGlobalObject -## Development Workflow - -### Code Formatting - -- `bun run prettier` - Format JS/TS files -- `bun run zig-format` - Format Zig files -- `bun run clang-format` - Format C++ files - -### Watching for Changes - -- `bun run watch` - Incremental Zig compilation with error checking -- `bun run watch-windows` - Windows-specific watch mode - ### Code Generation Code generation happens automatically as part of the build process. The main scripts are: @@ -177,47 +193,6 @@ Built-in JavaScript modules use special syntax and are organized as: - `internal/` - Internal modules not exposed to users - `builtins/` - Core JavaScript builtins (streams, console, etc.) -### Special Syntax in Built-in Modules - -1. **`$` prefix** - Access to private properties and JSC intrinsics: - - ```js - const arr = $Array.from(...); // Private global - map.$set(...); // Private method - const arr2 = $newArrayWithSize(5); // JSC intrinsic - ``` - -2. **`require()`** - Must use string literals, resolved at compile time: - - ```js - const fs = require("fs"); // Directly loads by numeric ID - ``` - -3. **Debug helpers**: - - `$debug()` - Like console.log but stripped in release builds - - `$assert()` - Assertions stripped in release builds - - `if($debug) {}` - Check if debug env var is set - -4. **Platform detection**: `process.platform` and `process.arch` are inlined and dead-code eliminated - -5. **Export syntax**: Use `export default` which gets converted to a return statement: - ```js - export default { - readFile, - writeFile, - }; - ``` - -Note: These are NOT ES modules. The preprocessor converts `$` to `@` (JSC's actual syntax) and handles the special functions. - -## CI - -Bun uses BuildKite for CI. To get the status of a PR, you can use the following command: - -```bash -bun ci -``` - ## Important Development Notes 1. **Never use `bun test` or `bun ` directly** - always use `bun bd test` or `bun bd `. `bun bd` compiles & runs the debug build. @@ -229,19 +204,8 @@ bun ci 7. **Avoid shell commands** - Don't use `find` or `grep` in tests; use Bun's Glob and built-in tools 8. **Memory management** - In Zig code, be careful with allocators and use defer for cleanup 9. **Cross-platform** - Run `bun run zig:check-all` to compile the Zig code on all platforms when making platform-specific changes -10. **Debug builds** - Use `BUN_DEBUG_QUIET_LOGS=1` to disable debug logging, or `BUN_DEBUG_=1` to enable specific scopes +10. **Debug builds** - Use `BUN_DEBUG_QUIET_LOGS=1` to disable debug logging, or `BUN_DEBUG_=1` to enable specific `Output.scoped(.${scopeName}, .visible)`s 11. **Be humble & honest** - NEVER overstate what you got done or what actually works in commits, PRs or in messages to the user. 12. **Branch names must start with `claude/`** - This is a requirement for the CI to work. -## Key APIs and Features - -### Bun-Specific APIs - -- **Bun.serve()** - High-performance HTTP server -- **Bun.spawn()** - Process spawning with better performance than Node.js -- **Bun.file()** - Fast file I/O operations -- **Bun.write()** - Unified API for writing to files, stdout, etc. -- **Bun.$ (Shell)** - Cross-platform shell scripting -- **Bun.SQLite** - Native SQLite integration -- **Bun.FFI** - Call native libraries from JavaScript -- **Bun.Glob** - Fast file pattern matching +**ONLY** push up changes after running `bun bd test ` and ensuring your tests pass. diff --git a/CMakeLists.txt b/CMakeLists.txt index ac6d50979f..8fe9a83f3f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,7 +24,6 @@ if(CMAKE_HOST_APPLE) include(SetupMacSDK) endif() include(SetupLLVM) -include(SetupCcache) # --- Project --- @@ -48,6 +47,16 @@ include(SetupEsbuild) include(SetupZig) include(SetupRust) +find_program(SCCACHE_PROGRAM sccache) +if(SCCACHE_PROGRAM AND NOT DEFINED ENV{NO_SCCACHE}) + include(SetupSccache) +else() + find_program(CCACHE_PROGRAM ccache) + if(CCACHE_PROGRAM) + include(SetupCcache) + endif() +endif() + # Generate dependency versions header include(GenerateDependencyVersions) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c39fd4463a..9d7785abb1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,26 +2,40 @@ Configuring a development environment for Bun can take 10-30 minutes depending o If you are using Windows, please refer to [this guide](https://bun.com/docs/project/building-windows) -## Install Dependencies +## Using Nix (Alternative) + +A Nix flake is provided as an alternative to manual dependency installation: + +```bash +nix develop +# or explicitly use the pure shell +# nix develop .#pure +export CMAKE_SYSTEM_PROCESSOR=$(uname -m) +bun bd +``` + +This provides all dependencies in an isolated, reproducible environment without requiring sudo. + +## Install Dependencies (Manual) Using your system's package manager, install Bun's dependencies: {% codetabs group="os" %} ```bash#macOS (Homebrew) -$ brew install automake ccache cmake coreutils gnu-sed go icu4c libiconv libtool ninja pkg-config rust ruby +$ brew install automake cmake coreutils gnu-sed go icu4c libiconv libtool ninja pkg-config rust ruby sccache ``` ```bash#Ubuntu/Debian -$ sudo apt install curl wget lsb-release software-properties-common cargo ccache cmake git golang libtool ninja-build pkg-config rustc ruby-full xz-utils +$ sudo apt install curl wget lsb-release software-properties-common cargo cmake git golang libtool ninja-build pkg-config rustc ruby-full xz-utils ``` ```bash#Arch -$ sudo pacman -S base-devel ccache cmake git go libiconv libtool make ninja pkg-config python rust sed unzip ruby +$ sudo pacman -S base-devel cmake git go libiconv libtool make ninja pkg-config python rust sed unzip ruby ``` ```bash#Fedora -$ sudo dnf install cargo clang19 llvm19 lld19 ccache cmake git golang libtool ninja-build pkg-config rustc ruby libatomic-static libstdc++-static sed unzip which libicu-devel 'perl(Math::BigInt)' +$ sudo dnf install cargo clang19 llvm19 lld19 cmake git golang libtool ninja-build pkg-config rustc ruby libatomic-static libstdc++-static sed unzip which libicu-devel 'perl(Math::BigInt)' ``` ```bash#openSUSE Tumbleweed @@ -51,6 +65,44 @@ $ brew install bun {% /codetabs %} +### Optional: Install `sccache` + +sccache is used to cache compilation artifacts, significantly speeding up builds. It must be installed with S3 support: + +```bash +# For macOS +$ brew install sccache + +# For Linux. Note that the version in your package manager may not have S3 support. +$ cargo install sccache --features=s3 +``` + +This will install `sccache` with S3 support. Our build scripts will automatically detect and use `sccache` with our shared S3 cache. **Note**: Not all versions of `sccache` are compiled with S3 support, hence we recommend installing it via `cargo`. + +#### Registering AWS Credentials for `sccache` (Core Developers Only) + +Core developers have write access to the shared S3 cache. To enable write access, you must log in with AWS credentials. The easiest way to do this is to use the [`aws` CLI](https://aws.amazon.com/cli/) and invoke [`aws configure` to provide your AWS security info](https://docs.aws.amazon.com/cli/latest/reference/configure/). + +The `cmake` scripts should automatically detect your AWS credentials from the environment or the `~/.aws/credentials` file. + + + Logging in to the `aws` CLI + + 1. Install the AWS CLI by following [the official guide](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html). + 2. Log in to your AWS account console. A team member should provide you with your credentials. + 3. Click your name in the top right > Security credentials. + 4. Scroll to "Access keys" and create a new access key. + 5. Run `aws configure` in your terminal and provide the access key ID and secret access key when prompted. + + + + Common Issues You May Encounter + + - To confirm that the cache is being used, you can use the `sccache --show-stats` command right after a build. This will expose very useful statistics, including cache hits/misses. + - If you have multiple AWS profiles configured, ensure that the correct profile is set in the `AWS_PROFILE` environment variable. + - `sccache` follows a server-client model. If you run into weird issues where `sccache` refuses to use S3, even though you have AWS credentials configured, try killing any running `sccache` servers with `sccache --stop-server` and then re-running the build. + + ## Install LLVM Bun requires LLVM 19 (`clang` is part of LLVM). This version requirement is to match WebKit (precompiled), as mismatching versions will cause memory allocation failures at runtime. In most cases, you can install LLVM through your system package manager: @@ -149,7 +201,7 @@ Bun generally takes about 2.5 minutes to compile a debug build when there are Zi - Batch up your changes - Ensure zls is running with incremental watching for LSP errors (if you use VSCode and install Zig and run `bun run build` once to download Zig, this should just work) - Prefer using the debugger ("CodeLLDB" in VSCode) to step through the code. -- Use debug logs. `BUN_DEBUG_=1` will enable debug logging for the corresponding `Output.scoped(., false)` logs. You can also set `BUN_DEBUG_QUIET_LOGS=1` to disable all debug logging that isn't explicitly enabled. To dump debug lgos into a file, `BUN_DEBUG=.log`. Debug logs are aggressively removed in release builds. +- Use debug logs. `BUN_DEBUG_=1` will enable debug logging for the corresponding `Output.scoped(., .hidden)` logs. You can also set `BUN_DEBUG_QUIET_LOGS=1` to disable all debug logging that isn't explicitly enabled. To dump debug lgos into a file, `BUN_DEBUG=.log`. Debug logs are aggressively removed in release builds. - src/js/\*\*.ts changes are pretty much instant to rebuild. C++ changes are a bit slower, but still much faster than the Zig code (Zig is one compilation unit, C++ is many). ## Code generation scripts @@ -317,15 +369,6 @@ $ bun run build -DUSE_STATIC_LIBATOMIC=OFF The built version of Bun may not work on other systems if compiled this way. -### ccache conflicts with building TinyCC on macOS - -If you run into issues with `ccache` when building TinyCC, try reinstalling ccache - -```bash -brew uninstall ccache -brew install ccache -``` - ## Using bun-debug - Disable logging: `BUN_DEBUG_QUIET_LOGS=1 bun-debug ...` (to disable all debug logging) diff --git a/LATEST b/LATEST index a93a6f7571..1892b92676 100644 --- a/LATEST +++ b/LATEST @@ -1 +1 @@ -1.2.23 +1.3.2 diff --git a/README.md b/README.md index 61733ac8e8..3c845722d1 100644 --- a/README.md +++ b/README.md @@ -230,7 +230,7 @@ bun upgrade --canary - Ecosystem - [Use React and JSX](https://bun.com/guides/ecosystem/react) - - [Use EdgeDB with Bun](https://bun.com/guides/ecosystem/edgedb) + - [Use Gel with Bun](https://bun.com/guides/ecosystem/gel) - [Use Prisma with Bun](https://bun.com/guides/ecosystem/prisma) - [Add Sentry to a Bun app](https://bun.com/guides/ecosystem/sentry) - [Create a Discord bot](https://bun.com/guides/ecosystem/discordjs) diff --git a/bench/install/README.md b/bench/install/README.md index e94ce66976..67943c7fb4 100644 --- a/bench/install/README.md +++ b/bench/install/README.md @@ -1,40 +1,29 @@ -# `install` benchmark +# Create T3 App -Requires [`hyperfine`](https://github.com/sharkdp/hyperfine). The goal of this benchmark is to compare installation performance of Bun with other package managers _when caches are hot_. +This is a [T3 Stack](https://create.t3.gg/) project bootstrapped with `create-t3-app`. -### With lockfile, online mode +## What's next? How do I make an app with this? -To run the benchmark with the standard "install" command for each package manager: +We try to keep this project as simple as possible, so you can start with just the scaffolding we set up for you, and add additional things later when they become necessary. -```sh -$ hyperfine --prepare 'rm -rf node_modules' --warmup 1 --runs 3 'bun install' 'pnpm install' 'yarn' 'npm install' -``` +If you are not familiar with the different technologies used in this project, please refer to the respective docs. If you still are in the wind, please join our [Discord](https://t3.gg/discord) and ask for help. -### With lockfile, offline mode +- [Next.js](https://nextjs.org) +- [NextAuth.js](https://next-auth.js.org) +- [Prisma](https://prisma.io) +- [Drizzle](https://orm.drizzle.team) +- [Tailwind CSS](https://tailwindcss.com) +- [tRPC](https://trpc.io) -Even though all packages are cached, some tools may hit the npm API during the version resolution step. (This is not the same as re-downloading a package.) To entirely avoid network calls, the other package managers require `--prefer-offline/--offline` flag. To run the benchmark using "offline" mode: +## Learn More -```sh -$ hyperfine --prepare 'rm -rf node_modules' --runs 1 'bun install' 'pnpm install --prefer-offline' 'yarn --offline' 'npm install --prefer-offline' -``` +To learn more about the [T3 Stack](https://create.t3.gg/), take a look at the following resources: -### Without lockfile, offline mode +- [Documentation](https://create.t3.gg/) +- [Learn the T3 Stack](https://create.t3.gg/en/faq#what-learning-resources-are-currently-available) — Check out these awesome tutorials -To run the benchmark with offline mode but without lockfiles: +You can check out the [create-t3-app GitHub repository](https://github.com/t3-oss/create-t3-app) — your feedback and contributions are welcome! -```sh -$ hyperfine --prepare 'rm -rf node_modules' --warmup 1 'rm bun.lock && bun install' 'rm pnpm-lock.yaml && pnpm install --prefer-offline' 'rm yarn.lock && yarn --offline' 'rm package-lock.json && npm install --prefer-offline' -``` +## How do I deploy this? -## - -To check that the app is working as expected: - -``` -$ bun run dev -$ npm run dev -$ yarn dev -$ pnpm dev -``` - -Then visit [http://localhost:3000](http://localhost:3000). +Follow our deployment guides for [Vercel](https://create.t3.gg/en/deployment/vercel), [Netlify](https://create.t3.gg/en/deployment/netlify) and [Docker](https://create.t3.gg/en/deployment/docker) for more information. diff --git a/bench/install/app/entry.client.tsx b/bench/install/app/entry.client.tsx deleted file mode 100644 index 186cd93449..0000000000 --- a/bench/install/app/entry.client.tsx +++ /dev/null @@ -1,18 +0,0 @@ -/** - * By default, Remix will handle hydrating your app on the client for you. - * You are free to delete this file if you'd like to, but if you ever want it revealed again, you can run `npx remix reveal` ✨ - * For more information, see https://remix.run/docs/en/main/file-conventions/entry.client - */ - -import { RemixBrowser } from "@remix-run/react"; -import { startTransition, StrictMode } from "react"; -import { hydrateRoot } from "react-dom/client"; - -startTransition(() => { - hydrateRoot( - document, - - - , - ); -}); diff --git a/bench/install/app/entry.server.tsx b/bench/install/app/entry.server.tsx deleted file mode 100644 index a83df79c87..0000000000 --- a/bench/install/app/entry.server.tsx +++ /dev/null @@ -1,101 +0,0 @@ -/** - * By default, Remix will handle generating the HTTP Response for you. - * You are free to delete this file if you'd like to, but if you ever want it revealed again, you can run `npx remix reveal` ✨ - * For more information, see https://remix.run/docs/en/main/file-conventions/entry.server - */ - -import type { EntryContext } from "@remix-run/node"; -import { Response } from "@remix-run/node"; -import { RemixServer } from "@remix-run/react"; -import isbot from "isbot"; -import { PassThrough } from "node:stream"; -import { renderToPipeableStream } from "react-dom/server"; - -const ABORT_DELAY = 5_000; - -export default function handleRequest( - request: Request, - responseStatusCode: number, - responseHeaders: Headers, - remixContext: EntryContext, -) { - return isbot(request.headers.get("user-agent")) - ? handleBotRequest(request, responseStatusCode, responseHeaders, remixContext) - : handleBrowserRequest(request, responseStatusCode, responseHeaders, remixContext); -} - -function handleBotRequest( - request: Request, - responseStatusCode: number, - responseHeaders: Headers, - remixContext: EntryContext, -) { - return new Promise((resolve, reject) => { - const { pipe, abort } = renderToPipeableStream( - , - { - onAllReady() { - const body = new PassThrough(); - - responseHeaders.set("Content-Type", "text/html"); - - resolve( - new Response(body, { - headers: responseHeaders, - status: responseStatusCode, - }), - ); - - pipe(body); - }, - onShellError(error: unknown) { - reject(error); - }, - onError(error: unknown) { - responseStatusCode = 500; - console.error(error); - }, - }, - ); - - setTimeout(abort, ABORT_DELAY); - }); -} - -function handleBrowserRequest( - request: Request, - responseStatusCode: number, - responseHeaders: Headers, - remixContext: EntryContext, -) { - return new Promise((resolve, reject) => { - const { pipe, abort } = renderToPipeableStream( - , - { - onShellReady() { - const body = new PassThrough(); - - responseHeaders.set("Content-Type", "text/html"); - - resolve( - new Response(body, { - headers: responseHeaders, - status: responseStatusCode, - }), - ); - - pipe(body); - }, - onShellError(error: unknown) { - reject(error); - }, - onError(error: unknown) { - console.error(error); - responseStatusCode = 500; - }, - }, - ); - - setTimeout(abort, ABORT_DELAY); - }); -} diff --git a/bench/install/app/root.tsx b/bench/install/app/root.tsx deleted file mode 100644 index 4d0236fb21..0000000000 --- a/bench/install/app/root.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { Links, LiveReload, Meta, Outlet, Scripts, ScrollRestoration } from "@remix-run/react"; - -export default function App() { - return ( - - - - - - - - - - - - - - - ); -} diff --git a/bench/install/app/routes/_index.tsx b/bench/install/app/routes/_index.tsx deleted file mode 100644 index 92fccd4ada..0000000000 --- a/bench/install/app/routes/_index.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import type { V2_MetaFunction } from "@remix-run/node"; - -export const meta: V2_MetaFunction = () => { - return [{ title: "New Remix App" }]; -}; - -export default function Index() { - return ( - - Welcome to Remix - - - - 15m Quickstart Blog Tutorial - - - - - Deep Dive Jokes App Tutorial - - - - - Remix Docs - - - - - ); -} diff --git a/bench/install/bun.lock b/bench/install/bun.lock new file mode 100644 index 0000000000..2f2b0b1451 --- /dev/null +++ b/bench/install/bun.lock @@ -0,0 +1,488 @@ +{ + "lockfileVersion": 1, + "workspaces": { + "": { + "name": "installbench", + "dependencies": { + "@auth/drizzle-adapter": "^1.7.2", + "@t3-oss/env-nextjs": "^0.12.0", + "@tanstack/react-query": "^5.69.0", + "@trpc/client": "^11.0.0", + "@trpc/react-query": "^11.0.0", + "@trpc/server": "^11.0.0", + "drizzle-orm": "^0.41.0", + "esbuild": "^0.25.11", + "next": "^15.2.3", + "next-auth": "5.0.0-beta.25", + "postgres": "^3.4.4", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "server-only": "^0.0.1", + "superjson": "^2.2.1", + "zod": "^3.24.2", + }, + "devDependencies": { + "@biomejs/biome": "1.9.4", + "@tailwindcss/postcss": "^4.0.15", + "@types/node": "^20.14.10", + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", + "drizzle-kit": "^0.30.5", + "postcss": "^8.5.3", + "tailwindcss": "^4.0.15", + "typescript": "^5.8.2", + }, + }, + }, + "packages": { + "@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="], + + "@auth/core": ["@auth/core@0.41.1", "", { "dependencies": { "@panva/hkdf": "1.2.1", "jose": "6.1.0", "oauth4webapi": "3.8.2", "preact": "10.24.3", "preact-render-to-string": "6.5.11" } }, "sha512-t9cJ2zNYAdWMacGRMT6+r4xr1uybIdmYa49calBPeTqwgAFPV/88ac9TEvCR85pvATiSPt8VaNf+Gt24JIT/uw=="], + + "@auth/drizzle-adapter": ["@auth/drizzle-adapter@1.11.1", "", { "dependencies": { "@auth/core": "0.41.1" } }, "sha512-cQTvDZqsyF7RPhDm/B6SvqdVP9EzQhy3oM4Muu7fjjmSYFLbSR203E6dH631ZHSKDn2b4WZkfMnjPDzRsPSAeA=="], + + "@biomejs/biome": ["@biomejs/biome@1.9.4", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "1.9.4", "@biomejs/cli-darwin-x64": "1.9.4", "@biomejs/cli-linux-arm64": "1.9.4", "@biomejs/cli-linux-arm64-musl": "1.9.4", "@biomejs/cli-linux-x64": "1.9.4", "@biomejs/cli-linux-x64-musl": "1.9.4", "@biomejs/cli-win32-arm64": "1.9.4", "@biomejs/cli-win32-x64": "1.9.4" }, "bin": { "biome": "bin/biome" } }, "sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog=="], + + "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@1.9.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw=="], + + "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@1.9.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg=="], + + "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@1.9.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g=="], + + "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@1.9.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA=="], + + "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@1.9.4", "", { "os": "linux", "cpu": "x64" }, "sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg=="], + + "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@1.9.4", "", { "os": "linux", "cpu": "x64" }, "sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg=="], + + "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@1.9.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg=="], + + "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@1.9.4", "", { "os": "win32", "cpu": "x64" }, "sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA=="], + + "@drizzle-team/brocli": ["@drizzle-team/brocli@0.10.2", "", {}, "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w=="], + + "@emnapi/runtime": ["@emnapi/runtime@1.6.0", "", { "dependencies": { "tslib": "2.8.1" } }, "sha512-obtUmAHTMjll499P+D9A3axeJFlhdjOWdKUNs/U6QIGT7V5RjcUW1xToAzjvmgTSQhDbYn/NwfTRoJcQ2rNBxA=="], + + "@esbuild-kit/core-utils": ["@esbuild-kit/core-utils@3.3.2", "", { "dependencies": { "esbuild": "0.18.20", "source-map-support": "0.5.21" } }, "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ=="], + + "@esbuild-kit/esm-loader": ["@esbuild-kit/esm-loader@2.6.5", "", { "dependencies": { "@esbuild-kit/core-utils": "3.3.2", "get-tsconfig": "4.13.0" } }, "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA=="], + + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.11", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Xt1dOL13m8u0WE8iplx9Ibbm+hFAO0GsU2P34UNoDGvZYkY8ifSiy6Zuc1lYxfG7svWE2fzqCUmFp5HCn51gJg=="], + + "@esbuild/android-arm": ["@esbuild/android-arm@0.25.11", "", { "os": "android", "cpu": "arm" }, "sha512-uoa7dU+Dt3HYsethkJ1k6Z9YdcHjTrSb5NUy66ZfZaSV8hEYGD5ZHbEMXnqLFlbBflLsl89Zke7CAdDJ4JI+Gg=="], + + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.11", "", { "os": "android", "cpu": "arm64" }, "sha512-9slpyFBc4FPPz48+f6jyiXOx/Y4v34TUeDDXJpZqAWQn/08lKGeD8aDp9TMn9jDz2CiEuHwfhRmGBvpnd/PWIQ=="], + + "@esbuild/android-x64": ["@esbuild/android-x64@0.25.11", "", { "os": "android", "cpu": "x64" }, "sha512-Sgiab4xBjPU1QoPEIqS3Xx+R2lezu0LKIEcYe6pftr56PqPygbB7+szVnzoShbx64MUupqoE0KyRlN7gezbl8g=="], + + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.11", "", { "os": "darwin", "cpu": "arm64" }, "sha512-VekY0PBCukppoQrycFxUqkCojnTQhdec0vevUL/EDOCnXd9LKWqD/bHwMPzigIJXPhC59Vd1WFIL57SKs2mg4w=="], + + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.11", "", { "os": "darwin", "cpu": "x64" }, "sha512-+hfp3yfBalNEpTGp9loYgbknjR695HkqtY3d3/JjSRUyPg/xd6q+mQqIb5qdywnDxRZykIHs3axEqU6l1+oWEQ=="], + + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.11", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-CmKjrnayyTJF2eVuO//uSjl/K3KsMIeYeyN7FyDBjsR3lnSJHaXlVoAK8DZa7lXWChbuOk7NjAc7ygAwrnPBhA=="], + + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.11", "", { "os": "freebsd", "cpu": "x64" }, "sha512-Dyq+5oscTJvMaYPvW3x3FLpi2+gSZTCE/1ffdwuM6G1ARang/mb3jvjxs0mw6n3Lsw84ocfo9CrNMqc5lTfGOw=="], + + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.11", "", { "os": "linux", "cpu": "arm" }, "sha512-TBMv6B4kCfrGJ8cUPo7vd6NECZH/8hPpBHHlYI3qzoYFvWu2AdTvZNuU/7hsbKWqu/COU7NIK12dHAAqBLLXgw=="], + + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.11", "", { "os": "linux", "cpu": "arm64" }, "sha512-Qr8AzcplUhGvdyUF08A1kHU3Vr2O88xxP0Tm8GcdVOUm25XYcMPp2YqSVHbLuXzYQMf9Bh/iKx7YPqECs6ffLA=="], + + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.11", "", { "os": "linux", "cpu": "ia32" }, "sha512-TmnJg8BMGPehs5JKrCLqyWTVAvielc615jbkOirATQvWWB1NMXY77oLMzsUjRLa0+ngecEmDGqt5jiDC6bfvOw=="], + + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.11", "", { "os": "linux", "cpu": "none" }, "sha512-DIGXL2+gvDaXlaq8xruNXUJdT5tF+SBbJQKbWy/0J7OhU8gOHOzKmGIlfTTl6nHaCOoipxQbuJi7O++ldrxgMw=="], + + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.11", "", { "os": "linux", "cpu": "none" }, "sha512-Osx1nALUJu4pU43o9OyjSCXokFkFbyzjXb6VhGIJZQ5JZi8ylCQ9/LFagolPsHtgw6himDSyb5ETSfmp4rpiKQ=="], + + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.11", "", { "os": "linux", "cpu": "ppc64" }, "sha512-nbLFgsQQEsBa8XSgSTSlrnBSrpoWh7ioFDUmwo158gIm5NNP+17IYmNWzaIzWmgCxq56vfr34xGkOcZ7jX6CPw=="], + + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.11", "", { "os": "linux", "cpu": "none" }, "sha512-HfyAmqZi9uBAbgKYP1yGuI7tSREXwIb438q0nqvlpxAOs3XnZ8RsisRfmVsgV486NdjD7Mw2UrFSw51lzUk1ww=="], + + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.11", "", { "os": "linux", "cpu": "s390x" }, "sha512-HjLqVgSSYnVXRisyfmzsH6mXqyvj0SA7pG5g+9W7ESgwA70AXYNpfKBqh1KbTxmQVaYxpzA/SvlB9oclGPbApw=="], + + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.11", "", { "os": "linux", "cpu": "x64" }, "sha512-HSFAT4+WYjIhrHxKBwGmOOSpphjYkcswF449j6EjsjbinTZbp8PJtjsVK1XFJStdzXdy/jaddAep2FGY+wyFAQ=="], + + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.11", "", { "os": "none", "cpu": "arm64" }, "sha512-hr9Oxj1Fa4r04dNpWr3P8QKVVsjQhqrMSUzZzf+LZcYjZNqhA3IAfPQdEh1FLVUJSiu6sgAwp3OmwBfbFgG2Xg=="], + + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.11", "", { "os": "none", "cpu": "x64" }, "sha512-u7tKA+qbzBydyj0vgpu+5h5AeudxOAGncb8N6C9Kh1N4n7wU1Xw1JDApsRjpShRpXRQlJLb9wY28ELpwdPcZ7A=="], + + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.11", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-Qq6YHhayieor3DxFOoYM1q0q1uMFYb7cSpLD2qzDSvK1NAvqFi8Xgivv0cFC6J+hWVw2teCYltyy9/m/14ryHg=="], + + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.11", "", { "os": "openbsd", "cpu": "x64" }, "sha512-CN+7c++kkbrckTOz5hrehxWN7uIhFFlmS/hqziSFVWpAzpWrQoAG4chH+nN3Be+Kzv/uuo7zhX716x3Sn2Jduw=="], + + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.11", "", { "os": "none", "cpu": "arm64" }, "sha512-rOREuNIQgaiR+9QuNkbkxubbp8MSO9rONmwP5nKncnWJ9v5jQ4JxFnLu4zDSRPf3x4u+2VN4pM4RdyIzDty/wQ=="], + + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.11", "", { "os": "sunos", "cpu": "x64" }, "sha512-nq2xdYaWxyg9DcIyXkZhcYulC6pQ2FuCgem3LI92IwMgIZ69KHeY8T4Y88pcwoLIjbed8n36CyKoYRDygNSGhA=="], + + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.11", "", { "os": "win32", "cpu": "arm64" }, "sha512-3XxECOWJq1qMZ3MN8srCJ/QfoLpL+VaxD/WfNRm1O3B4+AZ/BnLVgFbUV3eiRYDMXetciH16dwPbbHqwe1uU0Q=="], + + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.11", "", { "os": "win32", "cpu": "ia32" }, "sha512-3ukss6gb9XZ8TlRyJlgLn17ecsK4NSQTmdIXRASVsiS2sQ6zPPZklNJT5GR5tE/MUarymmy8kCEf5xPCNCqVOA=="], + + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.11", "", { "os": "win32", "cpu": "x64" }, "sha512-D7Hpz6A2L4hzsRpPaCYkQnGOotdUpDzSGRIv9I+1ITdHROSFUWW95ZPZWQmGka1Fg7W3zFJowyn9WGwMJ0+KPA=="], + + "@img/colour": ["@img/colour@1.0.0", "", {}, "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw=="], + + "@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.34.4", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.2.3" }, "os": "darwin", "cpu": "arm64" }, "sha512-sitdlPzDVyvmINUdJle3TNHl+AG9QcwiAMsXmccqsCOMZNIdW2/7S26w0LyU8euiLVzFBL3dXPwVCq/ODnf2vA=="], + + "@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.34.4", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.2.3" }, "os": "darwin", "cpu": "x64" }, "sha512-rZheupWIoa3+SOdF/IcUe1ah4ZDpKBGWcsPX6MT0lYniH9micvIU7HQkYTfrx5Xi8u+YqwLtxC/3vl8TQN6rMg=="], + + "@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.2.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-QzWAKo7kpHxbuHqUC28DZ9pIKpSi2ts2OJnoIGI26+HMgq92ZZ4vk8iJd4XsxN+tYfNJxzH6W62X5eTcsBymHw=="], + + "@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.2.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-Ju+g2xn1E2AKO6YBhxjj+ACcsPQRHT0bhpglxcEf+3uyPY+/gL8veniKoo96335ZaPo03bdDXMv0t+BBFAbmRA=="], + + "@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.2.3", "", { "os": "linux", "cpu": "arm" }, "sha512-x1uE93lyP6wEwGvgAIV0gP6zmaL/a0tGzJs/BIDDG0zeBhMnuUPm7ptxGhUbcGs4okDJrk4nxgrmxpib9g6HpA=="], + + "@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.2.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-I4RxkXU90cpufazhGPyVujYwfIm9Nk1QDEmiIsaPwdnm013F7RIceaCc87kAH+oUB1ezqEvC6ga4m7MSlqsJvQ=="], + + "@img/sharp-libvips-linux-ppc64": ["@img/sharp-libvips-linux-ppc64@1.2.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-Y2T7IsQvJLMCBM+pmPbM3bKT/yYJvVtLJGfCs4Sp95SjvnFIjynbjzsa7dY1fRJX45FTSfDksbTp6AGWudiyCg=="], + + "@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.2.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-RgWrs/gVU7f+K7P+KeHFaBAJlNkD1nIZuVXdQv6S+fNA6syCcoboNjsV2Pou7zNlVdNQoQUpQTk8SWDHUA3y/w=="], + + "@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.2.3", "", { "os": "linux", "cpu": "x64" }, "sha512-3JU7LmR85K6bBiRzSUc/Ff9JBVIFVvq6bomKE0e63UXGeRw2HPVEjoJke1Yx+iU4rL7/7kUjES4dZ/81Qjhyxg=="], + + "@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.2.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-F9q83RZ8yaCwENw1GieztSfj5msz7GGykG/BA+MOUefvER69K/ubgFHNeSyUu64amHIYKGDs4sRCMzXVj8sEyw=="], + + "@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.2.3", "", { "os": "linux", "cpu": "x64" }, "sha512-U5PUY5jbc45ANM6tSJpsgqmBF/VsL6LnxJmIf11kB7J5DctHgqm0SkuXzVWtIY90GnJxKnC/JT251TDnk1fu/g=="], + + "@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.34.4", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.2.3" }, "os": "linux", "cpu": "arm" }, "sha512-Xyam4mlqM0KkTHYVSuc6wXRmM7LGN0P12li03jAnZ3EJWZqj83+hi8Y9UxZUbxsgsK1qOEwg7O0Bc0LjqQVtxA=="], + + "@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.34.4", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.2.3" }, "os": "linux", "cpu": "arm64" }, "sha512-YXU1F/mN/Wu786tl72CyJjP/Ngl8mGHN1hST4BGl+hiW5jhCnV2uRVTNOcaYPs73NeT/H8Upm3y9582JVuZHrQ=="], + + "@img/sharp-linux-ppc64": ["@img/sharp-linux-ppc64@0.34.4", "", { "optionalDependencies": { "@img/sharp-libvips-linux-ppc64": "1.2.3" }, "os": "linux", "cpu": "ppc64" }, "sha512-F4PDtF4Cy8L8hXA2p3TO6s4aDt93v+LKmpcYFLAVdkkD3hSxZzee0rh6/+94FpAynsuMpLX5h+LRsSG3rIciUQ=="], + + "@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.34.4", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.2.3" }, "os": "linux", "cpu": "s390x" }, "sha512-qVrZKE9Bsnzy+myf7lFKvng6bQzhNUAYcVORq2P7bDlvmF6u2sCmK2KyEQEBdYk+u3T01pVsPrkj943T1aJAsw=="], + + "@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.34.4", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.2.3" }, "os": "linux", "cpu": "x64" }, "sha512-ZfGtcp2xS51iG79c6Vhw9CWqQC8l2Ot8dygxoDoIQPTat/Ov3qAa8qpxSrtAEAJW+UjTXc4yxCjNfxm4h6Xm2A=="], + + "@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.34.4", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.2.3" }, "os": "linux", "cpu": "arm64" }, "sha512-8hDVvW9eu4yHWnjaOOR8kHVrew1iIX+MUgwxSuH2XyYeNRtLUe4VNioSqbNkB7ZYQJj9rUTT4PyRscyk2PXFKA=="], + + "@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.34.4", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.2.3" }, "os": "linux", "cpu": "x64" }, "sha512-lU0aA5L8QTlfKjpDCEFOZsTYGn3AEiO6db8W5aQDxj0nQkVrZWmN3ZP9sYKWJdtq3PWPhUNlqehWyXpYDcI9Sg=="], + + "@img/sharp-wasm32": ["@img/sharp-wasm32@0.34.4", "", { "dependencies": { "@emnapi/runtime": "1.6.0" }, "cpu": "none" }, "sha512-33QL6ZO/qpRyG7woB/HUALz28WnTMI2W1jgX3Nu2bypqLIKx/QKMILLJzJjI+SIbvXdG9fUnmrxR7vbi1sTBeA=="], + + "@img/sharp-win32-arm64": ["@img/sharp-win32-arm64@0.34.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-2Q250do/5WXTwxW3zjsEuMSv5sUU4Tq9VThWKlU2EYLm4MB7ZeMwF+SFJutldYODXF6jzc6YEOC+VfX0SZQPqA=="], + + "@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.34.4", "", { "os": "win32", "cpu": "ia32" }, "sha512-3ZeLue5V82dT92CNL6rsal6I2weKw1cYu+rGKm8fOCCtJTR2gYeUfY3FqUnIJsMUPIH68oS5jmZ0NiJ508YpEw=="], + + "@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.34.4", "", { "os": "win32", "cpu": "x64" }, "sha512-xIyj4wpYs8J18sVN3mSQjwrw7fKUqRw+Z5rnHNCy5fYTxigBz81u5mOMPmFumwjcn8+ld1ppptMBCLic1nz6ig=="], + + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "1.5.5", "@jridgewell/trace-mapping": "0.3.31" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], + + "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "0.3.13", "@jridgewell/trace-mapping": "0.3.31" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="], + + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], + + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], + + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "3.1.2", "@jridgewell/sourcemap-codec": "1.5.5" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + + "@next/env": ["@next/env@15.5.6", "", {}, "sha512-3qBGRW+sCGzgbpc5TS1a0p7eNxnOarGVQhZxfvTdnV0gFI61lX7QNtQ4V1TSREctXzYn5NetbUsLvyqwLFJM6Q=="], + + "@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@15.5.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ES3nRz7N+L5Umz4KoGfZ4XX6gwHplwPhioVRc25+QNsDa7RtUF/z8wJcbuQ2Tffm5RZwuN2A063eapoJ1u4nPg=="], + + "@next/swc-darwin-x64": ["@next/swc-darwin-x64@15.5.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-JIGcytAyk9LQp2/nuVZPAtj8uaJ/zZhsKOASTjxDug0SPU9LAM3wy6nPU735M1OqacR4U20LHVF5v5Wnl9ptTA=="], + + "@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@15.5.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-qvz4SVKQ0P3/Im9zcS2RmfFL/UCQnsJKJwQSkissbngnB/12c6bZTCB0gHTexz1s6d/mD0+egPKXAIRFVS7hQg=="], + + "@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@15.5.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-FsbGVw3SJz1hZlvnWD+T6GFgV9/NYDeLTNQB2MXoPN5u9VA9OEDy6fJEfePfsUKAhJufFbZLgp0cPxMuV6SV0w=="], + + "@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@15.5.6", "", { "os": "linux", "cpu": "x64" }, "sha512-3QnHGFWlnvAgyxFxt2Ny8PTpXtQD7kVEeaFat5oPAHHI192WKYB+VIKZijtHLGdBBvc16tiAkPTDmQNOQ0dyrA=="], + + "@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@15.5.6", "", { "os": "linux", "cpu": "x64" }, "sha512-OsGX148sL+TqMK9YFaPFPoIaJKbFJJxFzkXZljIgA9hjMjdruKht6xDCEv1HLtlLNfkx3c5w2GLKhj7veBQizQ=="], + + "@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@15.5.6", "", { "os": "win32", "cpu": "arm64" }, "sha512-ONOMrqWxdzXDJNh2n60H6gGyKed42Ieu6UTVPZteXpuKbLZTH4G4eBMsr5qWgOBA+s7F+uB4OJbZnrkEDnZ5Fg=="], + + "@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@15.5.6", "", { "os": "win32", "cpu": "x64" }, "sha512-pxK4VIjFRx1MY92UycLOOw7dTdvccWsNETQ0kDHkBlcFH1GrTLUjSiHU1ohrznnux6TqRHgv5oflhfIWZwVROQ=="], + + "@panva/hkdf": ["@panva/hkdf@1.2.1", "", {}, "sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw=="], + + "@petamoriken/float16": ["@petamoriken/float16@3.9.3", "", {}, "sha512-8awtpHXCx/bNpFt4mt2xdkgtgVvKqty8VbjHI/WWWQuEw+KLzFot3f4+LkQY9YmOtq7A5GdOnqoIC8Pdygjk2g=="], + + "@swc/helpers": ["@swc/helpers@0.5.15", "", { "dependencies": { "tslib": "2.8.1" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="], + + "@t3-oss/env-core": ["@t3-oss/env-core@0.12.0", "", { "optionalDependencies": { "typescript": "5.9.3", "zod": "3.25.76" } }, "sha512-lOPj8d9nJJTt81mMuN9GMk8x5veOt7q9m11OSnCBJhwp1QrL/qR+M8Y467ULBSm9SunosryWNbmQQbgoiMgcdw=="], + + "@t3-oss/env-nextjs": ["@t3-oss/env-nextjs@0.12.0", "", { "dependencies": { "@t3-oss/env-core": "0.12.0" }, "optionalDependencies": { "typescript": "5.9.3", "zod": "3.25.76" } }, "sha512-rFnvYk1049RnNVUPvY8iQ55AuQh1Rr+qZzQBh3t++RttCGK4COpXGNxS4+45afuQq02lu+QAOy/5955aU8hRKw=="], + + "@tailwindcss/node": ["@tailwindcss/node@4.1.16", "", { "dependencies": { "@jridgewell/remapping": "2.3.5", "enhanced-resolve": "5.18.3", "jiti": "2.6.1", "lightningcss": "1.30.2", "magic-string": "0.30.21", "source-map-js": "1.2.1", "tailwindcss": "4.1.16" } }, "sha512-BX5iaSsloNuvKNHRN3k2RcCuTEgASTo77mofW0vmeHkfrDWaoFAFvNHpEgtu0eqyypcyiBkDWzSMxJhp3AUVcw=="], + + "@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.16", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.16", "@tailwindcss/oxide-darwin-arm64": "4.1.16", "@tailwindcss/oxide-darwin-x64": "4.1.16", "@tailwindcss/oxide-freebsd-x64": "4.1.16", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.16", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.16", "@tailwindcss/oxide-linux-arm64-musl": "4.1.16", "@tailwindcss/oxide-linux-x64-gnu": "4.1.16", "@tailwindcss/oxide-linux-x64-musl": "4.1.16", "@tailwindcss/oxide-wasm32-wasi": "4.1.16", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.16", "@tailwindcss/oxide-win32-x64-msvc": "4.1.16" } }, "sha512-2OSv52FRuhdlgyOQqgtQHuCgXnS8nFSYRp2tJ+4WZXKgTxqPy7SMSls8c3mPT5pkZ17SBToGM5LHEJBO7miEdg=="], + + "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.1.16", "", { "os": "android", "cpu": "arm64" }, "sha512-8+ctzkjHgwDJ5caq9IqRSgsP70xhdhJvm+oueS/yhD5ixLhqTw9fSL1OurzMUhBwE5zK26FXLCz2f/RtkISqHA=="], + + "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.1.16", "", { "os": "darwin", "cpu": "arm64" }, "sha512-C3oZy5042v2FOALBZtY0JTDnGNdS6w7DxL/odvSny17ORUnaRKhyTse8xYi3yKGyfnTUOdavRCdmc8QqJYwFKA=="], + + "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.1.16", "", { "os": "darwin", "cpu": "x64" }, "sha512-vjrl/1Ub9+JwU6BP0emgipGjowzYZMjbWCDqwA2Z4vCa+HBSpP4v6U2ddejcHsolsYxwL5r4bPNoamlV0xDdLg=="], + + "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.1.16", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TSMpPYpQLm+aR1wW5rKuUuEruc/oOX3C7H0BTnPDn7W/eMw8W+MRMpiypKMkXZfwH8wqPIRKppuZoedTtNj2tg=="], + + "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.1.16", "", { "os": "linux", "cpu": "arm" }, "sha512-p0GGfRg/w0sdsFKBjMYvvKIiKy/LNWLWgV/plR4lUgrsxFAoQBFrXkZ4C0w8IOXfslB9vHK/JGASWD2IefIpvw=="], + + "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.1.16", "", { "os": "linux", "cpu": "arm64" }, "sha512-DoixyMmTNO19rwRPdqviTrG1rYzpxgyYJl8RgQvdAQUzxC1ToLRqtNJpU/ATURSKgIg6uerPw2feW0aS8SNr/w=="], + + "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.1.16", "", { "os": "linux", "cpu": "arm64" }, "sha512-H81UXMa9hJhWhaAUca6bU2wm5RRFpuHImrwXBUvPbYb+3jo32I9VIwpOX6hms0fPmA6f2pGVlybO6qU8pF4fzQ=="], + + "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.1.16", "", { "os": "linux", "cpu": "x64" }, "sha512-ZGHQxDtFC2/ruo7t99Qo2TTIvOERULPl5l0K1g0oK6b5PGqjYMga+FcY1wIUnrUxY56h28FxybtDEla+ICOyew=="], + + "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.1.16", "", { "os": "linux", "cpu": "x64" }, "sha512-Oi1tAaa0rcKf1Og9MzKeINZzMLPbhxvm7rno5/zuP1WYmpiG0bEHq4AcRUiG2165/WUzvxkW4XDYCscZWbTLZw=="], + + "@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.1.16", "", { "cpu": "none" }, "sha512-B01u/b8LteGRwucIBmCQ07FVXLzImWESAIMcUU6nvFt/tYsQ6IHz8DmZ5KtvmwxD+iTYBtM1xwoGXswnlu9v0Q=="], + + "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.1.16", "", { "os": "win32", "cpu": "arm64" }, "sha512-zX+Q8sSkGj6HKRTMJXuPvOcP8XfYON24zJBRPlszcH1Np7xuHXhWn8qfFjIujVzvH3BHU+16jBXwgpl20i+v9A=="], + + "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.16", "", { "os": "win32", "cpu": "x64" }, "sha512-m5dDFJUEejbFqP+UXVstd4W/wnxA4F61q8SoL+mqTypId2T2ZpuxosNSgowiCnLp2+Z+rivdU0AqpfgiD7yCBg=="], + + "@tailwindcss/postcss": ["@tailwindcss/postcss@4.1.16", "", { "dependencies": { "@alloc/quick-lru": "5.2.0", "@tailwindcss/node": "4.1.16", "@tailwindcss/oxide": "4.1.16", "postcss": "8.5.6", "tailwindcss": "4.1.16" } }, "sha512-Qn3SFGPXYQMKR/UtqS+dqvPrzEeBZHrFA92maT4zijCVggdsXnDBMsPFJo1eArX3J+O+Gi+8pV4PkqjLCNBk3A=="], + + "@tanstack/query-core": ["@tanstack/query-core@5.90.5", "", {}, "sha512-wLamYp7FaDq6ZnNehypKI5fNvxHPfTYylE0m/ZpuuzJfJqhR5Pxg9gvGBHZx4n7J+V5Rg5mZxHHTlv25Zt5u+w=="], + + "@tanstack/react-query": ["@tanstack/react-query@5.90.5", "", { "dependencies": { "@tanstack/query-core": "5.90.5" }, "peerDependencies": { "react": "19.2.0" } }, "sha512-pN+8UWpxZkEJ/Rnnj2v2Sxpx1WFlaa9L6a4UO89p6tTQbeo+m0MS8oYDjbggrR8QcTyjKoYWKS3xJQGr3ExT8Q=="], + + "@trpc/client": ["@trpc/client@11.7.1", "", { "peerDependencies": { "@trpc/server": "11.7.1", "typescript": "5.9.3" } }, "sha512-uOnAjElKI892/U6aQMcBHYs3x7mme3Cvv1F87ytBL56rBvs7+DyK7r43zgaXKf13+GtPEI6ex5xjVUfyDW8XcQ=="], + + "@trpc/react-query": ["@trpc/react-query@11.7.1", "", { "peerDependencies": { "@tanstack/react-query": "5.90.5", "@trpc/client": "11.7.1", "@trpc/server": "11.7.1", "react": "19.2.0", "react-dom": "19.2.0", "typescript": "5.9.3" } }, "sha512-dEHDjIqSTzO8nLlCbtiFBMBwhbSkK1QP7aYVo3nP3sYBna0b+iCtrPXdxVPCSopr9/aIqDTEh+dMRZa7yBgjfQ=="], + + "@trpc/server": ["@trpc/server@11.7.1", "", { "peerDependencies": { "typescript": "5.9.3" } }, "sha512-N3U8LNLIP4g9C7LJ/sLkjuPHwqlvE3bnspzC4DEFVdvx2+usbn70P80E3wj5cjOTLhmhRiwJCSXhlB+MHfGeCw=="], + + "@types/cookie": ["@types/cookie@0.6.0", "", {}, "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="], + + "@types/node": ["@types/node@20.19.24", "", { "dependencies": { "undici-types": "6.21.0" } }, "sha512-FE5u0ezmi6y9OZEzlJfg37mqqf6ZDSF2V/NLjUyGrR9uTZ7Sb9F7bLNZ03S4XVUNRWGA7Ck4c1kK+YnuWjl+DA=="], + + "@types/react": ["@types/react@19.2.2", "", { "dependencies": { "csstype": "3.1.3" } }, "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA=="], + + "@types/react-dom": ["@types/react-dom@19.2.2", "", { "peerDependencies": { "@types/react": "19.2.2" } }, "sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw=="], + + "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], + + "caniuse-lite": ["caniuse-lite@1.0.30001752", "", {}, "sha512-vKUk7beoukxE47P5gcVNKkDRzXdVofotshHwfR9vmpeFKxmI5PBpgOMC18LUJUA/DvJ70Y7RveasIBraqsyO/g=="], + + "client-only": ["client-only@0.0.1", "", {}, "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="], + + "cookie": ["cookie@0.7.1", "", {}, "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w=="], + + "copy-anything": ["copy-anything@4.0.5", "", { "dependencies": { "is-what": "5.5.0" } }, "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA=="], + + "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], + + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + + "drizzle-kit": ["drizzle-kit@0.30.6", "", { "dependencies": { "@drizzle-team/brocli": "0.10.2", "@esbuild-kit/esm-loader": "2.6.5", "esbuild": "0.19.12", "esbuild-register": "3.6.0", "gel": "2.1.1" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-U4wWit0fyZuGuP7iNmRleQyK2V8wCuv57vf5l3MnG4z4fzNTjY/U13M8owyQ5RavqvqxBifWORaR3wIUzlN64g=="], + + "drizzle-orm": ["drizzle-orm@0.41.0", "", { "optionalDependencies": { "gel": "2.1.1", "postgres": "3.4.7" } }, "sha512-7A4ZxhHk9gdlXmTdPj/lREtP+3u8KvZ4yEN6MYVxBzZGex5Wtdc+CWSbu7btgF6TB0N+MNPrvW7RKBbxJchs/Q=="], + + "enhanced-resolve": ["enhanced-resolve@5.18.3", "", { "dependencies": { "graceful-fs": "4.2.11", "tapable": "2.3.0" } }, "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww=="], + + "env-paths": ["env-paths@3.0.0", "", {}, "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A=="], + + "esbuild": ["esbuild@0.25.11", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.11", "@esbuild/android-arm": "0.25.11", "@esbuild/android-arm64": "0.25.11", "@esbuild/android-x64": "0.25.11", "@esbuild/darwin-arm64": "0.25.11", "@esbuild/darwin-x64": "0.25.11", "@esbuild/freebsd-arm64": "0.25.11", "@esbuild/freebsd-x64": "0.25.11", "@esbuild/linux-arm": "0.25.11", "@esbuild/linux-arm64": "0.25.11", "@esbuild/linux-ia32": "0.25.11", "@esbuild/linux-loong64": "0.25.11", "@esbuild/linux-mips64el": "0.25.11", "@esbuild/linux-ppc64": "0.25.11", "@esbuild/linux-riscv64": "0.25.11", "@esbuild/linux-s390x": "0.25.11", "@esbuild/linux-x64": "0.25.11", "@esbuild/netbsd-arm64": "0.25.11", "@esbuild/netbsd-x64": "0.25.11", "@esbuild/openbsd-arm64": "0.25.11", "@esbuild/openbsd-x64": "0.25.11", "@esbuild/openharmony-arm64": "0.25.11", "@esbuild/sunos-x64": "0.25.11", "@esbuild/win32-arm64": "0.25.11", "@esbuild/win32-ia32": "0.25.11", "@esbuild/win32-x64": "0.25.11" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-KohQwyzrKTQmhXDW1PjCv3Tyspn9n5GcY2RTDqeORIdIJY8yKIF7sTSopFmn/wpMPW4rdPXI0UE5LJLuq3bx0Q=="], + + "esbuild-register": ["esbuild-register@3.6.0", "", { "dependencies": { "debug": "4.4.3" }, "peerDependencies": { "esbuild": "0.19.12" } }, "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg=="], + + "gel": ["gel@2.1.1", "", { "dependencies": { "@petamoriken/float16": "3.9.3", "debug": "4.4.3", "env-paths": "3.0.0", "semver": "7.7.3", "shell-quote": "1.8.3", "which": "4.0.0" }, "bin": { "gel": "dist/cli.mjs" } }, "sha512-Newg9X7mRYskoBjSw70l1YnJ/ZGbq64VPyR821H5WVkTGpHG2O0mQILxCeUhxdYERLFY9B4tUyKLyf3uMTjtKw=="], + + "get-tsconfig": ["get-tsconfig@4.13.0", "", { "dependencies": { "resolve-pkg-maps": "1.0.0" } }, "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ=="], + + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + + "is-what": ["is-what@5.5.0", "", {}, "sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw=="], + + "isexe": ["isexe@3.1.1", "", {}, "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ=="], + + "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], + + "jose": ["jose@6.1.0", "", {}, "sha512-TTQJyoEoKcC1lscpVDCSsVgYzUDg/0Bt3WE//WiTPK6uOCQC2KZS4MpugbMWt/zyjkopgZoXhZuCi00gLudfUA=="], + + "lightningcss": ["lightningcss@1.30.2", "", { "dependencies": { "detect-libc": "2.1.2" }, "optionalDependencies": { "lightningcss-android-arm64": "1.30.2", "lightningcss-darwin-arm64": "1.30.2", "lightningcss-darwin-x64": "1.30.2", "lightningcss-freebsd-x64": "1.30.2", "lightningcss-linux-arm-gnueabihf": "1.30.2", "lightningcss-linux-arm64-gnu": "1.30.2", "lightningcss-linux-arm64-musl": "1.30.2", "lightningcss-linux-x64-gnu": "1.30.2", "lightningcss-linux-x64-musl": "1.30.2", "lightningcss-win32-arm64-msvc": "1.30.2", "lightningcss-win32-x64-msvc": "1.30.2" } }, "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ=="], + + "lightningcss-android-arm64": ["lightningcss-android-arm64@1.30.2", "", { "os": "android", "cpu": "arm64" }, "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A=="], + + "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.30.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA=="], + + "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.30.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ=="], + + "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.30.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA=="], + + "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.30.2", "", { "os": "linux", "cpu": "arm" }, "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA=="], + + "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.30.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A=="], + + "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.30.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA=="], + + "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.30.2", "", { "os": "linux", "cpu": "x64" }, "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w=="], + + "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.30.2", "", { "os": "linux", "cpu": "x64" }, "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA=="], + + "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.30.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ=="], + + "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.2", "", { "os": "win32", "cpu": "x64" }, "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw=="], + + "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], + + "next": ["next@15.5.6", "", { "dependencies": { "@next/env": "15.5.6", "@swc/helpers": "0.5.15", "caniuse-lite": "1.0.30001752", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "15.5.6", "@next/swc-darwin-x64": "15.5.6", "@next/swc-linux-arm64-gnu": "15.5.6", "@next/swc-linux-arm64-musl": "15.5.6", "@next/swc-linux-x64-gnu": "15.5.6", "@next/swc-linux-x64-musl": "15.5.6", "@next/swc-win32-arm64-msvc": "15.5.6", "@next/swc-win32-x64-msvc": "15.5.6", "sharp": "0.34.4" }, "peerDependencies": { "react": "19.2.0", "react-dom": "19.2.0" }, "bin": { "next": "dist/bin/next" } }, "sha512-zTxsnI3LQo3c9HSdSf91O1jMNsEzIXDShXd4wVdg9y5shwLqBXi4ZtUUJyB86KGVSJLZx0PFONvO54aheGX8QQ=="], + + "next-auth": ["next-auth@5.0.0-beta.25", "", { "dependencies": { "@auth/core": "0.37.2" }, "peerDependencies": { "next": "15.5.6", "react": "19.2.0" } }, "sha512-2dJJw1sHQl2qxCrRk+KTQbeH+izFbGFPuJj5eGgBZFYyiYYtvlrBeUw1E/OJJxTRjuxbSYGnCTkUIRsIIW0bog=="], + + "oauth4webapi": ["oauth4webapi@3.8.2", "", {}, "sha512-FzZZ+bht5X0FKe7Mwz3DAVAmlH1BV5blSak/lHMBKz0/EBMhX6B10GlQYI51+oRp8ObJaX0g6pXrAxZh5s8rjw=="], + + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + + "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "3.3.11", "picocolors": "1.1.1", "source-map-js": "1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], + + "postgres": ["postgres@3.4.7", "", {}, "sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw=="], + + "preact": ["preact@10.24.3", "", {}, "sha512-Z2dPnBnMUfyQfSQ+GBdsGa16hz35YmLmtTLhM169uW944hYL6xzTYkJjC07j+Wosz733pMWx0fgON3JNw1jJQA=="], + + "preact-render-to-string": ["preact-render-to-string@6.5.11", "", { "peerDependencies": { "preact": "10.24.3" } }, "sha512-ubnauqoGczeGISiOh6RjX0/cdaF8v/oDXIjO85XALCQjwQP+SB4RDXXtvZ6yTYSjG+PC1QRP2AhPgCEsM2EvUw=="], + + "pretty-format": ["pretty-format@3.8.0", "", {}, "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew=="], + + "react": ["react@19.2.0", "", {}, "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ=="], + + "react-dom": ["react-dom@19.2.0", "", { "dependencies": { "scheduler": "0.27.0" }, "peerDependencies": { "react": "19.2.0" } }, "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ=="], + + "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], + + "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], + + "semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + + "server-only": ["server-only@0.0.1", "", {}, "sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA=="], + + "sharp": ["sharp@0.34.4", "", { "dependencies": { "@img/colour": "1.0.0", "detect-libc": "2.1.2", "semver": "7.7.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.4", "@img/sharp-darwin-x64": "0.34.4", "@img/sharp-libvips-darwin-arm64": "1.2.3", "@img/sharp-libvips-darwin-x64": "1.2.3", "@img/sharp-libvips-linux-arm": "1.2.3", "@img/sharp-libvips-linux-arm64": "1.2.3", "@img/sharp-libvips-linux-ppc64": "1.2.3", "@img/sharp-libvips-linux-s390x": "1.2.3", "@img/sharp-libvips-linux-x64": "1.2.3", "@img/sharp-libvips-linuxmusl-arm64": "1.2.3", "@img/sharp-libvips-linuxmusl-x64": "1.2.3", "@img/sharp-linux-arm": "0.34.4", "@img/sharp-linux-arm64": "0.34.4", "@img/sharp-linux-ppc64": "0.34.4", "@img/sharp-linux-s390x": "0.34.4", "@img/sharp-linux-x64": "0.34.4", "@img/sharp-linuxmusl-arm64": "0.34.4", "@img/sharp-linuxmusl-x64": "0.34.4", "@img/sharp-wasm32": "0.34.4", "@img/sharp-win32-arm64": "0.34.4", "@img/sharp-win32-ia32": "0.34.4", "@img/sharp-win32-x64": "0.34.4" } }, "sha512-FUH39xp3SBPnxWvd5iib1X8XY7J0K0X7d93sie9CJg2PO8/7gmg89Nve6OjItK53/MlAushNNxteBYfM6DEuoA=="], + + "shell-quote": ["shell-quote@1.8.3", "", {}, "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw=="], + + "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], + + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + + "source-map-support": ["source-map-support@0.5.21", "", { "dependencies": { "buffer-from": "1.1.2", "source-map": "0.6.1" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="], + + "styled-jsx": ["styled-jsx@5.1.6", "", { "dependencies": { "client-only": "0.0.1" }, "peerDependencies": { "react": "19.2.0" } }, "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA=="], + + "superjson": ["superjson@2.2.5", "", { "dependencies": { "copy-anything": "4.0.5" } }, "sha512-zWPTX96LVsA/eVYnqOM2+ofcdPqdS1dAF1LN4TS2/MWuUpfitd9ctTa87wt4xrYnZnkLtS69xpBdSxVBP5Rm6w=="], + + "tailwindcss": ["tailwindcss@4.1.16", "", {}, "sha512-pONL5awpaQX4LN5eiv7moSiSPd/DLDzKVRJz8Q9PgzmAdd1R4307GQS2ZpfiN7ZmekdQrfhZZiSE5jkLR4WNaA=="], + + "tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="], + + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + + "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], + + "which": ["which@4.0.0", "", { "dependencies": { "isexe": "3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg=="], + + "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], + + "@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="], + + "drizzle-kit/esbuild": ["esbuild@0.19.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.19.12", "@esbuild/android-arm": "0.19.12", "@esbuild/android-arm64": "0.19.12", "@esbuild/android-x64": "0.19.12", "@esbuild/darwin-arm64": "0.19.12", "@esbuild/darwin-x64": "0.19.12", "@esbuild/freebsd-arm64": "0.19.12", "@esbuild/freebsd-x64": "0.19.12", "@esbuild/linux-arm": "0.19.12", "@esbuild/linux-arm64": "0.19.12", "@esbuild/linux-ia32": "0.19.12", "@esbuild/linux-loong64": "0.19.12", "@esbuild/linux-mips64el": "0.19.12", "@esbuild/linux-ppc64": "0.19.12", "@esbuild/linux-riscv64": "0.19.12", "@esbuild/linux-s390x": "0.19.12", "@esbuild/linux-x64": "0.19.12", "@esbuild/netbsd-x64": "0.19.12", "@esbuild/openbsd-x64": "0.19.12", "@esbuild/sunos-x64": "0.19.12", "@esbuild/win32-arm64": "0.19.12", "@esbuild/win32-ia32": "0.19.12", "@esbuild/win32-x64": "0.19.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg=="], + + "next/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "3.3.11", "picocolors": "1.1.1", "source-map-js": "1.2.1" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="], + + "next-auth/@auth/core": ["@auth/core@0.37.2", "", { "dependencies": { "@panva/hkdf": "1.2.1", "@types/cookie": "0.6.0", "cookie": "0.7.1", "jose": "5.10.0", "oauth4webapi": "3.8.2", "preact": "10.11.3", "preact-render-to-string": "5.2.3" } }, "sha512-kUvzyvkcd6h1vpeMAojK2y7+PAV5H+0Cc9+ZlKYDFhDY31AlvsB+GW5vNO4qE3Y07KeQgvNO9U0QUx/fN62kBw=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.18.20", "", { "os": "android", "cpu": "arm" }, "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.18.20", "", { "os": "android", "cpu": "arm64" }, "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.18.20", "", { "os": "android", "cpu": "x64" }, "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.18.20", "", { "os": "darwin", "cpu": "arm64" }, "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.18.20", "", { "os": "darwin", "cpu": "x64" }, "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.18.20", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.18.20", "", { "os": "freebsd", "cpu": "x64" }, "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.18.20", "", { "os": "linux", "cpu": "arm" }, "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.18.20", "", { "os": "linux", "cpu": "arm64" }, "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.18.20", "", { "os": "linux", "cpu": "ia32" }, "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.18.20", "", { "os": "linux", "cpu": "ppc64" }, "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.18.20", "", { "os": "linux", "cpu": "s390x" }, "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.18.20", "", { "os": "linux", "cpu": "x64" }, "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.18.20", "", { "os": "none", "cpu": "x64" }, "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.18.20", "", { "os": "openbsd", "cpu": "x64" }, "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.18.20", "", { "os": "sunos", "cpu": "x64" }, "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.18.20", "", { "os": "win32", "cpu": "arm64" }, "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.18.20", "", { "os": "win32", "cpu": "ia32" }, "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.18.20", "", { "os": "win32", "cpu": "x64" }, "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ=="], + + "drizzle-kit/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.19.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA=="], + + "drizzle-kit/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.19.12", "", { "os": "android", "cpu": "arm" }, "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w=="], + + "drizzle-kit/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.19.12", "", { "os": "android", "cpu": "arm64" }, "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA=="], + + "drizzle-kit/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.19.12", "", { "os": "android", "cpu": "x64" }, "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew=="], + + "drizzle-kit/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.19.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g=="], + + "drizzle-kit/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.19.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A=="], + + "drizzle-kit/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.19.12", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA=="], + + "drizzle-kit/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.19.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg=="], + + "drizzle-kit/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.19.12", "", { "os": "linux", "cpu": "arm" }, "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w=="], + + "drizzle-kit/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.19.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA=="], + + "drizzle-kit/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.19.12", "", { "os": "linux", "cpu": "ia32" }, "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA=="], + + "drizzle-kit/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.19.12", "", { "os": "linux", "cpu": "none" }, "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA=="], + + "drizzle-kit/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.19.12", "", { "os": "linux", "cpu": "none" }, "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w=="], + + "drizzle-kit/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.19.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg=="], + + "drizzle-kit/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.19.12", "", { "os": "linux", "cpu": "none" }, "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg=="], + + "drizzle-kit/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.19.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg=="], + + "drizzle-kit/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.19.12", "", { "os": "linux", "cpu": "x64" }, "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg=="], + + "drizzle-kit/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.19.12", "", { "os": "none", "cpu": "x64" }, "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA=="], + + "drizzle-kit/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.19.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw=="], + + "drizzle-kit/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.19.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA=="], + + "drizzle-kit/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.19.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A=="], + + "drizzle-kit/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.19.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ=="], + + "drizzle-kit/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.19.12", "", { "os": "win32", "cpu": "x64" }, "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA=="], + + "next-auth/@auth/core/jose": ["jose@5.10.0", "", {}, "sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg=="], + + "next-auth/@auth/core/preact": ["preact@10.11.3", "", {}, "sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg=="], + + "next-auth/@auth/core/preact-render-to-string": ["preact-render-to-string@5.2.3", "", { "dependencies": { "pretty-format": "3.8.0" }, "peerDependencies": { "preact": "10.11.3" } }, "sha512-aPDxUn5o3GhWdtJtW0svRC2SS/l8D9MAgo2+AWml+BhDImb27ALf04Q2d+AHqUUOc6RdSXFIBVa2gxzgMKgtZA=="], + } +} diff --git a/bench/install/next-env.d.ts b/bench/install/next-env.d.ts new file mode 100644 index 0000000000..4f11a03dc6 --- /dev/null +++ b/bench/install/next-env.d.ts @@ -0,0 +1,5 @@ +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/bench/install/next.config.js b/bench/install/next.config.js new file mode 100644 index 0000000000..121c4f4c24 --- /dev/null +++ b/bench/install/next.config.js @@ -0,0 +1,10 @@ +/** + * Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially useful + * for Docker builds. + */ +import "./src/env.js"; + +/** @type {import("next").NextConfig} */ +const config = {}; + +export default config; diff --git a/bench/install/package.json b/bench/install/package.json index 02bb8e92fe..9db93cd9c4 100644 --- a/bench/install/package.json +++ b/bench/install/package.json @@ -1,31 +1,52 @@ { + "name": "installbench", + "version": "0.1.0", "private": true, - "sideEffects": false, + "type": "module", "scripts": { - "build": "remix build", - "dev": "remix dev", - "start": "remix-serve build", - "typecheck": "tsc", - "clean": "rm -rf node_modules", - "bench": "hyperfine --prepare 'rm -rf node_modules' --warmup 1 --runs 3 'bun install' 'pnpm install' 'yarn' 'npm install'" + "build": "next build", + "check": "biome check .", + "check:unsafe": "biome check --write --unsafe .", + "check:write": "biome check --write .", + "db:generate": "drizzle-kit generate", + "db:migrate": "drizzle-kit migrate", + "db:push": "drizzle-kit push", + "db:studio": "drizzle-kit studio", + "dev": "next dev --turbo", + "preview": "next build && next start", + "start": "next start", + "typecheck": "tsc --noEmit" }, "dependencies": { - "@remix-run/node": "^1.15.0", - "@remix-run/react": "^1.15.0", - "@remix-run/serve": "^1.15.0", - "isbot": "^3.6.5", - "react": "^18.2.0", - "react-dom": "^18.2.0" + "@auth/drizzle-adapter": "^1.7.2", + "@t3-oss/env-nextjs": "^0.12.0", + "@tanstack/react-query": "^5.69.0", + "@trpc/client": "^11.0.0", + "@trpc/react-query": "^11.0.0", + "@trpc/server": "^11.0.0", + "drizzle-orm": "^0.41.0", + "esbuild": "^0.25.11", + "next": "^15.2.3", + "next-auth": "5.0.0-beta.25", + "postgres": "^3.4.4", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "server-only": "^0.0.1", + "superjson": "^2.2.1", + "zod": "^3.24.2" }, "devDependencies": { - "@remix-run/dev": "^1.15.0", - "@remix-run/eslint-config": "^1.15.0", - "@types/react": "^18.0.25", - "@types/react-dom": "^18.0.8", - "eslint": "^8.27.0", - "typescript": "^4.8.4" + "@biomejs/biome": "1.9.4", + "@tailwindcss/postcss": "^4.0.15", + "@types/node": "^20.14.10", + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", + "drizzle-kit": "^0.30.5", + "postcss": "^8.5.3", + "tailwindcss": "^4.0.15", + "typescript": "^5.8.2" }, - "engines": { - "node": ">=14" + "ct3aMetadata": { + "initVersion": "7.39.3" } } diff --git a/bench/install/public/favicon.ico b/bench/install/public/favicon.ico deleted file mode 100644 index 8830cf6821..0000000000 Binary files a/bench/install/public/favicon.ico and /dev/null differ diff --git a/bench/install/remix.config.js b/bench/install/remix.config.js deleted file mode 100644 index a1a396661b..0000000000 --- a/bench/install/remix.config.js +++ /dev/null @@ -1,14 +0,0 @@ -/** @type {import('@remix-run/dev').AppConfig} */ -module.exports = { - ignoredRouteFiles: ["**/.*"], - // appDirectory: "app", - // assetsBuildDirectory: "public/build", - // serverBuildPath: "build/index.js", - // publicPath: "/build/", - future: { - v2_errorBoundary: true, - v2_meta: true, - v2_normalizeFormMethod: true, - v2_routeConvention: true, - }, -}; diff --git a/bench/install/remix.env.d.ts b/bench/install/remix.env.d.ts deleted file mode 100644 index dcf8c45e1d..0000000000 --- a/bench/install/remix.env.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -/// -/// diff --git a/bench/install/tsconfig.json b/bench/install/tsconfig.json deleted file mode 100644 index 20f8a386a6..0000000000 --- a/bench/install/tsconfig.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "include": ["remix.env.d.ts", "**/*.ts", "**/*.tsx"], - "compilerOptions": { - "lib": ["DOM", "DOM.Iterable", "ES2019"], - "isolatedModules": true, - "esModuleInterop": true, - "jsx": "react-jsx", - "moduleResolution": "node", - "resolveJsonModule": true, - "target": "ES2019", - "strict": true, - "allowJs": true, - "forceConsistentCasingInFileNames": true, - "baseUrl": ".", - "paths": { - "~/*": ["./app/*"] - }, - - // Remix takes care of building everything in `remix build`. - "noEmit": true - } -} diff --git a/bench/react-hello-world/bun.lock b/bench/react-hello-world/bun.lock index 56594f42eb..218c02e565 100644 --- a/bench/react-hello-world/bun.lock +++ b/bench/react-hello-world/bun.lock @@ -4,20 +4,16 @@ "": { "name": "react-hello-world", "dependencies": { - "react": "next", - "react-dom": "next", + "react": "^19.2.0", + "react-dom": "^19.2.0", }, }, }, "packages": { - "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + "react": ["react@19.2.0", "", {}, "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ=="], - "loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="], + "react-dom": ["react-dom@19.2.0", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.0" } }, "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ=="], - "react": ["react@18.3.0-next-b72ed698f-20230303", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-l6RbwXa9Peerh9pQEq62DDypxSQfavbybY0wV1vwZ63X0P5VaaEesZAz1KPpnVvXjTtQaOMQsIPvnQwmaVqzTQ=="], - - "react-dom": ["react-dom@18.3.0-next-b72ed698f-20230303", "", { "dependencies": { "loose-envify": "^1.1.0", "scheduler": "0.24.0-next-b72ed698f-20230303" }, "peerDependencies": { "react": "18.3.0-next-b72ed698f-20230303" } }, "sha512-0Gh/gmTT6H8KxswIQB/8shdTTfs6QIu86nNqZf3Y0RBqIwgTVxRaQVz14/Fw4/Nt81nK/Jt6KT4bx3yvOxZDGQ=="], - - "scheduler": ["scheduler@0.24.0-next-b72ed698f-20230303", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-ct4DMMFbc2kFxCdvbG+i/Jn1S1oqrIFSn2VX/mam+Ya0iuNy+lb8rgT7A+YBUqrQNDaNEqABYI2sOQgqoRxp7w=="], + "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], } } diff --git a/bench/react-hello-world/package.json b/bench/react-hello-world/package.json index b114852054..a30f9dd18b 100644 --- a/bench/react-hello-world/package.json +++ b/bench/react-hello-world/package.json @@ -4,13 +4,14 @@ "description": "", "main": "react-hello-world.node.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "echo \"Error: no test specified\" && exit 1", + "build:workerd": "bun build react-hello-world.workerd.jsx --outfile=react-hello-world.workerd.js --format=esm --production && (echo '// MessageChannel polyfill for workerd'; echo 'if (typeof MessageChannel === \"undefined\") {'; echo ' globalThis.MessageChannel = class MessageChannel {'; echo ' constructor() {'; echo ' this.port1 = { onmessage: null, postMessage: () => {} };'; echo ' this.port2 = {'; echo ' postMessage: (msg) => {'; echo ' if (this.port1.onmessage) {'; echo ' queueMicrotask(() => this.port1.onmessage({ data: msg }));'; echo ' }'; echo ' }'; echo ' };'; echo ' }'; echo ' };'; echo '}'; cat react-hello-world.workerd.js) > temp.js && mv temp.js react-hello-world.workerd.js" }, "keywords": [], "author": "Colin McDonnell", "license": "ISC", "dependencies": { - "react": "next", - "react-dom": "next" + "react": "^19.2.0", + "react-dom": "^19.2.0" } } diff --git a/bench/react-hello-world/react-hello-world.workerd.config.capnp b/bench/react-hello-world/react-hello-world.workerd.config.capnp new file mode 100644 index 0000000000..50d09f42c2 --- /dev/null +++ b/bench/react-hello-world/react-hello-world.workerd.config.capnp @@ -0,0 +1,23 @@ +using Workerd = import "/workerd/workerd.capnp"; + +const config :Workerd.Config = ( + services = [ + (name = "main", worker = .mainWorker), + ], + + sockets = [ + ( name = "http", + address = "*:3001", + http = (), + service = "main" + ), + ] +); + +const mainWorker :Workerd.Worker = ( + modules = [ + (name = "worker", esModule = embed "react-hello-world.workerd.js"), + ], + compatibilityDate = "2025-01-01", + compatibilityFlags = ["nodejs_compat_v2"], +); diff --git a/bench/react-hello-world/react-hello-world.workerd.js b/bench/react-hello-world/react-hello-world.workerd.js new file mode 100644 index 0000000000..0f7856b098 --- /dev/null +++ b/bench/react-hello-world/react-hello-world.workerd.js @@ -0,0 +1,68 @@ +// MessageChannel polyfill for workerd +if (typeof MessageChannel === 'undefined') { + globalThis.MessageChannel = class MessageChannel { + constructor() { + this.port1 = { onmessage: null, postMessage: () => {} }; + this.port2 = { + postMessage: (msg) => { + if (this.port1.onmessage) { + queueMicrotask(() => this.port1.onmessage({ data: msg })); + } + } + }; + } + }; +} +var iC=Object.create;var{getPrototypeOf:tC,defineProperty:XE,getOwnPropertyNames:JC}=Object;var VC=Object.prototype.hasOwnProperty;var Dc=(f,u,c)=>{c=f!=null?iC(tC(f)):{};let y=u||!f||!f.__esModule?XE(c,"default",{value:f,enumerable:!0}):c;for(let _ of JC(f))if(!VC.call(y,_))XE(y,_,{get:()=>f[_],enumerable:!0});return y};var wx=(f,u)=>()=>(u||f((u={exports:{}}).exports,u),u.exports);var BE=(f,u)=>{for(var c in u)XE(f,c,{get:u[c],enumerable:!0,configurable:!0,set:(y)=>u[c]=()=>y})};var SC=(f,u)=>()=>(f&&(u=f(f=0)),u);var Dy=wx((_R)=>{var ZE=Symbol.for("react.transitional.element"),FC=Symbol.for("react.portal"),KC=Symbol.for("react.fragment"),kC=Symbol.for("react.strict_mode"),dC=Symbol.for("react.profiler"),lC=Symbol.for("react.consumer"),bC=Symbol.for("react.context"),pC=Symbol.for("react.forward_ref"),qC=Symbol.for("react.suspense"),oC=Symbol.for("react.memo"),Ux=Symbol.for("react.lazy"),eC=Symbol.for("react.activity"),mx=Symbol.iterator;function aC(f){if(f===null||typeof f!=="object")return null;return f=mx&&f[mx]||f["@@iterator"],typeof f==="function"?f:null}var rx={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},Lx=Object.assign,Yx={};function zc(f,u,c){this.props=f,this.context=u,this.refs=Yx,this.updater=c||rx}zc.prototype.isReactComponent={};zc.prototype.setState=function(f,u){if(typeof f!=="object"&&typeof f!=="function"&&f!=null)throw Error("takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,f,u,"setState")};zc.prototype.forceUpdate=function(f){this.updater.enqueueForceUpdate(this,f,"forceUpdate")};function Nx(){}Nx.prototype=zc.prototype;function hE(f,u,c){this.props=f,this.context=u,this.refs=Yx,this.updater=c||rx}var iE=hE.prototype=new Nx;iE.constructor=hE;Lx(iE,zc.prototype);iE.isPureReactComponent=!0;var Mx=Array.isArray;function QE(){}var K={H:null,A:null,T:null,S:null},Dx=Object.prototype.hasOwnProperty;function tE(f,u,c){var y=c.ref;return{$$typeof:ZE,type:f,key:u,ref:y!==void 0?y:null,props:c}}function sC(f,u){return tE(f.type,u,f.props)}function JE(f){return typeof f==="object"&&f!==null&&f.$$typeof===ZE}function fR(f){var u={"=":"=0",":":"=2"};return"$"+f.replace(/[=:]/g,function(c){return u[c]})}var Hx=/\/+/g;function PE(f,u){return typeof f==="object"&&f!==null&&f.key!=null?fR(""+f.key):u.toString(36)}function uR(f){switch(f.status){case"fulfilled":return f.value;case"rejected":throw f.reason;default:switch(typeof f.status==="string"?f.then(QE,QE):(f.status="pending",f.then(function(u){f.status==="pending"&&(f.status="fulfilled",f.value=u)},function(u){f.status==="pending"&&(f.status="rejected",f.reason=u)})),f.status){case"fulfilled":return f.value;case"rejected":throw f.reason}}throw f}function $c(f,u,c,y,_){var E=typeof f;if(E==="undefined"||E==="boolean")f=null;var v=!1;if(f===null)v=!0;else switch(E){case"bigint":case"string":case"number":v=!0;break;case"object":switch(f.$$typeof){case ZE:case FC:v=!0;break;case Ux:return v=f._init,$c(v(f._payload),u,c,y,_)}}if(v)return _=_(f),v=y===""?"."+PE(f,0):y,Mx(_)?(c="",v!=null&&(c=v.replace(Hx,"$&/")+"/"),$c(_,u,c,"",function(g){return g})):_!=null&&(JE(_)&&(_=sC(_,c+(_.key==null||f&&f.key===_.key?"":(""+_.key).replace(Hx,"$&/")+"/")+v)),u.push(_)),1;v=0;var T=y===""?".":y+":";if(Mx(f))for(var x=0;xSx,useFormStatus:()=>Vx,useFormState:()=>Jx,unstable_batchedUpdates:()=>tx,requestFormReset:()=>ix,preloadModule:()=>hx,preload:()=>Zx,preinitModule:()=>Qx,preinit:()=>Px,prefetchDNS:()=>Bx,preconnect:()=>Xx,flushSync:()=>Gx,createPortal:()=>jx,__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE:()=>Wx});function zx(f){var u="https://react.dev/errors/"+f;if(1{$x=Dc(Dy(),1);zf={d:{f:Ku,r:function(){throw Error(zx(522))},D:Ku,C:Ku,L:Ku,m:Ku,X:Ku,S:Ku,M:Ku},p:0,findDOMNode:null},bR=Symbol.for("react.portal");$y=$x.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;Wx=zf});var SE=wx((Qn,kx)=>{Fx();function Kx(){if(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!=="function")return;try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(Kx)}catch(f){console.error(f)}}Kx(),kx.exports=VE});var lc=Dc(Dy(),1);var rv={};BE(rv,{version:()=>cg,renderToString:()=>ug,renderToStaticMarkup:()=>fg});var P_=Dc(Dy(),1),UT=Dc(SE(),1);function L(f){var u="https://react.dev/errors/"+f;if(1>>16)&65535)<<16)&4294967295,E=E<<15|E>>>17,E=461845907*(E&65535)+((461845907*(E>>>16)&65535)<<16)&4294967295,_^=E,_=_<<13|_>>>19,_=5*(_&65535)+((5*(_>>>16)&65535)<<16)&4294967295,_=(_&65535)+27492+(((_>>>16)+58964&65535)<<16)}switch(E=0,c){case 3:E^=(f.charCodeAt(u+2)&255)<<16;case 2:E^=(f.charCodeAt(u+1)&255)<<8;case 1:E^=f.charCodeAt(u)&255,E=3432918353*(E&65535)+((3432918353*(E>>>16)&65535)<<16)&4294967295,E=E<<15|E>>>17,_^=461845907*(E&65535)+((461845907*(E>>>16)&65535)<<16)&4294967295}return _^=f.length,_^=_>>>16,_=2246822507*(_&65535)+((2246822507*(_>>>16)&65535)<<16)&4294967295,_^=_>>>13,_=3266489909*(_&65535)+((3266489909*(_>>>16)&65535)<<16)&4294967295,(_^_>>>16)>>>0}var Qf=Object.assign,k=Object.prototype.hasOwnProperty,sR=RegExp("^[:A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD][:A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD\\-.0-9\\u00B7\\u0300-\\u036F\\u203F-\\u2040]*$"),bx={},px={};function gv(f){if(k.call(px,f))return!0;if(k.call(bx,f))return!1;if(sR.test(f))return px[f]=!0;return bx[f]=!0,!1}var fO=new Set("animationIterationCount aspectRatio borderImageOutset borderImageSlice borderImageWidth boxFlex boxFlexGroup boxOrdinalGroup columnCount columns flex flexGrow flexPositive flexShrink flexNegative flexOrder gridArea gridRow gridRowEnd gridRowSpan gridRowStart gridColumn gridColumnEnd gridColumnSpan gridColumnStart fontWeight lineClamp lineHeight opacity order orphans scale tabSize widows zIndex zoom fillOpacity floodOpacity stopOpacity strokeDasharray strokeDashoffset strokeMiterlimit strokeOpacity strokeWidth MozAnimationIterationCount MozBoxFlex MozBoxFlexGroup MozLineClamp msAnimationIterationCount msFlex msZoom msFlexGrow msFlexNegative msFlexOrder msFlexPositive msFlexShrink msGridColumn msGridColumnSpan msGridRow msGridRowSpan WebkitAnimationIterationCount WebkitBoxFlex WebKitBoxFlexGroup WebkitBoxOrdinalGroup WebkitColumnCount WebkitColumns WebkitFlex WebkitFlexGrow WebkitFlexPositive WebkitFlexShrink WebkitLineClamp".split(" ")),uO=new Map([["acceptCharset","accept-charset"],["htmlFor","for"],["httpEquiv","http-equiv"],["crossOrigin","crossorigin"],["accentHeight","accent-height"],["alignmentBaseline","alignment-baseline"],["arabicForm","arabic-form"],["baselineShift","baseline-shift"],["capHeight","cap-height"],["clipPath","clip-path"],["clipRule","clip-rule"],["colorInterpolation","color-interpolation"],["colorInterpolationFilters","color-interpolation-filters"],["colorProfile","color-profile"],["colorRendering","color-rendering"],["dominantBaseline","dominant-baseline"],["enableBackground","enable-background"],["fillOpacity","fill-opacity"],["fillRule","fill-rule"],["floodColor","flood-color"],["floodOpacity","flood-opacity"],["fontFamily","font-family"],["fontSize","font-size"],["fontSizeAdjust","font-size-adjust"],["fontStretch","font-stretch"],["fontStyle","font-style"],["fontVariant","font-variant"],["fontWeight","font-weight"],["glyphName","glyph-name"],["glyphOrientationHorizontal","glyph-orientation-horizontal"],["glyphOrientationVertical","glyph-orientation-vertical"],["horizAdvX","horiz-adv-x"],["horizOriginX","horiz-origin-x"],["imageRendering","image-rendering"],["letterSpacing","letter-spacing"],["lightingColor","lighting-color"],["markerEnd","marker-end"],["markerMid","marker-mid"],["markerStart","marker-start"],["overlinePosition","overline-position"],["overlineThickness","overline-thickness"],["paintOrder","paint-order"],["panose-1","panose-1"],["pointerEvents","pointer-events"],["renderingIntent","rendering-intent"],["shapeRendering","shape-rendering"],["stopColor","stop-color"],["stopOpacity","stop-opacity"],["strikethroughPosition","strikethrough-position"],["strikethroughThickness","strikethrough-thickness"],["strokeDasharray","stroke-dasharray"],["strokeDashoffset","stroke-dashoffset"],["strokeLinecap","stroke-linecap"],["strokeLinejoin","stroke-linejoin"],["strokeMiterlimit","stroke-miterlimit"],["strokeOpacity","stroke-opacity"],["strokeWidth","stroke-width"],["textAnchor","text-anchor"],["textDecoration","text-decoration"],["textRendering","text-rendering"],["transformOrigin","transform-origin"],["underlinePosition","underline-position"],["underlineThickness","underline-thickness"],["unicodeBidi","unicode-bidi"],["unicodeRange","unicode-range"],["unitsPerEm","units-per-em"],["vAlphabetic","v-alphabetic"],["vHanging","v-hanging"],["vIdeographic","v-ideographic"],["vMathematical","v-mathematical"],["vectorEffect","vector-effect"],["vertAdvY","vert-adv-y"],["vertOriginX","vert-origin-x"],["vertOriginY","vert-origin-y"],["wordSpacing","word-spacing"],["writingMode","writing-mode"],["xmlnsXlink","xmlns:xlink"],["xHeight","x-height"]]),cO=/["'&<>]/;function X(f){if(typeof f==="boolean"||typeof f==="number"||typeof f==="bigint")return""+f;f=""+f;var u=cO.exec(f);if(u){var c="",y,_=0;for(y=u.index;yf.insertionMode)return gf(3,null,y,null);break;case"html":if(f.insertionMode===0)return gf(1,null,y,null)}return 6<=f.insertionMode||2>f.insertionMode?gf(2,null,y,null):f.tagScope!==y?gf(f.insertionMode,f.selectedValue,y,null):f}function BT(f){return f===null?null:{update:f.update,enter:"none",exit:"none",share:f.update,name:f.autoName,autoName:f.autoName,nameIdx:0}}function pE(f,u){return u.tagScope&32&&(f.instructions|=128),gf(u.insertionMode,u.selectedValue,u.tagScope|12,BT(u.viewTransition))}function n_(f,u){f=BT(u.viewTransition);var c=u.tagScope|16;return f!==null&&f.share!=="none"&&(c|=64),gf(u.insertionMode,u.selectedValue,c,f)}var ox=new Map;function PT(f,u){if(typeof u!=="object")throw Error(L(62));var c=!0,y;for(y in u)if(k.call(u,y)){var _=u[y];if(_!=null&&typeof _!=="boolean"&&_!==""){if(y.indexOf("--")===0){var E=X(y);_=X((""+_).trim())}else E=ox.get(y),E===void 0&&(E=X(y.replace(yO,"-$1").toLowerCase().replace(_O,"-ms-")),ox.set(y,E)),_=typeof _==="number"?_===0||fO.has(y)?""+_:_+"px":X((""+_).trim());c?(c=!1,f.push(' style="',E,":",_)):f.push(";",E,":",_)}}c||f.push('"')}function qE(f,u,c){c&&typeof c!=="function"&&typeof c!=="symbol"&&f.push(" ",u,'=""')}function Af(f,u,c){typeof c!=="function"&&typeof c!=="symbol"&&typeof c!=="boolean"&&f.push(" ",u,'="',X(c),'"')}var QT=X("javascript:throw new Error('React form unexpectedly submitted.')");function FE(f,u){this.push('")}function ZT(f){if(typeof f!=="string")throw Error(L(480))}function hT(f,u){if(typeof u.$$FORM_ACTION==="function"){var c=f.nextFormID++;f=f.idPrefix+c;try{var y=u.$$FORM_ACTION(f);if(y){var _=y.data;_!=null&&_.forEach(ZT)}return y}catch(E){if(typeof E==="object"&&E!==null&&typeof E.then==="function")throw E}}return null}function ex(f,u,c,y,_,E,v,T){var x=null;if(typeof y==="function"){var g=hT(u,y);g!==null?(T=g.name,y=g.action||"",_=g.encType,E=g.method,v=g.target,x=g.data):(f.push(" ","formAction",'="',QT,'"'),v=E=_=y=T=null,iT(u,c))}return T!=null&&i(f,"name",T),y!=null&&i(f,"formAction",y),_!=null&&i(f,"formEncType",_),E!=null&&i(f,"formMethod",E),v!=null&&i(f,"formTarget",v),x}function i(f,u,c){switch(u){case"className":Af(f,"class",c);break;case"tabIndex":Af(f,"tabindex",c);break;case"dir":case"role":case"viewBox":case"width":case"height":Af(f,u,c);break;case"style":PT(f,c);break;case"src":case"href":if(c==="")break;case"action":case"formAction":if(c==null||typeof c==="function"||typeof c==="symbol"||typeof c==="boolean")break;c=Xy(""+c),f.push(" ",u,'="',X(c),'"');break;case"defaultValue":case"defaultChecked":case"innerHTML":case"suppressContentEditableWarning":case"suppressHydrationWarning":case"ref":break;case"autoFocus":case"multiple":case"muted":qE(f,u.toLowerCase(),c);break;case"xlinkHref":if(typeof c==="function"||typeof c==="symbol"||typeof c==="boolean")break;c=Xy(""+c),f.push(" ","xlink:href",'="',X(c),'"');break;case"contentEditable":case"spellCheck":case"draggable":case"value":case"autoReverse":case"externalResourcesRequired":case"focusable":case"preserveAlpha":typeof c!=="function"&&typeof c!=="symbol"&&f.push(" ",u,'="',X(c),'"');break;case"inert":case"allowFullScreen":case"async":case"autoPlay":case"controls":case"default":case"defer":case"disabled":case"disablePictureInPicture":case"disableRemotePlayback":case"formNoValidate":case"hidden":case"loop":case"noModule":case"noValidate":case"open":case"playsInline":case"readOnly":case"required":case"reversed":case"scoped":case"seamless":case"itemScope":c&&typeof c!=="function"&&typeof c!=="symbol"&&f.push(" ",u,'=""');break;case"capture":case"download":c===!0?f.push(" ",u,'=""'):c!==!1&&typeof c!=="function"&&typeof c!=="symbol"&&f.push(" ",u,'="',X(c),'"');break;case"cols":case"rows":case"size":case"span":typeof c!=="function"&&typeof c!=="symbol"&&!isNaN(c)&&1<=c&&f.push(" ",u,'="',X(c),'"');break;case"rowSpan":case"start":typeof c==="function"||typeof c==="symbol"||isNaN(c)||f.push(" ",u,'="',X(c),'"');break;case"xlinkActuate":Af(f,"xlink:actuate",c);break;case"xlinkArcrole":Af(f,"xlink:arcrole",c);break;case"xlinkRole":Af(f,"xlink:role",c);break;case"xlinkShow":Af(f,"xlink:show",c);break;case"xlinkTitle":Af(f,"xlink:title",c);break;case"xlinkType":Af(f,"xlink:type",c);break;case"xmlBase":Af(f,"xml:base",c);break;case"xmlLang":Af(f,"xml:lang",c);break;case"xmlSpace":Af(f,"xml:space",c);break;default:if(!(2",`addEventListener("submit",function(a){if(!a.defaultPrevented){var c=a.target,d=a.submitter,e=c.action,b=d;if(d){var f=d.getAttribute("formAction");null!=f&&(e=f,b=null)}"javascript:throw new Error('React form unexpectedly submitted.')"===e&&(a.preventDefault(),b?(a=document.createElement("input"),a.name=b.name,a.value=b.value,b.parentNode.insertBefore(a,b),b=new FormData(c),a.parentNode.removeChild(a)):b=new FormData(c),a=c.ownerDocument||c,(a.$$reactFormReplay=a.$$reactFormReplay||[]).push(c,d,b))}});`,"")):y.unshift(u.startInlineScript,">",`addEventListener("submit",function(a){if(!a.defaultPrevented){var c=a.target,d=a.submitter,e=c.action,b=d;if(d){var f=d.getAttribute("formAction");null!=f&&(e=f,b=null)}"javascript:throw new Error('React form unexpectedly submitted.')"===e&&(a.preventDefault(),b?(a=document.createElement("input"),a.name=b.name,a.value=b.value,b.parentNode.insertBefore(a,b),b=new FormData(c),a.parentNode.removeChild(a)):b=new FormData(c),a=c.ownerDocument||c,(a.$$reactFormReplay=a.$$reactFormReplay||[]).push(c,d,b))}});`,"")}}function nf(f,u){f.push(cf("link"));for(var c in u)if(k.call(u,c)){var y=u[c];if(y!=null)switch(c){case"children":case"dangerouslySetInnerHTML":throw Error(L(399,"link"));default:i(f,c,y)}}return f.push("/>"),null}var ax=/(<\/|<)(s)(tyle)/gi;function sx(f,u,c,y){return""+u+(c==="s"?"\\73 ":"\\53 ")+y}function jc(f,u,c){f.push(cf(c));for(var y in u)if(k.call(u,y)){var _=u[y];if(_!=null)switch(y){case"children":case"dangerouslySetInnerHTML":throw Error(L(399,c));default:i(f,y,_)}}return f.push("/>"),null}function fT(f,u){f.push(cf("title"));var c=null,y=null,_;for(_ in u)if(k.call(u,_)){var E=u[_];if(E!=null)switch(_){case"children":c=E;break;case"dangerouslySetInnerHTML":y=E;break;default:i(f,_,E)}}return f.push(">"),u=Array.isArray(c)?2>c.length?c[0]:null:c,typeof u!=="function"&&typeof u!=="symbol"&&u!==null&&u!==void 0&&f.push(X(""+u)),kf(f,y,c),f.push(Mc("title")),null}function L_(f,u){f.push(cf("script"));var c=null,y=null,_;for(_ in u)if(k.call(u,_)){var E=u[_];if(E!=null)switch(_){case"children":c=E;break;case"dangerouslySetInnerHTML":y=E;break;default:i(f,_,E)}}return f.push(">"),kf(f,y,c),typeof c==="string"&&f.push((""+c).replace(GT,XT)),f.push(Mc("script")),null}function KE(f,u,c){f.push(cf(c));var y=c=null,_;for(_ in u)if(k.call(u,_)){var E=u[_];if(E!=null)switch(_){case"children":c=E;break;case"dangerouslySetInnerHTML":y=E;break;default:i(f,_,E)}}return f.push(">"),kf(f,y,c),c}function R_(f,u,c){f.push(cf(c));var y=c=null,_;for(_ in u)if(k.call(u,_)){var E=u[_];if(E!=null)switch(_){case"children":c=E;break;case"dangerouslySetInnerHTML":y=E;break;default:i(f,_,E)}}return f.push(">"),kf(f,y,c),typeof c==="string"?(f.push(X(c)),null):c}var gO=/^[a-zA-Z][a-zA-Z:_\.\-\d]*$/,uT=new Map;function cf(f){var u=uT.get(f);if(u===void 0){if(!gO.test(f))throw Error(L(65,f));u="<"+f,uT.set(f,u)}return u}function CO(f,u,c,y,_,E,v,T,x){switch(u){case"div":case"span":case"svg":case"path":break;case"a":f.push(cf("a"));var g=null,C=null,R;for(R in c)if(k.call(c,R)){var O=c[R];if(O!=null)switch(R){case"children":g=O;break;case"dangerouslySetInnerHTML":C=O;break;case"href":O===""?Af(f,"href",""):i(f,R,O);break;default:i(f,R,O)}}if(f.push(">"),kf(f,C,g),typeof g==="string"){f.push(X(g));var w=null}else w=g;return w;case"g":case"p":case"li":break;case"select":f.push(cf("select"));var r=null,H=null,M;for(M in c)if(k.call(c,M)){var U=c[M];if(U!=null)switch(M){case"children":r=U;break;case"dangerouslySetInnerHTML":H=U;break;case"defaultValue":case"value":break;default:i(f,M,U)}}return f.push(">"),kf(f,H,r),r;case"option":var N=T.selectedValue;f.push(cf("option"));var G=null,B=null,z=null,I=null,e;for(e in c)if(k.call(c,e)){var l=c[e];if(l!=null)switch(e){case"children":G=l;break;case"selected":z=l;break;case"dangerouslySetInnerHTML":I=l;break;case"value":B=l;default:i(f,e,l)}}if(N!=null){var $=B!==null?""+B:TO(G);if(r_(N)){for(var a=0;a"),kf(f,I,G),G;case"textarea":f.push(cf("textarea"));var D=null,J=null,h=null,j;for(j in c)if(k.call(c,j)){var b=c[j];if(b!=null)switch(j){case"children":h=b;break;case"value":D=b;break;case"defaultValue":J=b;break;case"dangerouslySetInnerHTML":throw Error(L(91));default:i(f,j,b)}}if(D===null&&J!==null&&(D=J),f.push(">"),h!=null){if(D!=null)throw Error(L(92));if(r_(h)){if(1"),Du!=null&&Du.forEach(FE,f),null;case"button":f.push(cf("button"));var Eu=null,bc=null,pc=null,qc=null,oc=null,ec=null,ac=null,vu;for(vu in c)if(k.call(c,vu)){var uf=c[vu];if(uf!=null)switch(vu){case"children":Eu=uf;break;case"dangerouslySetInnerHTML":bc=uf;break;case"name":pc=uf;break;case"formAction":qc=uf;break;case"formEncType":oc=uf;break;case"formMethod":ec=uf;break;case"formTarget":ac=uf;break;default:i(f,vu,uf)}}var sc=ex(f,y,_,qc,oc,ec,ac,pc);if(f.push(">"),sc!=null&&sc.forEach(FE,f),kf(f,bc,Eu),typeof Eu==="string"){f.push(X(Eu));var fy=null}else fy=Eu;return fy;case"form":f.push(cf("form"));var xu=null,uy=null,rf=null,Tu=null,gu=null,Cu=null,Ru;for(Ru in c)if(k.call(c,Ru)){var xf=c[Ru];if(xf!=null)switch(Ru){case"children":xu=xf;break;case"dangerouslySetInnerHTML":uy=xf;break;case"action":rf=xf;break;case"encType":Tu=xf;break;case"method":gu=xf;break;case"target":Cu=xf;break;default:i(f,Ru,xf)}}var uc=null,cc=null;if(typeof rf==="function"){var Lf=hT(y,rf);Lf!==null?(rf=Lf.action||"",Tu=Lf.encType,gu=Lf.method,Cu=Lf.target,uc=Lf.data,cc=Lf.name):(f.push(" ","action",'="',QT,'"'),Cu=gu=Tu=rf=null,iT(y,_))}if(rf!=null&&i(f,"action",rf),Tu!=null&&i(f,"encType",Tu),gu!=null&&i(f,"method",gu),Cu!=null&&i(f,"target",Cu),f.push(">"),cc!==null&&(f.push('"),uc!=null&&uc.forEach(FE,f)),kf(f,uy,xu),typeof xu==="string"){f.push(X(xu));var cy=null}else cy=xu;return cy;case"menuitem":f.push(cf("menuitem"));for(var $u in c)if(k.call(c,$u)){var yy=c[$u];if(yy!=null)switch($u){case"children":case"dangerouslySetInnerHTML":throw Error(L(400));default:i(f,$u,yy)}}return f.push(">"),null;case"object":f.push(cf("object"));var Ou=null,_y=null,Au;for(Au in c)if(k.call(c,Au)){var nu=c[Au];if(nu!=null)switch(Au){case"children":Ou=nu;break;case"dangerouslySetInnerHTML":_y=nu;break;case"data":var Ey=Xy(""+nu);if(Ey==="")break;f.push(" ","data",'="',X(Ey),'"');break;default:i(f,Au,nu)}}if(f.push(">"),kf(f,_y,Ou),typeof Ou==="string"){f.push(X(Ou));var vy=null}else vy=Ou;return vy;case"title":var UE=T.tagScope&1,rE=T.tagScope&4;if(T.insertionMode===4||UE||c.itemProp!=null)var yc=fT(f,c);else rE?yc=null:(fT(_.hoistableChunks,c),yc=void 0);return yc;case"link":var LE=T.tagScope&1,YE=T.tagScope&4,NE=c.rel,Tf=c.href,zu=c.precedence;if(T.insertionMode===4||LE||c.itemProp!=null||typeof NE!=="string"||typeof Tf!=="string"||Tf===""){nf(f,c);var wu=null}else if(c.rel==="stylesheet")if(typeof zu!=="string"||c.disabled!=null||c.onLoad||c.onError)wu=nf(f,c);else{var tf=_.styles.get(zu),Wu=y.styleResources.hasOwnProperty(Tf)?y.styleResources[Tf]:void 0;if(Wu!==null){y.styleResources[Tf]=null,tf||(tf={precedence:X(zu),rules:[],hrefs:[],sheets:new Map},_.styles.set(zu,tf));var ju={state:0,props:Qf({},c,{"data-precedence":c.precedence,precedence:null})};if(Wu){Wu.length===2&&By(ju.props,Wu);var _c=_.preloads.stylesheets.get(Tf);_c&&0<_c.length?_c.length=0:ju.state=1}tf.sheets.set(Tf,ju),v&&v.stylesheets.add(ju)}else if(tf){var xy=tf.sheets.get(Tf);xy&&v&&v.stylesheets.add(xy)}x&&f.push(""),wu=null}else c.onLoad||c.onError?wu=nf(f,c):(x&&f.push(""),wu=YE?null:nf(_.hoistableChunks,c));return wu;case"script":var DE=T.tagScope&1,Ec=c.async;if(typeof c.src!=="string"||!c.src||!Ec||typeof Ec==="function"||typeof Ec==="symbol"||c.onLoad||c.onError||T.insertionMode===4||DE||c.itemProp!=null)var Ty=L_(f,c);else{var Gu=c.src;if(c.type==="module")var Xu=y.moduleScriptResources,gy=_.preloads.moduleScripts;else Xu=y.scriptResources,gy=_.preloads.scripts;var Bu=Xu.hasOwnProperty(Gu)?Xu[Gu]:void 0;if(Bu!==null){Xu[Gu]=null;var vc=c;if(Bu){Bu.length===2&&(vc=Qf({},c),By(vc,Bu));var Cy=gy.get(Gu);Cy&&(Cy.length=0)}var Ry=[];_.scripts.add(Ry),L_(Ry,vc)}x&&f.push(""),Ty=null}return Ty;case"style":var $E=T.tagScope&1,Pu=c.precedence,Jf=c.href,zE=c.nonce;if(T.insertionMode===4||$E||c.itemProp!=null||typeof Pu!=="string"||typeof Jf!=="string"||Jf===""){f.push(cf("style"));var Vf=null,Oy=null,mu;for(mu in c)if(k.call(c,mu)){var Qu=c[mu];if(Qu!=null)switch(mu){case"children":Vf=Qu;break;case"dangerouslySetInnerHTML":Oy=Qu;break;default:i(f,mu,Qu)}}f.push(">");var Mu=Array.isArray(Vf)?2>Vf.length?Vf[0]:null:Vf;typeof Mu!=="function"&&typeof Mu!=="symbol"&&Mu!==null&&Mu!==void 0&&f.push((""+Mu).replace(ax,sx)),kf(f,Oy,Vf),f.push(Mc("style"));var Ay=null}else{var Yf=_.styles.get(Pu);if((y.styleResources.hasOwnProperty(Jf)?y.styleResources[Jf]:void 0)!==null){y.styleResources[Jf]=null,Yf||(Yf={precedence:X(Pu),rules:[],hrefs:[],sheets:new Map},_.styles.set(Pu,Yf));var ny=_.nonce.style;if(!ny||ny===zE){Yf.hrefs.push(X(Jf));var wy=Yf.rules,Sf=null,my=null,Zu;for(Zu in c)if(k.call(c,Zu)){var xc=c[Zu];if(xc!=null)switch(Zu){case"children":Sf=xc;break;case"dangerouslySetInnerHTML":my=xc}}var Hu=Array.isArray(Sf)?2>Sf.length?Sf[0]:null:Sf;typeof Hu!=="function"&&typeof Hu!=="symbol"&&Hu!==null&&Hu!==void 0&&wy.push((""+Hu).replace(ax,sx)),kf(wy,my,Sf)}}Yf&&v&&v.styles.add(Yf),x&&f.push(""),Ay=void 0}return Ay;case"meta":var WE=T.tagScope&1,jE=T.tagScope&4;if(T.insertionMode===4||WE||c.itemProp!=null)var My=jc(f,c,"meta");else x&&f.push(""),My=jE?null:typeof c.charSet==="string"?jc(_.charsetChunks,c,"meta"):c.name==="viewport"?jc(_.viewportChunks,c,"meta"):jc(_.hoistableChunks,c,"meta");return My;case"listing":case"pre":f.push(cf(u));var Iu=null,Uu=null,ru;for(ru in c)if(k.call(c,ru)){var hu=c[ru];if(hu!=null)switch(ru){case"children":Iu=hu;break;case"dangerouslySetInnerHTML":Uu=hu;break;default:i(f,ru,hu)}}if(f.push(">"),Uu!=null){if(Iu!=null)throw Error(L(60));if(typeof Uu!=="object"||!("__html"in Uu))throw Error(L(61));var Nf=Uu.__html;Nf!==null&&Nf!==void 0&&(typeof Nf==="string"&&0_.highImagePreloads.size)Tc.delete(Ff),_.highImagePreloads.add(Df)}else if(!y.imageResources.hasOwnProperty(Ff)){y.imageResources[Ff]=sf;var gc=c.crossOrigin,Iy=typeof gc==="string"?gc==="use-credentials"?gc:"":void 0,$f=_.headers,Cc;$f&&0<$f.remainingCapacity&&typeof c.srcSet!=="string"&&(c.fetchPriority==="high"||500>$f.highImagePreloads.length)&&(Cc=N_(Q,"image",{imageSrcSet:c.srcSet,imageSizes:c.sizes,crossOrigin:Iy,integrity:c.integrity,nonce:c.nonce,type:c.type,fetchPriority:c.fetchPriority,referrerPolicy:c.refererPolicy}),0<=($f.remainingCapacity-=Cc.length+2))?(_.resets.image[Ff]=sf,$f.highImagePreloads&&($f.highImagePreloads+=", "),$f.highImagePreloads+=Cc):(Df=[],nf(Df,{rel:"preload",as:"image",href:P?void 0:Q,imageSrcSet:P,imageSizes:Hy,crossOrigin:Iy,integrity:c.integrity,type:c.type,fetchPriority:c.fetchPriority,referrerPolicy:c.referrerPolicy}),c.fetchPriority==="high"||10>_.highImagePreloads.size?_.highImagePreloads.add(Df):(_.bulkPreloads.add(Df),Tc.set(Ff,Df)))}}return jc(f,c,"img");case"base":case"area":case"br":case"col":case"embed":case"hr":case"keygen":case"param":case"source":case"track":case"wbr":return jc(f,c,u);case"annotation-xml":case"color-profile":case"font-face":case"font-face-src":case"font-face-uri":case"font-face-format":case"font-face-name":case"missing-glyph":break;case"head":if(2>T.insertionMode){var Rc=E||_.preamble;if(Rc.headChunks)throw Error(L(545,"``"));E!==null&&f.push(""),Rc.headChunks=[];var Uy=KE(Rc.headChunks,c,"head")}else Uy=R_(f,c,"head");return Uy;case"body":if(2>T.insertionMode){var Oc=E||_.preamble;if(Oc.bodyChunks)throw Error(L(545,"``"));E!==null&&f.push(""),Oc.bodyChunks=[];var ry=KE(Oc.bodyChunks,c,"body")}else ry=R_(f,c,"body");return ry;case"html":if(T.insertionMode===0){var Ac=E||_.preamble;if(Ac.htmlChunks)throw Error(L(545,"``"));E!==null&&f.push(""),Ac.htmlChunks=[""];var Ly=KE(Ac.htmlChunks,c,"html")}else Ly=R_(f,c,"html");return Ly;default:if(u.indexOf("-")!==-1){f.push(cf(u));var nc=null,Yy=null,Kf;for(Kf in c)if(k.call(c,Kf)){var p=c[Kf];if(p!=null){var Ny=Kf;switch(Kf){case"children":nc=p;break;case"dangerouslySetInnerHTML":Yy=p;break;case"style":PT(f,p);break;case"suppressContentEditableWarning":case"suppressHydrationWarning":case"ref":break;case"className":Ny="class";default:if(gv(Kf)&&typeof p!=="function"&&typeof p!=="symbol"&&p!==!1){if(p===!0)p="";else if(typeof p==="object")continue;f.push(" ",Ny,'="',X(p),'"')}}}}return f.push(">"),kf(f,Yy,nc),nc}}return R_(f,c,u)}var cT=new Map;function Mc(f){var u=cT.get(f);return u===void 0&&(u=""+f+">",cT.set(f,u)),u}function yT(f,u){f=f.preamble,f.htmlChunks===null&&u.htmlChunks&&(f.htmlChunks=u.htmlChunks),f.headChunks===null&&u.headChunks&&(f.headChunks=u.headChunks),f.bodyChunks===null&&u.bodyChunks&&(f.bodyChunks=u.bodyChunks)}function tT(f,u){u=u.bootstrapChunks;for(var c=0;c')}function RO(f,u,c,y){switch(c.insertionMode){case 0:case 1:case 3:case 2:return f.push('');case 4:return f.push('');case 5:return f.push('');case 6:return f.push('');case 7:return f.push('');case 8:return f.push('');case 9:return f.push('');default:throw Error(L(397))}}function OO(f,u){switch(u.insertionMode){case 0:case 1:case 3:case 2:return f.push("");case 4:return f.push("");case 5:return f.push("");case 6:return f.push("");case 7:return f.push("");case 8:return f.push("");case 9:return f.push("");default:throw Error(L(397))}}var AO=/[<\u2028\u2029]/g;function nO(f){return JSON.stringify(f).replace(AO,function(u){switch(u){case"<":return"\\u003c";case"\u2028":return"\\u2028";case"\u2029":return"\\u2029";default:throw Error("escapeJSStringsForInstructionScripts encountered a match it does not know how to replace. this means the match regex and the replacement characters are no longer in sync. This is a bug in React")}})}var wO=/[&><\u2028\u2029]/g;function jy(f){return JSON.stringify(f).replace(wO,function(u){switch(u){case"&":return"\\u0026";case">":return"\\u003e";case"<":return"\\u003c";case"\u2028":return"\\u2028";case"\u2029":return"\\u2029";default:throw Error("escapeJSObjectForInstructionScripts encountered a match it does not know how to replace. this means the match regex and the replacement characters are no longer in sync. This is a bug in React")}})}var Y_=!1,oE=!0;function mO(f){var{rules:u,hrefs:c}=f,y=0;if(c.length){this.push(Pc.startInlineStyle),this.push(' media="not all" data-precedence="'),this.push(f.precedence);for(this.push('" data-href="');y');for(y=0;y"),Y_=!0,u.length=0,c.length=0}}function MO(f){return f.state!==2?Y_=!0:!1}function JT(f,u,c){return Y_=!1,oE=!0,Pc=c,u.styles.forEach(mO,f),Pc=null,u.stylesheets.forEach(MO),Y_&&(c.stylesToHoist=!0),oE}function Lu(f){for(var u=0;u');for(f=0;f"),c.length=0,y.length=0}}function UO(f){if(f.state===0){f.state=1;var u=f.props;nf(du,{rel:"preload",as:"style",href:f.props.href,crossOrigin:u.crossOrigin,fetchPriority:u.fetchPriority,integrity:u.integrity,media:u.media,hrefLang:u.hrefLang,referrerPolicy:u.referrerPolicy});for(f=0;f; rel=dns-prefetch",0<=(c.remainingCapacity-=_.length+2));E?(y.resets.dns[f]=null,c.preconnects&&(c.preconnects+=", "),c.preconnects+=_):(_=[],nf(_,{href:f,rel:"dns-prefetch"}),y.preconnects.add(_))}Ic(u)}}else tu.D(f)}function DO(f,u){var c=Cf?Cf:null;if(c){var{resumableState:y,renderState:_}=c;if(typeof f==="string"&&f){var E=u==="use-credentials"?"credentials":typeof u==="string"?"anonymous":"default";if(!y.connectResources[E].hasOwnProperty(f)){y.connectResources[E][f]=null,y=_.headers;var v,T;if(T=y&&0; rel=preconnect",typeof u==="string"){var x=(""+u).replace(aE,sE);T+='; crossorigin="'+x+'"'}T=(v=T,0<=(y.remainingCapacity-=v.length+2))}T?(_.resets.connect[E][f]=null,y.preconnects&&(y.preconnects+=", "),y.preconnects+=v):(E=[],nf(E,{rel:"preconnect",href:f,crossOrigin:u}),_.preconnects.add(E))}Ic(c)}}else tu.C(f,u)}function $O(f,u,c){var y=Cf?Cf:null;if(y){var{resumableState:_,renderState:E}=y;if(u&&f){switch(u){case"image":if(c)var{imageSrcSet:v,imageSizes:T,fetchPriority:x}=c;var g=v?v+` +`+(T||""):f;if(_.imageResources.hasOwnProperty(g))return;_.imageResources[g]=sf,_=E.headers;var C;_&&0<_.remainingCapacity&&typeof v!=="string"&&x==="high"&&(C=N_(f,u,c),0<=(_.remainingCapacity-=C.length+2))?(E.resets.image[g]=sf,_.highImagePreloads&&(_.highImagePreloads+=", "),_.highImagePreloads+=C):(_=[],nf(_,Qf({rel:"preload",href:v?void 0:f,as:u},c)),x==="high"?E.highImagePreloads.add(_):(E.bulkPreloads.add(_),E.preloads.images.set(g,_)));break;case"style":if(_.styleResources.hasOwnProperty(f))return;v=[],nf(v,Qf({rel:"preload",href:f,as:u},c)),_.styleResources[f]=!c||typeof c.crossOrigin!=="string"&&typeof c.integrity!=="string"?sf:[c.crossOrigin,c.integrity],E.preloads.stylesheets.set(f,v),E.bulkPreloads.add(v);break;case"script":if(_.scriptResources.hasOwnProperty(f))return;v=[],E.preloads.scripts.set(f,v),E.bulkPreloads.add(v),nf(v,Qf({rel:"preload",href:f,as:u},c)),_.scriptResources[f]=!c||typeof c.crossOrigin!=="string"&&typeof c.integrity!=="string"?sf:[c.crossOrigin,c.integrity];break;default:if(_.unknownResources.hasOwnProperty(u)){if(v=_.unknownResources[u],v.hasOwnProperty(f))return}else v={},_.unknownResources[u]=v;if(v[f]=sf,(_=E.headers)&&0<_.remainingCapacity&&u==="font"&&(g=N_(f,u,c),0<=(_.remainingCapacity-=g.length+2)))E.resets.font[f]=sf,_.fontPreloads&&(_.fontPreloads+=", "),_.fontPreloads+=g;else switch(_=[],f=Qf({rel:"preload",href:f,as:u},c),nf(_,f),u){case"font":E.fontPreloads.add(_);break;default:E.bulkPreloads.add(_)}}Ic(y)}}else tu.L(f,u,c)}function zO(f,u){var c=Cf?Cf:null;if(c){var{resumableState:y,renderState:_}=c;if(f){var E=u&&typeof u.as==="string"?u.as:"script";switch(E){case"script":if(y.moduleScriptResources.hasOwnProperty(f))return;E=[],y.moduleScriptResources[f]=!u||typeof u.crossOrigin!=="string"&&typeof u.integrity!=="string"?sf:[u.crossOrigin,u.integrity],_.preloads.moduleScripts.set(f,E);break;default:if(y.moduleUnknownResources.hasOwnProperty(E)){var v=y.unknownResources[E];if(v.hasOwnProperty(f))return}else v={},y.moduleUnknownResources[E]=v;E=[],v[f]=sf}nf(E,Qf({rel:"modulepreload",href:f},u)),_.bulkPreloads.add(E),Ic(c)}}else tu.m(f,u)}function WO(f,u,c){var y=Cf?Cf:null;if(y){var{resumableState:_,renderState:E}=y;if(f){u=u||"default";var v=E.styles.get(u),T=_.styleResources.hasOwnProperty(f)?_.styleResources[f]:void 0;T!==null&&(_.styleResources[f]=null,v||(v={precedence:X(u),rules:[],hrefs:[],sheets:new Map},E.styles.set(u,v)),u={state:0,props:Qf({rel:"stylesheet",href:f,"data-precedence":u},c)},T&&(T.length===2&&By(u.props,T),(E=E.preloads.stylesheets.get(f))&&0; rel=preload; as="'+u+'"';for(var y in c)k.call(c,y)&&(f=c[y],typeof f==="string"&&(u+="; "+y.toLowerCase()+'="'+(""+f).replace(aE,sE)+'"'));return u}var Cv=/[<>\r\n]/g;function Rv(f){switch(f){case"<":return"%3C";case">":return"%3E";case` +`:return"%0A";case"\r":return"%0D";default:throw Error("escapeLinkHrefForHeaderContextReplacer encountered a match it does not know how to replace. this means the match regex and the replacement characters are no longer in sync. This is a bug in React")}}var aE=/["';,\r\n]/g;function sE(f){switch(f){case'"':return"%22";case"'":return"%27";case";":return"%3B";case",":return"%2C";case` +`:return"%0A";case"\r":return"%0D";default:throw Error("escapeStringForLinkHeaderQuotedParamValueContextReplacer encountered a match it does not know how to replace. this means the match regex and the replacement characters are no longer in sync. This is a bug in React")}}function XO(f){this.styles.add(f)}function BO(f){this.stylesheets.add(f)}function Qc(f,u){u.styles.forEach(XO,f),u.stylesheets.forEach(BO,f),u.suspenseyImages&&(f.suspenseyImages=!0)}function PO(f,u){var c=f.idPrefix,y=[],_=f.bootstrapScriptContent,E=f.bootstrapScripts,v=f.bootstrapModules;_!==void 0&&(y.push("")),_=c+"P:";var T=c+"S:";c+="B:";var x=new Set,g=new Set,C=new Set,R=new Map,O=new Set,w=new Set,r=new Set,H={images:new Map,stylesheets:new Map,scripts:new Map,moduleScripts:new Map};if(E!==void 0)for(var M=0;M')}if(v!==void 0)for(E=0;E');return{placeholderPrefix:_,segmentPrefix:T,boundaryPrefix:c,startInlineScript:""),tT(u,f)&&c}function aT(f,u,c,y){if(y.status===2)return!0;var _=c.contentState,E=y.id;if(E===-1){if((y.id=c.rootSegmentID)===-1)throw Error(L(392));return bE(f,u,y,_)}if(E===c.rootSegmentID)return bE(f,u,y,_);return bE(f,u,y,_),c=f.resumableState,f=f.renderState,u.push(f.startInlineScript),u.push(">"),(c.instructions&1)===0?(c.instructions|=1,u.push('$RS=function(a,b){a=document.getElementById(a);b=document.getElementById(b);for(a.parentNode.removeChild(a);a.firstChild;)b.parentNode.insertBefore(a.firstChild,b);b.parentNode.removeChild(b)};$RS("')):u.push('$RS("'),u.push(f.segmentPrefix),E=E.toString(16),u.push(E),u.push('","'),u.push(f.placeholderPrefix),u.push(E),u=u.push('")'),u}var U_=!1;function V_(f,u){try{if(!(0")}}else if(g)for(C=0;C"),u.push("requestAnimationFrame(function(){$RT=performance.now()});"),u.push("")}}tT(u,z)}var $=f.renderState;y=0;var a=$.viewportChunks;for(y=0;y"),(j.instructions&4)===0?(j.instructions|=4,$.push('$RX=function(b,c,d,e,f){var a=document.getElementById(b);a&&(b=a.previousSibling,b.data="$!",a=a.dataset,c&&(a.dgst=c),d&&(a.msg=d),e&&(a.stck=e),f&&(a.cstck=f),b._reactRetry&&b._reactRetry())};;$RX("')):$.push('$RX("'),$.push(b.boundaryPrefix);var Xf=of.toString(16);if($.push(Xf),$.push('"'),hf){$.push(",");var ef=nO(hf||"");$.push(ef)}var vf=$.push(")");if(!vf){f.destination=null,c++,J.splice(0,c);return}}J.splice(0,c);var s=f.completedBoundaries;for(c=0;chC,resumeAndPrerender:()=>ZC,resume:()=>QC,renderToReadableStream:()=>PC,prerender:()=>BC});var dc=Dc(Dy(),1),Sg=Dc(SE(),1);function Y(f){var u="https://react.dev/errors/"+f;if(1>>16)&65535)<<16)&4294967295,E=E<<15|E>>>17,E=461845907*(E&65535)+((461845907*(E>>>16)&65535)<<16)&4294967295,_^=E,_=_<<13|_>>>19,_=5*(_&65535)+((5*(_>>>16)&65535)<<16)&4294967295,_=(_&65535)+27492+(((_>>>16)+58964&65535)<<16)}switch(E=0,c){case 3:E^=(f.charCodeAt(u+2)&255)<<16;case 2:E^=(f.charCodeAt(u+1)&255)<<8;case 1:E^=f.charCodeAt(u)&255,E=3432918353*(E&65535)+((3432918353*(E>>>16)&65535)<<16)&4294967295,E=E<<15|E>>>17,_^=461845907*(E&65535)+((461845907*(E>>>16)&65535)<<16)&4294967295}return _^=f.length,_^=_>>>16,_=2246822507*(_&65535)+((2246822507*(_>>>16)&65535)<<16)&4294967295,_^=_>>>13,_=3266489909*(_&65535)+((3266489909*(_>>>16)&65535)<<16)&4294967295,(_^_>>>16)>>>0}var og=new MessageChannel,eg=[];og.port1.onmessage=function(){var f=eg.shift();f&&f()};function sv(f){eg.push(f),og.port2.postMessage(null)}function v0(f){setTimeout(function(){throw f})}var x0=Promise,ag=typeof queueMicrotask==="function"?queueMicrotask:function(f){x0.resolve(null).then(f).catch(v0)},bf=null,pf=0;function A(f,u){if(u.byteLength!==0)if(2048]/;function W(f){if(typeof f==="boolean"||typeof f==="number"||typeof f==="bigint")return""+f;f=""+f;var u=O0.exec(f);if(u){var c="",y,_=0;for(y=u.index;y');var M0=n("'),U0=n(""),fE=!1,iv=!0;function SA(f){var{rules:u,hrefs:c}=f,y=0;if(c.length){A(this,Fc.startInlineStyle),A(this,iA),A(this,f.precedence);for(A(this,tA);y'),bA=n("");function pA(f){var u=0');var nC=n(' id="');function d_(f,u){(u.instructions&32)===0&&(u.instructions|=32,f.push(nC,m(W("_"+u.idPrefix+"R_")),o))}var Dg=n("["),$g=n(",["),tv=n(","),zv=n("]");function eA(f,u){A(f,Dg);var c=Dg;u.stylesheets.forEach(function(y){if(y.state!==2)if(y.state===3)A(f,c),A(f,m(ly(""+y.props.href))),A(f,zv),c=$g;else{A(f,c);var _=y.props["data-precedence"],E=y.props,v=by(""+y.props.href);A(f,m(ly(v))),_=""+_,A(f,tv),A(f,m(ly(_)));for(var T in E)if(d.call(E,T)&&(_=E[T],_!=null))switch(T){case"href":case"rel":case"precedence":case"data-precedence":break;case"children":case"dangerouslySetInnerHTML":throw Error(Y(399,"link"));default:aA(f,T,_)}A(f,zv),c=$g,y.state=3}}),A(f,zv)}function aA(f,u,c){var y=u.toLowerCase();switch(typeof c){case"function":case"symbol":return}switch(u){case"innerHTML":case"dangerouslySetInnerHTML":case"suppressContentEditableWarning":case"suppressHydrationWarning":case"style":case"ref":return;case"className":y="class",u=""+c;break;case"hidden":if(c===!1)return;u="";break;case"src":case"href":c=by(c),u=""+c;break;default:if(2; rel=dns-prefetch",0<=(c.remainingCapacity-=_.length+2));E?(y.resets.dns[f]=null,c.preconnects&&(c.preconnects+=", "),c.preconnects+=_):(_=[],Hf(_,{href:f,rel:"dns-prefetch"}),y.preconnects.add(_))}Nc(u)}}else Su.D(f)}function fn(f,u){var c=Of?Of:null;if(c){var{resumableState:y,renderState:_}=c;if(typeof f==="string"&&f){var E=u==="use-credentials"?"credentials":typeof u==="string"?"anonymous":"default";if(!y.connectResources[E].hasOwnProperty(f)){y.connectResources[E][f]=null,y=_.headers;var v,T;if(T=y&&0; rel=preconnect",typeof u==="string"){var x=(""+u).replace(Vv,Sv);T+='; crossorigin="'+x+'"'}T=(v=T,0<=(y.remainingCapacity-=v.length+2))}T?(_.resets.connect[E][f]=null,y.preconnects&&(y.preconnects+=", "),y.preconnects+=v):(E=[],Hf(E,{rel:"preconnect",href:f,crossOrigin:u}),_.preconnects.add(E))}Nc(c)}}else Su.C(f,u)}function un(f,u,c){var y=Of?Of:null;if(y){var{resumableState:_,renderState:E}=y;if(u&&f){switch(u){case"image":if(c)var{imageSrcSet:v,imageSizes:T,fetchPriority:x}=c;var g=v?v+` +`+(T||""):f;if(_.imageResources.hasOwnProperty(g))return;_.imageResources[g]=cu,_=E.headers;var C;_&&0<_.remainingCapacity&&typeof v!=="string"&&x==="high"&&(C=uE(f,u,c),0<=(_.remainingCapacity-=C.length+2))?(E.resets.image[g]=cu,_.highImagePreloads&&(_.highImagePreloads+=", "),_.highImagePreloads+=C):(_=[],Hf(_,Zf({rel:"preload",href:v?void 0:f,as:u},c)),x==="high"?E.highImagePreloads.add(_):(E.bulkPreloads.add(_),E.preloads.images.set(g,_)));break;case"style":if(_.styleResources.hasOwnProperty(f))return;v=[],Hf(v,Zf({rel:"preload",href:f,as:u},c)),_.styleResources[f]=!c||typeof c.crossOrigin!=="string"&&typeof c.integrity!=="string"?cu:[c.crossOrigin,c.integrity],E.preloads.stylesheets.set(f,v),E.bulkPreloads.add(v);break;case"script":if(_.scriptResources.hasOwnProperty(f))return;v=[],E.preloads.scripts.set(f,v),E.bulkPreloads.add(v),Hf(v,Zf({rel:"preload",href:f,as:u},c)),_.scriptResources[f]=!c||typeof c.crossOrigin!=="string"&&typeof c.integrity!=="string"?cu:[c.crossOrigin,c.integrity];break;default:if(_.unknownResources.hasOwnProperty(u)){if(v=_.unknownResources[u],v.hasOwnProperty(f))return}else v={},_.unknownResources[u]=v;if(v[f]=cu,(_=E.headers)&&0<_.remainingCapacity&&u==="font"&&(g=uE(f,u,c),0<=(_.remainingCapacity-=g.length+2)))E.resets.font[f]=cu,_.fontPreloads&&(_.fontPreloads+=", "),_.fontPreloads+=g;else switch(_=[],f=Zf({rel:"preload",href:f,as:u},c),Hf(_,f),u){case"font":E.fontPreloads.add(_);break;default:E.bulkPreloads.add(_)}}Nc(y)}}else Su.L(f,u,c)}function cn(f,u){var c=Of?Of:null;if(c){var{resumableState:y,renderState:_}=c;if(f){var E=u&&typeof u.as==="string"?u.as:"script";switch(E){case"script":if(y.moduleScriptResources.hasOwnProperty(f))return;E=[],y.moduleScriptResources[f]=!u||typeof u.crossOrigin!=="string"&&typeof u.integrity!=="string"?cu:[u.crossOrigin,u.integrity],_.preloads.moduleScripts.set(f,E);break;default:if(y.moduleUnknownResources.hasOwnProperty(E)){var v=y.unknownResources[E];if(v.hasOwnProperty(f))return}else v={},y.moduleUnknownResources[E]=v;E=[],v[f]=cu}Hf(E,Zf({rel:"modulepreload",href:f},u)),_.bulkPreloads.add(E),Nc(c)}}else Su.m(f,u)}function yn(f,u,c){var y=Of?Of:null;if(y){var{resumableState:_,renderState:E}=y;if(f){u=u||"default";var v=E.styles.get(u),T=_.styleResources.hasOwnProperty(f)?_.styleResources[f]:void 0;T!==null&&(_.styleResources[f]=null,v||(v={precedence:m(W(u)),rules:[],hrefs:[],sheets:new Map},E.styles.set(u,v)),u={state:0,props:Zf({rel:"stylesheet",href:f,"data-precedence":u},c)},T&&(T.length===2&&oy(u.props,T),(E=E.preloads.stylesheets.get(f))&&0; rel=preload; as="'+u+'"';for(var y in c)d.call(c,y)&&(f=c[y],typeof f==="string"&&(u+="; "+y.toLowerCase()+'="'+(""+f).replace(Vv,Sv)+'"'));return u}var ux=/[<>\r\n]/g;function cx(f){switch(f){case"<":return"%3C";case">":return"%3E";case` +`:return"%0A";case"\r":return"%0D";default:throw Error("escapeLinkHrefForHeaderContextReplacer encountered a match it does not know how to replace. this means the match regex and the replacement characters are no longer in sync. This is a bug in React")}}var Vv=/["';,\r\n]/g;function Sv(f){switch(f){case'"':return"%22";case"'":return"%27";case";":return"%3B";case",":return"%2C";case` +`:return"%0A";case"\r":return"%0D";default:throw Error("escapeStringForLinkHeaderQuotedParamValueContextReplacer encountered a match it does not know how to replace. this means the match regex and the replacement characters are no longer in sync. This is a bug in React")}}function vn(f){this.styles.add(f)}function xn(f){this.stylesheets.add(f)}function Kc(f,u){u.styles.forEach(vn,f),u.stylesheets.forEach(xn,f),u.suspenseyImages&&(f.suspenseyImages=!0)}function wC(f){return 0f.depth?HC(u,f):IC(u,f),Uc=f)}var Wg={enqueueSetState:function(f,u){f=f._reactInternals,f.queue!==null&&f.queue.push(u)},enqueueReplaceState:function(f,u){f=f._reactInternals,f.replace=!0,f.queue=[u]},enqueueForceUpdate:function(){}},Fv={id:1,overflow:""};function rc(f,u,c){var y=f.id;f=f.overflow;var _=32-l_(y)-1;y&=~(1<<_),c+=1;var E=32-l_(u)+_;if(30>=v,_-=v,{id:1<<32-l_(u)+_|c<<_|y,overflow:E+f}}return{id:1<>>=0,f===0?32:31-(Cn(f)/Rn|0)|0}function Gf(){}var qf=Error(Y(460));function An(f,u,c){switch(c=f[c],c===void 0?f.push(u):c!==u&&(u.then(Gf,Gf),u=c),u.status){case"fulfilled":return u.value;case"rejected":throw u.reason;default:switch(typeof u.status==="string"?u.then(Gf,Gf):(f=u,f.status="pending",f.then(function(y){if(u.status==="pending"){var _=u;_.status="fulfilled",_.value=y}},function(y){if(u.status==="pending"){var _=u;_.status="rejected",_.reason=y}})),u.status){case"fulfilled":return u.value;case"rejected":throw u.reason}throw b_=u,qf}}var b_=null;function yE(){if(b_===null)throw Error(Y(459));var f=b_;return b_=null,f}function nn(f,u){return f===u&&(f!==0||1/f===1/u)||f!==f&&u!==u}var wn=typeof Object.is==="function"?Object.is:nn,Fu=null,yx=null,_x=null,Ex=null,p_=null,F=null,ky=!1,_E=!1,ey=0,ay=0,sy=-1,EE=0,Vc=null,eu=null,nE=0;function Vu(){if(Fu===null)throw Error(Y(321));return Fu}function jg(){if(0)":-1_||x[y]!==g[_]){var C=` +`+x[y].replace(" at new "," at ");return f.displayName&&C.includes("")&&(C=C.replace("",f.displayName)),C}while(1<=y&&0<=_);break}}}finally{jv=!1,Error.prepareStackTrace=c}return(c=f?f.displayName||f.name:"")?tc(c):""}function LC(f){if(typeof f==="string")return tc(f);if(typeof f==="function")return f.prototype&&f.prototype.isReactComponent?F_(f,!0):F_(f,!1);if(typeof f==="object"&&f!==null){switch(f.$$typeof){case ov:return F_(f.render,!1);case av:return F_(f.type,!1);case RE:var u=f,c=u._payload;u=u._init;try{f=u(c)}catch(_){return tc("Lazy")}return LC(f)}if(typeof f.name==="string"){f:{c=f.name,u=f.env;var y=f.debugLocation;if(y!=null&&(f=Error.prepareStackTrace,Error.prepareStackTrace=void 0,y=y.stack,Error.prepareStackTrace=f,y.startsWith(`Error: react-stack-top-frame +`)&&(y=y.slice(29)),f=y.indexOf(` +`),f!==-1&&(y=y.slice(f+1)),f=y.indexOf("react_stack_bottom_frame"),f!==-1&&(f=y.lastIndexOf(` +`,f)),f=f!==-1?y=y.slice(0,f):"",y=f.lastIndexOf(` +`),f=y===-1?f:f.slice(y+1),f.indexOf(c)!==-1)){c=` +`+f;break f}c=tc(c+(u?" ["+u+"]":""))}return c}}switch(f){case ev:return tc("SuspenseList");case CE:return tc("Suspense")}return""}function kc(f,u){return(500=x.insertionMode){f.hasBody=!0;break f}break;case"html":if(x.insertionMode===0){f.hasHtml=!0;break f}break;case"head":if(1>=x.insertionMode)break f}u.push(Lc(y))}v.lastPushedText=!1}else{switch(y){case y0:case dg:case lg:case kg:y=u.keyPath,u.keyPath=c,yu(f,u,_.children,-1),u.keyPath=y;return;case pg:y=u.blockedSegment,y===null?_.mode!=="hidden"&&(y=u.keyPath,u.keyPath=c,Ef(f,u,_.children,-1),u.keyPath=y):_.mode!=="hidden"&&(y.chunks.push(i0),y.lastPushedText=!1,v=u.keyPath,u.keyPath=c,Ef(f,u,_.children,-1),u.keyPath=v,y.chunks.push(t0),y.lastPushedText=!1);return;case ev:f:{if(y=_.children,_=_.revealOrder,_==="forwards"||_==="backwards"||_==="unstable_legacy-backwards"){if(e_(y)){Zg(f,u,c,y,_);break f}if(v=qg(y)){if(v=v.call(y)){if(x=v.next(),!x.done){do x=v.next();while(!x.done);Zg(f,u,c,y,_)}break f}}}_==="together"?(_=u.keyPath,v=u.row,x=u.row=dy(null),x.boundaries=[],x.together=!0,u.keyPath=c,yu(f,u,y,-1),--x.pendingTasks===0&&Uf(f,x),u.keyPath=_,u.row=v,v!==null&&0u.formatContext.insertionMode?u_(f,u.row,H,py(),py()):u_(f,u.row,H,null,null);f.trackedPostpones!==null&&(M.trackedContentKeyPath=c);var U=au(f,w.chunks.length,M,u.formatContext,!1,!1);w.children.push(U),w.lastPushedText=!1;var N=au(f,0,null,u.formatContext,!1,!1);if(N.parentFlushed=!0,f.trackedPostpones!==null){v=u.componentStack,x=[c[0],"Suspense Fallback",c[2]],C=[x[1],x[2],[],null],f.trackedPostpones.workingMap.set(x,C),M.trackedFallbackNode=C,u.blockedSegment=U,u.blockedPreamble=M.fallbackPreamble,u.keyPath=x,u.formatContext=Qv(f.resumableState,E),u.componentStack=Kv(v),U.status=6;try{Ef(f,u,r,-1),U.lastPushedText&&U.textEmbedded&&U.chunks.push(Nu),U.status=1,Sc(f,g,U)}catch(G){throw U.status=f.status===12?3:4,G}finally{u.blockedSegment=w,u.blockedPreamble=T,u.keyPath=y,u.formatContext=E}u=c_(f,null,_,-1,M,N,M.contentPreamble,M.contentState,u.abortSet,c,k_(f.resumableState,u.formatContext),u.context,u.treeContext,null,v),Yc(u),f.pingedTasks.push(u)}else{u.blockedBoundary=M,u.blockedPreamble=M.contentPreamble,u.hoistableState=M.contentState,u.blockedSegment=N,u.keyPath=c,u.formatContext=k_(f.resumableState,E),u.row=null,N.status=6;try{if(Ef(f,u,_,-1),N.lastPushedText&&N.textEmbedded&&N.chunks.push(Nu),N.status=1,Sc(f,M,N),__(M,N),M.pendingTasks===0&&M.status===0){if(M.status=1,!kc(f,M)){R!==null&&--R.pendingTasks===0&&Uf(f,R),f.pendingRootTasks===0&&u.blockedPreamble&&T_(f);break f}}else R!==null&&R.together&&kv(f,R)}catch(G){M.status=4,f.status===12?(N.status=3,v=f.fatalError):(N.status=4,v=G),x=su(u.componentStack),C=If(f,v,x),M.errorDigest=C,gx(f,M)}finally{u.blockedBoundary=g,u.blockedPreamble=T,u.hoistableState=O,u.blockedSegment=w,u.keyPath=y,u.formatContext=E,u.row=R}u=c_(f,null,r,-1,g,U,M.fallbackPreamble,M.fallbackState,H,[c[0],"Suspense Fallback",c[2]],Qv(f.resumableState,u.formatContext),u.context,u.treeContext,u.row,Kv(u.componentStack)),Yc(u),f.pingedTasks.push(u)}}return}if(typeof y==="object"&&y!==null)switch(y.$$typeof){case ov:if("ref"in _)for(w in v={},_)w!=="ref"&&(v[w]=_[w]);else v=_;y=hg(f,u,c,y.render,v,E),ig(f,u,c,y,ey!==0,ay,sy);return;case av:vE(f,u,c,y.type,_,E);return;case gE:if(x=_.children,v=u.keyPath,_=_.value,C=y._currentValue,y._currentValue=_,E=Uc,Uc=y={parent:E,depth:E===null?0:E.depth+1,context:y,parentValue:C,value:_},u.context=y,u.keyPath=c,yu(f,u,x,-1),f=Uc,f===null)throw Error(Y(403));f.context._currentValue=f.parentValue,f=Uc=f.parent,u.context=f,u.keyPath=v;return;case bg:_=_.children,y=_(y._context._currentValue),_=u.keyPath,u.keyPath=c,yu(f,u,y,-1),u.keyPath=_;return;case RE:if(v=y._init,y=v(y._payload),f.status===12)throw null;vE(f,u,c,y,_,E);return}throw Error(Y(130,y==null?y:typeof y,""))}}function wE(f,u,c,y,_){var{replay:E,blockedBoundary:v}=u,T=au(f,0,null,u.formatContext,!1,!1);T.id=c,T.parentFlushed=!0;try{u.replay=null,u.blockedSegment=T,Ef(f,u,y,_),T.status=1,Sc(f,v,T),v===null?f.completedRootSegment=T:(__(v,T),v.parentFlushed&&f.partialBoundaries.push(v))}finally{u.replay=E,u.blockedSegment=null}}function yu(f,u,c,y){u.replay!==null&&typeof u.replay.slots==="number"?wE(f,u,u.replay.slots,c,y):(u.node=c,u.childIndex=y,c=u.componentStack,Yc(u),dv(f,u),u.componentStack=c)}function dv(f,u){var{node:c,childIndex:y}=u;if(c!==null){if(typeof c==="object"){switch(c.$$typeof){case Fg:var{type:_,key:E,props:v}=c;c=v.ref;var T=c!==void 0?c:null,x=cE(_),g=E==null?y===-1?0:y:E;if(E=[u.keyPath,x,g],u.replay!==null)f:{var C=u.replay;y=C.nodes;for(c=0;cu.formatContext.insertionMode?u_(f,u.row,z,py(),py()):u_(f,u.row,z,null,null),v.parentFlushed=!0,v.rootSegmentID=_,u.blockedBoundary=v,u.hoistableState=v.contentState,u.keyPath=E,u.formatContext=k_(f.resumableState,r),u.row=null,u.replay={nodes:T,slots:x,pendingTasks:1};try{if(Ef(f,u,G,-1),u.replay.pendingTasks===1&&0f.progressiveChunkSize||wC(_.contentState)))_.rootSegmentID=f.nextSegmentId++,f.completedBoundaries.push(_),Yg(u,f.renderState,_.rootSegmentID),K_(f,u,c,y);else{if(E_+=_.byteSize,y&&Kc(y,_.contentState),c=_.row,c!==null&&kc(f,_)&&--c.pendingTasks===0&&Uf(f,c),V(u,J0),c=_.completedSegments,c.length!==1)throw Error(Y(391));v_(f,u,c[0],y)}return V(u,K0)}function Xv(f,u,c,y){return wA(u,f.renderState,c.parentFormatContext,c.id),v_(f,u,c,y),mA(u,c.parentFormatContext)}function Vg(f,u,c){E_=c.byteSize;for(var y=c.completedSegments,_=0;_"u")globalThis.MessageChannel=class{constructor(){this.port1={onmessage:null,postMessage:()=>{}},this.port2={postMessage:(u)=>{if(this.port1.onmessage)queueMicrotask(()=>this.port1.onmessage({data:u}))}}}};var Gn={"Content-Type":"text/html"},Xn=()=>lc.default.createElement("html",null,lc.default.createElement("body",null,lc.default.createElement("h1",null,"Hello World"),lc.default.createElement("p",null,"This is an example."))),tn={async fetch(f){return new Response(await nx(lc.default.createElement(Xn,null)),{headers:Gn})}};export{tn as default}; diff --git a/bench/react-hello-world/react-hello-world.workerd.jsx b/bench/react-hello-world/react-hello-world.workerd.jsx new file mode 100644 index 0000000000..b5703d3776 --- /dev/null +++ b/bench/react-hello-world/react-hello-world.workerd.jsx @@ -0,0 +1,40 @@ +// Cloudflare Workers version with export default fetch +// Run with: workerd serve react-hello-world.workerd.config.capnp + +// Polyfill MessageChannel for workerd +if (typeof MessageChannel === 'undefined') { + globalThis.MessageChannel = class MessageChannel { + constructor() { + this.port1 = { onmessage: null, postMessage: () => {} }; + this.port2 = { + postMessage: (msg) => { + if (this.port1.onmessage) { + queueMicrotask(() => this.port1.onmessage({ data: msg })); + } + } + }; + } + }; +} + +import React from "react"; +import { renderToReadableStream } from "react-dom/server"; + +const headers = { + "Content-Type": "text/html", +}; + +const App = () => ( + + + Hello World + This is an example. + + +); + +export default { + async fetch(request) { + return new Response(await renderToReadableStream(), { headers }); + }, +}; diff --git a/bench/snippets/require-builtins.mjs b/bench/snippets/require-builtins.mjs index 4d59f13532..1c1af9e79f 100644 --- a/bench/snippets/require-builtins.mjs +++ b/bench/snippets/require-builtins.mjs @@ -11,10 +11,10 @@ const builtin = ${JSON.stringify(builtin)}; const now = performance.now(); require(builtin); const end = performance.now(); -process.stdout.write(JSON.stringify({builtin, time: end - now}) + "\\n"); +process.stdout.write(JSON.stringify({ builtin, time: end - now }) + "\\n"); `, ); - const result = spawnSync(typeof Bun !== "undefined" ? "bun" : "node", [path], { + spawnSync(process.execPath, [path], { stdio: ["inherit", "inherit", "inherit"], env: { ...process.env, diff --git a/build.zig b/build.zig index fa76a2138c..eb16d47401 100644 --- a/build.zig +++ b/build.zig @@ -18,22 +18,6 @@ const OperatingSystem = @import("src/env.zig").OperatingSystem; const pathRel = fs.path.relative; -/// When updating this, make sure to adjust SetupZig.cmake -const recommended_zig_version = "0.14.0"; - -// comptime { -// if (!std.mem.eql(u8, builtin.zig_version_string, recommended_zig_version)) { -// @compileError( -// "" ++ -// "Bun requires Zig version " ++ recommended_zig_version ++ ", but you have " ++ -// builtin.zig_version_string ++ ". This is automatically configured via Bun's " ++ -// "CMake setup. You likely meant to run `bun run build`. If you are trying to " ++ -// "upgrade the Zig compiler, edit ZIG_COMMIT in cmake/tools/SetupZig.cmake or " ++ -// "comment this error out.", -// ); -// } -// } - const zero_sha = "0000000000000000000000000000000000000000"; const BunBuildOptions = struct { @@ -49,6 +33,7 @@ const BunBuildOptions = struct { enable_logs: bool = false, enable_asan: bool, enable_valgrind: bool, + use_mimalloc: bool, tracy_callstack_depth: u16, reported_nodejs_version: Version, /// To make iterating on some '@embedFile's faster, we load them at runtime @@ -97,7 +82,8 @@ const BunBuildOptions = struct { opts.addOption(bool, "enable_logs", this.enable_logs); opts.addOption(bool, "enable_asan", this.enable_asan); opts.addOption(bool, "enable_valgrind", this.enable_valgrind); - opts.addOption([]const u8, "reported_nodejs_version", b.fmt("{}", .{this.reported_nodejs_version})); + opts.addOption(bool, "use_mimalloc", this.use_mimalloc); + opts.addOption([]const u8, "reported_nodejs_version", b.fmt("{f}", .{this.reported_nodejs_version})); opts.addOption(bool, "zig_self_hosted_backend", this.no_llvm); opts.addOption(bool, "override_no_export_cpp_apis", this.override_no_export_cpp_apis); @@ -132,8 +118,8 @@ pub fn getOSVersionMin(os: OperatingSystem) ?Target.Query.OsVersion { pub fn getOSGlibCVersion(os: OperatingSystem) ?Version { return switch (os) { - // Compiling with a newer glibc than this will break certain cloud environments. - .linux => .{ .major = 2, .minor = 27, .patch = 0 }, + // Compiling with a newer glibc than this will break certain cloud environments. See symbols.test.ts. + .linux => .{ .major = 2, .minor = 26, .patch = 0 }, else => null, }; @@ -270,6 +256,7 @@ pub fn build(b: *Build) !void { .enable_logs = b.option(bool, "enable_logs", "Enable logs in release") orelse false, .enable_asan = b.option(bool, "enable_asan", "Enable asan") orelse false, .enable_valgrind = b.option(bool, "enable_valgrind", "Enable valgrind") orelse false, + .use_mimalloc = b.option(bool, "use_mimalloc", "Use mimalloc as default allocator") orelse false, .llvm_codegen_threads = b.option(u32, "llvm_codegen_threads", "Number of threads to use for LLVM codegen") orelse 1, }; @@ -287,14 +274,16 @@ pub fn build(b: *Build) !void { var o = build_options; var unit_tests = b.addTest(.{ .name = "bun-test", - .optimize = build_options.optimize, - .root_source_file = b.path("src/unit_test.zig"), .test_runner = .{ .path = b.path("src/main_test.zig"), .mode = .simple }, - .target = build_options.target, + .root_module = b.createModule(.{ + .optimize = build_options.optimize, + .root_source_file = b.path("src/unit_test.zig"), + .target = build_options.target, + .omit_frame_pointer = false, + .strip = false, + }), .use_llvm = !build_options.no_llvm, .use_lld = if (build_options.os == .mac) false else !build_options.no_llvm, - .omit_frame_pointer = false, - .strip = false, }); configureObj(b, &o, unit_tests); // Setting `linker_allow_shlib_undefined` causes the linker to ignore @@ -328,6 +317,7 @@ pub fn build(b: *Build) !void { var step = b.step("check", "Check for semantic analysis errors"); var bun_check_obj = addBunObject(b, &build_options); bun_check_obj.generated_bin = null; + // bun_check_obj.use_llvm = false; step.dependOn(&bun_check_obj.step); // The default install step will run zig build check. This is so ZLS @@ -500,6 +490,7 @@ fn addMultiCheck( .no_llvm = root_build_options.no_llvm, .enable_asan = root_build_options.enable_asan, .enable_valgrind = root_build_options.enable_valgrind, + .use_mimalloc = root_build_options.use_mimalloc, .override_no_export_cpp_apis = root_build_options.override_no_export_cpp_apis, }; @@ -612,7 +603,7 @@ fn configureObj(b: *Build, opts: *BunBuildOptions, obj: *Compile) void { obj.llvm_codegen_threads = opts.llvm_codegen_threads orelse 0; } - obj.no_link_obj = true; + obj.no_link_obj = opts.os != .windows; if (opts.enable_asan and !enableFastBuild(b)) { if (@hasField(Build.Module, "sanitize_address")) { @@ -720,6 +711,7 @@ fn addInternalImports(b: *Build, mod: *Module, opts: *BunBuildOptions) void { // Generated code exposed as individual modules. inline for (.{ .{ .file = "ZigGeneratedClasses.zig", .import = "ZigGeneratedClasses" }, + .{ .file = "bindgen_generated.zig", .import = "bindgen_generated" }, .{ .file = "ResolvedSourceTag.zig", .import = "ResolvedSourceTag" }, .{ .file = "ErrorCode.zig", .import = "ErrorCode" }, .{ .file = "runtime.out.js", .enable = opts.shouldEmbedCode() }, @@ -774,6 +766,13 @@ fn addInternalImports(b: *Build, mod: *Module, opts: *BunBuildOptions) void { mod.addImport("cpp", cppImport); cppImport.addImport("bun", mod); } + { + const ciInfoImport = b.createModule(.{ + .root_source_file = (std.Build.LazyPath{ .cwd_relative = opts.codegen_path }).path(b, "ci_info.zig"), + }); + mod.addImport("ci_info", ciInfoImport); + ciInfoImport.addImport("bun", mod); + } inline for (.{ .{ .import = "completions-bash", .file = b.path("completions/bun.bash") }, .{ .import = "completions-zsh", .file = b.path("completions/bun.zsh") }, @@ -799,7 +798,7 @@ fn addInternalImports(b: *Build, mod: *Module, opts: *BunBuildOptions) void { fn propagateImports(source_mod: *Module) !void { var seen = std.AutoHashMap(*Module, void).init(source_mod.owner.graph.arena); defer seen.deinit(); - var queue = std.ArrayList(*Module).init(source_mod.owner.graph.arena); + var queue = std.array_list.Managed(*Module).init(source_mod.owner.graph.arena); defer queue.deinit(); try queue.appendSlice(source_mod.import_table.values()); while (queue.pop()) |mod| { diff --git a/bun.lock b/bun.lock index be4ab107ae..6a66c586b2 100644 --- a/bun.lock +++ b/bun.lock @@ -8,14 +8,14 @@ "@lezer/cpp": "^1.1.3", "@types/bun": "workspace:*", "bun-tracestrings": "github:oven-sh/bun.report#912ca63e26c51429d3e6799aa2a6ab079b188fd8", - "esbuild": "^0.21.4", - "mitata": "^0.1.11", + "esbuild": "^0.21.5", + "mitata": "^0.1.14", "peechy": "0.4.34", - "prettier": "^3.5.3", - "prettier-plugin-organize-imports": "^4.0.0", + "prettier": "^3.6.2", + "prettier-plugin-organize-imports": "^4.3.0", "react": "^18.3.1", "react-dom": "^18.3.1", - "source-map-js": "^1.2.0", + "source-map-js": "^1.2.1", "typescript": "5.9.2", }, }, @@ -31,12 +31,6 @@ "dependencies": { "@types/node": "*", }, - "devDependencies": { - "@types/react": "^19", - }, - "peerDependencies": { - "@types/react": "^19", - }, }, }, "overrides": { @@ -162,8 +156,6 @@ "@types/node": ["@types/node@24.2.1", "", { "dependencies": { "undici-types": "~7.10.0" } }, "sha512-DRh5K+ka5eJic8CjH7td8QpYEV6Zo10gfRkjHCO3weqZHWDtAaSTFtl4+VMqOJ4N5jcuhZ9/l+yy8rVgw7BQeQ=="], - "@types/react": ["@types/react@19.1.10", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-EhBeSYX0Y6ye8pNebpKrwFJq7BoQ8J5SO6NlvNwwHjSj6adXJViPQrKlsyPw7hLBLvckEMO1yxeGdR82YBBlDg=="], - "aggregate-error": ["aggregate-error@3.1.0", "", { "dependencies": { "clean-stack": "^2.0.0", "indent-string": "^4.0.0" } }, "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA=="], "before-after-hook": ["before-after-hook@2.2.3", "", {}, "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ=="], @@ -192,8 +184,6 @@ "constant-case": ["constant-case@3.0.4", "", { "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3", "upper-case": "^2.0.2" } }, "sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ=="], - "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], - "deprecation": ["deprecation@2.3.1", "", {}, "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ=="], "detect-libc": ["detect-libc@2.0.4", "", {}, "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA=="], @@ -284,7 +274,7 @@ "prettier": ["prettier@3.6.2", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ=="], - "prettier-plugin-organize-imports": ["prettier-plugin-organize-imports@4.2.0", "", { "peerDependencies": { "prettier": ">=2.0", "typescript": ">=2.9", "vue-tsc": "^2.1.0 || 3" }, "optionalPeers": ["vue-tsc"] }, "sha512-Zdy27UhlmyvATZi67BTnLcKTo8fm6Oik59Sz6H64PgZJVs6NJpPD1mT240mmJn62c98/QaL+r3kx9Q3gRpDajg=="], + "prettier-plugin-organize-imports": ["prettier-plugin-organize-imports@4.3.0", "", { "peerDependencies": { "prettier": ">=2.0", "typescript": ">=2.9", "vue-tsc": "^2.1.0 || 3" }, "optionalPeers": ["vue-tsc"] }, "sha512-FxFz0qFhyBsGdIsb697f/EkvHzi5SZOhWAjxcx2dLt+Q532bAlhswcXGYB1yzjZ69kW8UoadFBw7TyNwlq96Iw=="], "react": ["react@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="], diff --git a/bunfig.toml b/bunfig.toml index 3eae059d7c..f1bba3259c 100644 --- a/bunfig.toml +++ b/bunfig.toml @@ -10,3 +10,4 @@ preload = "./test/preload.ts" [install] linker = "isolated" +minimumReleaseAge = 1 diff --git a/cmake/CompilerFlags.cmake b/cmake/CompilerFlags.cmake index 9f97e31dc1..cff32fb166 100644 --- a/cmake/CompilerFlags.cmake +++ b/cmake/CompilerFlags.cmake @@ -86,11 +86,20 @@ elseif(APPLE) endif() if(UNIX) - register_compiler_flags( - DESCRIPTION "Enable debug symbols" - -g3 -gz=zstd ${DEBUG} - -g1 ${RELEASE} - ) + # Nix LLVM doesn't support zstd compression, use zlib instead + if(DEFINED ENV{NIX_CC}) + register_compiler_flags( + DESCRIPTION "Enable debug symbols (zlib-compressed for Nix)" + -g3 -gz=zlib ${DEBUG} + -g1 ${RELEASE} + ) + else() + register_compiler_flags( + DESCRIPTION "Enable debug symbols (zstd-compressed)" + -g3 -gz=zstd ${DEBUG} + -g1 ${RELEASE} + ) + endif() register_compiler_flags( DESCRIPTION "Optimize debug symbols for LLDB" @@ -206,43 +215,6 @@ if(ENABLE_ASSERTIONS) DESCRIPTION "Do not eliminate null-pointer checks" -fno-delete-null-pointer-checks ) - - register_compiler_definitions( - DESCRIPTION "Enable libc++ assertions" - _LIBCPP_ENABLE_ASSERTIONS=1 - _LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_EXTENSIVE ${RELEASE} - _LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_DEBUG ${DEBUG} - ) - - register_compiler_definitions( - DESCRIPTION "Enable fortified sources" - _FORTIFY_SOURCE=3 - ) - - if(LINUX) - register_compiler_definitions( - DESCRIPTION "Enable glibc++ assertions" - _GLIBCXX_ASSERTIONS=1 - ) - endif() -else() - register_compiler_definitions( - DESCRIPTION "Disable debug assertions" - NDEBUG=1 - ) - - register_compiler_definitions( - DESCRIPTION "Disable libc++ assertions" - _LIBCPP_ENABLE_ASSERTIONS=0 - _LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_NONE - ) - - if(LINUX) - register_compiler_definitions( - DESCRIPTION "Disable glibc++ assertions" - _GLIBCXX_ASSERTIONS=0 - ) - endif() endif() # --- Diagnostics --- @@ -293,14 +265,6 @@ if(UNIX AND CI) ) endif() -# --- Features --- - -# Valgrind cannot handle SSE4.2 instructions -# This is needed for picohttpparser -if(ENABLE_VALGRIND AND ARCH STREQUAL "x64") - register_compiler_definitions(__SSE4_2__=0) -endif() - # --- Other --- # Workaround for CMake and clang-cl bug. diff --git a/cmake/Globals.cmake b/cmake/Globals.cmake index 941e98b421..176d15ea08 100644 --- a/cmake/Globals.cmake +++ b/cmake/Globals.cmake @@ -125,7 +125,8 @@ setx(CWD ${CMAKE_SOURCE_DIR}) setx(BUILD_PATH ${CMAKE_BINARY_DIR}) optionx(CACHE_PATH FILEPATH "The path to the cache directory" DEFAULT ${BUILD_PATH}/cache) -optionx(CACHE_STRATEGY "read-write|read-only|write-only|none" "The strategy to use for caching" DEFAULT "read-write") +optionx(CACHE_STRATEGY "auto|distributed|local|none" "The strategy to use for caching" DEFAULT +"auto") optionx(CI BOOL "If CI is enabled" DEFAULT OFF) optionx(ENABLE_ANALYSIS BOOL "If static analysis targets should be enabled" DEFAULT OFF) @@ -136,21 +137,44 @@ else() set(WARNING WARNING) endif() -# TODO: This causes flaky zig builds in CI, so temporarily disable it. -# if(CI) -# set(DEFAULT_VENDOR_PATH ${CACHE_PATH}/vendor) -# else() -# set(DEFAULT_VENDOR_PATH ${CWD}/vendor) -# endif() - optionx(VENDOR_PATH FILEPATH "The path to the vendor directory" DEFAULT ${CWD}/vendor) optionx(TMP_PATH FILEPATH "The path to the temporary directory" DEFAULT ${BUILD_PATH}/tmp) # --- Helper functions --- +# list_filter_out_regex() +# +# Description: +# Filters out elements from a list that match a regex pattern. +# +# Arguments: +# list - The list of strings to traverse +# pattern - The regex pattern to filter out +# touched - A variable to set if any items were removed +function(list_filter_out_regex list pattern touched) + set(result_list "${${list}}") + set(keep_list) + set(was_modified OFF) + + foreach(line IN LISTS result_list) + if(line MATCHES "${pattern}") + set(was_modified ON) + else() + list(APPEND keep_list ${line}) + endif() + endforeach() + + set(${list} "${keep_list}" PARENT_SCOPE) + set(${touched} ${was_modified} PARENT_SCOPE) +endfunction() + # setenv() # Description: # Sets an environment variable during the build step, and writes it to a .env file. +# +# See Also: +# unsetenv() +# # Arguments: # variable string - The variable to set # value string - The value to set the variable to @@ -163,13 +187,7 @@ function(setenv variable value) if(EXISTS ${ENV_PATH}) file(STRINGS ${ENV_PATH} ENV_FILE ENCODING UTF-8) - - foreach(line ${ENV_FILE}) - if(line MATCHES "^${variable}=") - list(REMOVE_ITEM ENV_FILE ${line}) - set(ENV_MODIFIED ON) - endif() - endforeach() + list_filter_out_regex(ENV_FILE "^${variable}=" ENV_MODIFIED) if(ENV_MODIFIED) list(APPEND ENV_FILE "${variable}=${value}") @@ -185,6 +203,28 @@ function(setenv variable value) message(STATUS "Set ENV ${variable}: ${value}") endfunction() +# See setenv() +# Description: +# Exact opposite of setenv(). +# Arguments: +# variable string - The variable to unset. +# See Also: +# setenv() +function(unsetenv variable) + set(ENV_PATH ${BUILD_PATH}/.env) + if(NOT EXISTS ${ENV_PATH}) + return() + endif() + + file(STRINGS ${ENV_PATH} ENV_FILE ENCODING UTF-8) + list_filter_out_regex(ENV_FILE "^${variable}=" ENV_MODIFIED) + + if(ENV_MODIFIED) + list(JOIN ENV_FILE "\n" ENV_FILE) + file(WRITE ${ENV_PATH} ${ENV_FILE}) + endif() +endfunction() + # satisfies_range() # Description: # Check if a version satisfies a version range or list of ranges @@ -317,7 +357,7 @@ function(find_command) ${FIND_VALIDATOR} ) - if(NOT FIND_REQUIRED STREQUAL "OFF" AND ${FIND_VARIABLE} MATCHES "NOTFOUND") + if(FIND_REQUIRED AND ${FIND_VARIABLE} MATCHES "NOTFOUND") set(error "Command not found: \"${FIND_NAME}\"") if(FIND_VERSION) @@ -917,10 +957,6 @@ function(register_compiler_flags) endforeach() endfunction() -function(register_compiler_definitions) - -endfunction() - # register_linker_flags() # Description: # Registers a linker flag, similar to `add_link_options()`. diff --git a/cmake/Options.cmake b/cmake/Options.cmake index 1e9b664321..ac6ce10c74 100644 --- a/cmake/Options.cmake +++ b/cmake/Options.cmake @@ -140,11 +140,6 @@ if(ENABLE_ASAN AND ENABLE_LTO) setx(ENABLE_LTO OFF) endif() -if(USE_VALGRIND AND NOT USE_BASELINE) - message(WARNING "If valgrind is enabled, baseline must also be enabled") - setx(USE_BASELINE ON) -endif() - if(BUILDKITE_COMMIT) set(DEFAULT_REVISION ${BUILDKITE_COMMIT}) else() @@ -202,4 +197,9 @@ optionx(USE_WEBKIT_ICU BOOL "Use the ICU libraries from WebKit" DEFAULT ${DEFAUL optionx(ERROR_LIMIT STRING "Maximum number of errors to show when compiling C++ code" DEFAULT "100") +# This is not an `option` because setting this variable to OFF is experimental +# and unsupported. This replaces the `use_mimalloc` variable previously in +# bun.zig, and enables C++ code to also be aware of the option. +set(USE_MIMALLOC_AS_DEFAULT_ALLOCATOR ON) + list(APPEND CMAKE_ARGS -DCMAKE_EXPORT_COMPILE_COMMANDS=ON) diff --git a/cmake/Sources.json b/cmake/Sources.json index cd86d86989..5ae4930693 100644 --- a/cmake/Sources.json +++ b/cmake/Sources.json @@ -31,6 +31,14 @@ "output": "BindgenSources.txt", "paths": ["src/**/*.bind.ts"] }, + { + "output": "BindgenV2Sources.txt", + "paths": ["src/**/*.bindv2.ts"] + }, + { + "output": "BindgenV2InternalSources.txt", + "paths": ["src/codegen/bindgenv2/**/*.ts"] + }, { "output": "ZigSources.txt", "paths": ["src/**/*.zig"] diff --git a/cmake/analysis/RunCppCheck.cmake b/cmake/analysis/RunCppCheck.cmake deleted file mode 100644 index a384a44863..0000000000 --- a/cmake/analysis/RunCppCheck.cmake +++ /dev/null @@ -1,33 +0,0 @@ -# https://cppcheck.sourceforge.io/ - -find_command( - VARIABLE - CPPCHECK_EXECUTABLE - COMMAND - cppcheck - REQUIRED - OFF -) - -set(CPPCHECK_COMMAND ${CPPCHECK_EXECUTABLE} - --cppcheck-build-dir=${BUILD_PATH}/cppcheck - --project=${BUILD_PATH}/compile_commands.json - --clang=${CMAKE_CXX_COMPILER} - --std=c++${CMAKE_CXX_STANDARD} - --report-progress - --showtime=summary -) - -register_command( - TARGET - cppcheck - COMMENT - "Running cppcheck" - COMMAND - ${CMAKE_COMMAND} -E make_directory cppcheck - && ${CPPCHECK_COMMAND} - CWD - ${BUILD_PATH} - TARGETS - ${bun} -) diff --git a/cmake/analysis/RunCppLint.cmake b/cmake/analysis/RunCppLint.cmake deleted file mode 100644 index 5b9264ecf5..0000000000 --- a/cmake/analysis/RunCppLint.cmake +++ /dev/null @@ -1,22 +0,0 @@ -find_command( - VARIABLE - CPPLINT_PROGRAM - COMMAND - cpplint - REQUIRED - OFF -) - -register_command( - TARGET - cpplint - COMMENT - "Running cpplint" - COMMAND - ${CPPLINT_PROGRAM} - ${BUN_CPP_SOURCES} - CWD - ${BUILD_PATH} - TARGETS - ${bun} -) diff --git a/cmake/analysis/RunIWYU.cmake b/cmake/analysis/RunIWYU.cmake deleted file mode 100644 index 0ea555f2f5..0000000000 --- a/cmake/analysis/RunIWYU.cmake +++ /dev/null @@ -1,67 +0,0 @@ -# IWYU = "Include What You Use" -# https://include-what-you-use.org/ - -setx(IWYU_SOURCE_PATH ${CACHE_PATH}/iwyu-${LLVM_VERSION}) -setx(IWYU_BUILD_PATH ${IWYU_SOURCE_PATH}/build) -setx(IWYU_PROGRAM ${IWYU_BUILD_PATH}/bin/include-what-you-use) - -register_repository( - NAME - iwyu - REPOSITORY - include-what-you-use/include-what-you-use - BRANCH - clang_${LLVM_VERSION} - PATH - ${IWYU_SOURCE_PATH} -) - -register_command( - TARGET - build-iwyu - COMMENT - "Building iwyu" - COMMAND - ${CMAKE_COMMAND} - -B${IWYU_BUILD_PATH} - -G${CMAKE_GENERATOR} - -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER} - -DCMAKE_CXX_COMPILER_LAUNCHER=${CMAKE_CXX_COMPILER_LAUNCHER} - -DIWYU_LLVM_ROOT_PATH=${LLVM_PREFIX} - && ${CMAKE_COMMAND} - --build ${IWYU_BUILD_PATH} - CWD - ${IWYU_SOURCE_PATH} - TARGETS - clone-iwyu -) - -find_command( - VARIABLE - PYTHON_EXECUTABLE - COMMAND - python3 - python - VERSION - >=3.0.0 - REQUIRED - OFF -) - -register_command( - TARGET - iwyu - COMMENT - "Running iwyu" - COMMAND - ${CMAKE_COMMAND} - -E env IWYU_BINARY=${IWYU_PROGRAM} - ${PYTHON_EXECUTABLE} - ${IWYU_SOURCE_PATH}/iwyu_tool.py - -p ${BUILD_PATH} - CWD - ${BUILD_PATH} - TARGETS - build-iwyu - ${bun} -) diff --git a/cmake/targets/BuildBun.cmake b/cmake/targets/BuildBun.cmake index 31b007050c..43b061846b 100644 --- a/cmake/targets/BuildBun.cmake +++ b/cmake/targets/BuildBun.cmake @@ -45,12 +45,6 @@ else() endif() set(LLVM_ZIG_CODEGEN_THREADS 0) -# This makes the build slower, so we turn it off for now. -# if (DEBUG) -# include(ProcessorCount) -# ProcessorCount(CPU_COUNT) -# set(LLVM_ZIG_CODEGEN_THREADS ${CPU_COUNT}) -# endif() # --- Dependencies --- @@ -71,9 +65,6 @@ set(BUN_DEPENDENCIES ) include(CloneZstd) -# foreach(dependency ${BUN_DEPENDENCIES}) -# include(Clone${dependency}) -# endforeach() # --- Codegen --- @@ -326,6 +317,10 @@ set(BUN_CPP_OUTPUTS ${CODEGEN_PATH}/cpp.zig ) +set(BUN_CI_INFO_OUTPUTS + ${CODEGEN_PATH}/ci_info.zig +) + register_command( TARGET bun-cppbind @@ -343,6 +338,21 @@ register_command( ${BUN_CPP_OUTPUTS} ) +register_command( + TARGET + bun-ci-info + COMMENT + "Generating CI info" + COMMAND + ${BUN_EXECUTABLE} + ${CWD}/src/codegen/ci_info.ts + ${CODEGEN_PATH}/ci_info.zig + SOURCES + ${BUN_JAVASCRIPT_CODEGEN_SOURCES} + OUTPUTS + ${BUN_CI_INFO_OUTPUTS} +) + register_command( TARGET bun-js-modules @@ -395,6 +405,54 @@ register_command( ${BUN_BAKE_RUNTIME_OUTPUTS} ) +set(BUN_BINDGENV2_SCRIPT ${CWD}/src/codegen/bindgenv2/script.ts) + +absolute_sources(BUN_BINDGENV2_SOURCES ${CWD}/cmake/sources/BindgenV2Sources.txt) +# These sources include the script itself. +absolute_sources(BUN_BINDGENV2_INTERNAL_SOURCES + ${CWD}/cmake/sources/BindgenV2InternalSources.txt) +string(REPLACE ";" "," BUN_BINDGENV2_SOURCES_COMMA_SEPARATED + "${BUN_BINDGENV2_SOURCES}") + +execute_process( + COMMAND ${BUN_EXECUTABLE} run ${BUN_BINDGENV2_SCRIPT} + --command=list-outputs + --sources=${BUN_BINDGENV2_SOURCES_COMMA_SEPARATED} + --codegen-path=${CODEGEN_PATH} + RESULT_VARIABLE bindgen_result + OUTPUT_VARIABLE bindgen_outputs +) +if(${bindgen_result}) + message(FATAL_ERROR "bindgenv2/script.ts exited with non-zero status") +endif() +foreach(output IN LISTS bindgen_outputs) + if(output MATCHES "\.cpp$") + list(APPEND BUN_BINDGENV2_CPP_OUTPUTS ${output}) + elseif(output MATCHES "\.zig$") + list(APPEND BUN_BINDGENV2_ZIG_OUTPUTS ${output}) + else() + message(FATAL_ERROR "unexpected bindgen output: [${output}]") + endif() +endforeach() + +register_command( + TARGET + bun-bindgen-v2 + COMMENT + "Generating bindings (v2)" + COMMAND + ${BUN_EXECUTABLE} run ${BUN_BINDGENV2_SCRIPT} + --command=generate + --codegen-path=${CODEGEN_PATH} + --sources=${BUN_BINDGENV2_SOURCES_COMMA_SEPARATED} + SOURCES + ${BUN_BINDGENV2_SOURCES} + ${BUN_BINDGENV2_INTERNAL_SOURCES} + OUTPUTS + ${BUN_BINDGENV2_CPP_OUTPUTS} + ${BUN_BINDGENV2_ZIG_OUTPUTS} +) + set(BUN_BINDGEN_SCRIPT ${CWD}/src/codegen/bindgen.ts) absolute_sources(BUN_BINDGEN_SOURCES ${CWD}/cmake/sources/BindgenSources.txt) @@ -573,6 +631,8 @@ set(BUN_ZIG_GENERATED_SOURCES ${BUN_ZIG_GENERATED_CLASSES_OUTPUTS} ${BUN_JAVASCRIPT_OUTPUTS} ${BUN_CPP_OUTPUTS} + ${BUN_CI_INFO_OUTPUTS} + ${BUN_BINDGENV2_ZIG_OUTPUTS} ) # In debug builds, these are not embedded, but rather referenced at runtime. @@ -636,6 +696,7 @@ register_command( -Denable_logs=$,true,false> -Denable_asan=$,true,false> -Denable_valgrind=$,true,false> + -Duse_mimalloc=$,true,false> -Dllvm_codegen_threads=${LLVM_ZIG_CODEGEN_THREADS} -Dversion=${VERSION} -Dreported_nodejs_version=${NODEJS_VERSION} @@ -712,6 +773,7 @@ list(APPEND BUN_CPP_SOURCES ${BUN_JAVASCRIPT_OUTPUTS} ${BUN_OBJECT_LUT_OUTPUTS} ${BUN_BINDGEN_CPP_OUTPUTS} + ${BUN_BINDGENV2_CPP_OUTPUTS} ) if(WIN32) @@ -768,7 +830,7 @@ set_target_properties(${bun} PROPERTIES CXX_STANDARD_REQUIRED YES CXX_EXTENSIONS YES CXX_VISIBILITY_PRESET hidden - C_STANDARD 17 + C_STANDARD 17 # Cannot uprev to C23 because MSVC doesn't have support. C_STANDARD_REQUIRED YES VISIBILITY_INLINES_HIDDEN YES ) @@ -849,6 +911,10 @@ if(WIN32) ) endif() +if(USE_MIMALLOC_AS_DEFAULT_ALLOCATOR) + target_compile_definitions(${bun} PRIVATE USE_MIMALLOC=1) +endif() + target_compile_definitions(${bun} PRIVATE _HAS_EXCEPTIONS=0 LIBUS_USE_OPENSSL=1 @@ -889,7 +955,7 @@ if(NOT WIN32) if (NOT ABI STREQUAL "musl") target_compile_options(${bun} PUBLIC -fsanitize=null - -fsanitize-recover=all + -fno-sanitize-recover=all -fsanitize=bounds -fsanitize=return -fsanitize=nullability-arg @@ -944,6 +1010,20 @@ if(NOT WIN32) ) if(ENABLE_ASAN) + target_compile_options(${bun} PUBLIC + -fsanitize=null + -fno-sanitize-recover=all + -fsanitize=bounds + -fsanitize=return + -fsanitize=nullability-arg + -fsanitize=nullability-assign + -fsanitize=nullability-return + -fsanitize=returns-nonnull-attribute + -fsanitize=unreachable + ) + target_link_libraries(${bun} PRIVATE + -fsanitize=null + ) target_compile_options(${bun} PUBLIC -fsanitize=address) target_link_libraries(${bun} PUBLIC -fsanitize=address) endif() @@ -990,7 +1070,6 @@ if(APPLE) -Wl,-no_compact_unwind -Wl,-stack_size,0x1200000 -fno-keep-static-consts - -Wl,-map,${bun}.linker-map ) if(DEBUG) @@ -1010,6 +1089,7 @@ if(APPLE) target_link_options(${bun} PUBLIC -dead_strip -dead_strip_dylibs + -Wl,-map,${bun}.linker-map ) endif() endif() @@ -1043,6 +1123,17 @@ if(LINUX) ) endif() + if (ENABLE_LTO) + # We are optimizing for size at a slight debug-ability cost + target_link_options(${bun} PUBLIC + -Wl,--no-eh-frame-hdr + ) + else() + target_link_options(${bun} PUBLIC + -Wl,--eh-frame-hdr + ) + endif() + target_link_options(${bun} PUBLIC --ld-path=${LLD_PROGRAM} -fno-pic @@ -1057,11 +1148,9 @@ if(LINUX) # make debug info faster to load -Wl,--gdb-index -Wl,-z,combreloc - -Wl,--no-eh-frame-hdr -Wl,--sort-section=name -Wl,--hash-style=both -Wl,--build-id=sha1 # Better for debugging than default - -Wl,-Map=${bun}.linker-map ) # don't strip in debug, this seems to be needed so that the Zig std library @@ -1076,6 +1165,7 @@ if(LINUX) if (NOT DEBUG AND NOT ENABLE_ASAN AND NOT ENABLE_VALGRIND) target_link_options(${bun} PUBLIC -Wl,-icf=safe + -Wl,-Map=${bun}.linker-map ) endif() @@ -1182,15 +1272,9 @@ if(LINUX) target_link_libraries(${bun} PUBLIC libatomic.so) endif() - if(USE_SYSTEM_ICU) - target_link_libraries(${bun} PRIVATE libicudata.a) - target_link_libraries(${bun} PRIVATE libicui18n.a) - target_link_libraries(${bun} PRIVATE libicuuc.a) - 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() + 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() if(WIN32) @@ -1243,32 +1327,32 @@ if(NOT BUN_CPP_ONLY) OUTPUTS ${BUILD_PATH}/${bunStripExe} ) - + # Then sign both executables on Windows if(WIN32 AND ENABLE_WINDOWS_CODESIGNING) set(SIGN_SCRIPT "${CMAKE_SOURCE_DIR}/.buildkite/scripts/sign-windows.ps1") - + # Verify signing script exists if(NOT EXISTS "${SIGN_SCRIPT}") message(FATAL_ERROR "Windows signing script not found: ${SIGN_SCRIPT}") endif() - + # Use PowerShell for Windows code signing (native Windows, no path issues) - find_program(POWERSHELL_EXECUTABLE + find_program(POWERSHELL_EXECUTABLE NAMES pwsh.exe powershell.exe - PATHS + PATHS "C:/Program Files/PowerShell/7" "C:/Program Files (x86)/PowerShell/7" "C:/Windows/System32/WindowsPowerShell/v1.0" DOC "Path to PowerShell executable" ) - + if(NOT POWERSHELL_EXECUTABLE) set(POWERSHELL_EXECUTABLE "powershell.exe") endif() - + message(STATUS "Using PowerShell executable: ${POWERSHELL_EXECUTABLE}") - + # Sign both bun-profile.exe and bun.exe after stripping register_command( TARGET @@ -1397,7 +1481,7 @@ if(NOT BUN_CPP_ONLY) list(APPEND bunFiles ${bun}.dSYM) endif() - if(APPLE OR LINUX) + if((APPLE OR LINUX) AND NOT ENABLE_ASAN) list(APPEND bunFiles ${bun}.linker-map) endif() diff --git a/cmake/targets/BuildHighway.cmake b/cmake/targets/BuildHighway.cmake index 5f8664486d..1796e8f97f 100644 --- a/cmake/targets/BuildHighway.cmake +++ b/cmake/targets/BuildHighway.cmake @@ -4,7 +4,7 @@ register_repository( REPOSITORY google/highway COMMIT - 12b325bc1793dee68ab2157995a690db859fe9e0 + ac0d5d297b13ab1b89f48484fc7911082d76a93f ) set(HIGHWAY_CMAKE_ARGS diff --git a/cmake/targets/BuildLibDeflate.cmake b/cmake/targets/BuildLibDeflate.cmake index 66e89fa4b8..3f6eba7304 100644 --- a/cmake/targets/BuildLibDeflate.cmake +++ b/cmake/targets/BuildLibDeflate.cmake @@ -4,7 +4,7 @@ register_repository( REPOSITORY ebiggers/libdeflate COMMIT - 96836d7d9d10e3e0d53e6edb54eb908514e336c4 + c8c56a20f8f621e6a966b716b31f1dedab6a41e3 ) register_cmake_command( diff --git a/cmake/targets/BuildLibuv.cmake b/cmake/targets/BuildLibuv.cmake index de95e20955..3072d95532 100644 --- a/cmake/targets/BuildLibuv.cmake +++ b/cmake/targets/BuildLibuv.cmake @@ -4,8 +4,8 @@ register_repository( REPOSITORY libuv/libuv COMMIT - # Corresponds to v1.51.0 - 5152db2cbfeb5582e9c27c5ea1dba2cd9e10759b + # Latest HEAD (includes recursion bug fix #4784) + f3ce527ea940d926c40878ba5de219640c362811 ) if(WIN32) diff --git a/cmake/tools/SetupCcache.cmake b/cmake/tools/SetupCcache.cmake index bc1db81494..fc1e64aa96 100644 --- a/cmake/tools/SetupCcache.cmake +++ b/cmake/tools/SetupCcache.cmake @@ -49,3 +49,6 @@ else() setenv(CCACHE_MAXSIZE 100G) setenv(CCACHE_SLOPPINESS "pch_defines,time_macros,locale,random_seed,clang_index_store,gcno_cwd") endif() + + + diff --git a/cmake/tools/SetupSccache.cmake b/cmake/tools/SetupSccache.cmake new file mode 100644 index 0000000000..cb4b5aa750 --- /dev/null +++ b/cmake/tools/SetupSccache.cmake @@ -0,0 +1,123 @@ +# Setup sccache as the C and C++ compiler launcher to speed up builds by caching +if(CACHE_STRATEGY STREQUAL "none") + return() +endif() + +set(SCCACHE_SHARED_CACHE_REGION "us-west-1") +set(SCCACHE_SHARED_CACHE_BUCKET "bun-build-sccache-store") + +# Function to check if the system AWS credentials have access to the sccache S3 bucket. +function(check_aws_credentials OUT_VAR) + # Install dependencies first + execute_process( + COMMAND ${BUN_EXECUTABLE} install --frozen-lockfile + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/scripts/build-cache + RESULT_VARIABLE INSTALL_EXIT_CODE + OUTPUT_VARIABLE INSTALL_OUTPUT + ERROR_VARIABLE INSTALL_ERROR + ) + + if(NOT INSTALL_EXIT_CODE EQUAL 0) + message(FATAL_ERROR "Failed to install dependencies in scripts/build-cache\n" + "Exit code: ${INSTALL_EXIT_CODE}\n" + "Output: ${INSTALL_OUTPUT}\n" + "Error: ${INSTALL_ERROR}") + endif() + + # Check AWS credentials + execute_process( + COMMAND + ${BUN_EXECUTABLE} + run + have-access.ts + --bucket ${SCCACHE_SHARED_CACHE_BUCKET} + --region ${SCCACHE_SHARED_CACHE_REGION} + WORKING_DIRECTORY + ${CMAKE_SOURCE_DIR}/scripts/build-cache + RESULT_VARIABLE HAVE_ACCESS_EXIT_CODE + ) + + if(HAVE_ACCESS_EXIT_CODE EQUAL 0) + set(HAS_CREDENTIALS TRUE) + else() + set(HAS_CREDENTIALS FALSE) + endif() + + set(${OUT_VAR} ${HAS_CREDENTIALS} PARENT_SCOPE) +endfunction() + +# Configure sccache to use the local cache only. +function(sccache_configure_local_filesystem) + unsetenv(SCCACHE_BUCKET) + unsetenv(SCCACHE_REGION) + setenv(SCCACHE_DIR "${CACHE_PATH}/sccache") +endfunction() + +# Configure sccache to use the distributed cache (S3 + local). +function(sccache_configure_distributed) + setenv(SCCACHE_BUCKET "${SCCACHE_SHARED_CACHE_BUCKET}") + setenv(SCCACHE_REGION "${SCCACHE_SHARED_CACHE_REGION}") + setenv(SCCACHE_DIR "${CACHE_PATH}/sccache") +endfunction() + +function(sccache_configure_environment_ci) + if(CACHE_STRATEGY STREQUAL "auto" OR CACHE_STRATEGY STREQUAL "distributed") + check_aws_credentials(HAS_AWS_CREDENTIALS) + if(HAS_AWS_CREDENTIALS) + sccache_configure_distributed() + message(NOTICE "sccache: Using distributed cache strategy.") + else() + message(FATAL_ERROR "CI CACHE_STRATEGY is set to '${CACHE_STRATEGY}', but no valid AWS " + "credentials were found. Note that 'auto' requires AWS credentials to access the shared " + "cache in CI.") + endif() + elseif(CACHE_STRATEGY STREQUAL "local") + # We disallow this because we want our CI runs to always used the shared cache to accelerate + # builds. + # none, distributed and auto are all okay. + # + # If local is configured, it's as good as "none", so this is probably user error. + message(FATAL_ERROR "CI CACHE_STRATEGY is set to 'local', which is not allowed.") + endif() +endfunction() + +function(sccache_configure_environment_developer) + # Local environments can use any strategy they like. S3 is set up in such a way so as to clean + # itself from old entries automatically. + if (CACHE_STRATEGY STREQUAL "auto" OR CACHE_STRATEGY STREQUAL "local") + # In the local environment, we prioritize using the local cache. This is because sccache takes + # into consideration the whole absolute path of the files being compiled, and it's very + # unlikely users will have the same absolute paths on their local machines. + sccache_configure_local_filesystem() + message(NOTICE "sccache: Using local cache strategy.") + elseif(CACHE_STRATEGY STREQUAL "distributed") + check_aws_credentials(HAS_AWS_CREDENTIALS) + if(HAS_AWS_CREDENTIALS) + sccache_configure_distributed() + message(NOTICE "sccache: Using distributed cache strategy.") + else() + message(FATAL_ERROR "CACHE_STRATEGY is set to 'distributed', but no valid AWS credentials " + "were found.") + endif() + endif() +endfunction() + +find_command(VARIABLE SCCACHE_PROGRAM COMMAND sccache REQUIRED ${CI}) +if(NOT SCCACHE_PROGRAM) + message(WARNING "sccache not found. Your builds will be slower.") + return() +endif() + +set(SCCACHE_ARGS CMAKE_C_COMPILER_LAUNCHER CMAKE_CXX_COMPILER_LAUNCHER) +foreach(arg ${SCCACHE_ARGS}) + setx(${arg} ${SCCACHE_PROGRAM}) + list(APPEND CMAKE_ARGS -D${arg}=${${arg}}) +endforeach() + +setenv(SCCACHE_LOG "info") + +if (CI) + sccache_configure_environment_ci() +else() + sccache_configure_environment_developer() +endif() diff --git a/cmake/tools/SetupZig.cmake b/cmake/tools/SetupZig.cmake index 925fdee0cb..1de53f05e3 100644 --- a/cmake/tools/SetupZig.cmake +++ b/cmake/tools/SetupZig.cmake @@ -20,7 +20,7 @@ else() unsupported(CMAKE_SYSTEM_NAME) endif() -set(ZIG_COMMIT "55fdbfa0c86be86b68d43a4ba761e6909eb0d7b2") +set(ZIG_COMMIT "c1423ff3fc7064635773a4a4616c5bf986eb00fe") optionx(ZIG_TARGET STRING "The zig target to use" DEFAULT ${DEFAULT_ZIG_TARGET}) if(CMAKE_BUILD_TYPE STREQUAL "Release") diff --git a/dockerhub/alpine/Dockerfile b/dockerhub/alpine/Dockerfile index 8d1ecbaddd..4d5a01876f 100644 --- a/dockerhub/alpine/Dockerfile +++ b/dockerhub/alpine/Dockerfile @@ -1,4 +1,4 @@ -FROM alpine:3.20 AS build +FROM alpine:3.22 AS build # https://github.com/oven-sh/bun/releases ARG BUN_VERSION=latest @@ -44,7 +44,7 @@ RUN apk --no-cache add ca-certificates curl dirmngr gpg gpg-agent unzip \ && rm -f "bun-linux-$build.zip" SHASUMS256.txt.asc SHASUMS256.txt \ && chmod +x /usr/local/bin/bun -FROM alpine:3.20 +FROM alpine:3.22 # Disable the runtime transpiler cache by default inside Docker containers. # On ephemeral containers, the cache is not useful diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000000..484dea173f --- /dev/null +++ b/docs/README.md @@ -0,0 +1,28 @@ + + + + + +Bun Documentation + +Official documentation for Bun: the fast, all-in-one JavaScript runtime. + +## Development + +Install the [Mintlify CLI](https://www.npmjs.com/package/mint) to preview the documentation locally: + +```bash +bun install -g mint +``` + +Run the development server: + +```bash +mint dev +``` + +The site will be available at `http://localhost:3000`. + +## Contributing + +Contributions are welcome! Please open an issue or submit a pull request. diff --git a/docs/api/file.md b/docs/api/file.md deleted file mode 100644 index 16e5359017..0000000000 --- a/docs/api/file.md +++ /dev/null @@ -1,19 +0,0 @@ -Bun.js has fast paths for common use cases that make Web APIs live up to the performance demands of servers and CLIs. - -`Bun.file(path)` returns a [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob) that represents a lazily-loaded file. - -When you pass a file blob to `Bun.write`, Bun automatically uses a faster system call: - -```js -const blob = Bun.file("input.txt"); -await Bun.write("output.txt", blob); -``` - -On Linux, this uses the [`copy_file_range`](https://man7.org/linux/man-pages/man2/copy_file_range.2.html) syscall and on macOS, this becomes `clonefile` (or [`fcopyfile`](https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/copyfile.3.html)). - -`Bun.write` also supports [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) objects. It automatically converts to a [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob). - -```js -// Eventually, this will stream the response to disk but today it buffers -await Bun.write("index.html", await fetch("https://example.com")); -``` diff --git a/docs/api/globals.md b/docs/api/globals.md deleted file mode 100644 index 1a98bb0899..0000000000 --- a/docs/api/globals.md +++ /dev/null @@ -1,387 +0,0 @@ -Bun implements the following globals. - -{% table %} - -- Global -- Source -- Notes - ---- - -- [`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) -- Web -- - ---- - -- [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) -- Web -- - ---- - -- [`alert`](https://developer.mozilla.org/en-US/docs/Web/API/Window/alert) -- Web -- Intended for command-line tools - ---- - -- [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob) -- Web -- - ---- - -- [`Buffer`](https://nodejs.org/api/buffer.html#class-buffer) -- Node.js -- See [Node.js > `Buffer`](https://bun.com/docs/runtime/nodejs-apis#node-buffer) - ---- - -- `Bun` -- Bun -- Subject to change as additional APIs are added - ---- - -- [`ByteLengthQueuingStrategy`](https://developer.mozilla.org/en-US/docs/Web/API/ByteLengthQueuingStrategy) -- Web -- - ---- - -- [`confirm`](https://developer.mozilla.org/en-US/docs/Web/API/Window/confirm) -- Web -- Intended for command-line tools - ---- - -- [`__dirname`](https://nodejs.org/api/globals.html#__dirname) -- Node.js -- - ---- - -- [`__filename`](https://nodejs.org/api/globals.html#__filename) -- Node.js -- - ---- - -- [`atob()`](https://developer.mozilla.org/en-US/docs/Web/API/atob) -- Web -- - ---- - -- [`btoa()`](https://developer.mozilla.org/en-US/docs/Web/API/btoa) -- Web -- - ---- - -- `BuildMessage` -- Bun -- - ---- - -- [`clearImmediate()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/clearImmediate) -- Web -- - ---- - -- [`clearInterval()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/clearInterval) -- Web -- - ---- - -- [`clearTimeout()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/clearTimeout) -- Web -- - ---- - -- [`console`](https://developer.mozilla.org/en-US/docs/Web/API/console) -- Web -- - ---- - -- [`CountQueuingStrategy`](https://developer.mozilla.org/en-US/docs/Web/API/CountQueuingStrategy) -- Web -- - ---- - -- [`Crypto`](https://developer.mozilla.org/en-US/docs/Web/API/Crypto) -- Web -- - ---- - -- [`crypto`](https://developer.mozilla.org/en-US/docs/Web/API/crypto) -- Web -- - ---- - -- [`CryptoKey`](https://developer.mozilla.org/en-US/docs/Web/API/CryptoKey) -- Web -- - ---- - -- [`CustomEvent`](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent) -- Web -- - ---- - -- [`Event`](https://developer.mozilla.org/en-US/docs/Web/API/Event) -- Web -- Also [`ErrorEvent`](https://developer.mozilla.org/en-US/docs/Web/API/ErrorEvent) [`CloseEvent`](https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent) [`MessageEvent`](https://developer.mozilla.org/en-US/docs/Web/API/MessageEvent). - ---- - -- [`EventTarget`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget) -- Web -- - ---- - -- [`exports`](https://nodejs.org/api/globals.html#exports) -- Node.js -- - ---- - -- [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/fetch) -- Web -- - ---- - -- [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData) -- Web -- - ---- - -- [`global`](https://nodejs.org/api/globals.html#global) -- Node.js -- See [Node.js > `global`](https://bun.com/docs/runtime/nodejs-apis#global). - ---- - -- [`globalThis`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/globalThis) -- Cross-platform -- Aliases to `global` - ---- - -- [`Headers`](https://developer.mozilla.org/en-US/docs/Web/API/Headers) -- Web -- - ---- - -- [`HTMLRewriter`](https://bun.com/docs/api/html-rewriter) -- Cloudflare -- - ---- - -- [`JSON`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON) -- Web -- - ---- - -- [`MessageEvent`](https://developer.mozilla.org/en-US/docs/Web/API/MessageEvent) -- Web -- - ---- - -- [`module`](https://nodejs.org/api/globals.html#module) -- Node.js -- - ---- - -- [`performance`](https://developer.mozilla.org/en-US/docs/Web/API/performance) -- Web -- - ---- - -- [`process`](https://nodejs.org/api/process.html) -- Node.js -- See [Node.js > `process`](https://bun.com/docs/runtime/nodejs-apis#node-process) - ---- - -- [`prompt`](https://developer.mozilla.org/en-US/docs/Web/API/Window/prompt) -- Web -- Intended for command-line tools - ---- - -- [`queueMicrotask()`](https://developer.mozilla.org/en-US/docs/Web/API/queueMicrotask) -- Web -- - ---- - -- [`ReadableByteStreamController`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableByteStreamController) -- Web -- - ---- - -- [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) -- Web -- - ---- - -- [`ReadableStreamDefaultController`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStreamDefaultController) -- Web -- - ---- - -- [`ReadableStreamDefaultReader`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStreamDefaultReader) -- Web -- - ---- - -- [`reportError`](https://developer.mozilla.org/en-US/docs/Web/API/reportError) -- Web -- - ---- - -- [`require()`](https://nodejs.org/api/globals.html#require) -- Node.js -- - ---- - -- `ResolveMessage` -- Bun -- - ---- - -- [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) -- Web -- - ---- - -- [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) -- Web -- - ---- - -- [`setImmediate()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/setImmediate) -- Web -- - ---- - -- [`setInterval()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/setInterval) -- Web -- - ---- - -- [`setTimeout()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/setTimeout) -- Web -- - ---- - -- [`ShadowRealm`](https://github.com/tc39/proposal-shadowrealm) -- Web -- Stage 3 proposal - ---- - -- [`SubtleCrypto`](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto) -- Web -- - ---- - -- [`DOMException`](https://developer.mozilla.org/en-US/docs/Web/API/DOMException) -- Web -- - ---- - -- [`TextDecoder`](https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder) -- Web -- - ---- - -- [`TextEncoder`](https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder) -- Web -- - ---- - -- [`TransformStream`](https://developer.mozilla.org/en-US/docs/Web/API/TransformStream) -- Web -- - ---- - -- [`TransformStreamDefaultController`](https://developer.mozilla.org/en-US/docs/Web/API/TransformStreamDefaultController) -- Web -- - ---- - -- [`URL`](https://developer.mozilla.org/en-US/docs/Web/API/URL) -- Web -- - ---- - -- [`URLSearchParams`](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams) -- Web -- - ---- - -- [`WebAssembly`](https://nodejs.org/api/globals.html#webassembly) -- Web -- - ---- - -- [`WritableStream`](https://developer.mozilla.org/en-US/docs/Web/API/WritableStream) -- Web -- - ---- - -- [`WritableStreamDefaultController`](https://developer.mozilla.org/en-US/docs/Web/API/WritableStreamDefaultController) -- Web -- - ---- - -- [`WritableStreamDefaultWriter`](https://developer.mozilla.org/en-US/docs/Web/API/WritableStreamDefaultWriter) -- Web -- - -{% /table %} diff --git a/docs/api/http.md b/docs/api/http.md deleted file mode 100644 index f3e5b48e55..0000000000 --- a/docs/api/http.md +++ /dev/null @@ -1,1408 +0,0 @@ -The page primarily documents the Bun-native `Bun.serve` API. Bun also implements [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) and the Node.js [`http`](https://nodejs.org/api/http.html) and [`https`](https://nodejs.org/api/https.html) modules. - -{% callout %} -These modules have been re-implemented to use Bun's fast internal HTTP infrastructure. Feel free to use these modules directly; frameworks like [Express](https://expressjs.com/) that depend on these modules should work out of the box. For granular compatibility information, see [Runtime > Node.js APIs](https://bun.com/docs/runtime/nodejs-apis). -{% /callout %} - -To start a high-performance HTTP server with a clean API, the recommended approach is [`Bun.serve`](#start-a-server-bun-serve). - -## `Bun.serve()` - -Use `Bun.serve` to start an HTTP server in Bun. - -```ts -Bun.serve({ - // `routes` requires Bun v1.2.3+ - routes: { - // Static routes - "/api/status": new Response("OK"), - - // Dynamic routes - "/users/:id": req => { - return new Response(`Hello User ${req.params.id}!`); - }, - - // Per-HTTP method handlers - "/api/posts": { - GET: () => new Response("List posts"), - POST: async req => { - const body = await req.json(); - return Response.json({ created: true, ...body }); - }, - }, - - // Wildcard route for all routes that start with "/api/" and aren't otherwise matched - "/api/*": Response.json({ message: "Not found" }, { status: 404 }), - - // Redirect from /blog/hello to /blog/hello/world - "/blog/hello": Response.redirect("/blog/hello/world"), - - // Serve a file by buffering it in memory - "/favicon.ico": new Response(await Bun.file("./favicon.ico").bytes(), { - headers: { - "Content-Type": "image/x-icon", - }, - }), - }, - - // (optional) fallback for unmatched routes: - // Required if Bun's version < 1.2.3 - fetch(req) { - return new Response("Not Found", { status: 404 }); - }, -}); -``` - -### Routing - -Routes in `Bun.serve()` receive a `BunRequest` (which extends [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request)) and return a [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) or `Promise`. This makes it easier to use the same code for both sending & receiving HTTP requests. - -```ts -// Simplified for brevity -interface BunRequest extends Request { - params: Record; - readonly cookies: CookieMap; -} -``` - -#### Async/await in routes - -You can use async/await in route handlers to return a `Promise`. - -```ts -import { sql, serve } from "bun"; - -serve({ - port: 3001, - routes: { - "/api/version": async () => { - const [version] = await sql`SELECT version()`; - return Response.json(version); - }, - }, -}); -``` - -#### Promise in routes - -You can also return a `Promise` from a route handler. - -```ts -import { sql, serve } from "bun"; - -serve({ - routes: { - "/api/version": () => { - return new Promise(resolve => { - setTimeout(async () => { - const [version] = await sql`SELECT version()`; - resolve(Response.json(version)); - }, 100); - }); - }, - }, -}); -``` - -#### Type-safe route parameters - -TypeScript parses route parameters when passed as a string literal, so that your editor will show autocomplete when accessing `request.params`. - -```ts -import type { BunRequest } from "bun"; - -Bun.serve({ - routes: { - // TypeScript knows the shape of params when passed as a string literal - "/orgs/:orgId/repos/:repoId": req => { - const { orgId, repoId } = req.params; - return Response.json({ orgId, repoId }); - }, - - "/orgs/:orgId/repos/:repoId/settings": ( - // optional: you can explicitly pass a type to BunRequest: - req: BunRequest<"/orgs/:orgId/repos/:repoId/settings">, - ) => { - const { orgId, repoId } = req.params; - return Response.json({ orgId, repoId }); - }, - }, -}); -``` - -Percent-encoded route parameter values are automatically decoded. Unicode characters are supported. Invalid unicode is replaced with the unicode replacement character `&0xFFFD;`. - -### Static responses - -Routes can also be `Response` objects (without the handler function). Bun.serve() optimizes it for zero-allocation dispatch - perfect for health checks, redirects, and fixed content: - -```ts -Bun.serve({ - routes: { - // Health checks - "/health": new Response("OK"), - "/ready": new Response("Ready", { - headers: { - // Pass custom headers - "X-Ready": "1", - }, - }), - - // Redirects - "/blog": Response.redirect("https://bun.com/blog"), - - // API responses - "/api/config": Response.json({ - version: "1.0.0", - env: "production", - }), - }, -}); -``` - -Static responses do not allocate additional memory after initialization. You can generally expect at least a 15% performance improvement over manually returning a `Response` object. - -Static route responses are cached for the lifetime of the server object. To reload static routes, call `server.reload(options)`. - -### File Responses vs Static Responses - -When serving files in routes, there are two distinct behaviors depending on whether you buffer the file content or serve it directly: - -```ts -Bun.serve({ - routes: { - // Static route - content is buffered in memory at startup - "/logo.png": new Response(await Bun.file("./logo.png").bytes()), - - // File route - content is read from filesystem on each request - "/download.zip": new Response(Bun.file("./download.zip")), - }, -}); -``` - -**Static routes** (`new Response(await file.bytes())`) buffer content in memory at startup: - -- **Zero filesystem I/O** during requests - content served entirely from memory -- **ETag support** - Automatically generates and validates ETags for caching -- **If-None-Match** - Returns `304 Not Modified` when client ETag matches -- **No 404 handling** - Missing files cause startup errors, not runtime 404s -- **Memory usage** - Full file content stored in RAM -- **Best for**: Small static assets, API responses, frequently accessed files - -**File routes** (`new Response(Bun.file(path))`) read from filesystem per request: - -- **Filesystem reads** on each request - checks file existence and reads content -- **Built-in 404 handling** - Returns `404 Not Found` if file doesn't exist or becomes inaccessible -- **Last-Modified support** - Uses file modification time for `If-Modified-Since` headers -- **If-Modified-Since** - Returns `304 Not Modified` when file hasn't changed since client's cached version -- **Range request support** - Automatically handles partial content requests with `Content-Range` headers -- **Streaming transfers** - Uses buffered reader with backpressure handling for efficient memory usage -- **Memory efficient** - Only buffers small chunks during transfer, not entire file -- **Best for**: Large files, dynamic content, user uploads, files that change frequently - -### HTTP Caching Behavior - -Both route types implement HTTP caching standards but with different strategies: - -#### Static Routes Caching - -- **ETag generation**: Automatically computes ETag hash from content at startup -- **If-None-Match**: Validates client ETag against server ETag -- **304 responses**: Returns `304 Not Modified` with empty body when ETags match -- **Cache headers**: Inherits any `Cache-Control` headers you provide in the Response -- **Consistency**: ETag remains constant until server restart or route reload - -#### File Routes Caching - -- **Last-Modified**: Uses file's `mtime` for `Last-Modified` header -- **If-Modified-Since**: Compares client date with file modification time -- **304 responses**: Returns `304 Not Modified` when file unchanged since client's cached version -- **Content-Length**: Automatically set based on current file size -- **Dynamic validation**: Checks file modification time on each request - -#### Status Code Handling - -Both route types automatically adjust status codes: - -- **200 → 204**: Empty files (0 bytes) return `204 No Content` instead of `200 OK` -- **200 → 304**: Successful cache validation returns `304 Not Modified` -- **File routes only**: Missing or inaccessible files return `404 Not Found` - -```ts -const server = Bun.serve({ - static: { - "/api/time": new Response(new Date().toISOString()), - }, - - fetch(req) { - return new Response("404!"); - }, -}); - -// Update the time every second. -setInterval(() => { - server.reload({ - static: { - "/api/time": new Response(new Date().toISOString()), - }, - - fetch(req) { - return new Response("404!"); - }, - }); -}, 1000); -``` - -Reloading routes only impact the next request. In-flight requests continue to use the old routes. After in-flight requests to old routes are finished, the old routes are freed from memory. - -To simplify error handling, static routes do not support streaming response bodies from `ReadableStream` or an `AsyncIterator`. Fortunately, you can still buffer the response in memory first: - -```ts -const time = await fetch("https://api.example.com/v1/data"); -// Buffer the response in memory first. -const blob = await time.blob(); - -const server = Bun.serve({ - static: { - "/api/data": new Response(blob), - }, - - fetch(req) { - return new Response("404!"); - }, -}); -``` - -### Route precedence - -Routes are matched in order of specificity: - -1. Exact routes (`/users/all`) -2. Parameter routes (`/users/:id`) -3. Wildcard routes (`/users/*`) -4. Global catch-all (`/*`) - -```ts -Bun.serve({ - routes: { - // Most specific first - "/api/users/me": () => new Response("Current user"), - "/api/users/:id": req => new Response(`User ${req.params.id}`), - "/api/*": () => new Response("API catch-all"), - "/*": () => new Response("Global catch-all"), - }, -}); -``` - -### Per-HTTP Method Routes - -Route handlers can be specialized by HTTP method: - -```ts -Bun.serve({ - routes: { - "/api/posts": { - // Different handlers per method - GET: () => new Response("List posts"), - POST: async req => { - const post = await req.json(); - return Response.json({ id: crypto.randomUUID(), ...post }); - }, - PUT: async req => { - const updates = await req.json(); - return Response.json({ updated: true, ...updates }); - }, - DELETE: () => new Response(null, { status: 204 }), - }, - }, -}); -``` - -You can pass any of the following methods: - -| Method | Usecase example | -| --------- | ------------------------------- | -| `GET` | Fetch a resource | -| `HEAD` | Check if a resource exists | -| `OPTIONS` | Get allowed HTTP methods (CORS) | -| `DELETE` | Delete a resource | -| `PATCH` | Update a resource | -| `POST` | Create a resource | -| `PUT` | Update a resource | - -When passing a function instead of an object, all methods will be handled by that function: - -```ts -const server = Bun.serve({ - routes: { - "/api/version": () => Response.json({ version: "1.0.0" }), - }, -}); - -await fetch(new URL("/api/version", server.url)); -await fetch(new URL("/api/version", server.url), { method: "PUT" }); -// ... etc -``` - -### Hot Route Reloading - -Update routes without server restarts using `server.reload()`: - -```ts -const server = Bun.serve({ - routes: { - "/api/version": () => Response.json({ version: "1.0.0" }), - }, -}); - -// Deploy new routes without downtime -server.reload({ - routes: { - "/api/version": () => Response.json({ version: "2.0.0" }), - }, -}); -``` - -### Error Handling - -Bun provides structured error handling for routes: - -```ts -Bun.serve({ - routes: { - // Errors are caught automatically - "/api/risky": () => { - throw new Error("Something went wrong"); - }, - }, - // Global error handler - error(error) { - console.error(error); - return new Response(`Internal Error: ${error.message}`, { - status: 500, - headers: { - "Content-Type": "text/plain", - }, - }); - }, -}); -``` - -### HTML imports - -Bun supports importing HTML files directly into your server code, enabling full-stack applications with both server-side and client-side code. HTML imports work in two modes: - -**Development (`bun --hot`):** Assets are bundled on-demand at runtime, enabling hot module replacement (HMR) for a fast, iterative development experience. When you change your frontend code, the browser automatically updates without a full page reload. - -**Production (`bun build`):** When building with `bun build --target=bun`, the `import index from "./index.html"` statement resolves to a pre-built manifest object containing all bundled client assets. `Bun.serve` consumes this manifest to serve optimized assets with zero runtime bundling overhead. This is ideal for deploying to production. - -```ts -import myReactSinglePageApp from "./index.html"; - -Bun.serve({ - routes: { - "/": myReactSinglePageApp, - }, -}); -``` - -HTML imports don't just serve HTML — it's a full-featured frontend bundler, transpiler, and toolkit built using Bun's [bundler](https://bun.com/docs/bundler), JavaScript transpiler and CSS parser. You can use this to build full-featured frontends with React, TypeScript, Tailwind CSS, and more. - -For a complete guide on building full-stack applications with HTML imports, including detailed examples and best practices, see [/docs/bundler/fullstack](https://bun.com/docs/bundler/fullstack). - -### Practical example: REST API - -Here's a basic database-backed REST API using Bun's router with zero dependencies: - -{% codetabs %} - -```ts#server.ts -import type { Post } from "./types.ts"; -import { Database } from "bun:sqlite"; - -const db = new Database("posts.db"); -db.exec(` - CREATE TABLE IF NOT EXISTS posts ( - id TEXT PRIMARY KEY, - title TEXT NOT NULL, - content TEXT NOT NULL, - created_at TEXT NOT NULL - ) -`); - -Bun.serve({ - routes: { - // List posts - "/api/posts": { - GET: () => { - const posts = db.query("SELECT * FROM posts").all(); - return Response.json(posts); - }, - - // Create post - POST: async req => { - const post: Omit = await req.json(); - const id = crypto.randomUUID(); - - db.query( - `INSERT INTO posts (id, title, content, created_at) - VALUES (?, ?, ?, ?)`, - ).run(id, post.title, post.content, new Date().toISOString()); - - return Response.json({ id, ...post }, { status: 201 }); - }, - }, - - // Get post by ID - "/api/posts/:id": req => { - const post = db - .query("SELECT * FROM posts WHERE id = ?") - .get(req.params.id); - - if (!post) { - return new Response("Not Found", { status: 404 }); - } - - return Response.json(post); - }, - }, - - error(error) { - console.error(error); - return new Response("Internal Server Error", { status: 500 }); - }, -}); -``` - -```ts#types.ts -export interface Post { - id: string; - title: string; - content: string; - created_at: string; -} -``` - -{% /codetabs %} - -### Routing performance - -`Bun.serve()`'s router builds on top uWebSocket's [tree-based approach](https://github.com/oven-sh/bun/blob/0d1a00fa0f7830f8ecd99c027fce8096c9d459b6/packages/bun-uws/src/HttpRouter.h#L57-L64) to add [SIMD-accelerated route parameter decoding](https://github.com/oven-sh/bun/blob/main/src/bun.js/bindings/decodeURIComponentSIMD.cpp#L21-L271) and [JavaScriptCore structure caching](https://github.com/oven-sh/bun/blob/main/src/bun.js/bindings/ServerRouteList.cpp#L100-L101) to push the performance limits of what modern hardware allows. - -### `fetch` request handler - -The `fetch` handler handles incoming requests that weren't matched by any route. It receives a [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) object and returns a [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) or [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise). - -```ts -Bun.serve({ - fetch(req) { - const url = new URL(req.url); - if (url.pathname === "/") return new Response("Home page!"); - if (url.pathname === "/blog") return new Response("Blog!"); - return new Response("404!"); - }, -}); -``` - -The `fetch` handler supports async/await: - -```ts -import { sleep, serve } from "bun"; -serve({ - async fetch(req) { - const start = performance.now(); - await sleep(10); - const end = performance.now(); - return new Response(`Slept for ${end - start}ms`); - }, -}); -``` - -Promise-based responses are also supported: - -```ts -Bun.serve({ - fetch(req) { - // Forward the request to another server. - return fetch("https://example.com"); - }, -}); -``` - -You can also access the `Server` object from the `fetch` handler. It's the second argument passed to the `fetch` function. - -```ts -// `server` is passed in as the second argument to `fetch`. -const server = Bun.serve({ - fetch(req, server) { - const ip = server.requestIP(req); - return new Response(`Your IP is ${ip}`); - }, -}); -``` - -### Changing the `port` and `hostname` - -To configure which port and hostname the server will listen on, set `port` and `hostname` in the options object. - -```ts -Bun.serve({ - port: 8080, // defaults to $BUN_PORT, $PORT, $NODE_PORT otherwise 3000 - hostname: "mydomain.com", // defaults to "0.0.0.0" - fetch(req) { - return new Response("404!"); - }, -}); -``` - -To randomly select an available port, set `port` to `0`. - -```ts -const server = Bun.serve({ - port: 0, // random port - fetch(req) { - return new Response("404!"); - }, -}); - -// server.port is the randomly selected port -console.log(server.port); -``` - -You can view the chosen port by accessing the `port` property on the server object, or by accessing the `url` property. - -```ts -console.log(server.port); // 3000 -console.log(server.url); // http://localhost:3000 -``` - -#### Configuring a default port - -Bun supports several options and environment variables to configure the default port. The default port is used when the `port` option is not set. - -- `--port` CLI flag - -```sh -$ bun --port=4002 server.ts -``` - -- `BUN_PORT` environment variable - -```sh -$ BUN_PORT=4002 bun server.ts -``` - -- `PORT` environment variable - -```sh -$ PORT=4002 bun server.ts -``` - -- `NODE_PORT` environment variable - -```sh -$ NODE_PORT=4002 bun server.ts -``` - -### Unix domain sockets - -To listen on a [unix domain socket](https://en.wikipedia.org/wiki/Unix_domain_socket), pass the `unix` option with the path to the socket. - -```ts -Bun.serve({ - unix: "/tmp/my-socket.sock", // path to socket - fetch(req) { - return new Response(`404!`); - }, -}); -``` - -### Abstract namespace sockets - -Bun supports Linux abstract namespace sockets. To use an abstract namespace socket, prefix the `unix` path with a null byte. - -```ts -Bun.serve({ - unix: "\0my-abstract-socket", // abstract namespace socket - fetch(req) { - return new Response(`404!`); - }, -}); -``` - -Unlike unix domain sockets, abstract namespace sockets are not bound to the filesystem and are automatically removed when the last reference to the socket is closed. - -## Error handling - -To activate development mode, set `development: true`. - -```ts -Bun.serve({ - development: true, - fetch(req) { - throw new Error("woops!"); - }, -}); -``` - -In development mode, Bun will surface errors in-browser with a built-in error page. - -{% image src="/images/exception_page.png" caption="Bun's built-in 500 page" /%} - -### `error` callback - -To handle server-side errors, implement an `error` handler. This function should return a `Response` to serve to the client when an error occurs. This response will supersede Bun's default error page in `development` mode. - -```ts -Bun.serve({ - fetch(req) { - throw new Error("woops!"); - }, - error(error) { - return new Response(`${error}\n${error.stack}`, { - headers: { - "Content-Type": "text/html", - }, - }); - }, -}); -``` - -{% callout %} -[Learn more about debugging in Bun](https://bun.com/docs/runtime/debugger) -{% /callout %} - -The call to `Bun.serve` returns a `Server` object. To stop the server, call the `.stop()` method. - -```ts -const server = Bun.serve({ - fetch() { - return new Response("Bun!"); - }, -}); - -server.stop(); -``` - -## TLS - -Bun supports TLS out of the box, powered by [BoringSSL](https://boringssl.googlesource.com/boringssl). Enable TLS by passing in a value for `key` and `cert`; both are required to enable TLS. - -```ts-diff - Bun.serve({ - fetch(req) { - return new Response("Hello!!!"); - }, - -+ tls: { -+ key: Bun.file("./key.pem"), -+ cert: Bun.file("./cert.pem"), -+ } - }); -``` - -The `key` and `cert` fields expect the _contents_ of your TLS key and certificate, _not a path to it_. This can be a string, `BunFile`, `TypedArray`, or `Buffer`. - -```ts -Bun.serve({ - fetch() {}, - - tls: { - // BunFile - key: Bun.file("./key.pem"), - // Buffer - key: fs.readFileSync("./key.pem"), - // string - key: fs.readFileSync("./key.pem", "utf8"), - // array of above - key: [Bun.file("./key1.pem"), Bun.file("./key2.pem")], - }, -}); -``` - -If your private key is encrypted with a passphrase, provide a value for `passphrase` to decrypt it. - -```ts-diff - Bun.serve({ - fetch(req) { - return new Response("Hello!!!"); - }, - - tls: { - key: Bun.file("./key.pem"), - cert: Bun.file("./cert.pem"), -+ passphrase: "my-secret-passphrase", - } - }); -``` - -Optionally, you can override the trusted CA certificates by passing a value for `ca`. By default, the server will trust the list of well-known CAs curated by Mozilla. When `ca` is specified, the Mozilla list is overwritten. - -```ts-diff - Bun.serve({ - fetch(req) { - return new Response("Hello!!!"); - }, - tls: { - key: Bun.file("./key.pem"), // path to TLS key - cert: Bun.file("./cert.pem"), // path to TLS cert -+ ca: Bun.file("./ca.pem"), // path to root CA certificate - } - }); -``` - -To override Diffie-Hellman parameters: - -```ts -Bun.serve({ - // ... - tls: { - // other config - dhParamsFile: "/path/to/dhparams.pem", // path to Diffie Hellman parameters - }, -}); -``` - -### Server name indication (SNI) - -To configure the server name indication (SNI) for the server, set the `serverName` field in the `tls` object. - -```ts -Bun.serve({ - // ... - tls: { - // ... other config - serverName: "my-server.com", // SNI - }, -}); -``` - -To allow multiple server names, pass an array of objects to `tls`, each with a `serverName` field. - -```ts -Bun.serve({ - // ... - tls: [ - { - key: Bun.file("./key1.pem"), - cert: Bun.file("./cert1.pem"), - serverName: "my-server1.com", - }, - { - key: Bun.file("./key2.pem"), - cert: Bun.file("./cert2.pem"), - serverName: "my-server2.com", - }, - ], -}); -``` - -## idleTimeout - -To configure the idle timeout, set the `idleTimeout` field in Bun.serve. - -```ts -Bun.serve({ - // 10 seconds: - idleTimeout: 10, - - fetch(req) { - return new Response("Bun!"); - }, -}); -``` - -This is the maximum amount of time a connection is allowed to be idle before the server closes it. A connection is idling if there is no data sent or received. - -## export default syntax - -Thus far, the examples on this page have used the explicit `Bun.serve` API. Bun also supports an alternate syntax. - -```ts#server.ts -import {type Serve} from "bun"; - -export default { - fetch(req) { - return new Response("Bun!"); - }, -} satisfies Serve; -``` - -Instead of passing the server options into `Bun.serve`, `export default` it. This file can be executed as-is; when Bun sees a file with a `default` export containing a `fetch` handler, it passes it into `Bun.serve` under the hood. - - - - - - - -## Streaming files - -To stream a file, return a `Response` object with a `BunFile` object as the body. - -```ts -Bun.serve({ - fetch(req) { - return new Response(Bun.file("./hello.txt")); - }, -}); -``` - -{% callout %} -⚡️ **Speed** — Bun automatically uses the [`sendfile(2)`](https://man7.org/linux/man-pages/man2/sendfile.2.html) system call when possible, enabling zero-copy file transfers in the kernel—the fastest way to send files. -{% /callout %} - -You can send part of a file using the [`slice(start, end)`](https://developer.mozilla.org/en-US/docs/Web/API/Blob/slice) method on the `Bun.file` object. This automatically sets the `Content-Range` and `Content-Length` headers on the `Response` object. - -```ts -Bun.serve({ - fetch(req) { - // parse `Range` header - const [start = 0, end = Infinity] = req.headers - .get("Range") // Range: bytes=0-100 - .split("=") // ["Range: bytes", "0-100"] - .at(-1) // "0-100" - .split("-") // ["0", "100"] - .map(Number); // [0, 100] - - // return a slice of the file - const bigFile = Bun.file("./big-video.mp4"); - return new Response(bigFile.slice(start, end)); - }, -}); -``` - -## Server Lifecycle Methods - -### server.stop() - Stop the server - -To stop the server from accepting new connections: - -```ts -const server = Bun.serve({ - fetch(req) { - return new Response("Hello!"); - }, -}); - -// Gracefully stop the server (waits for in-flight requests) -await server.stop(); - -// Force stop and close all active connections -await server.stop(true); -``` - -By default, `stop()` allows in-flight requests and WebSocket connections to complete. Pass `true` to immediately terminate all connections. - -### server.ref() and server.unref() - Process lifecycle control - -Control whether the server keeps the Bun process alive: - -```ts -// Don't keep process alive if server is the only thing running -server.unref(); - -// Restore default behavior - keep process alive -server.ref(); -``` - -### server.reload() - Hot reload handlers - -Update the server's handlers without restarting: - -```ts -const server = Bun.serve({ - routes: { - "/api/version": Response.json({ version: "v1" }), - }, - fetch(req) { - return new Response("v1"); - }, -}); - -// Update to new handler -server.reload({ - routes: { - "/api/version": Response.json({ version: "v2" }), - }, - fetch(req) { - return new Response("v2"); - }, -}); -``` - -This is useful for development and hot reloading. Only `fetch`, `error`, and `routes` can be updated. - -## Per-Request Controls - - - -### server.timeout(Request, seconds) - Custom request timeouts - -Set a custom idle timeout for individual requests: - -```ts -const server = Bun.serve({ - fetch(req, server) { - // Set 60 second timeout for this request - server.timeout(req, 60); - - // If they take longer than 60 seconds to send the body, the request will be aborted - await req.text(); - - return new Response("Done!"); - }, -}); -``` - -Pass `0` to disable the timeout for a request. - -### server.requestIP(Request) - Get client information - -Get client IP and port information: - -```ts -const server = Bun.serve({ - fetch(req, server) { - const address = server.requestIP(req); - if (address) { - return new Response( - `Client IP: ${address.address}, Port: ${address.port}`, - ); - } - return new Response("Unknown client"); - }, -}); -``` - -Returns `null` for closed requests or Unix domain sockets. - -## Working with Cookies - -Bun provides a built-in API for working with cookies in HTTP requests and responses. The `BunRequest` object includes a `cookies` property that provides a `CookieMap` for easily accessing and manipulating cookies. When using `routes`, `Bun.serve()` automatically tracks `request.cookies.set` and applies them to the response. - -### Reading cookies - -Read cookies from incoming requests using the `cookies` property on the `BunRequest` object: - -```ts -Bun.serve({ - routes: { - "/profile": req => { - // Access cookies from the request - const userId = req.cookies.get("user_id"); - const theme = req.cookies.get("theme") || "light"; - - return Response.json({ - userId, - theme, - message: "Profile page", - }); - }, - }, -}); -``` - -### Setting cookies - -To set cookies, use the `set` method on the `CookieMap` from the `BunRequest` object. - -```ts -Bun.serve({ - routes: { - "/login": req => { - const cookies = req.cookies; - - // Set a cookie with various options - cookies.set("user_id", "12345", { - maxAge: 60 * 60 * 24 * 7, // 1 week - httpOnly: true, - secure: true, - path: "/", - }); - - // Add a theme preference cookie - cookies.set("theme", "dark"); - - // Modified cookies from the request are automatically applied to the response - return new Response("Login successful"); - }, - }, -}); -``` - -`Bun.serve()` automatically tracks modified cookies from the request and applies them to the response. - -### Deleting cookies - -To delete a cookie, use the `delete` method on the `request.cookies` (`CookieMap`) object: - -```ts -Bun.serve({ - routes: { - "/logout": req => { - // Delete the user_id cookie - req.cookies.delete("user_id", { - path: "/", - }); - - return new Response("Logged out successfully"); - }, - }, -}); -``` - -Deleted cookies become a `Set-Cookie` header on the response with the `maxAge` set to `0` and an empty `value`. - -## Server Metrics - -### server.pendingRequests and server.pendingWebSockets - -Monitor server activity with built-in counters: - -```ts -const server = Bun.serve({ - fetch(req, server) { - return new Response( - `Active requests: ${server.pendingRequests}\n` + - `Active WebSockets: ${server.pendingWebSockets}`, - ); - }, -}); -``` - -### server.subscriberCount(topic) - WebSocket subscribers - -Get count of subscribers for a WebSocket topic: - -```ts -const server = Bun.serve({ - fetch(req, server) { - const chatUsers = server.subscriberCount("chat"); - return new Response(`${chatUsers} users in chat`); - }, - websocket: { - message(ws) { - ws.subscribe("chat"); - }, - }, -}); -``` - -## WebSocket Configuration - -### server.publish(topic, data, compress) - WebSocket Message Publishing - -The server can publish messages to all WebSocket clients subscribed to a topic: - -```ts -const server = Bun.serve({ - websocket: { - message(ws) { - // Publish to all "chat" subscribers - server.publish("chat", "Hello everyone!"); - }, - }, - - fetch(req) { - // ... - }, -}); -``` - -The `publish()` method returns: - -- Number of bytes sent if successful -- `0` if the message was dropped -- `-1` if backpressure was applied - -### WebSocket Handler Options - -When configuring WebSockets, several advanced options are available through the `websocket` handler: - -```ts -Bun.serve({ - websocket: { - // Maximum message size (in bytes) - maxPayloadLength: 64 * 1024, - - // Backpressure limit before messages are dropped - backpressureLimit: 1024 * 1024, - - // Close connection if backpressure limit is hit - closeOnBackpressureLimit: true, - - // Handler called when backpressure is relieved - drain(ws) { - console.log("Backpressure relieved"); - }, - - // Enable per-message deflate compression - perMessageDeflate: { - compress: true, - decompress: true, - }, - - // Send ping frames to keep connection alive - sendPings: true, - - // Handlers for ping/pong frames - ping(ws, data) { - console.log("Received ping"); - }, - pong(ws, data) { - console.log("Received pong"); - }, - - // Whether server receives its own published messages - publishToSelf: false, - }, -}); -``` - -## Benchmarks - -Below are Bun and Node.js implementations of a simple HTTP server that responds `Bun!` to each incoming `Request`. - -{% codetabs %} - -```ts#Bun -Bun.serve({ - fetch(req: Request) { - return new Response("Bun!"); - }, - port: 3000, -}); -``` - -```ts#Node -require("http") - .createServer((req, res) => res.end("Bun!")) - .listen(8080); -``` - -{% /codetabs %} -The `Bun.serve` server can handle roughly 2.5x more requests per second than Node.js on Linux. - -{% table %} - -- Runtime -- Requests per second - ---- - -- Node 16 -- ~64,000 - ---- - -- Bun -- ~160,000 - -{% /table %} - -{% image width="499" alt="image" src="https://user-images.githubusercontent.com/709451/162389032-fc302444-9d03-46be-ba87-c12bd8ce89a0.png" /%} - -## Reference - -{% details summary="See TypeScript definitions" %} - -```ts -interface Server extends Disposable { - /** - * Stop the server from accepting new connections. - * @param closeActiveConnections If true, immediately terminates all connections - * @returns Promise that resolves when the server has stopped - */ - stop(closeActiveConnections?: boolean): Promise; - - /** - * Update handlers without restarting the server. - * Only fetch and error handlers can be updated. - */ - reload(options: Serve): void; - - /** - * Make a request to the running server. - * Useful for testing or internal routing. - */ - fetch(request: Request | string): Response | Promise; - - /** - * Upgrade an HTTP request to a WebSocket connection. - * @returns true if upgrade successful, false if failed - */ - upgrade( - request: Request, - options?: { - headers?: Bun.HeadersInit; - data?: T; - }, - ): boolean; - - /** - * Publish a message to all WebSocket clients subscribed to a topic. - * @returns Bytes sent, 0 if dropped, -1 if backpressure applied - */ - publish( - topic: string, - data: string | ArrayBufferView | ArrayBuffer | SharedArrayBuffer, - compress?: boolean, - ): ServerWebSocketSendStatus; - - /** - * Get count of WebSocket clients subscribed to a topic. - */ - subscriberCount(topic: string): number; - - /** - * Get client IP address and port. - * @returns null for closed requests or Unix sockets - */ - requestIP(request: Request): SocketAddress | null; - - /** - * Set custom idle timeout for a request. - * @param seconds Timeout in seconds, 0 to disable - */ - timeout(request: Request, seconds: number): void; - - /** - * Keep process alive while server is running. - */ - ref(): void; - - /** - * Allow process to exit if server is only thing running. - */ - unref(): void; - - /** Number of in-flight HTTP requests */ - readonly pendingRequests: number; - - /** Number of active WebSocket connections */ - readonly pendingWebSockets: number; - - /** Server URL including protocol, hostname and port */ - readonly url: URL; - - /** Port server is listening on */ - readonly port: number; - - /** Hostname server is bound to */ - readonly hostname: string; - - /** Whether server is in development mode */ - readonly development: boolean; - - /** Server instance identifier */ - readonly id: string; -} - -interface WebSocketHandler { - /** Maximum WebSocket message size in bytes */ - maxPayloadLength?: number; - - /** Bytes of queued messages before applying backpressure */ - backpressureLimit?: number; - - /** Whether to close connection when backpressure limit hit */ - closeOnBackpressureLimit?: boolean; - - /** Called when backpressure is relieved */ - drain?(ws: ServerWebSocket): void | Promise; - - /** Seconds before idle timeout */ - idleTimeout?: number; - - /** Enable per-message deflate compression */ - perMessageDeflate?: - | boolean - | { - compress?: WebSocketCompressor | boolean; - decompress?: WebSocketCompressor | boolean; - }; - - /** Send ping frames to keep connection alive */ - sendPings?: boolean; - - /** Whether server receives its own published messages */ - publishToSelf?: boolean; - - /** Called when connection opened */ - open?(ws: ServerWebSocket): void | Promise; - - /** Called when message received */ - message( - ws: ServerWebSocket, - message: string | Buffer, - ): void | Promise; - - /** Called when connection closed */ - close?( - ws: ServerWebSocket, - code: number, - reason: string, - ): void | Promise; - - /** Called when ping frame received */ - ping?(ws: ServerWebSocket, data: Buffer): void | Promise; - - /** Called when pong frame received */ - pong?(ws: ServerWebSocket, data: Buffer): void | Promise; -} - -interface TLSOptions { - /** Certificate authority chain */ - ca?: string | Buffer | BunFile | Array; - - /** Server certificate */ - cert?: string | Buffer | BunFile | Array; - - /** Path to DH parameters file */ - dhParamsFile?: string; - - /** Private key */ - key?: string | Buffer | BunFile | Array; - - /** Reduce TLS memory usage */ - lowMemoryMode?: boolean; - - /** Private key passphrase */ - passphrase?: string; - - /** OpenSSL options flags */ - secureOptions?: number; - - /** Server name for SNI */ - serverName?: string; -} -``` - -{% /details %} diff --git a/docs/api/import-meta.md b/docs/api/import-meta.md deleted file mode 100644 index 7be078a2d1..0000000000 --- a/docs/api/import-meta.md +++ /dev/null @@ -1,69 +0,0 @@ -The `import.meta` object is a way for a module to access information about itself. It's part of the JavaScript language, but its contents are not standardized. Each "host" (browser, runtime, etc) is free to implement any properties it wishes on the `import.meta` object. - -Bun implements the following properties. - -```ts#/path/to/project/file.ts -import.meta.dir; // => "/path/to/project" -import.meta.file; // => "file.ts" -import.meta.path; // => "/path/to/project/file.ts" -import.meta.url; // => "file:///path/to/project/file.ts" - -import.meta.main; // `true` if this file is directly executed by `bun run` - // `false` otherwise - -import.meta.resolve("zod"); // => "file:///path/to/project/node_modules/zod/index.js" -``` - -{% table %} - ---- - -- `import.meta.dir` -- Absolute path to the directory containing the current file, e.g. `/path/to/project`. Equivalent to `__dirname` in CommonJS modules (and Node.js) - ---- - -- `import.meta.dirname` -- An alias to `import.meta.dir`, for Node.js compatibility - ---- - -- `import.meta.env` -- An alias to `process.env`. - ---- - -- `import.meta.file` -- The name of the current file, e.g. `index.tsx` - ---- - -- `import.meta.path` -- Absolute path to the current file, e.g. `/path/to/project/index.ts`. Equivalent to `__filename` in CommonJS modules (and Node.js) - ---- - -- `import.meta.filename` -- An alias to `import.meta.path`, for Node.js compatibility - ---- - -- `import.meta.main` -- Indicates whether the current file is the entrypoint to the current `bun` process. Is the file being directly executed by `bun run` or is it being imported? - ---- - -- `import.meta.resolve` -- Resolve a module specifier (e.g. `"zod"` or `"./file.tsx"`) to a url. Equivalent to [`import.meta.resolve` in browsers](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import.meta#resolve) - - ```ts - import.meta.resolve("zod"); - // => "file:///path/to/project/node_modules/zod/index.ts" - ``` - ---- - -- `import.meta.url` -- A `string` url to the current file, e.g. `file:///path/to/project/index.ts`. Equivalent to [`import.meta.url` in browsers](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import.meta#url) - -{% /table %} diff --git a/docs/benchmarks.md b/docs/benchmarks.md deleted file mode 100644 index fd80513aef..0000000000 --- a/docs/benchmarks.md +++ /dev/null @@ -1,120 +0,0 @@ -Bun.js focuses on performance, developer experience, and compatibility with the JavaScript ecosystem. - -## HTTP Requests - -```ts -// http.ts -export default { - port: 3000, - fetch(request: Request) { - return new Response("Hello World"); - }, -}; - -// bun ./http.ts -``` - -| Requests per second | OS | CPU | Bun version | -| ---------------------------------------------------------------------- | ----- | ------------------------------ | ----------- | -| [260,000](https://twitter.com/jarredsumner/status/1512040623200616449) | macOS | Apple Silicon M1 Max | 0.0.76 | -| [160,000](https://twitter.com/jarredsumner/status/1511988933587976192) | Linux | AMD Ryzen 5 3600 6-Core 2.2ghz | 0.0.76 | - -{% details summary="See benchmark details" %} -Measured with [`http_load_test`](https://github.com/uNetworking/uSockets/blob/master/examples/http_load_test.c) by running: - -```bash -$ ./http_load_test 20 127.0.0.1 3000 -``` - -{% /details %} - -## File System - -`cat` clone that runs [2x faster than GNU cat](https://twitter.com/jarredsumner/status/1511707890708586496) for large files on Linux - -```js -// cat.js -import { resolve } from "path"; -import { write, stdout, file, argv } from "bun"; - -const path = resolve(argv.at(-1)); - -await write( - // stdout is a Blob - stdout, - // file(path) returns a Blob - https://developer.mozilla.org/en-US/docs/Web/API/Blob - file(path), -); -``` - -Run this with `bun cat.js /path/to/big/file`. - -## Reading from standard input - -```ts -for await (const line of console) { - // line of text from stdin - console.log(line); -} -``` - -## React SSR - -```js -import { renderToReadableStream } from "react-dom/server"; - -const dt = new Intl.DateTimeFormat(); - -export default { - port: 3000, - async fetch(request: Request) { - return new Response( - await renderToReadableStream( - - - Hello World - - - Hello from React! - The date is {dt.format(new Date())} - - , - ), - ); - }, -}; -``` - -Write to stdout with `console.write`: - -```js -// no trailing newline -// works with strings and typed arrays -console.write("Hello World!"); -``` - -There are some more examples in the [examples](./examples) folder. - -PRs adding more examples are very welcome! - -## Fast paths for Web APIs - -Bun.js has fast paths for common use cases that make Web APIs live up to the performance demands of servers and CLIs. - -`Bun.file(path)` returns a [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob) that represents a lazily-loaded file. - -When you pass a file blob to `Bun.write`, Bun automatically uses a faster system call: - -```js -const blob = Bun.file("input.txt"); -await Bun.write("output.txt", blob); -``` - -On Linux, this uses the [`copy_file_range`](https://man7.org/linux/man-pages/man2/copy_file_range.2.html) syscall and on macOS, this becomes `clonefile` (or [`fcopyfile`](https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/copyfile.3.html)). - -`Bun.write` also supports [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) objects. It automatically converts to a [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob). - -```js -// Eventually, this will stream the response to disk but today it buffers -await Bun.write("index.html", await fetch("https://example.com")); -``` diff --git a/docs/bun-flavored-toml.md b/docs/bun-flavored-toml.md deleted file mode 100644 index ab7beb5920..0000000000 --- a/docs/bun-flavored-toml.md +++ /dev/null @@ -1,42 +0,0 @@ -[TOML](https://toml.io/) is a minimal configuration file format designed to be easy for humans to read. - -Bun implements a TOML parser with a few tweaks designed for better interoperability with INI files and with JavaScript. - -### ; and # are comments - -In Bun-flavored TOML, comments start with `#` or `;` - -```ini -# This is a comment -; This is also a comment -``` - -This matches the behavior of INI files. - -In TOML, comments start with `#` - -```toml -# This is a comment -``` - -### String escape characters - -Bun-flavored adds a few more escape sequences to TOML to work better with JavaScript strings. - -``` -# Bun-flavored TOML extras -\x{XX} - ASCII (U+00XX) -\u{x+} - unicode (U+0000000X) - (U+XXXXXXXX) -\v - vertical tab - -# Regular TOML -\b - backspace (U+0008) -\t - tab (U+0009) -\n - linefeed (U+000A) -\f - form feed (U+000C) -\r - carriage return (U+000D) -\" - quote (U+0022) -\\ - backslash (U+005C) -\uXXXX - unicode (U+XXXX) -\UXXXXXXXX - unicode (U+XXXXXXXX) -``` diff --git a/docs/bundler/bytecode.mdx b/docs/bundler/bytecode.mdx new file mode 100644 index 0000000000..748d81eebd --- /dev/null +++ b/docs/bundler/bytecode.mdx @@ -0,0 +1,465 @@ +--- +title: Bytecode Caching +description: Speed up JavaScript execution with bytecode caching in Bun's bundler +--- + +Bytecode caching is a build-time optimization that dramatically improves application startup time by pre-compiling your JavaScript to bytecode. For example, when compiling TypeScript's `tsc` with bytecode enabled, startup time improves by **2x**. + +## Usage + +### Basic usage + +Enable bytecode caching with the `--bytecode` flag: + +```bash terminal icon="terminal" +bun build ./index.ts --target=bun --bytecode --outdir=./dist +``` + +This generates two files: + +- `dist/index.js` - Your bundled JavaScript +- `dist/index.jsc` - The bytecode cache file + +At runtime, Bun automatically detects and uses the `.jsc` file: + +```bash terminal icon="terminal" +bun ./dist/index.js # Automatically uses index.jsc +``` + +### With standalone executables + +When creating executables with `--compile`, bytecode is embedded into the binary: + +```bash terminal icon="terminal" +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. + +### Combining with other optimizations + +Bytecode works great with minification and source maps: + +```bash terminal icon="terminal" +bun build --compile --bytecode --minify --sourcemap ./cli.ts --outfile=mycli +``` + +- `--minify` reduces code size before generating bytecode (less code -> less bytecode) +- `--sourcemap` preserves error reporting (errors still point to original source) +- `--bytecode` eliminates parsing overhead + +## Performance impact + +The performance improvement scales with your codebase size: + +| Application size | Typical startup improvement | +| ------------------------- | --------------------------- | +| Small CLI (< 100 KB) | 1.5-2x faster | +| Medium-large app (> 5 MB) | 2.5x-4x faster | + +Larger applications benefit more because they have more code to parse. + +## When to use bytecode + +### Great for: + +#### CLI tools + +- Invoked frequently (linters, formatters, git hooks) +- Startup time is the entire user experience +- Users notice the difference between 90ms and 45ms startup +- Example: TypeScript compiler, Prettier, ESLint + +#### Build tools and task runners + +- Run hundreds or thousands of times during development +- Milliseconds saved per run compound quickly +- Developer experience improvement +- Example: Build scripts, test runners, code generators + +#### Standalone executables + +- Distributed to users who care about snappy performance +- Single-file distribution is convenient +- File size less important than startup time +- Example: CLIs distributed via npm or as binaries + +### Skip it for: + +- ❌ **Small scripts** +- ❌ **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. + +When you update Bun, you must regenerate bytecode: + +```bash terminal icon="terminal" +# After updating Bun +bun build --bytecode ./index.ts --outdir=./dist +``` + +If bytecode doesn't match the current Bun version, it's automatically ignored and your code falls back to parsing the JavaScript source. Your app still runs - you just lose the performance optimization. + +**Best practice**: Generate bytecode as part of your CI/CD build process. Don't commit `.jsc` files to git. Regenerate them whenever you update Bun. + +### Source code still required + +- The `.js` file (your bundled source code) +- The `.jsc` file (the bytecode cache) + +At runtime: + +1. Bun loads the `.js` file, sees a `@bytecode` pragma, and checks the `.jsc` file +2. Bun loads the `.jsc` file +3. Bun validates the bytecode hash matches the source +4. If valid, Bun uses the bytecode +5. If invalid, Bun falls back to parsing the source + +### Bytecode is not obfuscation + +Bytecode **does not obscure your source code**. It's an optimization, not a security measure. + +## Production deployment + +### Docker + +Include bytecode generation in your Dockerfile: + +```dockerfile Dockerfile icon="docker" +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 \ + --outdir=./dist \ + --compile \ + ./src/server.ts --outfile=./dist/server + +FROM oven/bun:1 AS runner +WORKDIR /app +COPY --from=builder /dist/server /app/server +CMD ["./server"] +``` + +The bytecode is architecture-independent. + +### CI/CD + +Generate bytecode during your build pipeline: + +```yaml workflow.yml icon="file-code" +# GitHub Actions +- name: Build with bytecode + run: | + bun install + bun build --bytecode --minify \ + --outdir=./dist \ + --target=bun \ + ./src/index.ts +``` + +## Debugging + +### Verify bytecode is being used + +Check that the `.jsc` file exists: + +```bash terminal icon="terminal" +ls -lh dist/ +``` + +```txt +-rw-r--r-- 1 user staff 245K index.js +-rw-r--r-- 1 user staff 1.1M index.jsc +``` + +The `.jsc` file should be 2-8x larger than the `.js` file. + +To log if bytecode is being used, set `BUN_JSC_verboseDiskCache=1` in your environment. + +On success, it will log something like: + +```txt +[Disk cache] cache hit for sourceCode +``` + +If you see a cache miss, it will log something like: + +```txt +[Disk cache] cache miss for sourceCode +``` + +It's normal for it it to log a cache miss multiple times since Bun doesn't currently bytecode cache JavaScript code used in builtin modules. + +### Common issues + +**Bytecode silently ignored**: Usually caused by a Bun version update. The cache version doesn't match, so bytecode is rejected. Regenerate to fix. + +**File size too large**: This is expected. Consider: + +- Using `--minify` to reduce code size before bytecode generation +- 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: + +1. **Parsing**: The engine reads your JavaScript source code and converts it into an Abstract Syntax Tree (AST) +2. **Bytecode compilation**: The AST is compiled into bytecode - a lower-level representation that's faster to execute +3. **Execution**: The bytecode is executed by the engine's interpreter or JIT compiler + +Bytecode is an intermediate representation - it's lower-level than JavaScript source code, but higher-level than machine code. Think of it as assembly language for a virtual machine. Each bytecode instruction represents a single operation like "load this variable," "add two numbers," or "call this function." + +This happens **every single time** you run your code. If you have a CLI tool that runs 100 times a day, your code gets parsed 100 times. If you have a serverless function with frequent cold starts, parsing happens on every cold start. + +With bytecode caching, Bun moves steps 1 and 2 to the build step. At runtime, the engine loads the pre-compiled bytecode and jumps straight to execution. + +### Why lazy parsing makes this even better + +Modern JavaScript engines use a clever optimization called **lazy parsing**. They don't parse all your code upfront - instead, functions are only parsed when they're first called: + +```js +// Without bytecode caching: +function rarely_used() { + // This 500-line function is only parsed + // when it's actually called +} + +function main() { + console.log("Starting app"); + // rarely_used() is never called, so it's never parsed +} +``` + +This means parsing overhead isn't just a startup cost - it happens throughout your application's lifetime as different code paths execute. With bytecode caching, **all functions are pre-compiled**, even the ones that are lazily parsed. The parsing work happens once at build time instead of being distributed throughout your application's execution. + +## The bytecode format + +### Inside a .jsc file + +A `.jsc` file contains a serialized bytecode structure. Understanding what's inside helps explain both the performance benefits and the file size tradeoff. + +**Header section** (validated on every load): + +- **Cache version**: A hash tied to the JavaScriptCore framework version. This ensures bytecode generated with one version of Bun only runs with that exact version. +- **Code block type tag**: Identifies whether this is a Program, Module, Eval, or Function code block. + +**SourceCodeKey** (validates bytecode matches source): + +- **Source code hash**: A hash of the original JavaScript source code. Bun verifies this matches before using the bytecode. +- **Source code length**: The exact length of the source, for additional validation. +- **Compilation flags**: Critical compilation context like strict mode, whether it's a script vs module, eval context type, etc. The same source code compiled with different flags produces different bytecode. + +**Bytecode instructions**: + +- **Instruction stream**: The actual bytecode opcodes - the compiled representation of your JavaScript. This is a variable-length sequence of bytecode instructions. +- **Metadata table**: Each opcode has associated metadata - things like profiling counters, type hints, and execution counts (even if not yet populated). +- **Jump targets**: Pre-computed addresses for control flow (if/else, loops, switch statements). +- **Switch tables**: Optimized lookup tables for switch statements. + +**Constants and identifiers**: + +- **Constant pool**: All literal values in your code - numbers, strings, booleans, null, undefined. These are stored as actual JavaScript values (JSValues) so they don't need to be parsed from source at runtime. +- **Identifier table**: All variable and function names used in the code. Stored as deduplicated strings. +- **Source code representation markers**: Flags indicating how constants should be represented (as integers, doubles, big ints, etc.). + +**Function metadata** (for each function in your code): + +- **Register allocation**: How many registers (local variables) the function needs - `thisRegister`, `scopeRegister`, `numVars`, `numCalleeLocals`, `numParameters`. +- **Code features**: A bitmask of function characteristics: is it a constructor? an arrow function? does it use `super`? does it have tail calls? These affect how the function is executed. +- **Lexically scoped features**: Strict mode and other lexical context. +- **Parse mode**: The mode in which the function was parsed (normal, async, generator, async generator). + +**Nested structures**: + +- **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. +- **Exception handlers**: Try/catch/finally blocks with their boundaries and handler addresses pre-computed. +- **Expression info**: Maps bytecode positions back to source code locations for error reporting and debugging. + +### What bytecode does NOT contain + +Importantly, **bytecode does not embed your source code**. Instead: + +- The JavaScript source is stored separately (in the `.js` file) +- The bytecode only stores a hash and length of the source +- At load time, Bun validates the bytecode matches the current source code + +This is why you need to deploy both the `.js` and `.jsc` files. The `.jsc` file is useless without its corresponding `.js` file. + +## The tradeoff: file size + +Bytecode files are significantly larger than source code - typically 2-8x larger. + +### Why is bytecode so much larger? + +**Bytecode instructions are verbose**: +A single line of minified JavaScript might compile to dozens of bytecode instructions. For example: + +```js +const sum = arr.reduce((a, b) => a + b, 0); +``` + +Compiles to bytecode that: + +- Loads the `arr` variable +- Gets the `reduce` property +- Creates the arrow function (which itself has bytecode) +- Loads the initial value `0` +- Sets up the call with the right number of arguments +- Actually performs the call +- Stores the result in `sum` + +Each of these steps is a separate bytecode instruction with its own metadata. + +**Constant pools store everything**: +Every string literal, number, property name - everything gets stored in the constant pool. Even if your source code has `"hello"` a hundred times, the constant pool stores it once, but the identifier table and constant references add overhead. + +**Per-function metadata**: +Each function - even small one-line functions - gets its own complete metadata: + +- Register allocation info +- Code features bitmask +- Parse mode +- Exception handlers +- Expression info for debugging + +A file with 1,000 small functions has 1,000 sets of metadata. + +**Profiling data structures**: +Even though profiling data isn't populated yet, the _structures_ to hold profiling data are allocated. This includes: + +- Value profile slots (tracking what types flow through each operation) +- Array profile slots (tracking array access patterns) +- Binary arithmetic profile slots (tracking number types in math operations) +- Unary arithmetic profile slots + +These take up space even when empty. + +**Pre-computed control flow**: +Jump targets, switch tables, and exception handler boundaries are all pre-computed and stored. This makes execution faster but increases file size. + +### Mitigation strategies + +**Compression**: +Bytecode compresses extremely well with gzip/brotli (60-70% compression). The repetitive structure and metadata compress efficiently. + +**Minification first**: +Using `--minify` before bytecode generation helps: + +- Shorter identifiers → smaller identifier table +- Dead code elimination → less bytecode generated +- Constant folding → fewer constants in the pool + +**The tradeoff**: +You're trading 2-4x larger files for 2-4x faster startup. For CLIs, this is usually worth it. For long-running servers where a few megabytes of disk space don't matter, it's even less of an issue. + +## Versioning and portability + +### Cross-architecture portability: ✅ + +Bytecode is **architecture-independent**. You can: + +- Build on macOS ARM64, deploy to Linux x64 +- Build on Linux x64, deploy to AWS Lambda ARM64 +- Build on Windows x64, deploy to macOS ARM64 + +The bytecode contains abstract instructions that work on any architecture. Architecture-specific optimizations happen during JIT compilation at runtime, not in the cached bytecode. + +### Cross-version portability: ❌ + +Bytecode is **not stable across Bun versions**. Here's why: + +**Bytecode format changes**: +JavaScriptCore's bytecode format evolves. New opcodes get added, old ones get removed or changed, metadata structures change. Each version of JavaScriptCore has a different bytecode format. + +**Version validation**: +The cache version in the `.jsc` file header is a hash of the JavaScriptCore framework. When Bun loads bytecode: + +1. It extracts the cache version from the `.jsc` file +2. It computes the current JavaScriptCore version +3. If they don't match, the bytecode is **silently rejected** +4. Bun falls back to parsing the `.js` source code + +Your application still runs - you just lose the performance optimization. + +**Graceful degradation**: +This design means bytecode caching "fails open" - if anything goes wrong (version mismatch, corrupted file, missing file), your code still runs normally. You might see slower startup, but you won't see errors. + +## Unlinked vs. linked bytecode + +JavaScriptCore makes a crucial distinction between "unlinked" and "linked" bytecode. This separation is what makes bytecode caching possible: + +### Unlinked bytecode (what's cached) + +The bytecode saved in `.jsc` files is **unlinked bytecode**. It contains: + +- The compiled bytecode instructions +- Structural information about the code +- Constants and identifiers +- Control flow information + +But it **doesn't** contain: + +- Pointers to actual runtime objects +- JIT-compiled machine code +- Profiling data from previous runs +- Call link information (which functions call which) + +Unlinked bytecode is **immutable and shareable**. Multiple executions of the same code can all reference the same unlinked bytecode. + +### Linked bytecode (runtime execution) + +When Bun runs bytecode, it "links" it - creating a runtime wrapper that adds: + +- **Call link information**: As your code runs, the engine learns which functions call which and optimizes those call sites. +- **Profiling data**: The engine tracks how many times each instruction executes, what types of values flow through the code, array access patterns, etc. +- **JIT compilation state**: References to baseline JIT or optimizing JIT (DFG/FTL) compiled versions of hot code. +- **Runtime objects**: Pointers to actual JavaScript objects, prototypes, scopes, etc. + +This linked representation is created fresh every time you run your code. This allows: + +1. **Caching the expensive work** (parsing and compilation to unlinked bytecode) +2. **Still collecting runtime profiling data** to guide optimizations +3. **Still applying JIT optimizations** based on actual execution patterns + +Bytecode caching moves expensive work (parsing and compiling to bytecode) from runtime to build time. For applications that start frequently, this can halve your startup time at the cost of larger files on disk. + +For production CLIs and serverless deployments, the combination of `--bytecode --minify --sourcemap` gives you the best performance while maintaining debuggability. diff --git a/docs/bundler/css.md b/docs/bundler/css.mdx similarity index 86% rename from docs/bundler/css.md rename to docs/bundler/css.mdx index 6bb0fa5b6d..730332f173 100644 --- a/docs/bundler/css.md +++ b/docs/bundler/css.mdx @@ -1,3 +1,8 @@ +--- +title: CSS +description: Bun's bundler has built-in support for CSS with modern features +--- + Bun's bundler has built-in support for CSS with the following features: - Transpiling modern/future features to work on all browsers (including vendor prefixing) @@ -9,11 +14,11 @@ Bun's bundler has built-in support for CSS with the following features: Bun's CSS bundler lets you use modern/future CSS features without having to worry about browser compatibility — all thanks to its transpiling and vendor prefixing features which are enabled by default. -Bun's CSS parser and bundler is a direct Rust → Zig port of [LightningCSS](https://lightningcss.dev/), with a bundling approach inspired by esbuild. The transpiler converts modern CSS syntax into backwards-compatible equivalents that work across browsers. +Bun's CSS parser and bundler is a direct Rust → Zig port of LightningCSS, with a bundling approach inspired by esbuild. The transpiler converts modern CSS syntax into backwards-compatible equivalents that work across browsers. -A huge thanks goes to the amazing work from the authors of [LightningCSS](https://lightningcss.dev/) and [esbuild](https://esbuild.github.io/). +A huge thanks goes to the amazing work from the authors of LightningCSS and esbuild. -### Browser Compatibility +## Browser Compatibility By default, Bun's CSS bundler targets the following browsers: @@ -23,13 +28,13 @@ By default, Bun's CSS bundler targets the following browsers: - Chrome 87+ - Safari 14+ -### Syntax Lowering +## Syntax Lowering -#### Nesting +### Nesting The CSS Nesting specification allows you to write more concise and intuitive stylesheets by nesting selectors inside one another. Instead of repeating parent selectors across your CSS file, you can write child styles directly within their parent blocks. -```css +```scss title="styles.css" icon="file-code" /* With nesting */ .card { background: white; @@ -48,7 +53,7 @@ The CSS Nesting specification allows you to write more concise and intuitive sty Bun's CSS bundler automatically converts this nested syntax into traditional flat CSS that works in all browsers: -```css +```css title="styles.css" icon="file-code" /* Compiled output */ .card { background: white; @@ -67,7 +72,7 @@ Bun's CSS bundler automatically converts this nested syntax into traditional fla You can also nest media queries and other at-rules inside selectors, eliminating the need to repeat selector patterns: -```css +```css title="styles.css" icon="file-code" .responsive-element { display: block; @@ -79,7 +84,7 @@ You can also nest media queries and other at-rules inside selectors, eliminating This compiles to: -```css +```css title="styles.css" icon="file-code" .responsive-element { display: block; } @@ -91,11 +96,11 @@ This compiles to: } ``` -#### Color mix +### Color mix The `color-mix()` function gives you an easy way to blend two colors together according to a specified ratio in a chosen color space. This powerful feature lets you create color variations without manually calculating the resulting values. -```css +```scss title="styles.css" icon="file-code" .button { /* Mix blue and red in the RGB color space with a 30/70 proportion */ background-color: color-mix(in srgb, blue 30%, red); @@ -109,7 +114,7 @@ The `color-mix()` function gives you an easy way to blend two colors together ac Bun's CSS bundler evaluates these color mixes at build time when all color values are known (not CSS variables), generating static color values that work in all browsers: -```css +```css title="styles.css" icon="file-code" .button { /* Computed to the exact resulting color */ background-color: #b31a1a; @@ -122,11 +127,11 @@ Bun's CSS bundler evaluates these color mixes at build time when all color value This feature is particularly useful for creating color systems with programmatically derived shades, tints, and accents without needing preprocessors or custom tooling. -#### Relative colors +### Relative colors CSS now allows you to modify individual components of a color using relative color syntax. This powerful feature lets you create color variations by adjusting specific attributes like lightness, saturation, or individual channels without having to recalculate the entire color. -```css +```css title="styles.css" icon="file-code" .theme-color { /* Start with a base color and increase lightness by 15% */ --accent: lch(from purple calc(l + 15%) c h); @@ -147,27 +152,23 @@ Bun's CSS bundler computes these relative color modifications at build time (whe This approach is extremely useful for theme generation, creating accessible color variants, or building color scales based on mathematical relationships instead of hard-coding each value. -#### LAB colors +### LAB colors Modern CSS supports perceptually uniform color spaces like LAB, LCH, OKLAB, and OKLCH that offer significant advantages over traditional RGB. These color spaces can represent colors outside the standard RGB gamut, resulting in more vibrant and visually consistent designs. -```css +```css title="styles.css" icon="file-code" .vibrant-element { /* A vibrant red that exceeds sRGB gamut boundaries */ color: lab(55% 78 35); /* A smooth gradient using perceptual color space */ - background: linear-gradient( - to right, - oklch(65% 0.25 10deg), - oklch(65% 0.25 250deg) - ); + background: linear-gradient(to right, oklch(65% 0.25 10deg), oklch(65% 0.25 250deg)); } ``` Bun's CSS bundler automatically converts these advanced color formats to backwards-compatible alternatives for browsers that don't yet support them: -```css +```css title="styles.css" icon="file-code" .vibrant-element { /* Fallback to closest RGB approximation */ color: #ff0f52; @@ -177,21 +178,17 @@ Bun's CSS bundler automatically converts these advanced color formats to backwar color: lab(55% 78 35); background: linear-gradient(to right, #cd4e15, #3887ab); - background: linear-gradient( - to right, - oklch(65% 0.25 10deg), - oklch(65% 0.25 250deg) - ); + background: linear-gradient(to right, oklch(65% 0.25 10deg), oklch(65% 0.25 250deg)); } ``` This layered approach ensures optimal color rendering across all browsers while allowing you to use the latest color technologies in your designs. -#### Color function +### Color function The `color()` function provides a standardized way to specify colors in various predefined color spaces, expanding your design options beyond the traditional RGB space. This allows you to access wider color gamuts and create more vibrant designs. -```css +```css title="styles.css" icon="file-code" .vivid-element { /* Using the Display P3 color space for wider gamut colors */ color: color(display-p3 1 0.1 0.3); @@ -203,7 +200,7 @@ The `color()` function provides a standardized way to specify colors in various For browsers that don't support these advanced color functions yet, Bun's CSS bundler provides appropriate RGB fallbacks: -```css +```css title="styles.css" icon="file-code" .vivid-element { /* RGB fallback first for maximum compatibility */ color: #fa1a4c; @@ -217,11 +214,11 @@ For browsers that don't support these advanced color functions yet, Bun's CSS bu This functionality lets you use modern color spaces immediately while ensuring your designs remain functional across all browsers, with optimal colors displayed in supporting browsers and reasonable approximations elsewhere. -#### HWB colors +### HWB colors The HWB (Hue, Whiteness, Blackness) color model provides an intuitive way to express colors based on how much white or black is mixed with a pure hue. Many designers find this approach more natural for creating color variations compared to manipulating RGB or HSL values. -```css +```css title="styles.css" icon="file-code" .easy-theming { /* Pure cyan with no white or black added */ --primary: hwb(180 0% 0%); @@ -239,7 +236,7 @@ The HWB (Hue, Whiteness, Blackness) color model provides an intuitive way to exp Bun's CSS bundler automatically converts HWB colors to RGB for compatibility with all browsers: -```css +```css title="styles.css" icon="file-code" .easy-theming { --primary: #00ffff; --primary-light: #33ffff; @@ -250,11 +247,11 @@ Bun's CSS bundler automatically converts HWB colors to RGB for compatibility wit The HWB model makes it particularly easy to create systematic color variations for design systems, providing a more intuitive approach to creating consistent tints and shades than working directly with RGB or HSL values. -#### Color notation +### Color notation Modern CSS has introduced more intuitive and concise ways to express colors. Space-separated color syntax eliminates the need for commas in RGB and HSL values, while hex colors with alpha channels provide a compact way to specify transparency. -```css +```css title="styles.css" icon="file-code" .modern-styling { /* Space-separated RGB notation (no commas) */ color: rgb(50 100 200); @@ -272,7 +269,7 @@ Modern CSS has introduced more intuitive and concise ways to express colors. Spa Bun's CSS bundler automatically converts these modern color formats to ensure compatibility with older browsers: -```css +```css title="styles.css" icon="file-code" .modern-styling { /* Converted to comma format for older browsers */ color: rgb(50, 100, 200); @@ -289,11 +286,11 @@ Bun's CSS bundler automatically converts these modern color formats to ensure co This conversion process lets you write cleaner, more modern CSS while ensuring your styles work correctly across all browsers. -#### light-dark() color function +### light-dark() color function The `light-dark()` function provides an elegant solution for implementing color schemes that respect the user's system preference without requiring complex media queries. This function accepts two color values and automatically selects the appropriate one based on the current color scheme context. -```css +```css title="styles.css" icon="file-code" :root { /* Define color scheme support */ color-scheme: light dark; @@ -318,7 +315,7 @@ The `light-dark()` function provides an elegant solution for implementing color For browsers that don't support this feature yet, Bun's CSS bundler converts it to use CSS variables with proper fallbacks: -```css +```css title="styles.css" icon="file-code" :root { --lightningcss-light: initial; --lightningcss-dark: ; @@ -345,21 +342,19 @@ For browsers that don't support this feature yet, Bun's CSS bundler converts it } .themed-component { - background-color: var(--lightningcss-light, #ffffff) - var(--lightningcss-dark, #121212); + background-color: var(--lightningcss-light, #ffffff) var(--lightningcss-dark, #121212); color: var(--lightningcss-light, #333333) var(--lightningcss-dark, #eeeeee); - border-color: var(--lightningcss-light, #dddddd) - var(--lightningcss-dark, #555555); + border-color: var(--lightningcss-light, #dddddd) var(--lightningcss-dark, #555555); } ``` This approach gives you a clean way to handle light and dark themes without duplicating styles or writing complex media queries, while maintaining compatibility with browsers that don't yet support the feature natively. -#### Logical properties +### Logical properties CSS logical properties let you define layout, spacing, and sizing relative to the document's writing mode and text direction rather than physical screen directions. This is crucial for creating truly international layouts that automatically adapt to different writing systems. -```css +```css title="styles.css" icon="file-code" .multilingual-component { /* Margin that adapts to writing direction */ margin-inline-start: 1rem; @@ -378,7 +373,7 @@ CSS logical properties let you define layout, spacing, and sizing relative to th For browsers that don't fully support logical properties, Bun's CSS bundler compiles them to physical properties with appropriate directional adjustments: -```css +```css title="styles.css" icon="file-code" /* For left-to-right languages */ .multilingual-component:dir(ltr) { margin-left: 1rem; @@ -402,11 +397,11 @@ For browsers that don't fully support logical properties, Bun's CSS bundler comp If the `:dir()` selector isn't supported, additional fallbacks are automatically generated to ensure your layouts work properly across all browsers and writing systems. This makes creating internationalized designs much simpler while maintaining compatibility with older browsers. -#### :dir() selector +### :dir() selector The `:dir()` pseudo-class selector allows you to style elements based on their text direction (RTL or LTR), providing a powerful way to create direction-aware designs without JavaScript. This selector matches elements based on their directionality as determined by the document or explicit direction attributes. -```css +```css title="styles.css" icon="file-code" /* Apply different styles based on text direction */ .nav-arrow:dir(ltr) { transform: rotate(0deg); @@ -428,7 +423,7 @@ The `:dir()` pseudo-class selector allows you to style elements based on their t For browsers that don't support the `:dir()` selector yet, Bun's CSS bundler converts it to the more widely supported `:lang()` selector with appropriate language mappings: -```css +```css title="styles.css" icon="file-code" /* Converted to use language-based selectors as fallback */ .nav-arrow:lang(en, fr, de, es, it, pt, nl) { transform: rotate(0deg); @@ -449,11 +444,11 @@ For browsers that don't support the `:dir()` selector yet, Bun's CSS bundler con This conversion lets you write direction-aware CSS that works reliably across browsers, even those that don't yet support the `:dir()` selector natively. If multiple arguments to `:lang()` aren't supported, further fallbacks are automatically provided. -#### :lang() selector +### :lang() selector The `:lang()` pseudo-class selector allows you to target elements based on the language they're in, making it easy to apply language-specific styling. Modern CSS allows the `:lang()` selector to accept multiple language codes, letting you group language-specific rules more efficiently. -```css +```css title="styles.css" icon="file-code" /* Typography adjustments for CJK languages */ :lang(zh, ja, ko) { line-height: 1.8; @@ -472,7 +467,7 @@ blockquote:lang(de, nl, da, sv) { For browsers that don't support multiple arguments in the `:lang()` selector, Bun's CSS bundler converts this syntax to use the `:is()` selector to maintain the same behavior: -```css +```css title="styles.css" icon="file-code" /* Multiple languages grouped with :is() for better browser support */ :is(:lang(zh), :lang(ja), :lang(ko)) { line-height: 1.8; @@ -490,11 +485,11 @@ blockquote:is(:lang(de), :lang(nl), :lang(da), :lang(sv)) { If needed, Bun can provide additional fallbacks for `:is()` as well, ensuring your language-specific styles work across all browsers. This approach simplifies creating internationalized designs with distinct typographic and styling rules for different language groups. -#### :is() selector +### :is() selector The `:is()` pseudo-class function (formerly `:matches()`) allows you to create more concise and readable selectors by grouping multiple selectors together. It accepts a selector list as its argument and matches if any of the selectors in that list match, significantly reducing repetition in your CSS. -```css +```css title="styles.css" icon="file-code" /* Instead of writing these separately */ /* .article h1, @@ -547,13 +542,17 @@ For browsers that don't support `:is()`, Bun's CSS bundler provides fallbacks us } ``` -It's worth noting that the vendor-prefixed versions have some limitations compared to the standardized `:is()` selector, particularly with complex selectors. Bun handles these limitations intelligently, only using prefixed versions when they'll work correctly. + + The vendor-prefixed versions have some limitations compared to the standardized `:is()` selector, particularly with + complex selectors. Bun handles these limitations intelligently, only using prefixed versions when they'll work + correctly. + -#### :not() selector +### :not() selector The `:not()` pseudo-class allows you to exclude elements that match a specific selector. The modern version of this selector accepts multiple arguments, letting you exclude multiple patterns with a single, concise selector. -```css +```css title="styles.css" icon="file-code" /* Select all buttons except primary and secondary variants */ button:not(.primary, .secondary) { background-color: #f5f5f5; @@ -568,7 +567,7 @@ h2:not(.sidebar *, footer *) { For browsers that don't support multiple arguments in `:not()`, Bun's CSS bundler converts this syntax to a more compatible form while preserving the same behavior: -```css +```css title="styles.css" icon="file-code" /* Converted to use :not with :is() for compatibility */ button:not(:is(.primary, .secondary)) { background-color: #f5f5f5; @@ -582,7 +581,7 @@ h2:not(:is(.sidebar *, footer *)) { And if `:is()` isn't supported, Bun can generate further fallbacks: -```css +```css title="styles.css" icon="file-code" /* Even more fallbacks for maximum compatibility */ button:not(:-webkit-any(.primary, .secondary)) { background-color: #f5f5f5; @@ -602,11 +601,11 @@ button:not(:is(.primary, .secondary)) { This conversion ensures your negative selectors work correctly across all browsers while maintaining the correct specificity and behavior of the original selector. -#### Math functions +### Math functions CSS now includes a rich set of mathematical functions that let you perform complex calculations directly in your stylesheets. These include standard math functions (`round()`, `mod()`, `rem()`, `abs()`, `sign()`), trigonometric functions (`sin()`, `cos()`, `tan()`, `asin()`, `acos()`, `atan()`, `atan2()`), and exponential functions (`pow()`, `sqrt()`, `exp()`, `log()`, `hypot()`). -```css +```css title="styles.css" icon="file-code" .dynamic-sizing { /* Clamp a value between minimum and maximum */ width: clamp(200px, 50%, 800px); @@ -625,7 +624,7 @@ CSS now includes a rich set of mathematical functions that let you perform compl Bun's CSS bundler evaluates these mathematical expressions at build time when all values are known constants (not variables), resulting in optimized output: -```css +```css title="styles.css" icon="file-code" .dynamic-sizing { width: clamp(200px, 50%, 800px); padding: 15px; @@ -637,11 +636,11 @@ Bun's CSS bundler evaluates these mathematical expressions at build time when al This approach lets you write more expressive and maintainable CSS with meaningful mathematical relationships, which then gets compiled to optimized values for maximum browser compatibility and performance. -#### Media query ranges +### Media query ranges Modern CSS supports intuitive range syntax for media queries, allowing you to specify breakpoints using comparison operators like `<`, `>`, `<=`, and `>=` instead of the more verbose `min-` and `max-` prefixes. This syntax is more readable and matches how we normally think about values and ranges. -```css +```css title="styles.css" icon="file-code" /* Modern syntax with comparison operators */ @media (width >= 768px) { .container { @@ -666,7 +665,7 @@ Modern CSS supports intuitive range syntax for media queries, allowing you to sp Bun's CSS bundler converts these modern range queries to traditional media query syntax for compatibility with all browsers: -```css +```css title="styles.css" icon="file-code" /* Converted to traditional min/max syntax */ @media (min-width: 768px) { .container { @@ -689,11 +688,11 @@ Bun's CSS bundler converts these modern range queries to traditional media query This lets you write more intuitive and mathematical media queries while ensuring your stylesheets work correctly across all browsers, including those that don't support the modern range syntax. -#### Shorthands +### Shorthands CSS has introduced several modern shorthand properties that improve code readability and maintainability. Bun's CSS bundler ensures these convenient shorthands work on all browsers by converting them to their longhand equivalents when needed. -```css +```css title="styles.css" icon="file-code" /* Alignment shorthands */ .flex-container { /* Shorthand for align-items and justify-items */ @@ -729,7 +728,7 @@ CSS has introduced several modern shorthand properties that improve code readabi For browsers that don't support these modern shorthands, Bun converts them to their component longhand properties: -```css +```css title="styles.css" icon="file-code" .flex-container { /* Expanded alignment properties */ align-items: center; @@ -766,11 +765,11 @@ For browsers that don't support these modern shorthands, Bun converts them to th This conversion ensures your stylesheets remain clean and maintainable while providing the broadest possible browser compatibility. -#### Double position gradients +### Double position gradients The double position gradient syntax is a modern CSS feature that allows you to create hard color stops in gradients by specifying the same color at two adjacent positions. This creates a sharp transition rather than a smooth fade, which is useful for creating stripes, color bands, and other multi-color designs. -```css +```css title="styles.css" icon="file-code" .striped-background { /* Creates a sharp transition from green to red at 30%-40% */ background: linear-gradient( @@ -799,7 +798,7 @@ The double position gradient syntax is a modern CSS feature that allows you to c For browsers that don't support this syntax, Bun's CSS bundler automatically converts it to the traditional format by duplicating color stops: -```css +```css title="styles.css" icon="file-code" .striped-background { background: linear-gradient( to right, @@ -830,11 +829,11 @@ For browsers that don't support this syntax, Bun's CSS bundler automatically con This conversion lets you use the cleaner double position syntax in your source code while ensuring gradients display correctly in all browsers. -#### system-ui font +### system-ui font The `system-ui` generic font family lets you use the device's native UI font, creating interfaces that feel more integrated with the operating system. This provides a more native look and feel without having to specify different font stacks for each platform. -```css +```css title="styles.css" icon="file-code" .native-interface { /* Use the system's default UI font */ font-family: system-ui; @@ -848,7 +847,7 @@ The `system-ui` generic font family lets you use the device's native UI font, cr For browsers that don't support `system-ui`, Bun's CSS bundler automatically expands it to a comprehensive cross-platform font stack: -```css +```css title="styles.css" icon="file-code" .native-interface { /* Expanded to support all major platforms */ font-family: @@ -883,7 +882,7 @@ This approach gives you the simplicity of writing just `system-ui` in your sourc ## CSS Modules -Bun's bundler also supports bundling [CSS modules](https://css-tricks.com/css-modules-part-1-need/) in addition to [regular CSS](/docs/bundler/css) with support for the following features: +Bun's bundler also supports bundling CSS modules in addition to regular CSS with support for the following features: - Automatically detecting CSS module files (`.module.css`) with zero configuration - Composition (`composes` property) @@ -894,17 +893,17 @@ A CSS module is a CSS file (with the `.module.css` extension) where are all clas Under the hood, Bun's bundler transforms locally scoped class names into unique identifiers. -## Getting started +### Getting started Create a CSS file with the `.module.css` extension: -```css -/* styles.module.css */ +```css title="styles.module.css" icon="file-code" .button { color: red; } +``` -/* other-styles.module.css */ +```css title="other-styles.module.css" icon="file-code" .button { color: blue; } @@ -912,7 +911,7 @@ Create a CSS file with the `.module.css` extension: You can then import this file, for example into a TSX file: -```tsx +```tsx title="app.tsx" icon="/icons/typescript.svg" import styles from "./styles.module.css"; import otherStyles from "./other-styles.module.css"; @@ -926,10 +925,9 @@ export default function App() { } ``` -The `styles` object from importing the CSS module file will be an object with all class names as keys and -their unique identifiers as values: +The styles object from importing the CSS module file will be an object with all class names as keys and their unique identifiers as values: -```tsx +```ts title="app.tsx" icon="/icons/typescript.svg" import styles from "./styles.module.css"; import otherStyles from "./other-styles.module.css"; @@ -939,7 +937,7 @@ console.log(otherStyles); This will output: -```ts +```ts title="app.tsx" icon="/icons/typescript.svg" { button: "button_123"; } @@ -953,12 +951,11 @@ As you can see, the class names are unique to each file, avoiding any collisions ### Composition -CSS modules allow you to _compose_ class selectors together. This lets you reuse style rules across multiple classes. +CSS modules allow you to compose class selectors together. This lets you reuse style rules across multiple classes. For example: -```css -/* styles.module.css */ +```css title="styles.module.css" icon="file-code" .button { composes: background; color: red; @@ -971,7 +968,7 @@ For example: Would be the same as writing: -```css +```css title="styles.module.css" icon="file-code" .button { background-color: blue; color: red; @@ -982,13 +979,14 @@ Would be the same as writing: } ``` -{% callout %} There are a couple rules to keep in mind when using `composes`: -- A `composes` property must come before any regular CSS properties or declarations -- You can only use `composes` on a **simple selector with a single class name**: + + **Composition Rules:** - A `composes` property must come before any regular CSS properties or declarations - You can + only use `composes` on a simple selector with a single class name + -```css +```css title="styles.module.css" icon="file-code" #button { /* Invalid! `#button` is not a class selector */ composes: background; @@ -1001,28 +999,26 @@ There are a couple rules to keep in mind when using `composes`: } ``` -{% /callout %} - ### Composing from a separate CSS module file You can also compose from a separate CSS module file: -```css -/* background.module.css */ +```css title="background.module.css" icon="file-code" .background { background-color: blue; } +``` -/* styles.module.css */ +```css title="styles.module.css" icon="file-code" .button { composes: background from "./background.module.css"; color: red; } ``` -{% callout %} + When composing classes from separate files, be sure that they do not contain the same properties. -The CSS module spec says that composing classes from separate files with conflicting properties is -undefined behavior, meaning that the output may differ and be unreliable. -{% /callout %} +The CSS module spec says that composing classes from separate files with conflicting properties is undefined behavior, meaning that the output may differ and be unreliable. + + diff --git a/docs/bundler/css_modules.md b/docs/bundler/css_modules.md deleted file mode 100644 index 271ab4621d..0000000000 --- a/docs/bundler/css_modules.md +++ /dev/null @@ -1,145 +0,0 @@ -# CSS Modules - -Bun's bundler also supports bundling [CSS modules](https://css-tricks.com/css-modules-part-1-need/) in addition to [regular CSS](/docs/bundler/css) with support for the following features: - -- Automatically detecting CSS module files (`.module.css`) with zero configuration -- Composition (`composes` property) -- Importing CSS modules into JSX/TSX -- Warnings/errors for invalid usages of CSS modules - -A CSS module is a CSS file (with the `.module.css` extension) where are all class names and animations are scoped to the file. This helps you avoid class name collisions as CSS declarations are globally scoped by default. - -Under the hood, Bun's bundler transforms locally scoped class names into unique identifiers. - -## Getting started - -Create a CSS file with the `.module.css` extension: - -```css -/* styles.module.css */ -.button { - color: red; -} - -/* other-styles.module.css */ -.button { - color: blue; -} -``` - -You can then import this file, for example into a TSX file: - -```tsx -import styles from "./styles.module.css"; -import otherStyles from "./other-styles.module.css"; - -export default function App() { - return ( - <> - Red button! - Blue button! - > - ); -} -``` - -The `styles` object from importing the CSS module file will be an object with all class names as keys and -their unique identifiers as values: - -```tsx -import styles from "./styles.module.css"; -import otherStyles from "./other-styles.module.css"; - -console.log(styles); -console.log(otherStyles); -``` - -This will output: - -```ts -{ - button: "button_123"; -} - -{ - button: "button_456"; -} -``` - -As you can see, the class names are unique to each file, avoiding any collisions! - -### Composition - -CSS modules allow you to _compose_ class selectors together. This lets you reuse style rules across multiple classes. - -For example: - -```css -/* styles.module.css */ -.button { - composes: background; - color: red; -} - -.background { - background-color: blue; -} -``` - -Would be the same as writing: - -```css -.button { - background-color: blue; - color: red; -} - -.background { - background-color: blue; -} -``` - -{% callout %} -There are a couple rules to keep in mind when using `composes`: - -- A `composes` property must come before any regular CSS properties or declarations -- You can only use `composes` on a **simple selector with a single class name**: - -```css -#button { - /* Invalid! `#button` is not a class selector */ - composes: background; -} - -.button, -.button-secondary { - /* Invalid! `.button, .button-secondary` is not a simple selector */ - composes: background; -} -``` - -{% /callout %} - -### Composing from a separate CSS module file - -You can also compose from a separate CSS module file: - -```css -/* background.module.css */ -.background { - background-color: blue; -} - -/* styles.module.css */ -.button { - composes: background from "./background.module.css"; - color: red; -} -``` - -{% callout %} -When composing classes from separate files, be sure that they do not contain the same properties. - -The CSS module spec says that composing classes from separate files with conflicting properties is -undefined behavior, meaning that the output may differ and be unreliable. -{% /callout %} diff --git a/docs/bundler/esbuild.mdx b/docs/bundler/esbuild.mdx new file mode 100644 index 0000000000..10d6ae7591 --- /dev/null +++ b/docs/bundler/esbuild.mdx @@ -0,0 +1,297 @@ +--- +title: esbuild +description: Migration guide from esbuild to Bun's bundler +--- + +Bun's bundler API is inspired heavily by esbuild. Migrating to Bun's bundler from esbuild should be relatively painless. This guide will briefly explain why you might consider migrating to Bun's bundler and provide a side-by-side API comparison reference for those who are already familiar with esbuild's API. + +There are a few behavioral differences to note. + + + **Bundling by default.** Unlike esbuild, Bun always bundles by default. This is why the `--bundle` flag isn't + necessary in the Bun example. To transpile each file individually, use `Bun.Transpiler`. + + + + **It's just a bundler.** Unlike esbuild, Bun's bundler does not include a built-in development server or file watcher. + It's just a bundler. The bundler is intended for use in conjunction with `Bun.serve` and other runtime APIs to achieve + the same effect. As such, all options relating to HTTP/file watching are not applicable. + + +## Performance + +With a performance-minded API coupled with the extensively optimized Zig-based JS/TS parser, Bun's bundler is 1.75x faster than esbuild on esbuild's three.js benchmark. + +Bundling 10 copies of three.js from scratch, with sourcemaps and minification + +## CLI API + +Bun and esbuild both provide a command-line interface. + +```bash terminal icon="terminal" +# esbuild +esbuild --outdir=out --bundle + +# bun +bun build --outdir=out +``` + +In Bun's CLI, simple boolean flags like `--minify` do not accept an argument. Other flags like `--outdir ` do accept an argument; these flags can be written as `--outdir out` or `--outdir=out`. Some flags like `--define` can be specified several times: `--define foo=bar --define bar=baz`. + +| esbuild | bun build | Notes | +| ---------------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `--bundle` | n/a | Bun always bundles, use `--no-bundle` to disable this behavior. | +| `--define:K=V` | `--define K=V` | Small syntax difference; no colon.`esbuild --define:foo=bar``bun build --define foo=bar` | +| `--external:` | `--external ` | Small syntax difference; no colon.`esbuild --external:react``bun build --external react` | +| `--format` | `--format` | Bun supports `"esm"` and `"cjs"` currently, but more module formats are planned. esbuild defaults to `"iife"`. | +| `--loader:.ext=loader` | `--loader .ext:loader` | Bun supports a different set of built-in loaders than esbuild; see Bundler > Loaders for a complete reference. The esbuild loaders `dataurl`, `binary`, `base64`, `copy`, and `empty` are not yet implemented.The syntax for `--loader` is slightly different.`esbuild app.ts --bundle --loader:.svg=text``bun build app.ts --loader .svg:text` | +| `--minify` | `--minify` | No differences | +| `--outdir` | `--outdir` | No differences | +| `--outfile` | `--outfile` | No differences | +| `--packages` | `--packages` | No differences | +| `--platform` | `--target` | Renamed to `--target` for consistency with tsconfig. Does not support `neutral`. | +| `--serve` | n/a | Not applicable | +| `--sourcemap` | `--sourcemap` | No differences | +| `--splitting` | `--splitting` | No differences | +| `--target` | n/a | Not supported. Bun's bundler performs no syntactic down-leveling at this time. | +| `--watch` | `--watch` | No differences | +| `--allow-overwrite` | n/a | Overwriting is never allowed | +| `--analyze` | n/a | Not supported | +| `--asset-names` | `--asset-naming` | Renamed for consistency with naming in JS API | +| `--banner` | `--banner` | Only applies to js bundles | +| `--footer` | `--footer` | Only applies to js bundles | +| `--certfile` | n/a | Not applicable | +| `--charset=utf8` | n/a | Not supported | +| `--chunk-names` | `--chunk-naming` | Renamed for consistency with naming in JS API | +| `--color` | n/a | Always enabled | +| `--drop` | `--drop` | | +| `--entry-names` | `--entry-naming` | Renamed for consistency with naming in JS API | +| `--global-name` | n/a | Not applicable, Bun does not support `iife` output at this time | +| `--ignore-annotations` | `--ignore-dce-annotations` | | +| `--inject` | n/a | Not supported | +| `--jsx` | `--jsx-runtime ` | Supports `"automatic"` (uses jsx transform) and `"classic"` (uses `React.createElement`) | +| `--jsx-dev` | n/a | Bun reads `compilerOptions.jsx` from `tsconfig.json` to determine a default. If `compilerOptions.jsx` is `"react-jsx"`, or if `NODE_ENV=production`, Bun will use the jsx transform. Otherwise, it uses `jsxDEV`. The bundler does not support `preserve`. | +| `--jsx-factory` | `--jsx-factory` | | +| `--jsx-fragment` | `--jsx-fragment` | | +| `--jsx-import-source` | `--jsx-import-source` | | +| `--jsx-side-effects` | n/a | JSX is always assumed to be side-effect-free | +| `--keep-names` | n/a | Not supported | +| `--keyfile` | n/a | Not applicable | +| `--legal-comments` | n/a | Not supported | +| `--log-level` | n/a | Not supported. This can be set in `bunfig.toml` as `logLevel`. | +| `--log-limit` | n/a | Not supported | +| `--log-override:X=Y` | n/a | Not supported | +| `--main-fields` | n/a | Not supported | +| `--mangle-cache` | n/a | Not supported | +| `--mangle-props` | n/a | Not supported | +| `--mangle-quoted` | n/a | Not supported | +| `--metafile` | n/a | Not supported | +| `--minify-whitespace` | `--minify-whitespace` | | +| `--minify-identifiers` | `--minify-identifiers` | | +| `--minify-syntax` | `--minify-syntax` | | +| `--out-extension` | n/a | Not supported | +| `--outbase` | `--root` | | +| `--preserve-symlinks` | n/a | Not supported | +| `--public-path` | `--public-path` | | +| `--pure` | n/a | Not supported | +| `--reserve-props` | n/a | Not supported | +| `--resolve-extensions` | n/a | Not supported | +| `--servedir` | n/a | Not applicable | +| `--source-root` | n/a | Not supported | +| `--sourcefile` | n/a | Not supported. Bun does not support stdin input yet. | +| `--sourcemap` | `--sourcemap` | No differences | +| `--sources-content` | n/a | Not supported | +| `--supported` | n/a | Not supported | +| `--tree-shaking` | n/a | Always true | +| `--tsconfig` | `--tsconfig-override` | | +| `--version` | n/a | Run `bun --version` to see the version of Bun. | + +## JavaScript API + +| esbuild.build() | Bun.build() | Notes | +| ------------------- | -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `absWorkingDir` | n/a | Always set to `process.cwd()` | +| `alias` | n/a | Not supported | +| `allowOverwrite` | n/a | Always false | +| `assetNames` | `naming.asset` | Uses same templating syntax as esbuild, but `[ext]` must be included explicitly.`tsBun.build({ entrypoints: ["./index.tsx"], naming: { asset: "[name].[ext]", },});` | +| `banner` | n/a | Not supported | +| `bundle` | n/a | Always true. Use `Bun.Transpiler` to transpile without bundling. | +| `charset` | n/a | Not supported | +| `chunkNames` | `naming.chunk` | Uses same templating syntax as esbuild, but `[ext]` must be included explicitly.`tsBun.build({ entrypoints: ["./index.tsx"], naming: { chunk: "[name].[ext]", },});` | +| `color` | n/a | Bun returns logs in the `logs` property of the build result. | +| `conditions` | n/a | Not supported. Export conditions priority is determined by `target`. | +| `define` | `define` | | +| `drop` | n/a | Not supported | +| `entryNames` | `naming` or `naming.entry` | Bun supports a `naming` key that can either be a string or an object. Uses same templating syntax as esbuild, but `[ext]` must be included explicitly.`tsBun.build({ entrypoints: ["./index.tsx"], // when string, this is equivalent to entryNames naming: "[name].[ext]", // granular naming options naming: { entry: "[name].[ext]", asset: "[name].[ext]", chunk: "[name].[ext]", },});` | +| `entryPoints` | `entrypoints` | Capitalization difference | +| `external` | `external` | No differences | +| `footer` | n/a | Not supported | +| `format` | `format` | Only supports `"esm"` currently. Support for `"cjs"` and `"iife"` is planned. | +| `globalName` | n/a | Not supported | +| `ignoreAnnotations` | n/a | Not supported | +| `inject` | n/a | Not supported | +| `jsx` | `jsx` | Not supported in JS API, configure in `tsconfig.json` | +| `jsxDev` | `jsxDev` | Not supported in JS API, configure in `tsconfig.json` | +| `jsxFactory` | `jsxFactory` | Not supported in JS API, configure in `tsconfig.json` | +| `jsxFragment` | `jsxFragment` | Not supported in JS API, configure in `tsconfig.json` | +| `jsxImportSource` | `jsxImportSource` | Not supported in JS API, configure in `tsconfig.json` | +| `jsxSideEffects` | `jsxSideEffects` | Not supported in JS API, configure in `tsconfig.json` | +| `keepNames` | n/a | Not supported | +| `legalComments` | n/a | Not supported | +| `loader` | `loader` | Bun supports a different set of built-in loaders than esbuild; see Bundler > Loaders for a complete reference. The esbuild loaders `dataurl`, `binary`, `base64`, `copy`, and `empty` are not yet implemented. | +| `logLevel` | n/a | Not supported | +| `logLimit` | n/a | Not supported | +| `logOverride` | n/a | Not supported | +| `mainFields` | n/a | Not supported | +| `mangleCache` | n/a | Not supported | +| `mangleProps` | n/a | Not supported | +| `mangleQuoted` | n/a | Not supported | +| `metafile` | n/a | Not supported | +| `minify` | `minify` | In Bun, `minify` can be a boolean or an object.`tsawait Bun.build({ entrypoints: ['./index.tsx'], // enable all minification minify: true // granular options minify: { identifiers: true, syntax: true, whitespace: true }})` | +| `minifyIdentifiers` | `minify.identifiers` | See `minify` | +| `minifySyntax` | `minify.syntax` | See `minify` | +| `minifyWhitespace` | `minify.whitespace` | See `minify` | +| `nodePaths` | n/a | Not supported | +| `outExtension` | n/a | Not supported | +| `outbase` | `root` | Different name | +| `outdir` | `outdir` | No differences | +| `outfile` | `outfile` | No differences | +| `packages` | n/a | Not supported, use `external` | +| `platform` | `target` | Supports `"bun"`, `"node"` and `"browser"` (the default). Does not support `"neutral"`. | +| `plugins` | `plugins` | Bun's plugin API is a subset of esbuild's. Some esbuild plugins will work out of the box with Bun. | +| `preserveSymlinks` | n/a | Not supported | +| `publicPath` | `publicPath` | No differences | +| `pure` | n/a | Not supported | +| `reserveProps` | n/a | Not supported | +| `resolveExtensions` | n/a | Not supported | +| `sourceRoot` | n/a | Not supported | +| `sourcemap` | `sourcemap` | Supports `"inline"`, `"external"`, and `"none"` | +| `sourcesContent` | n/a | Not supported | +| `splitting` | `splitting` | No differences | +| `stdin` | n/a | Not supported | +| `supported` | n/a | Not supported | +| `target` | n/a | No support for syntax downleveling | +| `treeShaking` | n/a | Always true | +| `tsconfig` | n/a | Not supported | +| `write` | n/a | Set to true if `outdir`/`outfile` is set, otherwise false | + +## Plugin API + +Bun's plugin API is designed to be esbuild compatible. Bun doesn't support esbuild's entire plugin API surface, but the core functionality is implemented. Many third-party esbuild plugins will work out of the box with Bun. + + + Long term, we aim for feature parity with esbuild's API, so if something doesn't work please file an issue to help us + prioritize. + + +Plugins in Bun and esbuild are defined with a builder object. + +```ts title="myPlugin.ts" icon="/icons/typescript.svg" +import type { BunPlugin } from "bun"; + +const myPlugin: BunPlugin = { + name: "my-plugin", + setup(builder) { + // define plugin + }, +}; +``` + +The builder object provides some methods for hooking into parts of the bundling process. Bun implements `onResolve` and `onLoad`; it does not yet implement the esbuild hooks `onStart`, `onEnd`, and `onDispose`, and `resolve` utilities. `initialOptions` is partially implemented, being read-only and only having a subset of esbuild's options; use `config` (same thing but with Bun's `BuildConfig` format) instead. + +```ts title="myPlugin.ts" icon="/icons/typescript.svg" +import type { BunPlugin } from "bun"; +const myPlugin: BunPlugin = { + name: "my-plugin", + setup(builder) { + builder.onResolve( + { + /* onResolve.options */ + }, + args => { + return { + /* onResolve.results */ + }; + }, + ); + builder.onLoad( + { + /* onLoad.options */ + }, + args => { + return { + /* onLoad.results */ + }; + }, + ); + }, +}; +``` + +### onResolve + + + + + - 🟢 `filter` + - 🟢 `namespace` + + + + + - 🟢 `path` + - 🟢 `importer` + - 🔴 `namespace` + - 🔴 `resolveDir` + - 🔴 `kind` + - 🔴 `pluginData` + + + + + - 🟢 `namespace` + - 🟢 `path` + - 🔴 `errors` + - 🔴 `external` + - 🔴 `pluginData` + - 🔴 `pluginName` + - 🔴 `sideEffects` + - 🔴 `suffix` + - 🔴 `warnings` + - 🔴 `watchDirs` + - 🔴 `watchFiles` + + + + +### onLoad + + + + + - 🟢 `filter` + - 🟢 `namespace` + + + + + - 🟢 `path` + - 🔴 `namespace` + - 🔴 `suffix` + - 🔴 `pluginData` + + + + + - 🟢 `contents` + - 🟢 `loader` + - 🔴 `errors` + - 🔴 `pluginData` + - 🔴 `pluginName` + - 🔴 `resolveDir` + - 🔴 `warnings` + - 🔴 `watchDirs` + - 🔴 `watchFiles` + + + diff --git a/docs/bundler/executables.md b/docs/bundler/executables.mdx similarity index 70% rename from docs/bundler/executables.md rename to docs/bundler/executables.mdx index 56f1ac8ed7..af2229227e 100644 --- a/docs/bundler/executables.md +++ b/docs/bundler/executables.mdx @@ -1,33 +1,43 @@ +--- +title: "Single-file executable" +description: "Generate standalone executables from TypeScript or JavaScript files with Bun" +--- + Bun's bundler implements a `--compile` flag for generating a standalone binary from a TypeScript or JavaScript file. -{% codetabs %} + -```bash -$ bun build ./cli.ts --compile --outfile mycli +```bash terminal icon="terminal" +bun build ./cli.ts --compile --outfile mycli ``` -```ts#cli.ts +```ts cli.ts icon="/icons/typescript.svg" console.log("Hello world!"); ``` -{% /codetabs %} + This bundles `cli.ts` into an executable that can be executed directly: +```bash terminal icon="terminal" +./mycli ``` -$ ./mycli + +```txt Hello world! ``` All imported files and packages are bundled into the executable, along with a copy of the Bun runtime. All built-in Bun and Node.js APIs are supported. +--- + ## Cross-compile to other platforms The `--target` flag lets you compile your standalone executable for a different operating system, architecture, or version of Bun than the machine you're running `bun build` on. To build for Linux x64 (most servers): -```sh +```bash icon="terminal" terminal bun build --compile --target=bun-linux-x64 ./index.ts --outfile myapp # To support CPUs from before 2013, use the baseline version (nehalem) @@ -40,14 +50,14 @@ bun build --compile --target=bun-linux-x64-modern ./index.ts --outfile myapp To build for Linux ARM64 (e.g. Graviton or Raspberry Pi): -```sh +```bash icon="terminal" terminal # Note: the default architecture is x64 if no architecture is specified. bun build --compile --target=bun-linux-arm64 ./index.ts --outfile myapp ``` To build for Windows x64: -```sh +```bash icon="terminal" terminal bun build --compile --target=bun-windows-x64 ./path/to/my/app.ts --outfile myapp # To support CPUs from before 2013, use the baseline version (nehalem) @@ -61,17 +71,17 @@ bun build --compile --target=bun-windows-x64-modern ./path/to/my/app.ts --outfil To build for macOS arm64: -```sh +```bash icon="terminal" terminal bun build --compile --target=bun-darwin-arm64 ./path/to/my/app.ts --outfile myapp ``` To build for macOS x64: -```sh +```bash icon="terminal" terminal bun build --compile --target=bun-darwin-x64 ./path/to/my/app.ts --outfile myapp ``` -#### Supported targets +### Supported targets The order of the `--target` flag does not matter, as long as they're delimited by a `-`. @@ -86,21 +96,32 @@ The order of the `--target` flag does not matter, as long as they're delimited b | bun-linux-x64-musl | Linux | x64 | ✅ | ✅ | musl | | bun-linux-arm64-musl | Linux | arm64 | ✅ | N/A | musl | -On x64 platforms, Bun uses SIMD optimizations which require a modern CPU supporting AVX2 instructions. The `-baseline` build of Bun is for older CPUs that don't support these optimizations. Normally, when you install Bun we automatically detect which version to use but this can be harder to do when cross-compiling since you might not know the target CPU. You usually don't need to worry about it on Darwin x64, but it is relevant for Windows x64 and Linux x64. If you or your users see `"Illegal instruction"` errors, you might need to use the baseline version. + + On x64 platforms, Bun uses SIMD optimizations which require a modern CPU supporting AVX2 instructions. The `-baseline` + build of Bun is for older CPUs that don't support these optimizations. Normally, when you install Bun we automatically + detect which version to use but this can be harder to do when cross-compiling since you might not know the target CPU. + You usually don't need to worry about it on Darwin x64, but it is relevant for Windows x64 and Linux x64. If you or + your users see `"Illegal instruction"` errors, you might need to use the baseline version. + + +--- ## Build-time constants Use the `--define` flag to inject build-time constants into your executable, such as version numbers, build timestamps, or configuration values: -```bash -$ bun build --compile --define BUILD_VERSION='"1.2.3"' --define BUILD_TIME='"2024-01-15T10:30:00Z"' src/cli.ts --outfile mycli +```bash icon="terminal" terminal +bun build --compile --define BUILD_VERSION='"1.2.3"' --define BUILD_TIME='"2024-01-15T10:30:00Z"' src/cli.ts --outfile mycli ``` These constants are embedded directly into your compiled binary at build time, providing zero runtime overhead and enabling dead code elimination optimizations. -{% callout type="info" %} -For comprehensive examples and advanced patterns, see the [Build-time constants guide](/guides/runtime/build-time-constants). -{% /callout %} + + For comprehensive examples and advanced patterns, see the [Build-time constants + guide](https://bun.com/guides/runtime/build-time-constants). + + +--- ## Deploying to production @@ -112,7 +133,7 @@ With compiled executables, you can move that cost from runtime to build-time. When deploying to production, we recommend the following: -```sh +```bash icon="terminal" terminal bun build --compile --minify --sourcemap ./path/to/my/app.ts --outfile myapp ``` @@ -120,17 +141,22 @@ bun build --compile --minify --sourcemap ./path/to/my/app.ts --outfile myapp To improve startup time, enable bytecode compilation: -```sh +```bash icon="terminal" terminal bun build --compile --minify --sourcemap --bytecode ./path/to/my/app.ts --outfile myapp ``` Using bytecode compilation, `tsc` starts 2x faster: -{% image src="https://github.com/user-attachments/assets/dc8913db-01d2-48f8-a8ef-ac4e984f9763" width="689" /%} + +  + 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. -**Experimental:** Bytecode compilation is an experimental feature introduced in Bun v1.1.30. Only `cjs` format is supported (which means no top-level-await). Let us know if you run into any issues! + + **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! + ### What do these flags do? @@ -140,68 +166,77 @@ The `--sourcemap` argument embeds a sourcemap compressed with zstd, so that erro The `--bytecode` argument enables bytecode compilation. Every time you run JavaScript code in Bun, JavaScriptCore (the engine) will compile your source code into bytecode. We can move this parsing work from runtime to bundle time, saving you startup time. +--- + ## Embedding runtime arguments **`--compile-exec-argv="args"`** - Embed runtime arguments that are available via `process.execArgv`: -```bash +```bash icon="terminal" terminal bun build --compile --compile-exec-argv="--smol --user-agent=MyBot" ./app.ts --outfile myapp ``` -```js +```ts app.ts icon="/icons/typescript.svg" // In the compiled app console.log(process.execArgv); // ["--smol", "--user-agent=MyBot"] ``` +--- + ## Act as the Bun CLI -{% note %} - -New in Bun v1.2.16 - -{% /note %} +New in Bun v1.2.16 You can run a standalone executable as if it were the `bun` CLI itself by setting the `BUN_BE_BUN=1` environment variable. When this variable is set, the executable will ignore its bundled entrypoint and instead expose all the features of Bun's CLI. For example, consider an executable compiled from a simple script: -```sh -$ cat such-bun.js -console.log("you shouldn't see this"); +```bash icon="terminal" terminal +echo "console.log(\"you shouldn't see this\");" > such-bun.js +bun build --compile ./such-bun.js +``` -$ bun build --compile ./such-bun.js - [3ms] bundle 1 modules +```txt +[3ms] bundle 1 modules [89ms] compile such-bun ``` -Normally, running `./such-bun` with arguments would execute the script. However, with the `BUN_BE_BUN=1` environment variable, it acts just like the `bun` binary: +Normally, running `./such-bun` with arguments would execute the script. -```sh +```bash icon="terminal" terminal # Executable runs its own entrypoint by default -$ ./such-bun install -you shouldn't see this +./such-bun install +``` +```txt +you shouldn't see this +``` + +However, with the `BUN_BE_BUN=1` environment variable, it acts just like the `bun` binary: + +```bash icon="terminal" terminal # With the env var, the executable acts like the `bun` CLI -$ BUN_BE_BUN=1 ./such-bun install +bun_BE_BUN=1 ./such-bun install +``` + +```txt bun install v1.2.16-canary.1 (1d1db811) Checked 63 installs across 64 packages (no changes) [5.00ms] ``` This is useful for building CLI tools on top of Bun that may need to install packages, bundle dependencies, run different or local files and more without needing to download a separate binary or install bun. +--- + ## Full-stack executables -{% note %} - -New in Bun v1.2.17 - -{% /note %} +New in Bun v1.2.17 Bun's `--compile` flag can create standalone executables that contain both server and client code, making it ideal for full-stack applications. When you import an HTML file in your server code, Bun automatically bundles all frontend assets (JavaScript, CSS, etc.) and embeds them into the executable. When Bun sees the HTML import on the server, it kicks off a frontend build process to bundle JavaScript, CSS, and other assets. -{% codetabs %} + -```ts#server.ts +```ts server.ts icon="/icons/typescript.svg" import { serve } from "bun"; import index from "./index.html"; @@ -215,12 +250,12 @@ const server = serve({ console.log(`Server running at http://localhost:${server.port}`); ``` -```html#index.html +```html index.html icon="file-code" My App - + Hello World @@ -229,21 +264,21 @@ console.log(`Server running at http://localhost:${server.port}`); ``` -```js#app.js +```ts app.js icon="file-code" console.log("Hello from the client!"); ``` -```css#styles.css +```css styles.css icon="file-code" body { background-color: #f0f0f0; } ``` -{% /codetabs %} + To build this into a single executable: -```sh +```bash terminal icon="terminal" bun build --compile ./server.ts --outfile myapp ``` @@ -256,25 +291,27 @@ This creates a self-contained binary that includes: The result is a single file that can be deployed anywhere without needing Node.js, Bun, or any dependencies installed. Just run: -```sh +```bash terminal icon="terminal" ./myapp ``` Bun automatically handles serving the frontend assets with proper MIME types and cache headers. The HTML import is replaced with a manifest object that `Bun.serve` uses to efficiently serve pre-bundled assets. -For more details on building full-stack applications with Bun, see the [full-stack guide](/docs/bundler/fullstack). +For more details on building full-stack applications with Bun, see the [full-stack guide](/bundler/fullstack). + +--- ## Worker To use workers in a standalone executable, add the worker's entrypoint to the CLI arguments: -```sh -$ bun build --compile ./index.ts ./my-worker.ts --outfile myapp +```bash terminal icon="terminal" +bun build --compile ./index.ts ./my-worker.ts --outfile myapp ``` Then, reference the worker in your code: -```ts +```ts index.ts icon="/icons/typescript.svg" console.log("Hello from Bun!"); // Any of these will work: @@ -283,19 +320,21 @@ new Worker(new URL("./my-worker.ts", import.meta.url)); new Worker(new URL("./my-worker.ts", import.meta.url).href); ``` -As of Bun v1.1.25, when you add multiple entrypoints to a standalone executable, they will be bundled separately into the executable. +When you add multiple entrypoints to a standalone executable, they will be bundled separately into the executable. In the future, we may automatically detect usages of statically-known paths in `new Worker(path)` and then bundle those into the executable, but for now, you'll need to add it to the shell command manually like the above example. If you use a relative path to a file not included in the standalone executable, it will attempt to load that path from disk relative to the current working directory of the process (and then error if it doesn't exist). +--- + ## SQLite You can use `bun:sqlite` imports with `bun build --compile`. By default, the database is resolved relative to the current working directory of the process. -```js +```ts index.ts icon="/icons/typescript.svg" import db from "./my.db" with { type: "sqlite" }; console.log(db.query("select * from users LIMIT 1").get()); @@ -303,10 +342,12 @@ console.log(db.query("select * from users LIMIT 1").get()); That means if the executable is located at `/usr/bin/hello`, the user's terminal is located at `/home/me/Desktop`, it will look for `/home/me/Desktop/my.db`. +```bash terminal icon="terminal" +cd /home/me/Desktop +./hello ``` -$ cd /home/me/Desktop -$ ./hello -``` + +--- ## Embed assets & files @@ -314,7 +355,7 @@ Standalone executables support embedding files. To embed files into an executable with `bun build --compile`, import the file in your code. -```ts +```ts index.ts icon="/icons/typescript.svg" // this becomes an internal file path import icon from "./icon.png" with { type: "file" }; import { file } from "bun"; @@ -331,7 +372,7 @@ Embedded files can be read using `Bun.file`'s functions or the Node.js `fs.readF For example, to read the contents of the embedded file: -```js +```ts index.ts icon="/icons/typescript.svg" import icon from "./icon.png" with { type: "file" }; import { file } from "bun"; @@ -344,7 +385,7 @@ const bytes = await file(icon).arrayBuffer(); If your application wants to embed a SQLite database, set `type: "sqlite"` in the import attribute and the `embed` attribute to `"true"`. -```js +```ts index.ts icon="/icons/typescript.svg" import myEmbeddedDb from "./my.db" with { type: "sqlite", embed: "true" }; console.log(myEmbeddedDb.query("select * from users LIMIT 1").get()); @@ -354,9 +395,9 @@ This database is read-write, but all changes are lost when the executable exits ### Embed N-API Addons -As of Bun v1.0.23, you can embed `.node` files into executables. +You can embed `.node` files into executables. -```js +```ts index.ts icon="/icons/typescript.svg" const addon = require("./addon.node"); console.log(addon.hello()); @@ -368,13 +409,13 @@ Unfortunately, if you're using `@mapbox/node-pre-gyp` or other similar tools, yo To embed a directory with `bun build --compile`, use a shell glob in your `bun build` command: -```sh -$ bun build --compile ./index.ts ./public/**/*.png +```bash terminal icon="terminal" +bun build --compile ./index.ts ./public/**/*.png ``` Then, you can reference the files in your code: -```ts +```ts index.ts icon="/icons/typescript.svg" import icon from "./public/assets/icon.png" with { type: "file" }; import { file } from "bun"; @@ -392,7 +433,7 @@ This is honestly a workaround, and we expect to improve this in the future with To get a list of all embedded files, use `Bun.embeddedFiles`: -```js +```ts index.ts icon="/icons/typescript.svg" import "./icon.png" with { type: "file" }; import { embeddedFiles } from "bun"; @@ -413,141 +454,40 @@ By default, embedded files have a content hash appended to their name. This is u To disable the content hash, pass `--asset-naming` to `bun build --compile` like this: -```sh -$ bun build --compile --asset-naming="[name].[ext]" ./index.ts +```bash terminal icon="terminal" +bun build --compile --asset-naming="[name].[ext]" ./index.ts ``` +--- + ## Minification To trim down the size of the executable a little, pass `--minify` to `bun build --compile`. This uses Bun's minifier to reduce the code size. Overall though, Bun's binary is still way too big and we need to make it smaller. -## Using Bun.build() API - -You can also generate standalone executables using the `Bun.build()` JavaScript API. This is useful when you need programmatic control over the build process. - -### Basic usage - -```js -await Bun.build({ - entrypoints: ["./app.ts"], - outdir: "./dist", - compile: { - target: "bun-windows-x64", - outfile: "myapp.exe", - }, -}); -``` - -### Windows metadata with Bun.build() - -When targeting Windows, you can specify metadata through the `windows` object: - -```js -await Bun.build({ - entrypoints: ["./app.ts"], - outdir: "./dist", - compile: { - target: "bun-windows-x64", - outfile: "myapp.exe", - windows: { - title: "My Application", - publisher: "My Company Inc", - version: "1.2.3.4", - description: "A powerful application built with Bun", - copyright: "© 2024 My Company Inc", - hideConsole: false, // Set to true for GUI applications - icon: "./icon.ico", // Path to icon file - }, - }, -}); -``` - -### Cross-compilation with Bun.build() - -You can cross-compile for different platforms: - -```js -// Build for multiple platforms -const platforms = [ - { target: "bun-windows-x64", outfile: "app-windows.exe" }, - { target: "bun-linux-x64", outfile: "app-linux" }, - { target: "bun-darwin-arm64", outfile: "app-macos" }, -]; - -for (const platform of platforms) { - await Bun.build({ - entrypoints: ["./app.ts"], - outdir: "./dist", - compile: platform, - }); -} -``` +--- ## Windows-specific flags -When compiling a standalone executable for Windows, there are several platform-specific options that can be used to customize the generated `.exe` file: +When compiling a standalone executable on Windows, there are two platform-specific options that can be used to customize metadata on the generated `.exe` file: -### Visual customization +- `--windows-icon=path/to/icon.ico` to customize the executable file icon. +- `--windows-hide-console` to disable the background terminal, which can be used for applications that do not need a TTY. -- `--windows-icon=path/to/icon.ico` - Set the executable file icon -- `--windows-hide-console` - Disable the background terminal window (useful for GUI applications) +These flags currently cannot be used when cross-compiling because they depend on Windows APIs. -### Metadata customization - -You can embed version information and other metadata into your Windows executable: - -- `--windows-title ` - Set the product name (appears in file properties) -- `--windows-publisher ` - Set the company name -- `--windows-version ` - Set the version number (e.g. "1.2.3.4") -- `--windows-description ` - Set the file description -- `--windows-copyright ` - Set the copyright information - -#### Example with all metadata flags: - -```sh -bun build --compile ./app.ts \ - --outfile myapp.exe \ - --windows-title "My Application" \ - --windows-publisher "My Company Inc" \ - --windows-version "1.2.3.4" \ - --windows-description "A powerful application built with Bun" \ - --windows-copyright "© 2024 My Company Inc" -``` - -This metadata will be visible in Windows Explorer when viewing the file properties: - -1. Right-click the executable in Windows Explorer -2. Select "Properties" -3. Go to the "Details" tab - -#### Version string format - -The `--windows-version` flag accepts version strings in the following formats: - -- `"1"` - Will be normalized to "1.0.0.0" -- `"1.2"` - Will be normalized to "1.2.0.0" -- `"1.2.3"` - Will be normalized to "1.2.3.0" -- `"1.2.3.4"` - Full version format - -Each version component must be a number between 0 and 65535. - -{% callout %} - -These flags currently cannot be used when cross-compiling because they depend on Windows APIs. They are only available when building on Windows itself. - -{% /callout %} +--- ## Code signing on macOS To codesign a standalone executable on macOS (which fixes Gatekeeper warnings), use the `codesign` command. -```sh -$ codesign --deep --force -vvvv --sign "XXXXXXXXXX" ./myapp +```bash terminal icon="terminal" +codesign --deep --force -vvvv --sign "XXXXXXXXXX" ./myapp ``` We recommend including an `entitlements.plist` file with JIT permissions. -```xml#entitlements.plist +```xml icon="xml" title="info.plist" @@ -568,23 +508,21 @@ We recommend including an `entitlements.plist` file with JIT permissions. To codesign with JIT support, pass the `--entitlements` flag to `codesign`. -```sh -$ codesign --deep --force -vvvv --sign "XXXXXXXXXX" --entitlements entitlements.plist ./myapp +```bash terminal icon="terminal" +codesign --deep --force -vvvv --sign "XXXXXXXXXX" --entitlements entitlements.plist ./myapp ``` After codesigning, verify the executable: -```sh -$ codesign -vvv --verify ./myapp +```bash terminal icon="terminal" +codesign -vvv --verify ./myapp ./myapp: valid on disk ./myapp: satisfies its Designated Requirement ``` -{% callout %} +Codesign support requires Bun v1.2.4 or newer. -Codesign support requires Bun v1.2.4 or newer. - -{% /callout %} +--- ## Unsupported CLI arguments diff --git a/docs/bundler/fullstack.md b/docs/bundler/fullstack.md deleted file mode 100644 index 16ed1d8402..0000000000 --- a/docs/bundler/fullstack.md +++ /dev/null @@ -1,418 +0,0 @@ -To get started, import HTML files and pass them to the `routes` option in `Bun.serve()`. - -```ts -import { sql, serve } from "bun"; -import dashboard from "./dashboard.html"; -import homepage from "./index.html"; - -const server = serve({ - routes: { - // ** HTML imports ** - // Bundle & route index.html to "/". This uses HTMLRewriter to scan the HTML for ` - - - -``` - -Becomes something like this: - -```html#index.html - - - - Home - - - - - - - -``` - -### How to use with React - -To use React in your client-side code, import `react-dom/client` and render your app. - -{% codetabs %} - -```ts#src/backend.ts -import dashboard from "../public/dashboard.html"; -import { serve } from "bun"; - -serve({ - routes: { - "/": dashboard, - }, - - async fetch(req) { - // ...api requests - return new Response("hello world"); - }, -}); -``` - -```ts#src/frontend.tsx -import "./styles.css"; -import { createRoot } from "react-dom/client"; -import { App } from "./app.tsx"; - -document.addEventListener("DOMContentLoaded", () => { - const root = createRoot(document.getElementById("root")); - root.render(); -}); -``` - -```html#public/dashboard.html - - - - Dashboard - - - - - - -``` - -```css#src/styles.css -body { - background-color: red; -} -``` - -```tsx#src/app.tsx -export function App() { - return Hello World; -} -``` - -{% /codetabs %} - -### Development mode - -When building locally, enable development mode by setting `development: true` in `Bun.serve()`. - -```js-diff -import homepage from "./index.html"; -import dashboard from "./dashboard.html"; - -Bun.serve({ - routes: { - "/": homepage, - "/dashboard": dashboard, - } - -+ development: true, - - fetch(req) { - // ... api requests - }, -}); -``` - -When `development` is `true`, Bun will: - -- Include the `SourceMap` header in the response so that devtools can show the original source code -- Disable minification -- Re-bundle assets on each request to a .html file -- Enable hot module reloading (unless `hmr: false` is set) - -#### Echo console logs from browser to terminal - -Bun.serve() supports echoing console logs from the browser to the terminal. - -To enable this, pass `console: true` in the `development` object in `Bun.serve()`. - -```ts -import homepage from "./index.html"; - -Bun.serve({ - // development can also be an object. - development: { - // Enable Hot Module Reloading - hmr: true, - - // Echo console logs from the browser to the terminal - console: true, - }, - - routes: { - "/": homepage, - }, -}); -``` - -When `console: true` is set, Bun will stream console logs from the browser to the terminal. This reuses the existing WebSocket connection from HMR to send the logs. - -#### Production mode - -Hot reloading and `development: true` helps you iterate quickly, but in production, your server should be as fast as possible and have as few external dependencies as possible. - -##### Ahead of time bundling (recommended) - -As of Bun v1.2.17, you can use `Bun.build` or `bun build` to bundle your full-stack application ahead of time. - -```sh -$ bun build --target=bun --production --outdir=dist ./src/index.ts -``` - -When Bun's bundler sees an HTML import from server-side code, it will bundle the referenced JavaScript/TypeScript/TSX/JSX and CSS files into a manifest object that Bun.serve() can use to serve the assets. - -```ts -import { serve } from "bun"; -import index from "./index.html"; - -serve({ - routes: { "/": index }, -}); -``` - -{% details summary="Internally, the `index` variable is a manifest object that looks something like this" %} - -```json -{ - "index": "./index.html", - "files": [ - { - "input": "index.html", - "path": "./index-f2me3qnf.js", - "loader": "js", - "isEntry": true, - "headers": { - "etag": "eet6gn75", - "content-type": "text/javascript;charset=utf-8" - } - }, - { - "input": "index.html", - "path": "./index.html", - "loader": "html", - "isEntry": true, - "headers": { - "etag": "r9njjakd", - "content-type": "text/html;charset=utf-8" - } - }, - { - "input": "index.html", - "path": "./index-gysa5fmk.css", - "loader": "css", - "isEntry": true, - "headers": { - "etag": "50zb7x61", - "content-type": "text/css;charset=utf-8" - } - }, - { - "input": "logo.svg", - "path": "./logo-kygw735p.svg", - "loader": "file", - "isEntry": false, - "headers": { - "etag": "kygw735p", - "content-type": "application/octet-stream" - } - }, - { - "input": "react.svg", - "path": "./react-ck11dneg.svg", - "loader": "file", - "isEntry": false, - "headers": { - "etag": "ck11dneg", - "content-type": "application/octet-stream" - } - } - ] -} -``` - -{% /details %} - -##### Runtime bundling - -When adding a build step is too complicated, you can set `development: false` in `Bun.serve()`. - -- Enable in-memory caching of bundled assets. Bun will bundle assets lazily on the first request to an `.html` file, and cache the result in memory until the server restarts. -- Enables `Cache-Control` headers and `ETag` headers -- Minifies JavaScript/TypeScript/TSX/JSX files - -## Plugins - -Bun's [bundler plugins](https://bun.com/docs/bundler/plugins) are also supported when bundling static routes. - -To configure plugins for `Bun.serve`, add a `plugins` array in the `[serve.static]` section of your `bunfig.toml`. - -### Using TailwindCSS in HTML routes - -For example, enable TailwindCSS on your routes by installing and adding the `bun-plugin-tailwind` plugin: - -```sh -$ bun add bun-plugin-tailwind -``` - -```toml#bunfig.toml -[serve.static] -plugins = ["bun-plugin-tailwind"] -``` - -This will allow you to use TailwindCSS utility classes in your HTML and CSS files. All you need to do is import `tailwindcss` somewhere: - -```html#index.html - - - - Home - - - - - - -``` - -Or in your CSS: - -```css#style.css -@import "tailwindcss"; -``` - -### Custom plugins - -Any JS file or module which exports a [valid bundler plugin object](https://bun.com/docs/bundler/plugins#usage) (essentially an object with a `name` and `setup` field) can be placed inside the `plugins` array: - -```toml#bunfig.toml -[serve.static] -plugins = ["./my-plugin-implementation.ts"] -``` - -Bun will lazily resolve and load each plugin and use them to bundle your routes. - -Note: this is currently in `bunfig.toml` to make it possible to know statically which plugins are in use when we eventually integrate this with the `bun build` CLI. These plugins work in `Bun.build()`'s JS API, but are not yet supported in the CLI. - -## How this works - -Bun uses [`HTMLRewriter`](/docs/api/html-rewriter) to scan for ` - ``` - -2. **`` processing** - - Processes CSS imports and `` tags - - Concatenates CSS files - - Rewrites `url` and asset paths to include content-addressable hashes in URLs - - ```html - - ``` - -3. **`` & asset processing** - - Links to assets are rewritten to include content-addressable hashes in URLs - - Small assets in CSS files are inlined into `data:` URLs, reducing the total number of HTTP requests sent over the wire - -4. **Rewrite HTML** - - Combines all ` + + + +``` + +Becomes something like this: + +```html title="index.html" icon="file-code" + + + + Home + + + + + + + +``` + +## React Integration + +To use React in your client-side code, import `react-dom/client` and render your app. + + +```ts title="src/backend.ts" icon="/icons/typescript.svg" +import dashboard from "../public/dashboard.html"; +import { serve } from "bun"; + +serve({ +routes: { +"/": dashboard, +}, +async fetch(req) { +// ...api requests +return new Response("hello world"); +}, +}); + +```` + +```tsx title="src/frontend.tsx" icon="/icons/typescript.svg" +import { createRoot } from 'react-dom/client'; +import App from './app'; + +const container = document.getElementById('root'); +const root = createRoot(container!); +root.render(); +```` + +```html title="public/dashboard.html" icon="file-code" + + + + Dashboard + + + + + + + +``` + +```tsx title="src/app.tsx" icon="/icons/typescript.svg" +import { useState } from "react"; + +export default function App() { + const [count, setCount] = useState(0); + + return ( + + Dashboard + setCount(count + 1)}>Count: {count} + + ); +} +``` + + + +## Development Mode + +When building locally, enable development mode by setting `development: true` in `Bun.serve()`. + +```ts title="src/backend.ts" icon="/icons/typescript.svg" +import homepage from "./index.html"; +import dashboard from "./dashboard.html"; + +Bun.serve({ + routes: { + "/": homepage, + "/dashboard": dashboard, + }, + + development: true, + + fetch(req) { + // ... api requests + }, +}); +``` + +### Development Mode Features + +When `development` is `true`, Bun will: + +- Include the SourceMap header in the response so that devtools can show the original source code +- Disable minification +- Re-bundle assets on each request to a `.html` file +- Enable hot module reloading (unless `hmr: false` is set) +- Echo console logs from browser to terminal + +### Advanced Development Configuration + +`Bun.serve()` supports echoing console logs from the browser to the terminal. + +To enable this, pass `console: true` in the development object in `Bun.serve()`. + +```ts title="src/backend.ts" icon="/icons/typescript.svg" +import homepage from "./index.html"; + +Bun.serve({ + // development can also be an object. + development: { + // Enable Hot Module Reloading + hmr: true, + + // Echo console logs from the browser to the terminal + console: true, + }, + + routes: { + "/": homepage, + }, +}); +``` + +When `console: true` is set, Bun will stream console logs from the browser to the terminal. This reuses the existing WebSocket connection from HMR to send the logs. + +### Development vs Production + +| Feature | Development | Production | +| ------------------- | --------------------- | ----------- | +| **Source maps** | ✅ Enabled | ❌ Disabled | +| **Minification** | ❌ Disabled | ✅ Enabled | +| **Hot reloading** | ✅ Enabled | ❌ Disabled | +| **Asset bundling** | 🔄 On each request | 💾 Cached | +| **Console logging** | 🖥️ Browser → Terminal | ❌ Disabled | +| **Error details** | 📝 Detailed | 🔒 Minimal | + +## Production Mode + +Hot reloading and `development: true` helps you iterate quickly, but in production, your server should be as fast as possible and have as few external dependencies as possible. + +### Ahead of Time Bundling (Recommended) + +As of Bun v1.2.17, you can use `Bun.build` or `bun build` to bundle your full-stack application ahead of time. + +```bash terminal icon="terminal" +bun build --target=bun --production --outdir=dist ./src/index.ts +``` + +When Bun's bundler sees an HTML import from server-side code, it will bundle the referenced JavaScript/TypeScript/TSX/JSX and CSS files into a manifest object that `Bun.serve()` can use to serve the assets. + +```ts title="src/backend.ts" icon="/icons/typescript.svg" +import { serve } from "bun"; +import index from "./index.html"; + +serve({ + routes: { "/": index }, +}); +``` + +### Runtime Bundling + +When adding a build step is too complicated, you can set `development: false` in `Bun.serve()`. + +This will: + +- Enable in-memory caching of bundled assets. Bun will bundle assets lazily on the first request to an `.html` file, and cache the result in memory until the server restarts. +- Enable `Cache-Control` headers and `ETag` headers +- Minify JavaScript/TypeScript/TSX/JSX files + +```ts title="src/backend.ts" icon="/icons/typescript.svg" +import { serve } from "bun"; +import homepage from "./index.html"; + +serve({ + routes: { + "/": homepage, + }, + + // Production mode + development: false, +}); +``` + +## API Routes + +### HTTP Method Handlers + +Define API endpoints with HTTP method handlers: + +```ts title="src/backend.ts" icon="/icons/typescript.svg" +import { serve } from "bun"; + +serve({ + routes: { + "/api/users": { + async GET(req) { + // Handle GET requests + const users = await getUsers(); + return Response.json(users); + }, + + async POST(req) { + // Handle POST requests + const userData = await req.json(); + const user = await createUser(userData); + return Response.json(user, { status: 201 }); + }, + + async PUT(req) { + // Handle PUT requests + const userData = await req.json(); + const user = await updateUser(userData); + return Response.json(user); + }, + + async DELETE(req) { + // Handle DELETE requests + await deleteUser(req.params.id); + return new Response(null, { status: 204 }); + }, + }, + }, +}); +``` + +### Dynamic Routes + +Use URL parameters in your routes: + +```ts title="src/backend.ts" icon="/icons/typescript.svg" +serve({ + routes: { + // Single parameter + "/api/users/:id": async req => { + const { id } = req.params; + const user = await getUserById(id); + return Response.json(user); + }, + + // Multiple parameters + "/api/users/:userId/posts/:postId": async req => { + const { userId, postId } = req.params; + const post = await getPostByUser(userId, postId); + return Response.json(post); + }, + + // Wildcard routes + "/api/files/*": async req => { + const filePath = req.params["*"]; + const file = await getFile(filePath); + return new Response(file); + }, + }, +}); +``` + +### Request Handling + +```ts title="src/backend.ts" icon="/icons/typescript.svg" +serve({ + routes: { + "/api/data": { + async POST(req) { + // Parse JSON body + const body = await req.json(); + + // Access headers + const auth = req.headers.get("Authorization"); + + // Access URL parameters + const { id } = req.params; + + // Access query parameters + const url = new URL(req.url); + const page = url.searchParams.get("page") || "1"; + + // Return response + return Response.json({ + message: "Data processed", + page: parseInt(page), + authenticated: !!auth, + }); + }, + }, + }, +}); +``` + +## Plugins + +Bun's bundler plugins are also supported when bundling static routes. + +To configure plugins for `Bun.serve`, add a `plugins` array in the `[serve.static]` section of your `bunfig.toml`. + +### TailwindCSS Plugin + +You can use TailwindCSS by installing and adding the `tailwindcss` package and `bun-plugin-tailwind` plugin. + +```bash terminal icon="terminal" +bun add tailwindcss bun-plugin-tailwind +``` + +```toml title="bunfig.toml" icon="settings" +[serve.static] +plugins = ["bun-plugin-tailwind"] +``` + +This will allow you to use TailwindCSS utility classes in your HTML and CSS files. All you need to do is import `tailwindcss` somewhere: + +```html title="index.html" icon="file-code" + + + + + + + + +``` + +Alternatively, you can import TailwindCSS in your CSS file: + +```css title="style.css" icon="file-code" +@import "tailwindcss"; + +.custom-class { + @apply bg-red-500 text-white; +} +``` + +```html index.html icon="file-code" + + + + + + + + +``` + +### Custom Plugins + +Any JS file or module which exports a valid bundler plugin object (essentially an object with a `name` and `setup` field) can be placed inside the plugins array: + +```toml title="bunfig.toml" icon="settings" +[serve.static] +plugins = ["./my-plugin-implementation.ts"] +``` + +```ts title="my-plugin-implementation.ts" icon="/icons/typescript.svg" +import type { BunPlugin } from "bun"; + +const myPlugin: BunPlugin = { + name: "my-custom-plugin", + setup(build) { + // Plugin implementation + build.onLoad({ filter: /\.custom$/ }, async args => { + const text = await Bun.file(args.path).text(); + return { + contents: `export default ${JSON.stringify(text)};`, + loader: "js", + }; + }); + }, +}; + +export default myPlugin; +``` + +Bun will lazily resolve and load each plugin and use them to bundle your routes. + + + This is currently in `bunfig.toml` to make it possible to know statically which plugins are in use when we eventually + integrate this with the `bun build` CLI. These plugins work in `Bun.build()`'s JS API, but are not yet supported in + the CLI. + + +## How It Works + +Bun uses `HTMLRewriter` to scan for ` +``` + + + +- Processes CSS imports and `` tags +- Concatenates CSS files +- Rewrites url and asset paths to include content-addressable hashes in URLs + +```html title="index.html" icon="file-code" + +``` + + + +- Links to assets are rewritten to include content-addressable hashes in URLs +- Small assets in CSS files are inlined into `data:` URLs, reducing the total number of HTTP requests sent over the wire + + +- Combines all ` +
This is an example.
+ + + +
${error}\n${error.stack}
The date is {dt.format(new Date())}