Features let a package declare optional parts of its source tree that can be included or excluded at build time. Use them to ship a library with optional backends, experimental modules, or platform-specific code without paying the compile cost when a consumer doesn't need them.
Features are a rewatch extension and are not part of the legacy bsb build-configuration spec.
Add a feature property to any entry in sources. The directory is included in the build only when the feature is active for that package.
{
"name": "@example/lib",
"sources": [
{ "dir": "src" },
{ "dir": "src-native", "feature": "native" },
{ "dir": "src-experimental", "feature": "experimental" }
]
}Untagged source directories (no feature) are always compiled. A tagged source's feature cascades down into nested subdirs: a child that doesn't declare its own feature inherits the parent's.
A top-level features map declares names and optional implications. Listing a feature here is only required when you want one feature to imply another; leaf features can stay undeclared and still work as source-dir tags.
{
"features": {
"full": ["native", "experimental"]
}
}With the above, requesting full transitively enables native and experimental. Cycles (e.g. a -> b -> a) are rejected at build time with a clear error.
When you run rewatch build or rewatch watch, pass --features to restrict compilation to a specific set. Without the flag, every feature is active and the whole source tree compiles:
rewatch build # all features active (default)
rewatch build --features native # only untagged + native
rewatch build --features native,full # multiple features; also expands `full`
The CLI flag applies only to the current package — the one you're building from. It does not flow down to dependencies; each dependency's active feature set comes from its consumer declarations (see below).
Passing an empty value (--features "" or --features ,) is rejected. Omit the flag to mean "all features".
When consuming another ReScript package that uses features, switch the entry in dependencies or dev-dependencies from the shorthand string to an object form and list which features you want:
{
"dependencies": [
"@plain/dep",
{ "name": "@example/lib", "features": ["native"] }
]
}Rules:
- Shorthand (
"@plain/dep") — the consumer wants every feature of that dependency. This is the existing behavior; nothing changes for configs that don't opt into features. - Object with
features— the consumer restricts the dependency to the listed features (and whatever they transitively imply through the dependency's ownfeaturesmap). An explicit empty list ("features": []) means "only untagged source dirs, no feature-gated code". - Object without
features— equivalent to the shorthand. All features active.
When the same dependency is referenced by multiple consumers with different feature sets, the union of requests wins. If any consumer asks for all features, the dependency builds with all of its features. Features are always additive — enabling more features never removes modules, so the union is always safe.
type: "dev"and--prodare orthogonal to features. A source directory may declare bothtype: "dev"and afeature; it will only build when both filters pass (not in--prod, and the feature is active).rewatch cleanignores--featuresand always cleans the full set of build artifacts across every feature-gated directory. This keepscleanpredictable regardless of which features happen to be active.
Toggling a feature off between builds removes its source files from the build's view. The next rewatch build sees the shrunken file set and cleans up the corresponding artifacts (.mjs, .cmj, etc.) through the same diff mechanism that handles deleted source files.
For rewatch watch, a change to features in rescript.json triggers a full rebuild that recomputes the active set and re-registers watches on the active source directories. The CLI --features flag is evaluated once at watcher start; to change it you must restart the watcher.
- Unknown feature names in CLI input or source tags are accepted as leaf features (they simply match nothing unless a source directory is tagged with that exact name).
- Cycles in the top-level
featuresmap are a hard error that names the cycle participants.
{
"name": "@example/lib",
"sources": [
{ "dir": "src" },
{ "dir": "src-native", "feature": "native" },
{ "dir": "src-web", "feature": "web" },
{ "dir": "src-experimental", "feature": "experimental" }
],
"features": {
"all-backends": ["native", "web"]
},
"dependencies": [
"@plain/dep",
{ "name": "@other/heavy", "features": ["native"] }
]
}rewatch buildat@example/lib— compiles every source dir;@other/heavybuilds with just itsnativefeature because that's all the consumer requested;@plain/depbuilds with all of its features.rewatch build --features all-backends— compilessrc,src-native,src-web; skipssrc-experimental.rewatch build --features experimental— compilessrc,src-experimental; skips the backends.