Build Configurations and tps.json

tps.json is the configuration file the Transpose compiler reads for every build. This page covers where the compiler looks for it, how the per-configuration variants tps.Debug.json and tps.Release.json are merged, and how the active build configuration decides whether the minified or non-minified output is wired into the generated HTML.

For the full list of individual options, see Global Configuration. For everything about the resources section specifically, see Resources.

Where the compiler looks for tps.json

The compiler reads tps.json from the project (.csproj) directory — one location, no search path. If the file is absent the compiler falls back to its built-in defaults and the build still runs.

tps.json is JSON with // and /* */ comments and trailing commas allowed.

Property names are case-sensitive

Unlike h5, which matched keys case-insensitively, Transpose reads the keys verbatim: outputFormatting is read, OutputFormatting is silently ignored. Use camelCase exactly as documented. (The values of outputFormatting are matched case-insensitively, so "both" and "Both" are equivalent.)

Per-configuration files

Alongside tps.json you can ship a configuration-specific file whose name carries the MSBuild configuration:

  • tps.Debug.json
  • tps.Release.json
  • tps.<AnyConfiguration>.json — any custom configuration name works too (for example tps.Staging.json for a Staging build configuration).

The configuration comes from the build: dotnet build -c Release looks for tps.Release.json, dotnet build -c Debug looks for tps.Debug.json. These files live in the project directory, next to tps.json.

How the files are merged

When a build configuration is active, the compiler loads tps.json as the base and merges the matching tps.<Configuration>.json on top of it.

flowchart LR A["tps.json<br/>(base)"] --> M{merge} B["tps.Release.json<br/>(overrides)"] --> M M --> C["Effective config<br/>for this build"]

The rules in detail:

  • Scalar settings (output, fileName, outputFormatting, html.*, reflection.*, cleanOutputFolder, …): a key present in the configuration-specific file wins. A key absent from it keeps the base value.
  • Arrays (resources and cleanOutputFolderExclude) are concatenated, base first, then the overlay — they do not replace.
  • If tps.<Configuration>.json does not exist, only tps.json is used.
  • If no configuration is passed to the build, only tps.json is read; no per-configuration file is picked up.
Arrays are concatenated, not replaced

Because the resources arrays from both files are appended, declaring the same bundle in both produces two entries. Resources are de-duplicated by their resolved output name, with the overlay's entry winning, so a deliberate override works — but keeping a resource in only one of the two files is clearer.

What to put where

Keep everything shared in tps.json and put only the values that genuinely differ per configuration in the override file. A common split is to emit both output variants for Release while keeping Debug builds readable and fast:

{
"output": "$(OutDir)tps/",
"fileName": "app.js",
"outputFormatting": "Formatted"
}

How the configuration affects the output

The outputFormatting option controls which flavours of the JavaScript are produced:

Value Produces
Formatted Readable .js only
Minified .min.js only
Both Both .js and .min.js (the default)

When both variants exist, the active build configuration decides which one is referenced from the generated HTML:

  • Release — only the minified files are wired up, and the minified page is written out as index.html.
  • Debug — only the non-minified files are wired up in index.html.
  • No configuration — both index.html (non-minified) and index.min.html (minified) are written.

This same configuration-driven selection applies to resources pulled from referenced libraries. A Transpose library package embeds its compiled JavaScript in both a formatted and a pre-minified variant, so a consuming project extracts the .min.js versions in Release and the plain .js versions in Debug without minifying library code itself. See the Resources page for how a library's own resources section interacts with that.

Debug and Release are structurally different builds

Beyond which JavaScript variant is wired up, the two configurations emit different .NET assemblies:

  • Debug emits a metadata-only assembly: full metadata, but no method bodies. This is sound because a Transpose assembly binds against the browser-side stand-in BCL and is never executed on .NET, and it makes the build measurably faster.
  • Release emits full IL.

Because a package needs real IL, the SDK refuses to package a Debug build: GeneratePackageOnBuild is forced off in Debug and dotnet pack -c Debug fails with TPS1001. Pack Release.

Incremental builds

The SDK passes --incremental by default, so a rebuild reuses the previous build's cache: nothing is compiled at all when every input hashes the same, and an edit confined to method bodies keeps the cached JavaScript of every untouched type. The output is byte-identical either way.

Turn it off for a project with:

<PropertyGroup>
  <TransposeIncremental>false</TransposeIncremental>
</PropertyGroup>

The tps CLI itself defaults to incremental off; pass --incremental to opt in when invoking it directly.

© 2026 Curiosity. All rights reserved.