Compiler Pipeline
When you run npm run build (or any other
bedrock-build command), a small,
deterministic pipeline runs. Knowing what each stage does makes
debugging build issues much shorter, and explains why the workspace is
shaped the way it is.
This page covers the pipeline for each of the four subcommands
(build, watch, deploy, pack). For flag-level reference, see the
CLI reference.
The flow at a glance
src/main.ts ─► esbuild ─► dist/packs/BP/scripts/main.js
packs/BP/* ─► copy ─► dist/packs/BP/
packs/RP/* ─► copy ─► dist/packs/RP/Two operations, run in parallel for speed. One bundles TypeScript into
a single JavaScript file. The other mirrors pack assets from source to
output. The compiler owns the dist/ tree and only writes there.
build mode
The standard npm run build invocation goes through these steps:
- Load config. Parse
bedrock.config.json, apply defaults, validate required fields, and fail fast with exit code 2 on schema problems. - Optional clean. If
--cleanis passed, remove<out>/recursively before writing anything. - Bundle the entry. Run esbuild on
entry(defaultsrc/main.ts) and write the result to<out>/packs/BP/scripts/main.js. - Copy BP assets. Walk
<packs.bp>/and copy everything into<out>/packs/BP/, except for anyscripts/folder at the BP root (that's reserved for the bundled output). - Copy RP assets. Walk
<packs.rp>/and copy everything into<out>/packs/RP/. - Log the result. Elapsed time plus a one-line summary.
The esbuild config
The bundler is pinned to a single configuration. It is not user- overridable in v1, by design.
| Option | Value |
|---|---|
target | es2020 |
format | esm |
platform | neutral |
bundle | true |
external | ["@minecraft/server", "@minecraft/server-ui", "@minecraft/server-net", "@minecraft/server-admin", "@minecraft/server-gametest"] |
sourcemap | inline (dev) / false (release) |
minify | false (dev) / true (release) |
The @minecraft/server* family of modules is marked external because
Minecraft provides them at runtime. Bundling them in would balloon the
output and break the runtime resolver.
Dev mode vs release mode
| Mode | Sourcemaps | Minified | NODE_ENV | When to use |
|---|---|---|---|---|
| dev (default) | inline | no | unset | local iteration, hot reload, debugging |
release (--release) | none | yes | production | distribution, .mcaddon packaging, CI |
Inline sourcemaps make the bundled JS bigger but let you map runtime errors back to TypeScript lines, which matters during development. Release mode strips them and minifies for smaller, faster-loading packs. See Dev vs Release builds for the longer take.
watch mode
npm run watch runs an initial dev build, then keeps two watchers
alive:
- esbuild's watch context for the entry file and everything it imports. Source changes trigger an incremental rebuild, which is fast (typically single-digit milliseconds after the first cold build).
- chokidar for
<packs.bp>/**/*(excluding itsscripts/folder) and<packs.rp>/**/*. Pack file changes copy that single file to itsdist/mirror. No full re-walk on each save.
Changes are debounced 50ms so a save that triggers a flurry of file events (common on Windows) collapses into one rebuild. Each rebuild logs a timestamp plus what changed. Ctrl+C exits cleanly.
Watch mode never minifies and never deploys. If you want
deploy-on-save, use npm run deploy:watch (next section).
deploy mode
npm run deploy runs a build first, then takes over the copying step
at the other end of the pipeline:
- Run
build(honoring--releaseif passed). - Resolve the deploy target from
deploy.target: forretail, walk six known Bedrock install layouts (modern launcher, Microsoft Store UWP, Education, Preview variants) and pick the first that exists. Forcustom, usedeploy.customPathdirectly. - Remove
<comMojang>/development_behavior_packs/<name>/and<comMojang>/development_resource_packs/<name>/(clean slate). - Copy
dist/packs/BP/anddist/packs/RP/into those locations. - If
--watchis set, stay running and re-deploy on every rebuild.
The clean-slate copy is deliberate. If a file is deleted from the
source pack, you want it gone from the deployed pack too. Anything else
risks "ghost files" that disappear on the next clean build but linger
in Minecraft. See the
hot reload guide for the end-to-end
flow including the /reload step inside Minecraft.
pack mode
npm run pack is the distribution path. It always runs a release
build, then zips the output into a .mcaddon:
<name>-<version>.mcaddon
<name>_BP/ ← contents of dist/packs/BP/
<name>_RP/ ← contents of dist/packs/RP/Two subfolders inside the zip, named <name>_BP and <name>_RP. This
is the layout Minecraft expects when a user double-clicks the
.mcaddon to install: both packs land in one step. See
Packaging a .mcaddon for the
distribution side.
pack always implies --release. There is no --dev-pack flag.
Shipping a dev build (with inline sourcemaps and no minify) wastes
file size and exposes source-mapped code paths users don't need. If
you find yourself reaching for "pack in dev mode," what you probably
want is deploy --watch to test changes live.
Where this is configured
The compiler's behavior is driven entirely by
bedrock.config.json. The most
relevant fields:
entry: the TypeScript entry file esbuild bundles.packs.bpandpacks.rp: the source directories copied intodist/.out: where everything lands.deploy.targetanddeploy.customPath: where thedeploycommand sends output.
Related
- CLI reference for every flag and every exit code.
- Dev vs Release builds for when to flip the mode.
- Hot reload setup for the full edit-save-reload loop.
- Workspace Layout for what lives where on disk.