Skip to content

Config file (zntc.config.json)

The ZNTC CLI auto-loads zntc.config.json from the current directory. JSON-schema-aware editors (VSCode / IntelliJ / Zed) give you autocomplete + type checking via the $schema reference.

{
"$schema": "https://ohah.github.io/zntc/schemas/transpile-options.schema.json",
"target": "es2022",
"sourcemap": true,
"minifySyntax": true,
"platform": "browser"
}

Running zntc input.ts in the same directory picks up these options automatically.

Terminal window
zntc input.ts # uses values from config.json
zntc input.ts --quotes=double # CLI argument overrides config (CLI > config)

ZNTC merges options in this order (later wins):

  1. Zig defaults
  2. zntc.config.json
  3. tsconfig.json (selective: compilerOptions.target, etc.)
  4. CLI arguments

You can override config values from the CLI but not the other way around. To temporarily disable the config, rename or delete the file.

Most options follow simple “higher wins”, but a few have asymmetric/special behaviors that bite users.

A boolean flag like --minify cannot distinguish between “not passed” and “passed as false”. Hence the asymmetry below.

zntc.config.json
{ "minify": true, "sourcesContent": false }
Terminal window
zntc --bundle entry.ts # CLI passes neither --minify nor --sources-content
# → minify=true (default=false, so config's true applies)
# → sourcesContent=false (default=true, so config's false applies)

Rule: only config values opposite to the default actually take effect.

Defaultconfig=trueconfig=false
false✅ applied(no-op — already false)
true(no-op — already true)✅ applied

For precise control across CLI and config, use the functional config form’s command/mode branching.

defineConfig(({ command, mode }) => ({
minify: command === 'bundle' && mode === 'production',
}));
zntc.config.ts
defineConfig({ plugins: [a, b] });
Terminal window
zntc --bundle --plugin ./c.js --plugin ./d.js entry.ts
# → plugins = [a, b, c, d] (config + CLI concat)

Other array options (external, inject, drop, …) follow “CLI replaces config when non-empty”, but plugins is concatenated. Since order affects hook results (see first-match / chaining policy in the Plugins guide), be intentional with registration order.

You can pass tsconfig contents as a raw JSON string on the CLI — bypassing both -p path and auto-discovery.

Terminal window
zntc --bundle entry.ts --tsconfig-raw='{"compilerOptions":{"jsx":"preserve"}}'

Useful in CI / Docker where you’d rather not write a tsconfig file just to flip an option. Priority: --tsconfig-raw > -p path > auto-discovery.

tsconfig + zntc.config + CLI 3-way (e.g. jsx)

Section titled “tsconfig + zntc.config + CLI 3-way (e.g. jsx)”

If the same option is defined in all three, priority resolves “highest wins”.

tsconfig.json
{ "compilerOptions": { "jsx": "preserve" } }
zntc.config.ts
export default defineConfig({ jsx: 'automatic' });
Terminal window
zntc --bundle --jsx=transform App.tsx
# → jsx=transform (CLI wins)
# → config's automatic and tsconfig's preserve are both ignored

If only zntc.config is present (no CLI flag), automatic applies. If even that is missing, tsconfig’s preserve is the fallback.

Works out of the box — the $schema field is enough.

To use a local schema instead of the online URL (ZNTC repo contributors only):

Terminal window
zig build schema

See the Transpile Options reference — the schema covers the same fields as the TranspileOptions TS interface.

Note: bundler-only options (external, alias, define, etc.) are limited in zntc.config.json. For complex bundler configs, use zntc.config.ts (TypeScript config with plugin support).

The following options have no CLI flag (or only a partial one) and are typically expressed as objects in the config file.

Defaults consumed by zntc dev / zntc --serve. CLI flags (--port / --host / --open) always take precedence.

zntc.config.ts
export default defineConfig({
server: {
port: 5173,
host: true, // true → 0.0.0.0 (matches Vite)
strictPort: false, // true exits on port conflict instead of trying the next port
open: false,
},
});
FieldTypeNotes
portnumberCLI --port overrides
hoststring | booleantrue = 0.0.0.0. CLI --host overrides
strictPortbooleanDisable port-conflict fallback
openbooleanOpen browser on startup. CLI --open overrides

alias — Object or Array (Vite-compatible)

Section titled “alias — Object or Array (Vite-compatible)”

alias accepts two shapes:

// 1. Object form (esbuild-compatible): exact + prefix matching
defineConfig({ alias: { react: 'preact/compat' } });
// 2. Array form (Vite resolve.alias): RegExp `find` supported
defineConfig({
alias: [{ find: /^@\/(.*)$/, replacement: './src/$1' }],
});
  • zntc.config.ts / .js — both forms are supported
  • zntc.config.json — Object form only (JSON cannot serialize RegExp)
  • buildSync — Array form not supported (RegExp matching is delegated to the host runtime, so only async build() / watch() accept it)

compiler — library-specific 1st-party transforms

Section titled “compiler — library-specific 1st-party transforms”

Compatible surface with @next/swc’s compiler option. Carries styled-components / emotion 1st-party transform settings.

defineConfig({
compiler: {
styledComponents: true,
emotion: { autoLabel: 'dev-only' },
},
});

For the full option list, see the Babel migration guide.

Place <%= ZNTC_KEY %> tokens directly inside index.html. They get substituted with .env values during both dev and build — completely independent of the JS-side import.meta.env.X mechanism.

<!DOCTYPE html>
<html>
<head>
<title><%= ZNTC_APP_TITLE %></title>
<meta name="version" content="<%= ZNTC_BUILD_VERSION %>" />
</head>
<body><div id="root"></div></body>
</html>
.env
ZNTC_APP_TITLE=My App
ZNTC_BUILD_VERSION=2026.05

Spec:

  • Token form: <%= KEY %> (whitespace inside delimiters allowed — <%=KEY%> / <%= KEY %> all work).
  • Prefix is restricted to ZNTC_ only. Even if the JS side envPrefixes allows VITE_*, those keys are not exposed in HTML — prevents secret leakage.
  • Other prefix tokens (<%= VITE_API_KEY %>) are left as-is plus warning (the raw token is visible on the page, making the mistake easy to spot).
  • Missing keys (<%= ZNTC_UNDEFINED %>) are replaced with an empty string plus warning (same as Vite / CRA).
  • Expression evaluation (<%= mode === 'prod' ? '/' : '/dev/' %>) is not supported — key-only.

When using defineConfig(({ command, mode, env }) => ...) in zntc.config.ts, command can take:

commandWhen
"bundle"zntc build / anything else (default)
"serve"zntc dev / zntc preview / --serve
"watch"--watch

Unlike Vite ("build" \| "serve"), ZNTC splits "bundle" and "watch" separately.

zntc.config.tszntc.config.json
Plugins✅ Full support
Dynamic values✅ (functions, imports)
JSON schema autocomplete
CLI auto-discoverybundle/serve onlyAll commands
Learning curveMediumLow

Recommendation:

  • Simple transpile / small projects → zntc.config.json
  • Plugins / dynamic config / bundling → zntc.config.ts

If both exist, zntc.config.ts takes precedence on the bundle path.

When you upgrade ZNTC, the URL stays the same but the option list may have changed. In VSCode, reopen the workspace or run “JSON: Clear Schema Cache” to force a refresh.

ZNTC repo contributors: after editing src/transpile.zig’s TranspileOptionsDto, run:

Terminal window
zig build schema

This regenerates documents/public/schemas/transpile-options.schema.json.