Audience: architects, platform engineers, plugin authors, and contributors who need to understand how Parcel is built, configure it deeply, embed it, operate it at scale, or extend it. For day-to-day usage and the full CLI option reference, see the User Guide.
Note
This guide documents Parcel v2.16.4 (
parcelon npm). It is a Yarn-workspaces monorepo of ~80 published packages plus a set of Rust crates that provide native-speed implementations of the hottest code paths. Minimum runtime: Node.js ≥ 16.
Table of Contents
- Executive Summary
- Architecture Overview
- Repository Structure
- The Plugin Pipeline
- The Build Orchestration Model
- System Requirements
- Configuration Reference
- The Plugin System (Authoring)
- The Native (Rust) Layer
- Caching & Incrementality
- Concurrency: The Worker Farm
- Programmatic API & Embedding
- Security Analysis
- Operations & Observability
- Troubleshooting (Operational)
- Code Quality & Conventions
- Potential Improvements
- Contributing
- Quick Reference
1. Executive Summary
Parcel is a web application bundler whose defining trait is zero configuration: a single default config (@parcel/config-default) maps every supported file type to a compiler, so most projects ship without a config file. Its architecture is a plugin pipeline — resolvers, transformers, bundlers, namers, runtimes, packagers, optimizers, compressors, reporters, and validators — orchestrated by a content-addressable request graph that makes rebuilds incremental and cache-persistent across restarts.
Three engineering decisions define the system:
- Plugin pipeline over a monolith. Every stage of the build is a swappable plugin selected by glob in
.parcelrc. The core orchestrates; the plugins do the work. - Native hot paths in Rust. The JavaScript/TypeScript compiler (SWC), the HTML transformer, and the module resolver are implemented in Rust and exposed to Node via N-API bindings.
- Aggressive, persistent caching with parallelism. A request graph tracks fine-grained dependencies; a worker farm fans transformations across all CPU cores; results persist in
.parcel-cache(LMDB or filesystem).
| Attribute | Value |
|---|---|
| Package name | parcel |
| Version | 2.16.4 |
| License | MIT |
| Language(s) | JavaScript (Flow-typed), Rust |
| Runtime | Node.js ≥ 16 |
| Repo model | Yarn 4 workspaces monorepo (packages/*/*) + Cargo workspace (crates/*) |
| Release tooling | Lerna |
| CLI entry | parcel → packages/core/parcel/src/bin.js → cli.js |
| Core engine | @parcel/core → packages/core/core/src/Parcel.js |
2. Architecture Overview
The asset pipeline
┌─────────────────────────────────────────────────────────┐
entry points ───► │ RESOLVER(s) specifier → file path │
│ TRANSFORMER(s) compile each asset (per file-type) │
│ BUNDLER group assets into bundles │
│ NAMER(s) assign output filenames │
│ RUNTIME(s) inject runtime code (HMR, loaders, …) │
│ PACKAGER(s) combine a bundle's assets into output │
│ OPTIMIZER(s) minify / transform final output │
│ COMPRESSOR(s) gzip/brotli/raw variants │
└─────────────────────────────────────────────────────────┘
│
▼
dist/ + REPORTER(s) (dev server, CLI, tracer)
VALIDATOR(s) run in parallel (type-check, lint)
Each stage is a plugin category. The default mapping of file globs to plugins lives in packages/configs/default/index.json (see §4).
The orchestration core (@parcel/core)
The Parcel class (packages/core/core/src/Parcel.js) is the conductor. It does not transform files itself; instead it:
- Resolves options (
resolveOptions.js) and loads the merged config (loadParcelConfig.js). - Starts a worker farm and a shared options reference.
- Opens the cache and initializes a RequestTracker.
- Runs a
ParcelBuildRequestthrough the request tracker, producing a BundleGraph. - Emits build events to reporters (the dev server and CLI are reporters).
Graph data structures (@parcel/graph)
Parcel models the entire build as graphs. The packages/core/graph/src package provides the primitives:
| Structure | Role |
|---|---|
Graph / ContentGraph |
Generic directed graph with typed edges; content-keyed nodes |
AdjacencyList |
Cache-friendly, compact edge storage (typed-array backed) |
| Asset graph | All source assets and their dependencies |
| Bundle graph | The asset graph grouped into bundles and their relationships |
| Request graph | The dependency graph of build requests — the basis of incrementality |
Note
The
AdjacencyListis a hand-optimized, typed-array-backed edge store (seedocs/AdjacencyList.mdin the repo). It exists because the build graphs can contain hundreds of thousands of edges; a naive object-of-arrays representation would dominate memory and GC time.
Native acceleration (Rust)
Performance-critical work is delegated to Rust crates compiled to native Node addons (and WASM for the browser REPL). See §9.
3. Repository Structure
Parcel is a monorepo. The Yarn workspaces glob is packages/*/*, so every package lives two levels deep under packages/<category>/<name>.
| Path | Category | What lives here |
|---|---|---|
packages/core/* |
Core | The engine: core (orchestrator), parcel (CLI), graph, cache, fs, workers, package-manager, diagnostic, codeframe, logger, profiler, types/types-internal, plugin, register, feature-flags, utils |
packages/resolvers/* |
Resolvers | default, glob, repl-runtimes |
packages/transformers/* |
Transformers | js, babel, css, postcss, html, posthtml, sass, less, stylus, typescript-types, typescript-tsc, vue, elm, image, svg, json, toml, yaml, graphql, glsl, webextension, … |
packages/bundlers/* |
Bundlers | default, library |
packages/namers/* |
Namers | default |
packages/runtimes/* |
Runtimes | js, hmr, service-worker, webextension, rsc |
packages/packagers/* |
Packagers | js, css, html, svg, xml, ts, wasm, raw, raw-url, react-static, webextension |
packages/optimizers/* |
Optimizers | swc, css, cssnano, htmlnano, html, image, svg, svgo, terser, esbuild, data-url, blob-url, inline-requires |
packages/compressors/* |
Compressors | raw, gzip, brotli |
packages/validators/* |
Validators | typescript, eslint |
packages/reporters/* |
Reporters | cli, dev-server, dev-server-sw, json, tracer, bundle-analyzer, bundle-stats, bundle-buddy, build-metrics, lsp-reporter, sourcemap-visualiser |
packages/configs/* |
Configs | default (the zero-config .parcelrc), repl, webextension |
packages/utils/* |
Utilities | create-parcel, create-react-app, node-resolver-core, node-resolver-rs, parcel-lsp, babel-preset-env, rsc, error-overlay, … |
packages/dev/* |
Dev tooling | repl, parcel-link, bundle-stats-cli, babel-register, eslint-*, esm-fuzzer |
packages/examples/* |
Examples | Reference apps (html, react-hmr, react-server-components, ts-example, kitchen-sink, …) |
crates/* |
Rust | core, parcel-resolver, html, macros, node-bindings, dev-dep-resolver |
packages/transformers/js/core |
Rust (SWC) | The Rust source of the JS/TS transformer |
Tip
When tracing a bug, start at the package that owns the stage where it manifests. A wrong output filename →
namers/default; a broken minified JS bundle →optimizers/swcorpackagers/js; a resolution failure →resolvers/default+crates/parcel-resolver.
Important
The JS source is type-checked with Flow (
// @flowheaders), not TypeScript. Runyarn check(flow check) when modifying core. Public plugin types are published as TypeScript declarations from@parcel/types.
4. The Plugin Pipeline
The zero-config behavior is entirely data: the default config (packages/configs/default/index.json) is a .parcelrc that wires every plugin. This is the single most important architecture artifact — it is the pipeline.
Default transformer map (by glob)
| File glob | Transformer chain |
|---|---|
*.{js,mjs,jsm,jsx,es6,cjs,ts,tsx} |
@parcel/transformer-babel → @parcel/transformer-js → @parcel/transformer-react-refresh-wrap |
*.mdx |
@parcel/transformer-mdx (if installed) → @parcel/transformer-js |
*.{css,pcss} |
@parcel/transformer-postcss → @parcel/transformer-css |
*.{sass,scss} |
@parcel/transformer-sass |
*.less |
@parcel/transformer-less |
*.{styl,stylus} |
@parcel/transformer-stylus |
*.sss |
@parcel/transformer-sugarss |
*.{htm,html,xhtml} |
@parcel/transformer-posthtml → @parcel/transformer-html |
*.pug |
@parcel/transformer-pug |
*.coffee |
@parcel/transformer-coffeescript |
*.elm |
@parcel/transformer-elm |
*.vue (+ template:/script:/style:/custom:) |
@parcel/transformer-vue |
*.{json,json5} |
@parcel/transformer-json |
*.jsonld |
@parcel/transformer-jsonld |
*.toml |
@parcel/transformer-toml |
*.{yaml,yml} |
@parcel/transformer-yaml |
*.{glsl,vert,frag} |
@parcel/transformer-glsl |
*.{gql,graphql} |
@parcel/transformer-graphql |
*.{png,jpg,jpeg,webp,gif,tiff,avif,heic,heif} |
@parcel/transformer-image |
*.svg |
@parcel/transformer-svg |
*.{xml,rss,atom} |
@parcel/transformer-xml |
*.{webmanifest} / webmanifest:* |
@parcel/transformer-webmanifest |
*.node |
@parcel/transformer-node |
types:*.{ts,tsx} |
@parcel/transformer-typescript-types |
bundle-text:*, data-url:* |
(...) → @parcel/transformer-inline-string |
worklet:*.{…} |
@parcel/transformer-worklet → (...) |
react-static:* |
@parcel/transformer-react-static → (...) |
url:* |
(...) → @parcel/transformer-raw |
The "..." token means "splice in the default pipeline for this glob here" — it's how named pipelines extend rather than replace the defaults.
Default remaining stages
"namers": ["@parcel/namer-default"],
"runtimes": ["@parcel/runtime-rsc", "@parcel/runtime-js",
"@parcel/runtime-browser-hmr", "@parcel/runtime-service-worker"],
"optimizers": { "*.css": ["@parcel/optimizer-css"],
"*.{html,xhtml}": ["@parcel/optimizer-html"],
"*.{js,mjs,cjs}": ["@parcel/optimizer-swc"],
"*.svg": ["@parcel/optimizer-svg"],
"*.{jpg,jpeg,png}": ["@parcel/optimizer-image"],
"data-url:*": ["...", "@parcel/optimizer-data-url"] },
"packagers": { "*.{html,xhtml}": "@parcel/packager-html", "*.css": "@parcel/packager-css",
"*.{js,mjs,cjs}": "@parcel/packager-js", "*.svg": "@parcel/packager-svg",
"*.{xml,rss,atom}": "@parcel/packager-xml", "*.ts": "@parcel/packager-ts",
"*.wasm": "@parcel/packager-wasm",
"react-static:*.html": "@parcel/packager-react-static",
"*.{jsonld,svg,webmanifest}": "@parcel/packager-raw-url", "*": "@parcel/packager-raw" },
"compressors": { "*": ["@parcel/compressor-raw"] },
"resolvers": ["@parcel/resolver-default"],
"reporters": ["@parcel/reporter-dev-server"]
Customizing the pipeline
Create a .parcelrc in your project root that extends the default and adds/overrides only what you need:
{
"extends": "@parcel/config-default",
"transformers": {
"*.{md}": ["my-markdown-transformer"]
},
"reporters": ["...", "@parcel/reporter-bundle-analyzer"]
}
Caution
Omitting
"extends": "@parcel/config-default"replaces the entire zero-config pipeline with whatever you list — almost never what you want. Always extend the default unless you are deliberately building a fully custom pipeline.
The CLI flags --config <path> and --reporter <name> let you point at an alternate config or add a reporter for a single run without editing .parcelrc.
5. The Build Orchestration Model
Lifecycle of Parcel.run()
packages/core/core/src/Parcel.js:
_init()(lazy, runs once): init sourcemaps & Rust →resolveOptions()→loadParcelConfig()→ create/adopt WorkerFarm →cache.ensure()→ create a shared options reference → build the ReporterRunner →RequestTracker.init()._build(): enable profiling/tracing if requested → emitbuildStart→ invalidate build-time nodes → create aParcelBuildRequest→requestTracker.runRequest(request, {force: true})→ receive{bundleGraph, bundleInfo, changedAssets, assetRequests}→ assemble abuildSuccessevent carrying aBundleGraph<IPackagedBundle>._end():requestTracker.writeToCache()then dispose resources.- On
buildFailure, throw aBuildErrorcarryingdiagnostics.
Watch mode
watch(cb) initializes once, subscribes to the filesystem watcher, kicks off a first build through a single-concurrency PromiseQueue, and returns an AsyncSubscription. Each change aborts the in-flight build via an AbortController and starts the next. unsubscribe() tears down the watcher, runs watchEnd, and ends the instance when the last watcher detaches.
Incrementality via the request graph
The RequestTracker records every build request (resolve, transform, bundle, package, …) and the file/option/env invalidations each depends on. On the next run it re-executes only requests whose inputs changed. Because the request graph is serialized to the cache, this incrementality survives process restarts — the source of Parcel's "second build is instant" behavior.
Note
Error: Lazy bundling does not work with content hashing(resolveOptions.js:145) is enforced here: lazily-built async bundles can't have stable content hashes, so the two features are mutually exclusive by design.
6. System Requirements
| Component | Requirement | Notes |
|---|---|---|
| Node.js | ≥ 16.0.0 | Enforced via engines.node. Use an LTS release. |
| CPU | Multi-core recommended | Parcel parallelizes across cores via the worker farm |
| Native binaries | OS/arch must be supported | Prebuilt Rust addons ship as optional deps for macOS, Linux, Windows (and FreeBSD/OpenBSD watcher support) |
| Disk | Space for .parcel-cache |
Cache grows with project size; safe to delete |
| Watchman (optional) | Recommended on Linux | Improves file-watching reliability (see User Guide §6) |
For contributors (building Parcel itself)
Per CONTRIBUTING.md, you additionally need:
- Yarn v4 (the repo pins
packageManager: yarn@4.9.1; Corepack-enabled) - Rust (stable) —
rust-toolchainpins the channel; native builds viayarn build-native - Flow — for type-checking the JS source
yarn # install workspaces
yarn build-native # compile the Rust crates to native addons
yarn build # build JS bundles + run gulp pipeline
7. Configuration Reference
Parcel reads configuration from three places, in order of locality: CLI flags (highest), .parcelrc (pipeline), and package.json (targets/source/browser support).
.parcelrc
The plugin pipeline (see §4). Supports extends, per-glob plugin arrays, and the "..." splice token.
package.json fields
| Field | Purpose |
|---|---|
source |
Entry source file(s) when not passed on the CLI |
main / module / browser / types |
Library output targets (each becomes a build target) |
targets |
Explicit target definitions: distDir, publicUrl, context, engines, outputFormat, isLibrary, optimize, sourceMap, scopeHoist |
browserslist |
Browser support matrix driving JS/CSS down-compilation |
@parcel/* keys |
Some plugins read scoped config keys from package.json |
Default target options (set by CLI)
normalizeOptions() in cli.js derives defaultTargetOptions from flags:
| Option | Default | Source flag |
|---|---|---|
shouldOptimize |
true in build, else false |
--no-optimize |
sourceMaps |
true |
--no-source-maps |
shouldScopeHoist |
enabled in build | --no-scope-hoist |
publicUrl |
/ |
--public-url |
distDir |
dist/ |
--dist-dir |
Resolved-option defaults (resolveOptions.js)
| Setting | Default |
|---|---|
| Cache directory | .parcel-cache (DEFAULT_CACHE_DIRNAME) |
| Cache backend | LMDBCache when available, else FSCache |
| Public URL | / |
| Dist directory | <cwd>/dist |
Environment variables
| Variable | Effect | Read at |
|---|---|---|
NODE_ENV |
Build mode default + exposed to user code/config | cli.js |
CI |
Disables auto-install in build mode | cli.js |
PORT / HMR_PORT / HMR_HOST |
Dev-server & HMR defaults | cli.js |
PARCEL_WORKERS |
Number of worker threads | WorkerFarm.js |
PARCEL_MAX_CONCURRENT_CALLS |
Max concurrent calls per worker | WorkerFarm.js |
PARCEL_WORKER_BACKEND |
threads or process |
workers/backend.js |
PARCEL_INCREMENTAL_BUNDLING |
false disables incremental bundling |
cli.js |
NODE_OPTIONS |
Forwarded to workers | workers/Worker.js |
PARCEL_BUILD_ENV / PARCEL_SELF_BUILD |
Internal: control Babel register on the CLI bin | bin.js |
PARCEL_DUMP_GRAPHVIZ / PARCEL_BUILD_REPL |
Debug: dump build graphs to GraphViz | dumpGraphToGraphViz.js |
SKIP_PLUGIN_COMPATIBILITY_CHECK |
Skip plugin/version compat check (used by canary releases) | loadParcelPlugin.js |
PARCEL_DEBUG_CACHE_FILEPATH |
Debug cache path | test-utils |
Feature flags
--feature-flag <name=value> toggles experimental behavior. The defaults (packages/core/feature-flags) are:
| Flag | Default |
|---|---|
exampleFeature |
false |
useWatchmanWatcher |
false |
parcel build src/index.html --feature-flag useWatchmanWatcher=true
Unknown flag names are ignored with a warning.
8. The Plugin System (Authoring)
Every plugin is an npm package whose default export is an instance of one of the wrapper classes from @parcel/plugin (packages/core/plugin/src/PluginAPI.js). You construct the wrapper with an options object containing your hook methods; the wrapper stashes it under the Symbol.for('parcel-plugin-config') key, which the core reads when loading the plugin (loadParcelPlugin.js).
Plugin categories
| Wrapper class | Responsibility |
|---|---|
Resolver |
Map an import specifier to a resolved file (or exclude it) |
Transformer |
Compile/transform a single asset; declare its dependencies |
Bundler |
Decide how assets are grouped into bundles |
Namer |
Produce the output filename for a bundle |
Runtime |
Inject extra assets/code into bundles (loaders, HMR client, …) |
Packager |
Combine a bundle's assets into final serialized output |
Optimizer |
Post-process packaged output (minify, transform) |
Compressor |
Emit compressed variants (gzip, brotli) |
Reporter |
React to build lifecycle events (progress, dev server, metrics) |
Validator |
Validate assets in parallel (type-check, lint) without blocking output |
Authoring example (a Transformer)
import {Transformer} from '@parcel/plugin';
export default new Transformer({
async loadConfig({config}) {
// optionally read project config; result is cached & invalidation-tracked
},
async transform({asset, options, logger}) {
let source = await asset.getCode();
let compiled = doSomething(source);
asset.setCode(compiled);
asset.type = 'js';
return [asset]; // may return multiple assets
},
});
Register it in .parcelrc:
{
"extends": "@parcel/config-default",
"transformers": { "*.foo": ["parcel-transformer-foo"] }
}
Important
Parcel checks plugin/engine compatibility at load time. The
Error: Plugin ${pluginName} has no exports.(loadParcelPlugin.js:232) means your package's default export is not a@parcel/pluginwrapper instance. TheSKIP_PLUGIN_COMPATIBILITY_CHECKenv var bypasses the version check and exists for canary releases — do not set it in normal use.
Tip
Plugin hooks receive an
optionsbag, a scopedlogger, and config helpers. Declaring file/config dependencies inside hooks is what lets Parcel invalidate and rebuild only what changed — always declare dependencies rather than reading files out of band.
9. The Native (Rust) Layer
Parcel implements its hottest paths in Rust, compiled to native Node addons via N-API (crates/node-bindings) and to WASM for the browser REPL.
| Crate | Role |
|---|---|
crates/node-bindings |
N-API bridge exposing Rust functions to Node (resolver, transformer, …) |
crates/parcel-resolver |
The module resolution algorithm (largest crate, ~207 KB of source) |
crates/html |
HTML parsing/serialization/optimization (~231 KB; backs the HTML transformer/optimizer) |
crates/macros |
Build-time macro evaluation |
crates/core |
Shared Rust core types |
crates/dev-dep-resolver |
Dev-dependency resolution |
packages/transformers/js/core |
The SWC-based JS/TS transformer (Rust; ~364 KB of source) |
Native binaries are loaded by packages/core/rust/index.js, which selects the correct prebuilt binary per platform/arch and throws a clear diagnostic when none matches:
Unsupported architecture on Linux: ${arch}
Failed to load native binding
Warning
Native addons are delivered as optional dependencies. Installing with
--no-optional/--omit=optional, or building images for an unsupported platform, yieldsFailed to load native binding. Always allow optional dependencies to install. WASM builds are available for the REPL butresolveAsync()/getInvalidations()are explicitly unsupported in WASM (crates/node-bindings/src/resolver.rs).
10. Caching & Incrementality
- Location:
.parcel-cache(override with--cache-dir; disable with--no-cache). - Backend:
LMDBCache(memory-mapped key-value store) when available, otherwiseFSCache(filesystem). Selected inresolveOptions.js. - Granularity: Per-request results keyed by content + invalidations, stored via the request graph and the serializer (
serializer.js,registerCoreWithSerializer.js). - Persistence: The request graph is written to the cache on
_end()/writeToCache(), so incrementality survives restarts. - Invalidation: File changes, env-var changes, and option changes invalidate dependent requests only.
Operational guidance:
| Situation | Action |
|---|---|
| Suspected stale build | rm -rf .parcel-cache or build once with --no-cache |
| Reproducible CI builds | Keep cache out of CI artifacts; rely on lockfile + content hashing |
| Disk pressure | .parcel-cache is safe to delete at any time |
Tip
Add
.parcel-cacheanddistto.gitignore. Never commit the cache — it's machine- and content-specific.
11. Concurrency: The Worker Farm
@parcel/workers (packages/core/workers) spreads transformation work across CPU cores.
| Concern | Mechanism |
|---|---|
| Worker count | Auto-detected CPU count, override with PARCEL_WORKERS |
| Backend | Worker threads or child processes (PARCEL_WORKER_BACKEND; backend.js) |
| Concurrency cap | PARCEL_MAX_CONCURRENT_CALLS per worker |
| Shared state | createSharedReference() shares the resolved options across workers without copying |
| Reuse | createWorkerFarm() (exported from @parcel/core) lets you reuse one farm across multiple Parcel instances |
Relevant guard rails (from WorkerFarm.js):
Please provide a worker path!
Cannot add a worker call if workerfarm is ending.
Could not detect cpu count!
Note
The
ERR_WORKER_INVALID_EXEC_ARGVcrash reported on newer Node releases originates in worker-threadexecArgvhandling. Pin to a supported Node LTS; if you must run newer Node, ensureNODE_OPTIONScarries no worker-incompatible flags.
12. Programmatic API & Embedding
Embed Parcel via @parcel/core. The package exports Parcel (default + named), BuildError, createWorkerFarm, and serializer helpers.
import {Parcel, createWorkerFarm} from '@parcel/core';
import {MemoryFS} from '@parcel/fs';
// Reuse one worker farm across many builds (e.g. a test runner or a CI matrix):
const workerFarm = createWorkerFarm();
const bundler = new Parcel({
entries: ['src/index.html'],
defaultConfig: '@parcel/config-default',
workerFarm,
mode: 'production',
defaultTargetOptions: {distDir: 'build', shouldOptimize: true},
});
const {bundleGraph, buildTime} = await bundler.run();
console.log(`Built ${bundleGraph.getBundles().length} bundles in ${buildTime}ms`);
await workerFarm.end(); // you own the farm, so you end it
Key option fields (InitialParcelOptions): entries, defaultConfig, config, mode, env, hmrOptions, serveOptions, targets, shouldDisableCache, cacheDir, watchDir, watchBackend, watchIgnore, shouldAutoInstall, logLevel, shouldProfile, shouldTrace, shouldBuildLazily, lazyIncludes/lazyExcludes, shouldBundleIncrementally, detailedReport, additionalReporters, defaultTargetOptions, featureFlags, workerFarm, shouldPatchConsole.
Important
If you pass your own
workerFarm, Parcel disposes only the references it created and leaves the farm running — you are responsible forworkerFarm.end(). If you let Parcel create the farm, it ends it for you on_end(). Passing a farm that is already ending throwsSupplied WorkerFarm is ending.
Note
MemoryFS(from@parcel/fs) lets you build entirely in memory — useful for tests and serverless contexts. NoteMemoryFSis reportedly slower than the default filesystem for large inputs.
13. Security Analysis
Parcel is a build tool that executes on developer machines and CI, not a network service. Its security surface is the supply chain and the dev server.
| Area | Behavior | Guidance |
|---|---|---|
| Auto-install | By default Parcel installs missing plugins/transitive build deps via the detected package manager (@parcel/package-manager). |
Disable in untrusted contexts with --no-autoinstall. Auto-install is already disabled in CI builds (via the CI env var). |
| Install scripts | The package runs postinstall: patch-package. A reported issue (npm RFC #868) notes install scripts may be blocked by default in future npm. |
Audit patches/ and lockfiles in security-sensitive setups. |
| Native binaries | Prebuilt Rust addons are fetched as optional deps. | Verify integrity via your lockfile; prefer pinned versions and a private registry mirror in regulated environments. |
| Dev server | parcel serve binds to all interfaces by default and serves your project. |
On shared networks, bind to localhost (--host 127.0.0.1) or use --https. CORS can be disabled with --no-cors. |
| HTTPS | Auto-generates a self-signed certificate, or accepts --cert/--key. |
Use a trusted cert for anything beyond local dev. Certificate and/or key not found (getCertificate.js) indicates a misconfigured cert path. |
| Plugin trust | Plugins are arbitrary code executed at build time. | Only install plugins you trust; pin versions; review .parcelrc. |
| Secrets | NODE_ENV and other env vars are exposed to bundled code. |
Never put secrets in client-bundled env vars; only public values belong in browser bundles. |
Caution
Because the dev server listens on all interfaces by default, anyone on your local network can reach your in-development app. Bind to
127.0.0.1when working on sensitive features or untrusted networks.
14. Operations & Observability
Profiling & tracing
| Tool | How | Output |
|---|---|---|
| Sampling profiler | --profile or Ctrl+E while watching |
CPU sampling profile |
| Build tracer | --trace |
Adds @parcel/reporter-tracer; emits a trace of build phases |
| Heap snapshot | Ctrl+Y while watching | V8 heap snapshot |
| Detailed report | --detailed-report [count] |
Per-asset timings and sizes |
| Phase times | PARCEL_SHOW_PHASE_TIMES env (CLIReporter.js) |
Per-phase timing output |
Diagnostic reporters available
@parcel/reporter-bundle-analyzer, -bundle-stats, -bundle-buddy, -build-metrics, -json, -sourcemap-visualiser, and the LSP reporter (@parcel/reporter-lsp) for editor integration. Add any of them via --reporter <name> or .parcelrc.
parcel build src/index.html --reporter @parcel/reporter-bundle-analyzer
Editor / IDE integration
@parcel/parcel-lsp + @parcel/reporter-lsp-reporter provide a Language Server Protocol surface (and packages/utils/parcelforvscode) so editors can show Parcel diagnostics inline.
Logging
--log-level none|error|warn|info|verbose. The diagnostics system (@parcel/diagnostic, @parcel/codeframe, @parcel/markdown-ansi) renders rich, code-framed errors with hints and documentation links.
15. Troubleshooting (Operational)
Failed to load native binding in Docker/CI
Your image lacks the platform's prebuilt addon. Ensure optional dependencies install and the base image matches a supported platform/arch. Don't use --no-optional/--omit=optional.
Supplied WorkerFarm is ending
(Programmatic API.) You passed a workerFarm to new Parcel() after calling workerFarm.end(). Create a fresh farm or don't end it until all builds finish.
Could not detect cpu count!
The environment doesn't expose CPU info to Node. Set PARCEL_WORKERS explicitly.
Plugin ${pluginName} has no exports.
A .parcelrc references a package whose default export isn't a @parcel/plugin wrapper. Fix the plugin's export, or remove it from .parcelrc.
Invalid backend: ${backend}
PARCEL_WORKER_BACKEND is set to an unrecognized value. Use threads or process.
Scope hoisting is not supported with SSG
The React-static packager can't scope-hoist. Build that target with --no-scope-hoist or adjust its target config.
Build OOMs on a huge graph (Node capacity overflow! / Edge capacity overflow!)
The graph's AdjacencyList hit its capacity. Reduce the build's scope, split into multiple targets, or raise Node's heap (NODE_OPTIONS=--max-old-space-size=...).
Dev server reload broken on Linux
Install Watchman or pass --watch-backend watchman (also see feature flag useWatchmanWatcher).
16. Code Quality & Conventions
| Aspect | Convention |
|---|---|
| Types | Flow for JS source (// @flow); TypeScript declarations for the published @parcel/types API |
| Lint/format | ESLint + Prettier for JS/JSON/MD; cargo fmt (nightly) for Rust (yarn lint, yarn format) |
| Tests | Mocha unit tests + @parcel/integration-tests; cargo test for Rust (yarn test) |
| Monorepo mgmt | Yarn 4 workspaces + Lerna for versioning/publishing |
| Native build | scripts/build-native.js (`yarn build-native[-release |
| Self-hosting | Parcel builds itself: build-bundles runs parcel build on selected packages |
| Error handling | Centralized ThrowableDiagnostic + code frames; build failures become BuildError with structured diagnostics |
| Plugin contract | Single symbol (parcel-plugin-config) decouples the wrapper from the loader |
| Releases | Canary/dev/release flows via Lerna (dev:release, canary:release, release) with --dist-tag channels |
Architectural strengths: clean plugin boundaries; graph-based incrementality; native acceleration isolated behind N-API; rich diagnostics. Complexity hotspots: the core orchestrator and graph packages (Parcel.js, BundleGraph.js, RequestTracker.js, graph/*) carry most of the system's intrinsic complexity and are the densest reading.
17. Potential Improvements
Derived from code structure and community-reported issues (these are observations, not commitments):
- Node compatibility: the
ERR_WORKER_INVALID_EXEC_ARGVcrash on Node 24.7.0+ (#10238) suggests the workerexecArgvhandling needs forward-compat hardening. - RSC maturity: multiple open issues tag
[RSC](hydration error from HTML minification #10205, invalid entrypoint with multiple targets #10310, duplicate stylesheet refs #10309, asset URL creation #10305) — the React Server Components path is still stabilizing. - Web-extension service workers:
Bundles conflicting names(#10223) and import-maps breakage (#10240) indicate the webextension target needs naming/manifest fixes. - Node polyfills: the
urlpolyfill is reportedly not a faithful stand-in for Node's nativeurl(#7801). - MemoryFS performance: reportedly much slower than the default FS for large inputs (#9413) — a candidate for optimization or documentation.
- CSS modules / packaging edge cases:
Got unexpected nullwithcomposes(#9635) and CSS-as-JS misclassification from inline module scripts (#8572). - Build-mode
modeswitch: community request to add amodeto target config and thebuildCLI (#10226).
18. Contributing
From CONTRIBUTING.md:
- Install prerequisites: git, Node (LTS), Yarn v4 (or Corepack), Rust (stable), Flow.
- Fork, branch, then:
yarn # install yarn build-native # compile Rust addons - Learn the internals starting at
packages/core/core/src/Parcel.jsand thedocs/folder (DefaultBundler.md,Scopehoisting*.md,Symbol Propagation.md,AdjacencyList.md, …). - Add tests for fixes/features. Commit
yarn.lockif it changed. - Ensure CI checks pass locally:
yarn check(Flow),yarn lint,yarn test. - For significant changes, propose first via GitHub Discussions.
yarn test:unit # mocha + cargo test
yarn test:integration # integration-tests workspace
yarn lint # eslint + prettier + cargo fmt --check
yarn check # flow check
19. Quick Reference
Key source locations
| Concern | File |
|---|---|
| CLI definition | packages/core/parcel/src/cli.js |
| Orchestrator | packages/core/core/src/Parcel.js |
| Option resolution | packages/core/core/src/resolveOptions.js |
| Config loading | packages/core/core/src/loadParcelConfig.js, loadParcelPlugin.js |
| Default pipeline | packages/configs/default/index.json |
| Plugin author API | packages/core/plugin/src/PluginAPI.js |
| Graph primitives | packages/core/graph/src/{Graph,ContentGraph,AdjacencyList}.js |
| Worker farm | packages/core/workers/src/WorkerFarm.js |
| Native loader | packages/core/rust/index.js |
Build tooling commands (repo)
| Command | Action |
|---|---|
yarn build |
Build JS bundles + gulp pipeline |
yarn build-native |
Compile Rust crates to native addons |
yarn build-native-wasm |
Compile WASM (for the REPL) |
yarn test |
Unit + integration tests |
yarn check / yarn lint / yarn format |
Flow / lint / format |
yarn clean |
Clean caches, lib output, reinstall |
Operational env vars
PARCEL_WORKERS, PARCEL_MAX_CONCURRENT_CALLS, PARCEL_WORKER_BACKEND, PARCEL_INCREMENTAL_BUNDLING, NODE_OPTIONS, NODE_ENV, CI, SKIP_PLUGIN_COMPATIBILITY_CHECK.
Plugin categories (10)
Resolver · Transformer · Bundler · Namer · Runtime · Packager · Optimizer · Compressor · Reporter · Validator