diff --git a/.buildkite/ci.mjs b/.buildkite/ci.mjs index 9f39a72a3d0948..3fb1e643f32f31 100755 --- a/.buildkite/ci.mjs +++ b/.buildkite/ci.mjs @@ -543,9 +543,8 @@ function getTestBunStep(platform, options, testOptions = {}) { label: `${getPlatformLabel(platform)} - test-bun`, depends_on: depends, agents: getTestAgent(platform, options), - cancel_on_build_failing: isMergeQueue(), retry: getRetry(), - soft_fail: isMainBranch() ? true : [{ exit_status: 2 }], + cancel_on_build_failing: isMergeQueue(), parallelism: unifiedTests ? undefined : os === "darwin" ? 2 : 10, command: os === "windows" @@ -590,6 +589,7 @@ function getBuildImageStep(platform, options) { DEBUG: "1", }, retry: getRetry(), + cancel_on_build_failing: isMergeQueue(), command: command.filter(Boolean).join(" "), timeout_in_minutes: 3 * 60, }; diff --git a/.vscode/launch.json b/.vscode/launch.json index 02a747cde74589..817b7533d3e3a2 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -22,7 +22,7 @@ }, "console": "internalConsole", // Don't pause when the GC runs while the debugger is open. - "postRunCommands": ["process handle -p true -s false -n false SIGUSR1"], + "postRunCommands": ["command source '${workspaceFolder}/misctools/lldb/lldb_commands'"], }, { "type": "lldb", @@ -38,7 +38,7 @@ }, "console": "internalConsole", // Don't pause when the GC runs while the debugger is open. - "postRunCommands": ["process handle -p true -s false -n false SIGUSR1"], + "postRunCommands": ["command source '${workspaceFolder}/misctools/lldb/lldb_commands'"], }, { "type": "lldb", @@ -60,7 +60,7 @@ }, "console": "internalConsole", // Don't pause when the GC runs while the debugger is open. - "postRunCommands": ["process handle -p true -s false -n false SIGUSR1"], + "postRunCommands": ["command source '${workspaceFolder}/misctools/lldb/lldb_commands'"], }, { "type": "lldb", @@ -76,7 +76,7 @@ }, "console": "internalConsole", // Don't pause when the GC runs while the debugger is open. - "postRunCommands": ["process handle -p true -s false -n false SIGUSR1"], + "postRunCommands": ["command source '${workspaceFolder}/misctools/lldb/lldb_commands'"], }, { "type": "lldb", @@ -92,7 +92,7 @@ }, "console": "internalConsole", // Don't pause when the GC runs while the debugger is open. - "postRunCommands": ["process handle -p true -s false -n false SIGUSR1"], + "postRunCommands": ["command source '${workspaceFolder}/misctools/lldb/lldb_commands'"], }, { "type": "lldb", @@ -108,7 +108,7 @@ }, "console": "internalConsole", // Don't pause when the GC runs while the debugger is open. - "postRunCommands": ["process handle -p true -s false -n false SIGUSR1"], + "postRunCommands": ["command source '${workspaceFolder}/misctools/lldb/lldb_commands'"], }, { "type": "lldb", @@ -125,7 +125,7 @@ }, "console": "internalConsole", // Don't pause when the GC runs while the debugger is open. - "postRunCommands": ["process handle -p true -s false -n false SIGUSR1"], + "postRunCommands": ["command source '${workspaceFolder}/misctools/lldb/lldb_commands'"], "serverReadyAction": { "pattern": "https://debug.bun.sh/#localhost:([0-9]+)/", "uriFormat": "https://debug.bun.sh/#ws://localhost:%s/", @@ -147,7 +147,7 @@ }, "console": "internalConsole", // Don't pause when the GC runs while the debugger is open. - "postRunCommands": ["process handle -p true -s false -n false SIGUSR1"], + "postRunCommands": ["command source '${workspaceFolder}/misctools/lldb/lldb_commands'"], "serverReadyAction": { "pattern": "https://debug.bun.sh/#localhost:([0-9]+)/", "uriFormat": "https://debug.bun.sh/#ws://localhost:%s/", @@ -169,7 +169,7 @@ }, "console": "internalConsole", // Don't pause when the GC runs while the debugger is open. - "postRunCommands": ["process handle -p true -s false -n false SIGUSR1"], + "postRunCommands": ["command source '${workspaceFolder}/misctools/lldb/lldb_commands'"], }, { "type": "lldb", @@ -188,7 +188,7 @@ }, "console": "internalConsole", // Don't pause when the GC runs while the debugger is open. - "postRunCommands": ["process handle -p true -s false -n false SIGUSR1"], + "postRunCommands": ["command source '${workspaceFolder}/misctools/lldb/lldb_commands'"], }, { "type": "lldb", @@ -203,7 +203,7 @@ }, "console": "internalConsole", // Don't pause when the GC runs while the debugger is open. - "postRunCommands": ["process handle -p true -s false -n false SIGUSR1"], + "postRunCommands": ["command source '${workspaceFolder}/misctools/lldb/lldb_commands'"], }, { "type": "lldb", @@ -221,7 +221,7 @@ }, "console": "internalConsole", // Don't pause when the GC runs while the debugger is open. - "postRunCommands": ["process handle -p true -s false -n false SIGUSR1"], + "postRunCommands": ["command source '${workspaceFolder}/misctools/lldb/lldb_commands'"], }, { "type": "lldb", @@ -236,7 +236,7 @@ }, "console": "internalConsole", // Don't pause when the GC runs while the debugger is open. - "postRunCommands": ["process handle -p true -s false -n false SIGUSR1"], + "postRunCommands": ["command source '${workspaceFolder}/misctools/lldb/lldb_commands'"], }, { "type": "lldb", @@ -253,7 +253,7 @@ }, "console": "internalConsole", // Don't pause when the GC runs while the debugger is open. - "postRunCommands": ["process handle -p true -s false -n false SIGUSR1"], + "postRunCommands": ["command source '${workspaceFolder}/misctools/lldb/lldb_commands'"], "serverReadyAction": { "pattern": "https://debug.bun.sh/#localhost:([0-9]+)/", "uriFormat": "https://debug.bun.sh/#ws://localhost:%s/", @@ -275,7 +275,7 @@ }, "console": "internalConsole", // Don't pause when the GC runs while the debugger is open. - "postRunCommands": ["process handle -p true -s false -n false SIGUSR1"], + "postRunCommands": ["command source '${workspaceFolder}/misctools/lldb/lldb_commands'"], "serverReadyAction": { "pattern": "https://debug.bun.sh/#localhost:([0-9]+)/", "uriFormat": "https://debug.bun.sh/#ws://localhost:%s/", @@ -297,7 +297,7 @@ }, "console": "internalConsole", // Don't pause when the GC runs while the debugger is open. - "postRunCommands": ["process handle -p true -s false -n false SIGUSR1"], + "postRunCommands": ["command source '${workspaceFolder}/misctools/lldb/lldb_commands'"], }, { "type": "lldb", @@ -313,7 +313,7 @@ }, "console": "internalConsole", // Don't pause when the GC runs while the debugger is open. - "postRunCommands": ["process handle -p true -s false -n false SIGUSR1"], + "postRunCommands": ["command source '${workspaceFolder}/misctools/lldb/lldb_commands'"], }, { "type": "lldb", @@ -329,7 +329,7 @@ }, "console": "internalConsole", // Don't pause when the GC runs while the debugger is open. - "postRunCommands": ["process handle -p true -s false -n false SIGUSR1"], + "postRunCommands": ["command source '${workspaceFolder}/misctools/lldb/lldb_commands'"], }, { "type": "lldb", @@ -345,7 +345,7 @@ }, "console": "internalConsole", // Don't pause when the GC runs while the debugger is open. - "postRunCommands": ["process handle -p true -s false -n false SIGUSR1"], + "postRunCommands": ["command source '${workspaceFolder}/misctools/lldb/lldb_commands'"], }, { "type": "lldb", @@ -361,7 +361,7 @@ }, "console": "internalConsole", // Don't pause when the GC runs while the debugger is open. - "postRunCommands": ["process handle -p true -s false -n false SIGUSR1"], + "postRunCommands": ["command source '${workspaceFolder}/misctools/lldb/lldb_commands'"], }, { "type": "lldb", @@ -378,7 +378,7 @@ }, "console": "internalConsole", // Don't pause when the GC runs while the debugger is open. - "postRunCommands": ["process handle -p true -s false -n false SIGUSR1"], + "postRunCommands": ["command source '${workspaceFolder}/misctools/lldb/lldb_commands'"], "serverReadyAction": { "pattern": "https://debug.bun.sh/#localhost:([0-9]+)/", "uriFormat": "https://debug.bun.sh/#ws://localhost:%s/", @@ -400,7 +400,7 @@ }, "console": "internalConsole", // Don't pause when the GC runs while the debugger is open. - "postRunCommands": ["process handle -p true -s false -n false SIGUSR1"], + "postRunCommands": ["command source '${workspaceFolder}/misctools/lldb/lldb_commands'"], "serverReadyAction": { "pattern": "https://debug.bun.sh/#localhost:([0-9]+)/", "uriFormat": "https://debug.bun.sh/#ws://localhost:%s/", @@ -421,7 +421,7 @@ }, "console": "internalConsole", // Don't pause when the GC runs while the debugger is open. - "postRunCommands": ["process handle -p true -s false -n false SIGUSR1"], + "postRunCommands": ["command source '${workspaceFolder}/misctools/lldb/lldb_commands'"], }, // bun test [*] { @@ -437,7 +437,7 @@ }, "console": "internalConsole", // Don't pause when the GC runs while the debugger is open. - "postRunCommands": ["process handle -p true -s false -n false SIGUSR1"], + "postRunCommands": ["command source '${workspaceFolder}/misctools/lldb/lldb_commands'"], }, { "type": "lldb", @@ -452,7 +452,7 @@ }, "console": "internalConsole", // Don't pause when the GC runs while the debugger is open. - "postRunCommands": ["process handle -p true -s false -n false SIGUSR1"], + "postRunCommands": ["command source '${workspaceFolder}/misctools/lldb/lldb_commands'"], }, { "type": "lldb", @@ -468,7 +468,7 @@ }, "console": "internalConsole", // Don't pause when the GC runs while the debugger is open. - "postRunCommands": ["process handle -p true -s false -n false SIGUSR1"], + "postRunCommands": ["command source '${workspaceFolder}/misctools/lldb/lldb_commands'"], "serverReadyAction": { "pattern": "https://debug.bun.sh/#localhost:([0-9]+)/", "uriFormat": "https://debug.bun.sh/#ws://localhost:%s/", @@ -488,7 +488,7 @@ }, "console": "internalConsole", // Don't pause when the GC runs while the debugger is open. - "postRunCommands": ["process handle -p true -s false -n false SIGUSR1"], + "postRunCommands": ["command source '${workspaceFolder}/misctools/lldb/lldb_commands'"], }, { "type": "lldb", @@ -503,7 +503,7 @@ }, "console": "internalConsole", // Don't pause when the GC runs while the debugger is open. - "postRunCommands": ["process handle -p true -s false -n false SIGUSR1"], + "postRunCommands": ["command source '${workspaceFolder}/misctools/lldb/lldb_commands'"], }, // Windows: bun test [file] { @@ -1125,7 +1125,7 @@ ], "console": "internalConsole", // Don't pause when the GC runs while the debugger is open. - "postRunCommands": ["process handle -p true -s false -n false SIGUSR1"], + "postRunCommands": ["command source '${workspaceFolder}/misctools/lldb/lldb_commands'"], }, ], "inputs": [ diff --git a/LATEST b/LATEST index c99926d3301167..f2f5ed4e0b1684 100644 --- a/LATEST +++ b/LATEST @@ -1 +1 @@ -1.1.38 \ No newline at end of file +1.1.39 \ No newline at end of file diff --git a/build.zig b/build.zig index 9a1e3b25a7fd82..0fc377326fbf50 100644 --- a/build.zig +++ b/build.zig @@ -328,6 +328,12 @@ pub fn build(b: *Build) !void { }); } + // zig build translate-c-headers + { + const step = b.step("translate-c", "Copy generated translated-c-headers.zig to zig-out"); + step.dependOn(&b.addInstallFile(getTranslateC(b, b.host, .Debug).getOutput(), "translated-c-headers.zig").step); + } + // zig build enum-extractor { // const step = b.step("enum-extractor", "Extract enum definitions (invoked by a code generator)"); @@ -380,6 +386,25 @@ pub fn addMultiCheck( } } +fn getTranslateC(b: *Build, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode) *Step.TranslateC { + const translate_c = b.addTranslateC(.{ + .root_source_file = b.path("src/c-headers-for-zig.h"), + .target = target, + .optimize = optimize, + .link_libc = true, + }); + inline for ([_](struct { []const u8, bool }){ + .{ "WINDOWS", translate_c.target.result.os.tag == .windows }, + .{ "POSIX", translate_c.target.result.os.tag != .windows }, + .{ "LINUX", translate_c.target.result.os.tag == .linux }, + .{ "DARWIN", translate_c.target.result.os.tag.isDarwin() }, + }) |entry| { + const str, const value = entry; + translate_c.defineCMacroRaw(b.fmt("{s}={d}", .{ str, @intFromBool(value) })); + } + return translate_c; +} + pub fn addBunObject(b: *Build, opts: *BunBuildOptions) *Compile { const obj = b.addObject(.{ .name = if (opts.optimize == .Debug) "bun-debug" else "bun", @@ -428,13 +453,8 @@ pub fn addBunObject(b: *Build, opts: *BunBuildOptions) *Compile { addInternalPackages(b, obj, opts); obj.root_module.addImport("build_options", opts.buildOptionsModule(b)); - const translate_plugin_api = b.addTranslateC(.{ - .root_source_file = b.path("./packages/bun-native-bundler-plugin-api/bundler_plugin.h"), - .target = opts.target, - .optimize = opts.optimize, - .link_libc = true, - }); - obj.root_module.addImport("bun-native-bundler-plugin-api", translate_plugin_api.createModule()); + const translate_c = getTranslateC(b, opts.target, opts.optimize); + obj.root_module.addImport("translated-c-headers", translate_c.createModule()); return obj; } diff --git a/cmake/tools/SetupWebKit.cmake b/cmake/tools/SetupWebKit.cmake index 47ad1b9e34f971..b876c54da52e4d 100644 --- a/cmake/tools/SetupWebKit.cmake +++ b/cmake/tools/SetupWebKit.cmake @@ -2,7 +2,7 @@ option(WEBKIT_VERSION "The version of WebKit to use") option(WEBKIT_LOCAL "If a local version of WebKit should be used instead of downloading") if(NOT WEBKIT_VERSION) - set(WEBKIT_VERSION 58549ddc4d9e7164823fe9d4e86c46c003e46a19) + set(WEBKIT_VERSION 3845bf370ff4e9a5c0b96036255142c7904be963) endif() if(WEBKIT_LOCAL) diff --git a/docs/api/utils.md b/docs/api/utils.md index 3b87922106af74..8c96472f01be70 100644 --- a/docs/api/utils.md +++ b/docs/api/utils.md @@ -771,3 +771,28 @@ console.log(obj); // => { foo: "bar" } ``` Internally, [`structuredClone`](https://developer.mozilla.org/en-US/docs/Web/API/structuredClone) and [`postMessage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage) serialize and deserialize the same way. This exposes the underlying [HTML Structured Clone Algorithm](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm) to JavaScript as an ArrayBuffer. + +## `estimateShallowMemoryUsageOf` in `bun:jsc` + +The `estimateShallowMemoryUsageOf` function returns a best-effort estimate of the memory usage of an object in bytes, excluding the memory usage of properties or other objects it references. For accurate per-object memory usage, use `Bun.generateHeapSnapshot`. + +```js +import { estimateShallowMemoryUsageOf } from "bun:jsc"; + +const obj = { foo: "bar" }; +const usage = estimateShallowMemoryUsageOf(obj); +console.log(usage); // => 16 + +const buffer = Buffer.alloc(1024 * 1024); +estimateShallowMemoryUsageOf(buffer); +// => 1048624 + +const req = new Request("https://bun.sh"); +estimateShallowMemoryUsageOf(req); +// => 167 + +const array = Array(1024).fill({ a: 1 }); +// Arrays are usually not stored contiguously in memory, so this will not return a useful value (which isn't a bug). +estimateShallowMemoryUsageOf(array); +// => 16 +``` diff --git a/docs/bundler/index.md b/docs/bundler/index.md index a13cd4871ccb57..dbb7a95038a5da 100644 --- a/docs/bundler/index.md +++ b/docs/bundler/index.md @@ -1259,7 +1259,7 @@ $ bun build ./index.tsx --outdir ./out --drop=console --drop=debugger --drop=any ### `experimentalCss` -Whether to enable _experimental_ support for bundling CSS files. Defaults to `false`. +Whether to enable _experimental_ support for bundling CSS files. Defaults to `false`. In 1.2, this property will be deleted, and CSS bundling will always be enabled. This supports bundling CSS files imported from JS, as well as CSS entrypoints. @@ -1275,6 +1275,12 @@ const result = await Bun.build({ {% /codetabs %} +### `throw` + +If set to `true`, `Bun.build` will throw on build failure. See the section ["Logs and Errors"](#logs-and-errors) for more details on the error message structure. + +In 1.2, this will default to `true`, with the previous behavior as `throw: false` + ## Outputs The `Bun.build` function returns a `Promise`, defined as: @@ -1414,7 +1420,70 @@ Refer to [Bundler > Executables](https://bun.sh/docs/bundler/executables) for co ## Logs and errors -`Bun.build` only throws if invalid options are provided. Read the `success` property to determine if the build was successful; the `logs` property will contain additional details. + + + +By default, `Bun.build` only throws if invalid options are provided. Read the `success` property to determine if the build was successful; the `logs` property will contain additional details. ```ts const result = await Bun.build({ @@ -1457,6 +1526,27 @@ if (!result.success) { } ``` +In Bun 1.2, throwing an aggregate error like this will become the default beahavior. You can opt-into it early using the `throw: true` option. + +```ts +try { + const result = await Bun.build({ + entrypoints: ["./index.tsx"], + outdir: "./out", + }); +} catch (e) { + // TypeScript does not allow annotations on the catch clause + const error = e as AggregateError; + console.error("Build Failed"); + + // Example: Using the built-in formatter + console.error(error); + + // Example: Serializing the failure as a JSON string. + console.error(JSON.stringify(error, null, 2)); +} +``` + ## Reference ```ts @@ -1478,39 +1568,23 @@ interface BuildConfig { * * @default "esm" */ - format?: /** - - * ECMAScript Module format - */ - | "esm" - /** - * CommonJS format - * **Experimental** - */ - | "cjs" - /** - * IIFE format - * **Experimental** - */ - | "iife"; + format?: "esm" | "cjs" | "iife"; naming?: | string | { chunk?: string; entry?: string; asset?: string; - }; // | string; + }; root?: string; // project root splitting?: boolean; // default true, enable code splitting plugins?: BunPlugin[]; - // manifest?: boolean; // whether to return manifest external?: string[]; packages?: "bundle" | "external"; publicPath?: string; define?: Record; - // origin?: string; // e.g. http://mydomain.com loader?: { [k in string]: Loader }; - sourcemap?: "none" | "linked" | "inline" | "external" | "linked"; // default: "none", true -> "inline" + sourcemap?: "none" | "linked" | "inline" | "external" | "linked" | boolean; // default: "none", true -> "inline" /** * package.json `exports` conditions used when resolving imports * @@ -1519,6 +1593,18 @@ interface BuildConfig { * https://nodejs.org/api/packages.html#exports */ conditions?: Array | string; + + /** + * Controls how environment variables are handled during bundling. + * + * Can be one of: + * - `"inline"`: Injects environment variables into the bundled output by converting `process.env.FOO` + * references to string literals containing the actual environment variable values + * - `"disable"`: Disables environment variable injection entirely + * - A string ending in `*`: Inlines environment variables that match the given prefix. + * For example, `"MY_PUBLIC_*"` will only include env vars starting with "MY_PUBLIC_" + */ + env?: "inline" | "disable" | `${string}*`; minify?: | boolean | { @@ -1536,20 +1622,6 @@ interface BuildConfig { * Force emitting @__PURE__ annotations even if minify.whitespace is true. */ emitDCEAnnotations?: boolean; - // treeshaking?: boolean; - - // jsx?: - // | "automatic" - // | "classic" - // | /* later: "preserve" */ { - // runtime?: "automatic" | "classic"; // later: "preserve" - // /** Only works when runtime=classic */ - // factory?: string; // default: "React.createElement" - // /** Only works when runtime=classic */ - // fragment?: string; // default: "React.Fragment" - // /** Only works when runtime=automatic */ - // importSource?: string; // default: "react" - // }; /** * Generate bytecode for the output. This can dramatically improve cold @@ -1562,6 +1634,37 @@ interface BuildConfig { * @default false */ bytecode?: boolean; + /** + * Add a banner to the bundled code such as "use client"; + */ + banner?: string; + /** + * Add a footer to the bundled code such as a comment block like + * + * `// made with bun!` + */ + footer?: string; + + /** + * **Experimental** + * + * Enable CSS support. + */ + experimentalCss?: boolean; + + /** + * Drop function calls to matching property accesses. + */ + drop?: string[]; + + /** + * When set to `true`, the returned promise rejects with an AggregateError when a build failure happens. + * When set to `false`, the `success` property of the returned object will be `false` when a build failure happens. + * + * This defaults to `false` in Bun 1.1 and will change to `true` in Bun 1.2 + * as most usage of `Bun.build` forgets to check for errors. + */ + throw?: boolean; } interface BuildOutput { @@ -1619,32 +1722,3 @@ declare class ResolveMessage { toString(): string; } ``` - - diff --git a/docs/bundler/plugins.md b/docs/bundler/plugins.md index 6ac9654f0fa616..8e6b79c0e7df9f 100644 --- a/docs/bundler/plugins.md +++ b/docs/bundler/plugins.md @@ -2,11 +2,47 @@ Bun provides a universal plugin API that can be used to extend both the _runtime Plugins intercept imports and perform custom loading logic: reading files, transpiling code, etc. They can be used to add support for additional file types, like `.scss` or `.yaml`. In the context of Bun's bundler, plugins can be used to implement framework-level features like CSS extraction, macros, and client-server code co-location. -For more complete documentation of the Plugin API, see [Runtime > Plugins](https://bun.sh/docs/runtime/plugins). +## Lifecycle hooks + +Plugins can register callbacks to be run at various points in the lifecycle of a bundle: + +- [`onStart()`](#onstart): Run once the bundler has started a bundle +- [`onResolve()`](#onresolve): Run before a module is resolved +- [`onLoad()`](#onload): Run before a module is loaded. +- [`onBeforeParse()`](#onbeforeparse): Run zero-copy native addons in the parser thread before a file is parsed. + +### Reference + +A rough overview of the types (please refer to Bun's `bun.d.ts` for the full type definitions): + +```ts +type PluginBuilder = { + onStart(callback: () => void): void; + onResolve: ( + args: { filter: RegExp; namespace?: string }, + callback: (args: { path: string; importer: string }) => { + path: string; + namespace?: string; + } | void, + ) => void; + onLoad: ( + args: { filter: RegExp; namespace?: string }, + defer: () => Promise, + callback: (args: { path: string }) => { + loader?: Loader; + contents?: string; + exports?: Record; + }, + ) => void; + config: BuildConfig; +}; + +type Loader = "js" | "jsx" | "ts" | "tsx" | "css" | "json" | "toml"; +``` ## Usage -A plugin is defined as simple JavaScript object containing a `name` property and a `setup` function. Register a plugin with Bun using the `plugin` function. +A plugin is defined as simple JavaScript object containing a `name` property and a `setup` function. ```tsx#myPlugin.ts import type { BunPlugin } from "bun"; @@ -22,9 +58,343 @@ const myPlugin: BunPlugin = { This plugin can be passed into the `plugins` array when calling `Bun.build`. ```ts -Bun.build({ +await Bun.build({ entrypoints: ["./app.ts"], outdir: "./out", plugins: [myPlugin], }); ``` + +## Plugin lifecycle + +### Namespaces + +`onLoad` and `onResolve` accept an optional `namespace` string. What is a namespaace? + +Every module has a namespace. Namespaces are used to prefix the import in transpiled code; for instance, a loader with a `filter: /\.yaml$/` and `namespace: "yaml:"` will transform an import from `./myfile.yaml` into `yaml:./myfile.yaml`. + +The default namespace is `"file"` and it is not necessary to specify it, for instance: `import myModule from "./my-module.ts"` is the same as `import myModule from "file:./my-module.ts"`. + +Other common namespaces are: + +- `"bun"`: for Bun-specific modules (e.g. `"bun:test"`, `"bun:sqlite"`) +- `"node"`: for Node.js modules (e.g. `"node:fs"`, `"node:path"`) + +### `onStart` + +```ts +onStart(callback: () => void): Promise | void; +``` + +Registers a callback to be run when the bundler starts a new bundle. + +```ts +import { plugin } from "bun"; + +plugin({ + name: "onStart example", + + setup(build) { + build.onStart(() => { + console.log("Bundle started!"); + }); + }, +}); +``` + +The callback can return a `Promise`. After the bundle process has initialized, the bundler waits until all `onStart()` callbacks have completed before continuing. + +For example: + +```ts +const result = await Bun.build({ + entrypoints: ["./app.ts"], + outdir: "./dist", + sourcemap: "external", + plugins: [ + { + name: "Sleep for 10 seconds", + setup(build) { + build.onStart(async () => { + await Bunlog.sleep(10_000); + }); + }, + }, + { + name: "Log bundle time to a file", + setup(build) { + build.onStart(async () => { + const now = Date.now(); + await Bun.$`echo ${now} > bundle-time.txt`; + }); + }, + }, + ], +}); +``` + +In the above example, Bun will wait until the first `onStart()` (sleeping for 10 seconds) has completed, _as well as_ the second `onStart()` (writing the bundle time to a file). + +Note that `onStart()` callbacks (like every other lifecycle callback) do not have the ability to modify the `build.config` object. If you want to mutate `build.config`, you must do so directly in the `setup()` function. + +### `onResolve` + +```ts +onResolve( + args: { filter: RegExp; namespace?: string }, + callback: (args: { path: string; importer: string }) => { + path: string; + namespace?: string; + } | void, +): void; +``` + +To bundle your project, Bun walks down the dependency tree of all modules in your project. For each imported module, Bun actually has to find and read that module. The "finding" part is known as "resolving" a module. + +The `onResolve()` plugin lifecycle callback allows you to configure how a module is resolved. + +The first argument to `onResolve()` is an object with a `filter` and [`namespace`](#what-is-a-namespace) property. The filter is a regular expression which is run on the import string. Effectively, these allow you to filter which modules your custom resolution logic will apply to. + +The second argument to `onResolve()` is a callback which is run for each module import Bun finds that matches the `filter` and `namespace` defined in the first argument. + +The callback receives as input the _path_ to the matching module. The callback can return a _new path_ for the module. Bun will read the contents of the _new path_ and parse it as a module. + +For example, redirecting all imports to `images/` to `./public/images/`: + +```ts +import { plugin } from "bun"; + +plugin({ + name: "onResolve example", + setup(build) { + build.onResolve({ filter: /.*/, namespace: "file" }, args => { + if (args.path.startsWith("images/")) { + return { + path: args.path.replace("images/", "./public/images/"), + }; + } + }); + }, +}); +``` + +### `onLoad` + +```ts +onLoad( + args: { filter: RegExp; namespace?: string }, + defer: () => Promise, + callback: (args: { path: string, importer: string, namespace: string, kind: ImportKind }) => { + loader?: Loader; + contents?: string; + exports?: Record; + }, +): void; +``` + +After Bun's bundler has resolved a module, it needs to read the contents of the module and parse it. + +The `onLoad()` plugin lifecycle callback allows you to modify the _contents_ of a module before it is read and parsed by Bun. + +Like `onResolve()`, the first argument to `onLoad()` allows you to filter which modules this invocation of `onLoad()` will apply to. + +The second argument to `onLoad()` is a callback which is run for each matching module _before_ Bun loads the contents of the module into memory. + +This callback receives as input the _path_ to the matching module, the _importer_ of the module (the module that imported the module), the _namespace_ of the module, and the _kind_ of the module. + +The callback can return a new `contents` string for the module as well as a new `loader`. + +For example: + +```ts +import { plugin } from "bun"; + +const envPlugin: BunPlugin = { + name: "env plugin", + setup(build) { + build.onLoad({ filter: /env/, namespace: "file" }, args => { + return { + contents: `export default ${JSON.stringify(process.env)}`, + loader: "js", + }; + }); + }, +}); + +Bun.build({ + entrypoints: ["./app.ts"], + outdir: "./dist", + plugins: [envPlugin], +}); + +// import env from "env" +// env.FOO === "bar" +``` + +This plugin will transform all imports of the form `import env from "env"` into a JavaScript module that exports the current environment variables. + +#### `.defer()` + +One of the arguments passed to the `onLoad` callback is a `defer` function. This function returns a `Promise` that is resolved when all _other_ modules have been loaded. + +This allows you to delay execution of the `onLoad` callback until all other modules have been loaded. + +This is useful for returning contens of a module that depends on other modules. + +##### Example: tracking and reporting unused exports + +```ts +import { plugin } from "bun"; + +plugin({ + name: "track imports", + setup(build) { + const transpiler = new Bun.Transpiler(); + + let trackedImports: Record = {}; + + // Each module that goes through this onLoad callback + // will record its imports in `trackedImports` + build.onLoad({ filter: /\.ts/ }, async ({ path }) => { + const contents = await Bun.file(path).arrayBuffer(); + + const imports = transpiler.scanImports(contents); + + for (const i of imports) { + trackedImports[i.path] = (trackedImports[i.path] || 0) + 1; + } + + return undefined; + }); + + build.onLoad({ filter: /stats\.json/ }, async ({ defer }) => { + // Wait for all files to be loaded, ensuring + // that every file goes through the above `onLoad()` function + // and their imports tracked + await defer(); + + // Emit JSON containing the stats of each import + return { + contents: `export default ${JSON.stringify(trackedImports)}`, + loader: "json", + }; + }); + }, +}); +``` + +Note that the `.defer()` function currently has the limitation that it can only be called once per `onLoad` callback. + +## Native plugins + +One of the reasons why Bun's bundler is so fast is that it is written in native code and leverages multi-threading to load and parse modules in parallel. + +However, one limitation of plugins written in JavaScript is that JavaScript itself is single-threaded. + +Native plugins are written as [NAPI](/docs/node-api) modules and can be run on multiple threads. This allows native plugins to run much faster than JavaScript plugins. + +In addition, native plugins can skip unnecessary work such as the UTF-8 -> UTF-16 conversion needed to pass strings to JavaScript. + +These are the following lifecycle hooks which are available to native plugins: + +- [`onBeforeParse()`](#onbeforeparse): Called on any thread before a file is parsed by Bun's bundler. + +Native plugins are NAPI modules which expose lifecycle hooks as C ABI functions. + +To create a native plugin, you must export a C ABI function which matches the signature of the native lifecycle hook you want to implement. + +### Creating a native plugin in Rust + +Native plugins are NAPI modules which expose lifecycle hooks as C ABI functions. + +To create a native plugin, you must export a C ABI function which matches the signature of the native lifecycle hook you want to implement. + +```bash +bun add -g @napi-rs/cli +napi new +``` + +Then install this crate: + +```bash +cargo add bun-native-plugin +``` + +Now, inside the `lib.rs` file, we'll use the `bun_native_plugin::bun` proc macro to define a function which +will implement our native plugin. + +Here's an example implementing the `onBeforeParse` hook: + +```rs +use bun_native_plugin::{define_bun_plugin, OnBeforeParse, bun, Result, anyhow, BunLoader}; +use napi_derive::napi; + +/// Define the plugin and its name +define_bun_plugin!("replace-foo-with-bar"); + +/// Here we'll implement `onBeforeParse` with code that replaces all occurrences of +/// `foo` with `bar`. +/// +/// We use the #[bun] macro to generate some of the boilerplate code. +/// +/// The argument of the function (`handle: &mut OnBeforeParse`) tells +/// the macro that this function implements the `onBeforeParse` hook. +#[bun] +pub fn replace_foo_with_bar(handle: &mut OnBeforeParse) -> Result<()> { + // Fetch the input source code. + let input_source_code = handle.input_source_code()?; + + // Get the Loader for the file + let loader = handle.output_loader(); + + + let output_source_code = input_source_code.replace("foo", "bar"); + + handle.set_output_source_code(output_source_code, BunLoader::BUN_LOADER_JSX); + + Ok(()) +} +``` + +And to use it in Bun.build(): + +```typescript +import myNativeAddon from "./my-native-addon"; +Bun.build({ + entrypoints: ["./app.tsx"], + plugins: [ + { + name: "my-plugin", + + setup(build) { + build.onBeforeParse( + { + namespace: "file", + filter: "**/*.tsx", + }, + { + napiModule: myNativeAddon, + symbol: "replace_foo_with_bar", + // external: myNativeAddon.getSharedState() + }, + ); + }, + }, + ], +}); +``` + +### `onBeforeParse` + +```ts +onBeforeParse( + args: { filter: RegExp; namespace?: string }, + callback: { napiModule: NapiModule; symbol: string; external?: unknown }, +): void; +``` + +This lifecycle callback is run immediately before a file is parsed by Bun's bundler. + +As input, it receives the file's contents and can optionally return new source code. + +This callback can be called from any thread and so the napi module implementation must be thread-safe. diff --git a/docs/bundler/vs-esbuild.md b/docs/bundler/vs-esbuild.md index 1266914c057233..35bb62f6f70271 100644 --- a/docs/bundler/vs-esbuild.md +++ b/docs/bundler/vs-esbuild.md @@ -695,7 +695,7 @@ In Bun's CLI, simple boolean flags like `--minify` do not accept an argument. Ot - In Bun, `minify` can be a boolean or an object. ```ts - Bun.build({ + await Bun.build({ entrypoints: ['./index.tsx'], // enable all minification minify: true diff --git a/docs/cli/bun-install.md b/docs/cli/bun-install.md index 20832cc53e916d..f06e1843e50c04 100644 --- a/docs/cli/bun-install.md +++ b/docs/cli/bun-install.md @@ -47,6 +47,9 @@ registry = "https://registry.yarnpkg.com/" # Install for production? This is the equivalent to the "--production" CLI argument production = false +# Save a text-based lockfile? This is equivalent to the "--save-text-lockfile" CLI argument +saveTextLockfile = false + # Disallow changes to lockfile? This is the equivalent to the "--frozen-lockfile" CLI argument frozenLockfile = false @@ -108,6 +111,7 @@ export interface Install { scopes: Scopes; registry: Registry; production: boolean; + saveTextLockfile: boolean; frozenLockfile: boolean; dryRun: boolean; optional: boolean; diff --git a/docs/cli/install.md b/docs/cli/install.md index 56212b5c4707ea..2fa9db74610841 100644 --- a/docs/cli/install.md +++ b/docs/cli/install.md @@ -174,6 +174,9 @@ peer = true # equivalent to `--production` flag production = false +# equivalent to `--save-text-lockfile` flag +saveTextLockfile = false + # equivalent to `--frozen-lockfile` flag frozenLockfile = false diff --git a/docs/guides/install/registry-scope.md b/docs/guides/install/registry-scope.md index a36cde6fb04113..0eeb973deee125 100644 --- a/docs/guides/install/registry-scope.md +++ b/docs/guides/install/registry-scope.md @@ -2,7 +2,9 @@ name: Configure a private registry for an organization scope with bun install --- -Bun does not read `.npmrc` files; instead private registries are configured via `bunfig.toml`. To configure a registry for a particular npm scope: +Private registries can be configured using either [`.npmrc`](https://bun.sh/docs/install/npmrc) or [`bunfig.toml`](https://bun.sh/docs/runtime/bunfig#install-registry). While both are supported, we recommend using **bunfig.toml** for enhanced flexibility and Bun-specific options. + +To configure a registry for a particular npm scope: ```toml#bunfig.toml [install.scopes] diff --git a/docs/guides/test/svelte-test.md b/docs/guides/test/svelte-test.md new file mode 100644 index 00000000000000..062cd3312d750a --- /dev/null +++ b/docs/guides/test/svelte-test.md @@ -0,0 +1,120 @@ +--- +name: "import, require, and test Svelte components with bun test" +--- + +Bun's [Plugin API](/docs/runtime/plugins) lets you add custom loaders to your project. The `test.preload` option in `bunfig.toml` lets you configure your loader to start before your tests run. + +Firstly, install `@testing-library/svelte`, `svelte`, and `@happy-dom/global-registrator`. + +```bash +$ bun add @testing-library/svelte svelte@4 @happy-dom/global-registrator +``` + +Then, save this plugin in your project. + +```ts#svelte-loader.js +import { plugin } from "bun"; +import { compile } from "svelte/compiler"; +import { readFileSync } from "fs"; +import { beforeEach, afterEach } from "bun:test"; +import { GlobalRegistrator } from "@happy-dom/global-registrator"; + +beforeEach(async () => { + await GlobalRegistrator.register(); +}); + +afterEach(async () => { + await GlobalRegistrator.unregister(); +}); + +plugin({ + name: "svelte loader", + setup(builder) { + builder.onLoad({ filter: /\.svelte(\?[^.]+)?$/ }, ({ path }) => { + try { + const source = readFileSync( + path.substring( + 0, + path.includes("?") ? path.indexOf("?") : path.length + ), + "utf-8" + ); + + const result = compile(source, { + filename: path, + generate: "client", + dev: false, + }); + + return { + contents: result.js.code, + loader: "js", + }; + } catch (err) { + throw new Error(`Failed to compile Svelte component: ${err.message}`); + } + }); + }, +}); + +``` + +--- + +Add this to `bunfig.toml` to tell Bun to preload the plugin, so it loads before your tests run. + +```toml#bunfig.toml +[test] +# Tell Bun to load this plugin before your tests run +preload = ["./svelte-loader.js"] + +# This also works: +# test.preload = ["./svelte-loader.js"] +``` + +--- + +Add an example `.svelte` file in your project. + +```html#Counter.svelte + + + +``` + +--- + +Now you can `import` or `require` `*.svelte` files in your tests, and it will load the Svelte component as a JavaScript module. + +```ts#hello-svelte.test.ts +import { test, expect } from "bun:test"; +import { render, fireEvent } from "@testing-library/svelte"; +import Counter from "./Counter.svelte"; + +test("Counter increments when clicked", async () => { + const { getByText, component } = render(Counter); + const button = getByText("+1"); + + // Initial state + expect(component.$$.ctx[0]).toBe(0); // initialCount is the first prop + + // Click the increment button + await fireEvent.click(button); + + // Check the new state + expect(component.$$.ctx[0]).toBe(1); +}); +``` + +--- + +Use `bun test` to run your tests. + +```bash +$ bun test +``` + +--- diff --git a/docs/install/index.md b/docs/install/index.md index cdf41b9311469d..5631b55adb1192 100644 --- a/docs/install/index.md +++ b/docs/install/index.md @@ -86,6 +86,9 @@ peer = true # equivalent to `--production` flag production = false +# equivalent to `--save-text-lockfile` flag +saveTextLockfile = false + # equivalent to `--frozen-lockfile` flag frozenLockfile = false diff --git a/docs/install/lockfile.md b/docs/install/lockfile.md index 66fb28e2b2e060..315b90f48d7194 100644 --- a/docs/install/lockfile.md +++ b/docs/install/lockfile.md @@ -72,6 +72,24 @@ $ bun install --yarn print = "yarn" ``` +### Text-based lockfile + +Bun v1.1.39 introduced `bun.lock`, a JSONC formatted lockfile. `bun.lock` is human-readable and git-diffable without configuration, at [no cost to performance](https://bun.sh/blog/bun-lock-text-lockfile#cached-bun-install-gets-30-faster). + +To generate the lockfile, use `--save-text-lockfile` with `bun install`. You can do this for new projects and existing projects already using `bun.lockb` (resolutions will be preserved). + +```bash +$ bun install --save-text-lockfile +$ head -n3 bun.lock +{ + "lockfileVersion": 0, + "workspaces": { +``` + +Once `bun.lock` is generated, Bun will use it for all subsequent installs and updates through commands that read and modify the lockfile. If both lockfiles exist, `bun.lock` will be choosen over `bun.lockb`. + +Bun v1.2.0 will switch the default lockfile format to `bun.lock`. + {% /codetabs %} {% details summary="Configuring lockfile" %} diff --git a/docs/install/npmrc.md b/docs/install/npmrc.md index ae3c074892a10c..3153dd6febcb54 100644 --- a/docs/install/npmrc.md +++ b/docs/install/npmrc.md @@ -6,7 +6,7 @@ Bun supports loading configuration options from [`.npmrc`](https://docs.npmjs.co {% /callout %} -# Supported options +## Supported options ### `registry`: Set the default registry diff --git a/docs/project/bindgen.md b/docs/project/bindgen.md index 3144d7f57f58c5..83ee48d63a3664 100644 --- a/docs/project/bindgen.md +++ b/docs/project/bindgen.md @@ -61,7 +61,10 @@ This function declaration is equivalent to: declare function add(a: number, b: number = 1): number; ``` -The code generator will provide `bun.gen.math.jsAdd`, which is the native function implementation. To pass to JavaScript, use `bun.gen.math.createAddCallback(global)` +The code generator will provide `bun.gen.math.jsAdd`, which is the native +function implementation. To pass to JavaScript, use +`bun.gen.math.createAddCallback(global)`. JS files in `src/js/` may use +`$bindgenFn("math.bind.ts", "add")` to get a handle to the implementation. ## Strings @@ -104,7 +107,7 @@ export const action = fn({ In Zig, each variant gets a number, based on the order the schema defines. -``` +```zig fn action1(a: i32) i32 { return a; } @@ -180,9 +183,9 @@ export const add = fn({ // enforce in i32 range a: t.i32.enforceRange(), // clamp to u16 range - c: t.u16, + b: t.u16, // enforce in arbitrary range, with a default if not provided - b: t.i32.enforceRange(0, 1000).default(5), + c: t.i32.enforceRange(0, 1000).default(5), // clamp to arbitrary range, or null d: t.u16.clamp(0, 10).optional, }, @@ -190,6 +193,29 @@ export const add = fn({ }); ``` +Various Node.js validator functions such as `validateInteger`, `validateNumber`, and more are available. Use these when implementing Node.js APIs, so the error messages match 1:1 what Node would do. + +Unlike `enforceRange`, which is taken from WebIDL, `validate*` functions are much more strict on the input they accept. For example, Node's numerical validator check `typeof value === 'number'`, while WebIDL uses `ToNumber` for lossy conversion. + +```ts +import { t, fn } from "bindgen"; + +export const add = fn({ + args: { + global: t.globalObject, + // throw if not given a number + a: t.f64.validateNumber(), + // valid in i32 range + a: t.i32.validateInt32(), + // f64 within safe integer range + b: t.f64.validateInteger(), + // f64 in given range + c: t.f64.validateNumber(-10000, 10000), + }, + ret: t.i32, +}); +``` + ## Callbacks TODO diff --git a/docs/runtime/bunfig.md b/docs/runtime/bunfig.md index 1bfcd540e5bb89..8a19be1ce510f5 100644 --- a/docs/runtime/bunfig.md +++ b/docs/runtime/bunfig.md @@ -238,6 +238,17 @@ By default Bun uses caret ranges; if the `latest` version of a package is `2.4.1 exact = false ``` +### `install.saveTextLockfile` + +Generate `bun.lock`, a human-readable text-based lockfile. Once generated, Bun will use this file instead of `bun.lockb`, choosing it over the binary lockfile if both are present. + +Default `false`. In Bun v1.2.0 the default lockfile format will change to `bun.lock`. + +```toml +[install] +saveTextLockfile = true +``` +