diff options
author | 2023-05-16 10:47:00 -0700 | |
---|---|---|
committer | 2023-05-16 10:47:00 -0700 | |
commit | 366eba78f0b9b2c58cdd46b906275fc8382da977 (patch) | |
tree | 8966c3964aeacc4a7f61a69164467e9ebf37b6e1 | |
parent | 60bc804c58b18e99763b2d05e81bafa46800092f (diff) | |
download | bun-366eba78f0b9b2c58cdd46b906275fc8382da977.tar.gz bun-366eba78f0b9b2c58cdd46b906275fc8382da977.tar.zst bun-366eba78f0b9b2c58cdd46b906275fc8382da977.zip |
Tweaks to bundler docs (#2867)
* WIP
* Fix typo
* Updates
* Document --compile
* Add bundler benchmark
* Remove esbuild
* Add bench to docs
* Add buttons
* Updates
-rw-r--r-- | bench/bundle/.gitignore | 171 | ||||
-rw-r--r-- | bench/bundle/README.md | 40 | ||||
-rwxr-xr-x | bench/bundle/bun.lockb | bin | 0 -> 1139 bytes | |||
-rw-r--r-- | bench/bundle/index.ts | 1 | ||||
-rw-r--r-- | bench/bundle/package.json | 8 | ||||
-rwxr-xr-x | bench/bundle/run-bench.sh | 3 | ||||
-rw-r--r-- | bench/bundle/tsconfig.json | 20 | ||||
-rw-r--r-- | docs/bundler/executables.md | 33 | ||||
-rw-r--r-- | docs/bundler/migration.md | 2 | ||||
-rw-r--r-- | docs/cli/build.md | 194 | ||||
-rw-r--r-- | docs/index.md | 2 | ||||
-rw-r--r-- | docs/nav.ts | 3 | ||||
-rw-r--r-- | packages/bun-types/bun.d.ts | 22 |
13 files changed, 454 insertions, 45 deletions
diff --git a/bench/bundle/.gitignore b/bench/bundle/.gitignore new file mode 100644 index 000000000..6463d8435 --- /dev/null +++ b/bench/bundle/.gitignore @@ -0,0 +1,171 @@ +# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore + +# Logs + +logs +_.log +npm-debug.log_ +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) + +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# Runtime data + +pids +_.pid +_.seed +\*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover + +lib-cov + +# Coverage directory used by tools like istanbul + +coverage +\*.lcov + +# nyc test coverage + +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) + +.grunt + +# Bower dependency directory (https://bower.io/) + +bower_components + +# node-waf configuration + +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) + +build/Release + +# Dependency directories + +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) + +web_modules/ + +# TypeScript cache + +\*.tsbuildinfo + +# Optional npm cache directory + +.npm + +# Optional eslint cache + +.eslintcache + +# Optional stylelint cache + +.stylelintcache + +# Microbundle cache + +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history + +.node_repl_history + +# Output of 'npm pack' + +\*.tgz + +# Yarn Integrity file + +.yarn-integrity + +# dotenv environment variable files + +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) + +.cache +.parcel-cache + +# Next.js build output + +.next +out + +# Nuxt.js build / generate output + +.nuxt +dist + +# Gatsby files + +.cache/ + +# Comment in the public line in if your project uses Gatsby and not Next.js + +# https://nextjs.org/blog/next-9-1#public-directory-support + +# public + +# vuepress build output + +.vuepress/dist + +# vuepress v2.x temp and cache directory + +.temp +.cache + +# Docusaurus cache and generated files + +.docusaurus + +# Serverless directories + +.serverless/ + +# FuseBox cache + +.fusebox/ + +# DynamoDB Local files + +.dynamodb/ + +# TernJS port file + +.tern-port + +# Stores VSCode versions used for testing VSCode extensions + +.vscode-test + +# yarn v2 + +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.\* + +esbuild
\ No newline at end of file diff --git a/bench/bundle/README.md b/bench/bundle/README.md new file mode 100644 index 000000000..e973fcd35 --- /dev/null +++ b/bench/bundle/README.md @@ -0,0 +1,40 @@ +# Bundler benchmark + +This is a performance benchmark of the following bundlers: + +- Bun +- esbuild +- Parcel 2 +- Rollup + Terser +- Webpack + +It is an exact copy of [`esbuild`'s benchmark](https://github.com/evanw/esbuild/blob/main/Makefile), aside from the fast that Bun [has been added](https://github.com/colinhacks/esbuild/commit/1b928b7981aa7edfadf77fcf8931bb8d6f38cd96). The benchmark bundles 10 copies of the large [three.js](https://threejs.org/), with minification and source maps enabled. + +To run the benchmark: + +```sh +$ chmod +x run-bench.sh +$ ./run-bench.sh +``` + +Various output will be written to the console by each bundler. Scan through the results for lines that look like this underneath each bundler output: + +```sh +real <number> +user <number> +sys <number> +``` + +These lines are generated by the `time` command which is used to benchmark each build. + +## Results + +The `real` results, as run on a 16-inch M1 Macbook Pro: + +| Bundler | Time | +| ------- | ------ | +| Bun | 0.17s | +| esbuild | 0.33s | +| Rollup | 18.82s | +| Webpack | 26.21 | +| Parcel | 17.95s | diff --git a/bench/bundle/bun.lockb b/bench/bundle/bun.lockb Binary files differnew file mode 100755 index 000000000..b5ea51df5 --- /dev/null +++ b/bench/bundle/bun.lockb diff --git a/bench/bundle/index.ts b/bench/bundle/index.ts new file mode 100644 index 000000000..f67b2c645 --- /dev/null +++ b/bench/bundle/index.ts @@ -0,0 +1 @@ +console.log("Hello via Bun!");
\ No newline at end of file diff --git a/bench/bundle/package.json b/bench/bundle/package.json new file mode 100644 index 000000000..c80d9b81e --- /dev/null +++ b/bench/bundle/package.json @@ -0,0 +1,8 @@ +{ + "name": "bundle", + "module": "index.ts", + "type": "module", + "devDependencies": { + "bun-types": "^0.5.0" + } +}
\ No newline at end of file diff --git a/bench/bundle/run-bench.sh b/bench/bundle/run-bench.sh new file mode 100755 index 000000000..c14a370d5 --- /dev/null +++ b/bench/bundle/run-bench.sh @@ -0,0 +1,3 @@ +git clone git@github.com:colinhacks/esbuild.git +cd esbuild +make bench-three
\ No newline at end of file diff --git a/bench/bundle/tsconfig.json b/bench/bundle/tsconfig.json new file mode 100644 index 000000000..5c0ced989 --- /dev/null +++ b/bench/bundle/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "lib": [ + "ESNext" + ], + "module": "esnext", + "target": "esnext", + "moduleResolution": "bundler", + "strict": true, + "downlevelIteration": true, + "skipLibCheck": true, + "jsx": "react-jsx", + "allowSyntheticDefaultImports": true, + "forceConsistentCasingInFileNames": true, + "allowJs": true, + "types": [ + "bun-types" // add Bun global + ] + } +}
\ No newline at end of file diff --git a/docs/bundler/executables.md b/docs/bundler/executables.md new file mode 100644 index 000000000..9a0fc639e --- /dev/null +++ b/docs/bundler/executables.md @@ -0,0 +1,33 @@ +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 +``` + +```ts#cli.ts +console.log("Hello world!"); +``` + +{% /codetabs %} + +This bundles `cli.ts` into an executable that can be executed directly: + +``` +$ ./mycli +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. + +{% callout %} + +**Note** — Currently, the `--compile` flag can only accept a single entrypoint at a time and does not support the following flags: + +- `--outdir` — use `outfile` instead. +- `--external` +- `--splitting` +- `--publicPath` + +{% /callout %} diff --git a/docs/bundler/migration.md b/docs/bundler/migration.md index e76d50103..1bf9d52dc 100644 --- a/docs/bundler/migration.md +++ b/docs/bundler/migration.md @@ -6,7 +6,7 @@ Bun's bundler API is inspired heavily by [esbuild](https://esbuild.github.io/). 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`](/docs/api/transpiler.md). +- **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`](/docs/api/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 diff --git a/docs/cli/build.md b/docs/cli/build.md index f7113ae9f..e0cd36651 100644 --- a/docs/cli/build.md +++ b/docs/cli/build.md @@ -15,6 +15,10 @@ $ bun build ./index.tsx --outdir ./build {% /codetabs %} +It's fast. The numbers below represent performance on esbuild's [three.js benchmark](https://github.com/oven-sh/bun/tree/main/bench/bundle). + +{% image src="/images/bundler-speed.png" caption="Bundling 10 copies of three.js from scratch, with sourcemaps and minification" /%} + ## Why bundle? The bundler is a key piece of infrastructure in the JavaScript ecosystem. As a brief overview of why bundling is so important: @@ -207,7 +211,7 @@ Refer to the [Bundler > Loaders](/docs/bundler/loaders#file) page for more compl ### Plugins -The behavior described in this table can be overridden with [plugins](/docs/bundler/plugins). Refer to the [Bundler > Loaders](/docs/bundler/plugins) page for complete documentation. +The behavior described in this table can be overridden or extended with [plugins](/docs/bundler/plugins). Refer to the [Bundler > Loaders](/docs/bundler/plugins) page for complete documentation. ## API @@ -217,9 +221,9 @@ The behavior described in this table can be overridden with [plugins](/docs/bund {% codetabs group="a" %} -```ts #JavaScript +```ts#JavaScript const result = await Bun.build({ - entrypoints: ['./index.ts'] + entrypoints: ["./index.ts"], }); // => { success: boolean, outputs: BuildArtifact[], logs: BuildMessage[] } ``` @@ -253,11 +257,11 @@ $ bun build --entrypoints ./index.ts --outdir ./out {% /codetabs %} -If `outdir` is not passed to the JavaScript API, bundled code will not be written to disk. Bundled files are returned in an array of `BuildArtifact` objects. These objects are Blobs with extra properties. +If `outdir` is not passed to the JavaScript API, bundled code will not be written to disk. Bundled files are returned in an array of `BuildArtifact` objects. These objects are Blobs with extra properties; see [Outputs](#outputs) for complete documentation. ```ts const result = await Bun.build({ - entrypoints: ['./index.ts'] + entrypoints: ["./index.ts"], }); for (const result of result.outputs) { @@ -272,7 +276,7 @@ for (const result of result.outputs) { } ``` -When `outdir` is set, the `.path` on `BuildArtifact` will be the absolute path to where it was written to. +When `outdir` is set, the `path` property on a `BuildArtifact` will be the absolute path to where it was written to. ### `target` @@ -312,6 +316,8 @@ Depending on the target, Bun will apply different module resolution rules and op All bundles generated with `target: "bun"` are marked with a special `// @bun` pragma, which indicates to the Bun runtime that there's no need to re-transpile the file before execution. + If any entrypoints contains a Bun shebang (`#!/usr/bin/env bun`) the bundler will default to `target: "bun"` instead of `"browser`. + --- - `node` @@ -319,6 +325,10 @@ Depending on the target, Bun will apply different module resolution rules and op {% /table %} +{% callout %} + +{% /callout %} + ### `format` Specifies the module format to be used in the generated bundles. @@ -1051,18 +1061,133 @@ $ bun build ./index.tsx --outdir ./out --loader .png:dataurl --loader .txt:file {% /codetabs %} +## Outputs + +The `Bun.build` function returns a `Promise<BuildOutput>`, defined as: + +```ts +interface BuildOutput { + outputs: BuildArtifact[]; + success: boolean; + logs: Array<object>; // see docs for details +} -## Error handling +interface BuildArtifact extends Blob { + kind: "entry-point" | "chunk" | "asset" | "sourcemap"; + path: string; + loader: Loader; + hash: string | null; + sourcemap: BuildArtifact | null; +} +``` -`Bun.build` only throws if invalid options are provided. You should read the `success` and `logs` properties of the result to determine whether the build was successful. +The `outputs` array contains all the files that were generated by the build. Each artifact implements the `Blob` interface. + +```ts +const build = Bun.build({ + /* */ +}); + +for (const output of build.outputs) { + await output.arrayBuffer(); // => ArrayBuffer + await output.text(); // string +} +``` + +Each artifact also contains the following properties: + +{% table %} + +--- + +- `kind` +- What kind of build output this file is. A build generates bundled entrypoints, code-split "chunks", sourcemaps, and copied assets (like images). + +--- + +- `path` +- Absolute path to the file on disk + +--- + +- `loader` +- The loader was used to interpret the file. See [Bundler > Loaders](/docs/bundler/loaders) to see how Bun maps file extensions to the appropriate built-in loader. + +--- + +- `hash` +- The hash of the file contents. Always defined for assets. + +--- + +- `sourcemap` +- The sourcemap file corresponding to this file, if generated. Only defined for entrypoints and chunks. + +{% /table %} + +Similar to `BunFile`, `BuildArtifact` objects can be passed directly into `new Response()`. + +```ts +const build = Bun.build({ + /* */ +}); + +const artifact = build.outputs[0]; + +// Content-Type header is automatically set +return new Response(artifact); +``` + +The Bun runtime implements special pretty-printing of `BuildArtifact` object to make debugging easier. + +{% codetabs %} + +```ts#Build_script +// build.ts +const build = Bun.build({/* */}); + +const artifact = build.outputs[0]; +console.log(artifact); +``` + +```sh#Shell_output +$ bun run build.ts +BuildArtifact (entry-point) { + path: "./index.js", + loader: "tsx", + kind: "entry-point", + hash: "824a039620219640", + Blob (114 bytes) { + type: "text/javascript;charset=utf-8" + }, + sourcemap: null +} +``` + +{% /codetabs %} + +### Executables + +Bun supports "compiling" a JavaScript/TypeScript entrypoint into a standalone executable. This executable contains a copy of the Bun binary. + +```sh +$ bun build ./cli.tsx --outfile mycli --compile +$ ./mycli +``` + +Refer to [Bundler > Executables](/docs/bundler/executables) for complete documentation. + +## 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. ```ts const result = await Bun.build({ - entrypoints: ['./index.tsx'], - outdir: './out', + entrypoints: ["./index.tsx"], + outdir: "./out", }); -if(!result.success) { +if (!result.success) { console.error("Build failed"); for (const message of result.logs) { // Bun will pretty print the message object @@ -1089,7 +1214,7 @@ class ResolveMessage extends BuildMessage { } ``` -If you want to throw an error from a failed build, consider passing the logs to an `AggregateError`. If uncaught, Bun will pretty print the contained messages nicely. +If you want to throw an error from a failed build, consider passing the logs to an `AggregateError`. If uncaught, Bun will pretty-print the contained messages nicely. ```ts if (!result.success) { @@ -1143,24 +1268,43 @@ interface BuildArtifact extends Blob { path: string; loader: Loader; hash?: string; - kind: "entry-point" | "chunk" | "asset" | "sourecemap"; + kind: "entry-point" | "chunk" | "asset" | "sourcemap"; sourcemap?: BuildArtifact; } -type Loader = - | "js" - | "jsx" - | "ts" - | "tsx" - | "json" - | "toml" - | "file" - | "napi" - | "wasm" - | "text"; +type Loader = "js" | "jsx" | "ts" | "tsx" | "json" | "toml" | "file" | "napi" | "wasm" | "text"; + +interface BuildOutput { + outputs: BuildArtifact[]; + success: boolean; + logs: Array<BuildMessage | ResolveMessage>; +} + +declare class ResolveMessage { + readonly name: "ResolveMessage"; + readonly position: Position | null; + readonly code: string; + readonly message: string; + readonly referrer: string; + readonly specifier: string; + readonly importKind: + | "entry_point" + | "stmt" + | "require" + | "import" + | "dynamic" + | "require_resolve" + | "at" + | "at_conditional" + | "url" + | "internal"; + readonly level: "error" | "warning" | "info" | "debug" | "verbose"; + + toString(): string; +} ``` -<!-- +<!-- interface BuildManifest { inputs: { [path: string]: { diff --git a/docs/index.md b/docs/index.md index b0bff82f1..6b5891cdf 100644 --- a/docs/index.md +++ b/docs/index.md @@ -25,6 +25,8 @@ Get started with one of the quick links below, or read on to learn more about Bu {% arrowbutton href="/docs/installation" text="Install Bun" /%} {% arrowbutton href="/docs/quickstart" text="Do the quickstart" /%} {% arrowbutton href="/docs/cli/install" text="Install a package" /%} +{% arrowbutton href="//docs/templates" text="Use a project template" /%} +{% arrowbutton href="/docs/cli/build" text="Bundle code for production" /%} {% arrowbutton href="/docs/api/http" text="Build an HTTP server" /%} {% arrowbutton href="/docs/api/websockets" text="Build a Websocket server" /%} {% arrowbutton href="/docs/api/file-io" text="Read and write files" /%} diff --git a/docs/nav.ts b/docs/nav.ts index f1dd92c35..448d661af 100644 --- a/docs/nav.ts +++ b/docs/nav.ts @@ -166,6 +166,9 @@ export default { page("bundler/plugins", "Plugins", { description: `Implement custom loaders and module resolution logic with Bun's plugin system.`, }), + page("bundler/executables", "Executables", { + description: "Compile a TypeScript or JavaScript file to a standalone cross-platform executable", + }), page("bundler/migration", "Migration", { description: `Guides for migrating from other bundlers to Bun.`, }), diff --git a/packages/bun-types/bun.d.ts b/packages/bun-types/bun.d.ts index 3dea90969..dffe37b35 100644 --- a/packages/bun-types/bun.d.ts +++ b/packages/bun-types/bun.d.ts @@ -999,28 +999,12 @@ declare module "bun" { path: string; loader: Loader; hash: string | null; - kind: "entry-point" | "chunk"; + kind: "entry-point" | "chunk" | "asset" | "sourcemap"; sourcemap: BuildArtifact | null; } - interface SourceMapBuildArtifact extends Blob { - path: string; - loader: Loader; - hash: null; - kind: "sourecemap"; - sourcemap: null; - } - - interface AssetBuildArtifact extends Blob { - path: string; - loader: Loader; - hash: string; - kind: "asset"; - sourcemap: null; - } - interface BuildOutput { - outputs: Array<BuildArtifact | AssetBuildArtifact | SourceMapBuildArtifact>; + outputs: Array<BuildArtifact>; success: boolean; logs: Array<BuildMessage | ResolveMessage>; } @@ -2751,7 +2735,7 @@ declare module "bun" { /** * The config object passed to `Bun.build` as is. Can be mutated. */ - config: BuildConfig & { plugins: BunPlugin[]; }; + config: BuildConfig & { plugins: BunPlugin[] }; } interface BunPlugin { |