Skip to content

Config file (zntc.config.ts)

The ZNTC CLI auto-loads a config file from the current directory. Use zntc.config.ts when you need plugins, dynamic values, or bundle settings; use zntc.config.json for simple transpilation.

Write a type-safe config with defineConfig from @zntc/core.

zntc.config.ts
import { defineConfig } from "@zntc/core";
export default defineConfig({
target: "es2022",
platform: "browser",
sourcemap: true,
minifySyntax: true,
});

Running zntc input.ts from the same directory applies these options automatically. CLI args always win over config.

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

A functional config can branch on command / mode / env.

export default defineConfig(({ command, mode }) => ({
minify: command === "bundle" && mode === "production",
}));

If you don’t need plugins, write JSON instead. The $schema reference gives autocomplete + type checking in VSCode / IntelliJ / Zed.

{
"$schema": "https://ohah.github.io/zntc/schemas/transpile-options.schema.json",
"target": "es2022",
"sourcemap": true,
"minifySyntax": true,
"platform": "browser"
}
zntc.config.tszntc.config.json
Plugins✅ full support
Dynamic values✅ (functions, import)
JSON schema autocomplete
CLI auto-discoverybundle/serve onlyall commands
Learning costmediumlow

Recommendation: simple transpilation / small projects → zntc.config.json; plugins / dynamic config / bundling → zntc.config.ts. When both exist, zntc.config.ts wins (bundle path). Supported extensions: zntc.config.{ts,mts,cts,mjs,js,cjs,json}.

ZNTC merges options in this order (later wins):

  1. Zig defaults
  2. zntc.config.{ts,json}
  3. tsconfig.json (a few fields like compilerOptions.target)
  4. CLI args

CLI args can override config values, but not the reverse. To temporarily disable the config file, rename or delete it.

In config files you use camelCase keys + kebab-case enums (e.g. platform: "react-native", jsx: "automatic-dev"). The tables below are every available option.

OptionTypeDefaultDescription
targetes5, es2015es2025, esnextesnextES downlevel target. Features introduced after the version are auto-downlowered
unsupportedinteger (u32)0Set the UnsupportedFeatures bitmask directly. Use to inject browserslist resolution — takes precedence over target
runtimePolyfills"off" | "auto" | "usage" | "entry" | object"off"core-js runtime API polyfill injection. "auto"/"usage" are bundle-graph usage based, "entry" injects everything required by the target
coreJsstringinstalled versioncore-js-compat version hint, same as runtimePolyfills.coreJs

When runtimePolyfills is an object it accepts mode / provider / targets / coreJs / include / exclude / proposals. For modes, execution order, and @babel/preset-env useBuiltIns mapping see the Runtime polyfills guide and the Transpile options reference.

OptionTypeDefaultDescription
flowbooleanfalseEnable Flow type stripping
jsxInJsbooleanfalseAllow JSX in .js / .jsx files too (default is .tsx only)
experimentalDecoratorsbooleanfalseLegacy TC39 stage-1 decorators
emitDecoratorMetadatabooleanfalseEmit decorator metadata (requires experimentalDecorators)
OptionTypeDefaultDescription
jsxclassic, automatic, automatic-devclassicJSX runtime selection
jsxFactorystring"React.createElement"Classic-mode factory
jsxFragmentstring"React.Fragment"Classic-mode Fragment
jsxImportSourcestring"react"Automatic-mode import source
OptionTypeDefaultDescription
formatesm, cjsesmModule format
quotesdouble, single, preservedoubleString quote style
platformbrowser, node, neutral, react-nativebrowserTarget platform. Affects Node builtin externals, import.meta polyfill, etc.
useDefineForClassFieldsbooleantrueApply [[Define]] semantics to class fields
asciiOnlybooleanfalseEscape non-ASCII chars as hex
charsetUtf8booleanfalseKeep non-ASCII chars as-is
OptionTypeDefaultDescription
splittingbooleanfalseSplit chunks at dynamic import boundaries + extract shared modules
manualChunks(id, meta) => string | null or [{name, patterns}]Rollup-compatible custom splitting. JS API is functional, zntc.config.json is record form. Detailed guide
inlineDynamicImportsbooleanfalseAbsorb dynamic import targets into the importer chunk + __esm wrap (single-file output)
externalstring[][]Specifiers to exclude from the bundle. Registered as phantom Modules in the graph
preserveModulesbooleanfalseKeep original directory structure instead of bundling (Rollup-compatible)
outputExportsauto, named, default, noneautoCJS/UMD entry export form (Rollup output.exports compatible)
OptionTypeDefaultDescription
minifyWhitespacebooleanfalseRemove whitespace
minifyIdentifiersbooleanfalseMangle local identifiers
minifySyntaxbooleanfalseSyntax-level optimization
OptionTypeDefaultDescription
dropConsolebooleanfalseRemove console.* calls
dropDebuggerbooleanfalseRemove debugger statements
OptionTypeDefaultDescription
sourcemapbooleanfalseGenerate sourcemap JSON
sourcemapModelinked, external, inlinelinkedSourcemap output form. linked = external file + sourceMappingURL comment
sourcemapDebugIdsbooleanfalseInsert Sentry-compatible Debug IDs
sourcesContentbooleantrueInclude original source in sourcemap
sourceRootstring""Sourcemap sourceRoot field
OptionTypeDefaultDescription
defineArray<{key, value}>[]Identifier substitution. value is raw JSON — strings include quotes (e.g. value: "\"1.0.0\"")
OptionTypeDefaultDescription
logLevel"silent" | "error" | "warning" | "info" | "debug" | "verbose""warning"Filter for the NAPI build result errors/warnings arrays. "silent" empties both, "error" empties only warnings
logLimitnumber0Max items per errors/warnings array. 0 is unlimited

The full enum values and detailed semantics are managed in the Transpile options reference as the single source of truth (Zig TranspileOptionsDto).

Config file shape — common object options

Section titled “Config file shape — common object options”

These options are object-shaped with no or limited CLI flags, and are mainly handled in the config file.

Defaults used by zntc dev / zntc --serve. CLI flags (--port / --host / --open) always win.

zntc.config.ts
export default defineConfig({
server: {
port: 5173,
host: true, // true → 0.0.0.0 (same as Vite)
strictPort: false, // true: exit on port conflict instead of trying next port
open: false,
},
});
FieldTypeNotes
portnumberCLI --port override
hoststring | booleantrue = 0.0.0.0. CLI --host override
strictPortbooleanNo fallback on conflict
openbooleanAuto-open browser after start. CLI --open override

alias — Object or Array (Vite-compatible)

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

alias supports two forms:

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

compiler — per-library first-party transforms

Section titled “compiler — per-library first-party transforms”

A surface compatible with @next/swc’s compiler. Accepts styled-components / emotion first-party transform options.

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

For the full option list see the Babel migration guide.

Environment variables in index.html — EJS tokens

Section titled “Environment variables in index.html — EJS tokens”

Tokens like <%= ZNTC_KEY %> in the index.html body are automatically replaced with .env values in both dev and build. This is a separate path from the JS-side import.meta.env.X — usable directly inside HTML.

<!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 around delimiters allowed — <%=KEY%> / <%= KEY %> both OK).
  • Key prefix: ZNTC_ only. Even if the JS-side envPrefixes allows VITE_*, those are not exposed in HTML body — prevents secret leakage.
  • Other-prefix keys (<%= VITE_API_KEY %>) are preserved + warning (the token is exposed verbatim on the site, so it’s immediately detectable).
  • Missing keys (<%= ZNTC_UNDEFINED %>) become empty string + warning (same as Vite / CRA).
  • Expression evaluation (<%= mode === 'prod' ? '/' : '/dev/' %>) is unsupported — key-only.

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

commandTrigger
"bundle"zntc build / others (default)
"serve"zntc dev / zntc preview / --serve
"watch"--watch

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

Most options are simply “higher wins”, but a few have asymmetric/special behavior users frequently trip over.

A boolean flag like --minify can’t distinguish “not given” from “given as false” on the CLI. So the following asymmetry applies.

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

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

Defaultconfig=trueconfig=false
false✅ applied(ignored — already false)
true(ignored — already true)✅ applied

To precisely control both CLI and config, use the command/mode branch of a functional config.

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, …) use only CLI when CLI is non-empty (overwrite), whereas plugins is merged. Since order affects hook results (see the first-match / chaining policy in the Plugins guide), write registration order deliberately.

You can pass tsconfig content directly as a JSON string on the CLI — bypassing both file-based -p path and auto-discovery.

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

Useful for injecting options dynamically in CI / Docker without creating a tsconfig file. Priority: --tsconfig-raw > -p path > auto-discovery.

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

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

When the same option is defined in three places, the highest-priority one 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 both ignored

With only zntc.config and no CLI, automatic applies; with no zntc.config either, tsconfig’s preserve is the fallback.

If zntc.config.json has a $schema field it works automatically with no extra setup. Autocomplete and hover docs appear right in the JSON file.

To use a local file instead of the online schema:

Terminal window
# Generate the schema file at the project root
zig build schema

(Only usable inside the ZNTC repo. npm package users should use the URL approach.)

Upgrading the ZNTC version keeps the schema URL the same, but the internal option list may have changed. To force-refresh the JSON cache in VSCode, reopen the workspace or run “JSON: Clear Schema Cache”.

Internal ZNTC repo developers run:

Terminal window
zig build schema

to regenerate documents/public/schemas/transpile-options.schema.json — must run whenever the TranspileOptionsDto struct in src/transpile.zig changes.