Skip to content

Commit

Permalink
Introduce bytecode caching, polish "cjs" bundler output format (#14232
Browse files Browse the repository at this point in the history
)

Co-authored-by: Dylan Conway <[email protected]>
  • Loading branch information
Jarred-Sumner and dylan-conway authored Oct 1, 2024
1 parent 857a472 commit 2f7ff95
Show file tree
Hide file tree
Showing 39 changed files with 1,284 additions and 234 deletions.
27 changes: 9 additions & 18 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ BUN_RELEASE_BIN = $(PACKAGE_DIR)/bun
PRETTIER ?= $(shell which prettier 2>/dev/null || echo "./node_modules/.bin/prettier")
ESBUILD = "$(shell which esbuild 2>/dev/null || echo "./node_modules/.bin/esbuild")"
DSYMUTIL ?= $(shell which dsymutil 2>/dev/null || which dsymutil-15 2>/dev/null)
WEBKIT_DIR ?= $(realpath src/bun.js/WebKit)
WEBKIT_DIR ?= $(realpath vendor/WebKit)
WEBKIT_RELEASE_DIR ?= $(WEBKIT_DIR)/WebKitBuild/Release
WEBKIT_DEBUG_DIR ?= $(WEBKIT_DIR)/WebKitBuild/Debug
WEBKIT_RELEASE_DIR_LTO ?= $(WEBKIT_DIR)/WebKitBuild/ReleaseLTO
Expand Down Expand Up @@ -138,8 +138,8 @@ endif
SED = $(shell which gsed 2>/dev/null || which sed 2>/dev/null)

BUN_DIR ?= $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))
BUN_DEPS_DIR ?= $(shell pwd)/src/deps
BUN_DEPS_OUT_DIR ?= $(shell pwd)/build/bun-deps
BUN_DEPS_DIR ?= $(shell pwd)/vendor
BUN_DEPS_OUT_DIR ?= $(shell pwd)/build/release
CPU_COUNT = 2
ifeq ($(OS_NAME),darwin)
CPU_COUNT = $(shell sysctl -n hw.logicalcpu)
Expand Down Expand Up @@ -689,19 +689,10 @@ assert-deps:
@test $(shell cargo --version | awk '{print $$2}' | cut -d. -f2) -gt 57 || (echo -e "ERROR: cargo version must be at least 1.57."; exit 1)
@echo "You have the dependencies installed! Woo"

# the following allows you to run `make submodule` to update or init submodules. but we will exclude webkit
# unless you explicitly clone it yourself (a huge download)
SUBMODULE_NAMES=$(shell cat .gitmodules | grep 'path = ' | awk '{print $$3}')
ifeq ("$(wildcard src/bun.js/WebKit/.git)", "")
SUBMODULE_NAMES := $(filter-out src/bun.js/WebKit, $(SUBMODULE_NAMES))
endif

.PHONY: init-submodules
init-submodules: submodule # (backwards-compatibility alias)

.PHONY: submodule
submodule: ## to init or update all submodules
git submodule update --init --recursive --progress --depth=1 --checkout $(SUBMODULE_NAMES)

.PHONY: build-obj
build-obj:
Expand Down Expand Up @@ -804,7 +795,7 @@ cls:
@echo -e "\n\n---\n\n"

jsc-check:
@ls $(JSC_BASE_DIR) >/dev/null 2>&1 || (echo -e "Failed to access WebKit build. Please compile the WebKit submodule using the Dockerfile at $(shell pwd)/src/javascript/WebKit/Dockerfile and then copy from /output in the Docker container to $(JSC_BASE_DIR). You can override the directory via JSC_BASE_DIR. \n\n DOCKER_BUILDKIT=1 docker build -t bun-webkit $(shell pwd)/src/bun.js/WebKit -f $(shell pwd)/src/bun.js/WebKit/Dockerfile --progress=plain\n\n docker container create bun-webkit\n\n # Get the container ID\n docker container ls\n\n docker cp DOCKER_CONTAINER_ID_YOU_JUST_FOUND:/output $(JSC_BASE_DIR)" && exit 1)
@ls $(JSC_BASE_DIR) >/dev/null 2>&1 || (echo -e "Failed to access WebKit build. Please compile the WebKit submodule using the Dockerfile at $(shell pwd)/src/javascript/WebKit/Dockerfile and then copy from /output in the Docker container to $(JSC_BASE_DIR). You can override the directory via JSC_BASE_DIR. \n\n DOCKER_BUILDKIT=1 docker build -t bun-webkit $(shell pwd)/vendor/WebKit -f $(shell pwd)/vendor/WebKit/Dockerfile --progress=plain\n\n docker container create bun-webkit\n\n # Get the container ID\n docker container ls\n\n docker cp DOCKER_CONTAINER_ID_YOU_JUST_FOUND:/output $(JSC_BASE_DIR)" && exit 1)
@ls $(JSC_INCLUDE_DIR) >/dev/null 2>&1 || (echo "Failed to access WebKit include directory at $(JSC_INCLUDE_DIR)." && exit 1)
@ls $(JSC_LIB) >/dev/null 2>&1 || (echo "Failed to access WebKit lib directory at $(JSC_LIB)." && exit 1)

Expand Down Expand Up @@ -945,7 +936,7 @@ jsc-bindings: headers bindings

.PHONY: clone-submodules
clone-submodules:
git -c submodule."src/bun.js/WebKit".update=none submodule update --init --recursive --depth=1 --progress
git -c submodule."vendor/WebKit".update=none submodule update --init --recursive --depth=1 --progress


.PHONY: headers
Expand Down Expand Up @@ -1265,7 +1256,7 @@ jsc-build-mac-compile:
-DENABLE_STATIC_JSC=ON \
-DENABLE_SINGLE_THREADED_VM_ENTRY_SCOPE=ON \
-DALLOW_LINE_AND_COLUMN_NUMBER_IN_BUILTINS=ON \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DUSE_THIN_ARCHIVES=OFF \
-DBUN_FAST_TLS=ON \
-DENABLE_FTL_JIT=ON \
Expand All @@ -1277,7 +1268,7 @@ jsc-build-mac-compile:
$(WEBKIT_DIR) \
$(WEBKIT_RELEASE_DIR) && \
CFLAGS="$(CFLAGS) -ffat-lto-objects" CXXFLAGS="$(CXXFLAGS) -ffat-lto-objects" \
cmake --build $(WEBKIT_RELEASE_DIR) --config Release --target jsc
cmake --build $(WEBKIT_RELEASE_DIR) --config RelWithDebInfo --target jsc

.PHONY: jsc-build-mac-compile-lto
jsc-build-mac-compile-lto:
Expand Down Expand Up @@ -1379,7 +1370,7 @@ jsc-build-linux-compile-config-debug:
$(WEBKIT_DEBUG_DIR)

# If you get "Error: could not load cache"
# run rm -rf src/bun.js/WebKit/CMakeCache.txt
# run rm -rf vendor/WebKit/CMakeCache.txt
.PHONY: jsc-build-linux-compile-build
jsc-build-linux-compile-build:
mkdir -p $(WEBKIT_RELEASE_DIR) && \
Expand Down Expand Up @@ -1414,7 +1405,7 @@ jsc-build-copy-debug:
cp $(WEBKIT_DEBUG_DIR)/lib/libbmalloc.a $(BUN_DEPS_OUT_DIR)/libbmalloc.a

clean-jsc:
cd src/bun.js/WebKit && rm -rf **/CMakeCache.txt **/CMakeFiles && rm -rf src/bun.js/WebKit/WebKitBuild
cd vendor/WebKit && rm -rf **/CMakeCache.txt **/CMakeFiles && rm -rf vendor/WebKit/WebKitBuild
clean-bindings:
rm -rf $(OBJ_DIR)/*.o $(DEBUG_OBJ_DIR)/*.o $(DEBUG_OBJ_DIR)/webcore/*.o $(DEBUG_BINDINGS_OBJ) $(OBJ_DIR)/webcore/*.o $(BINDINGS_OBJ) $(OBJ_DIR)/*.d $(DEBUG_OBJ_DIR)/*.d

Expand Down
2 changes: 1 addition & 1 deletion cmake/tools/SetupWebKit.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -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 4a2db3254a9535949a5d5380eb58cf0f77c8e15a)
set(WEBKIT_VERSION 76798f7b2fb287ee9f1ecce98bae895a2d026d93)
endif()

if(WEBKIT_LOCAL)
Expand Down
20 changes: 19 additions & 1 deletion docs/bundler/executables.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,12 +100,30 @@ When deploying to production, we recommend the following:
bun build --compile --minify --sourcemap ./path/to/my/app.ts --outfile myapp
```

**What do these flags do?**
### Bytecode compilation

To improve startup time, enable bytecode compilation:

```sh
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!

### What do these flags do?

The `--minify` argument optimizes the size of the transpiled output code. If you have a large application, this can save megabytes of space. For smaller applications, it might still improve start time a little.

The `--sourcemap` argument embeds a sourcemap compressed with zstd, so that errors & stacktraces point to their original locations instead of the transpiled location. Bun will automatically decompress & resolve the sourcemap when an error occurs.

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.

## Worker

To use workers in a standalone executable, add the worker's entrypoint to the CLI arguments:
Expand Down
122 changes: 100 additions & 22 deletions docs/bundler/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,8 @@ Depending on the target, Bun will apply different module resolution rules and op

If any entrypoints contains a Bun shebang (`#!/usr/bin/env bun`) the bundler will default to `target: "bun"` instead of `"browser"`.

When using `target: "bun"` and `format: "cjs"` together, the `// @bun @bun-cjs` pragma is added and the CommonJS wrapper function is not compatible with Node.js.

---

- `node`
Expand Down Expand Up @@ -1157,6 +1159,11 @@ Each artifact also contains the following properties:

---

- `bytecode`
- Generate bytecode for any JavaScript/TypeScript entrypoints. This can greatly improve startup times for large applications. Only supported for `"cjs"` format, only supports `"target": "bun"` and dependent on a matching version of Bun. This adds a corresponding `.jsc` file for each entrypoint

---

- `sourcemap`
- The sourcemap file corresponding to this file, if generated. Only defined for entrypoints and chunks.

Expand Down Expand Up @@ -1266,33 +1273,104 @@ interface Bun {
build(options: BuildOptions): Promise<BuildOutput>;
}

interface BuildOptions {
entrypoints: string[]; // required
outdir?: string; // default: no write (in-memory only)
format?: "esm"; // later: "cjs" | "iife"
target?: "browser" | "bun" | "node"; // "browser"
splitting?: boolean; // true
plugins?: BunPlugin[]; // [] // See https://bun.sh/docs/bundler/plugins
loader?: { [k in string]: Loader }; // See https://bun.sh/docs/bundler/loaders
manifest?: boolean; // false
external?: string[]; // []
sourcemap?: "none" | "inline" | "linked" | "external" | "linked" | boolean; // "none"
root?: string; // computed from entrypoints
interface BuildConfig {
entrypoints: string[]; // list of file path
outdir?: string; // output directory
target?: Target; // default: "browser"
/**
* Output module format. Top-level await is only supported for `"esm"`.
*
* Can be:
* - `"esm"`
* - `"cjs"` (**experimental**)
* - `"iife"` (**experimental**)
*
* @default "esm"
*/
format?: /**
* ECMAScript Module format
*/
| "esm"
/**
* CommonJS format
* **Experimental**
*/
| "cjs"
/**
* IIFE format
* **Experimental**
*/
| "iife";
naming?:
| string
| {
entry?: string; // '[dir]/[name].[ext]'
chunk?: string; // '[name]-[hash].[ext]'
asset?: string; // '[name]-[hash].[ext]'
};
publicPath?: string; // e.g. http://mydomain.com/
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<string, string>;
// origin?: string; // e.g. http://mydomain.com
loader?: { [k in string]: Loader };
sourcemap?: "none" | "linked" | "inline" | "external" | "linked"; // default: "none", true -> "inline"
/**
* package.json `exports` conditions used when resolving imports
*
* Equivalent to `--conditions` in `bun build` or `bun run`.
*
* https://nodejs.org/api/packages.html#exports
*/
conditions?: Array<string> | string;
minify?:
| boolean // false
| boolean
| {
identifiers?: boolean;
whitespace?: boolean;
syntax?: boolean;
identifiers?: boolean;
};
/**
* Ignore dead code elimination/tree-shaking annotations such as @__PURE__ and package.json
* "sideEffects" fields. This should only be used as a temporary workaround for incorrect
* annotations in libraries.
*/
ignoreDCEAnnotations?: boolean;
/**
* 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
* start times, but will make the final output larger and slightly increase
* memory usage.
*
* Bytecode is currently only supported for CommonJS (`format: "cjs"`).
*
* Must be `target: "bun"`
* @default false
*/
bytecode?: boolean;
}

interface BuildOutput {
Expand All @@ -1304,9 +1382,9 @@ interface BuildOutput {
interface BuildArtifact extends Blob {
path: string;
loader: Loader;
hash?: string;
kind: "entry-point" | "chunk" | "asset" | "sourcemap";
sourcemap?: BuildArtifact;
hash: string | null;
kind: "entry-point" | "chunk" | "asset" | "sourcemap" | "bytecode";
sourcemap: BuildArtifact | null;
}

type Loader =
Expand Down
2 changes: 1 addition & 1 deletion docs/bundler/vs-esbuild.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ In Bun's CLI, simple boolean flags like `--minify` do not accept an argument. Ot

- `--format`
- `--format`
- Bun only supports `"esm"` currently but other module formats are planned. esbuild defaults to `"iife"`.
- Bun supports `"esm"` and `"cjs"` currently, but more module formats are planned. esbuild defaults to `"iife"`.

---

Expand Down
42 changes: 38 additions & 4 deletions packages/bun-types/bun.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1497,13 +1497,35 @@ declare module "bun" {
kind: ImportKind;
}

type ModuleFormat = "esm"; // later: "cjs", "iife"

interface BuildConfig {
entrypoints: string[]; // list of file path
outdir?: string; // output directory
target?: Target; // default: "browser"
format?: ModuleFormat; // later: "cjs", "iife"
/**
* Output module format. Top-level await is only supported for `"esm"`.
*
* Can be:
* - `"esm"`
* - `"cjs"` (**experimental**)
* - `"iife"` (**experimental**)
*
* @default "esm"
*/
format?: /**
* ECMAScript Module format
*/
| "esm"
/**
* CommonJS format
* **Experimental**
*/
| "cjs"
/**
* IIFE format
* **Experimental**
*/
| "iife";
naming?:
| string
| {
Expand Down Expand Up @@ -1561,6 +1583,18 @@ declare module "bun" {
// /** Only works when runtime=automatic */
// importSource?: string; // default: "react"
// };

/**
* Generate bytecode for the output. This can dramatically improve cold
* start times, but will make the final output larger and slightly increase
* memory usage.
*
* Bytecode is currently only supported for CommonJS (`format: "cjs"`).
*
* Must be `target: "bun"`
* @default false
*/
bytecode?: boolean;
}

namespace Password {
Expand Down Expand Up @@ -1781,7 +1815,7 @@ declare module "bun" {
path: string;
loader: Loader;
hash: string | null;
kind: "entry-point" | "chunk" | "asset" | "sourcemap";
kind: "entry-point" | "chunk" | "asset" | "sourcemap" | "bytecode";
sourcemap: BuildArtifact | null;
}

Expand Down
Loading

0 comments on commit 2f7ff95

Please sign in to comment.