Migration Guide
Migrating from esbuild
Section titled “Migrating from esbuild”ZNTC provides a similar bundling model to esbuild, but the CLI flag surface is still a subset. The table below reflects options actually exposed by the current zntc CLI.
CLI option mapping
Section titled “CLI option mapping”| esbuild | ZNTC | Note |
|---|---|---|
esbuild src/index.ts --bundle | zntc --bundle src/index.ts | Same |
--outfile=dist/out.js | -o dist/out.js | Short form supported |
--outdir=dist | --outdir dist | Same |
--outbase=src | --outbase=src | Same |
--format=esm | --format=esm | Same (esm/cjs/iife/umd/amd) |
--platform=node | --platform=node | Same (browser/node/neutral/react-native) |
--target=es2020 | --target=es2020 | Same (engine targets: chrome80, node20) |
--bundle | --bundle | Same |
--splitting | --splitting | Same (--outdir required) |
--packages=external | --packages=external | Treat all bare package imports as external |
--external:react | --external react | Space instead of : |
--minify | --minify | Same (--minify-{whitespace,syntax,identifiers} granular) |
--sourcemap | --sourcemap | Same |
(config only: sourceRoot) | --source-root=... | ZNTC exposes as CLI flag |
--sources-content=false | --sources-content=false | Same |
--define:X=Y | --define:X=Y | Same |
--alias:react=preact/compat | --alias:react=preact/compat | Same |
--inject:./shim.js | --inject:./shim.js | Same |
--pure:Pure.* | --pure:Pure.* | Register a pure call pattern |
--drop:console | --drop=console | = instead of : (console/debugger) |
--drop-labels=DEV | --drop-labels=DEV | Same. Use commas for multiple labels |
--keep-names | --keep-names | Same |
--banner:js=... | --banner:js=... | Same |
--footer:js=... | --footer:js=... | Same |
--intro=... | --intro=... | Insert text before bundle code inside the wrapper |
--outro=... | --outro=... | Insert text after bundle code inside the wrapper |
--global-name=foo | --global-name=foo | IIFE/UMD global name |
--global:react=React | --global:react=React | external specifier → IIFE/UMD global |
--public-path=/static/ | --public-path=/static/ | Same |
--out-extension:.js=.mjs | --out-extension:.js=.mjs | Same |
--entry-names=[name]-[hash] | --entry-names=[name]-[hash] | Same |
--chunk-names=chunks/[hash] | --chunk-names=chunks/[hash] | Same |
--asset-names=assets/[hash] | --asset-names=assets/[hash] | Same |
--loader:.css=text | --loader:.css=text | Same (text/file/dataurl/json/copy) |
--jsx=automatic | --jsx=automatic | Same (classic/automatic/automatic-dev) |
--jsx-dev | --jsx-dev | Same |
--jsx-factory=h | --jsx-factory=h | Same |
--jsx-fragment=Fragment | --jsx-fragment=Fragment | Same |
--jsx-import-source=preact | --jsx-import-source=preact | Same |
--jsx-side-effects | --jsx-side-effects | Preserve unused JSX expressions |
--tsconfig=tsconfig.json | -p tsconfig.json or --tsconfig-path=... | -p short form |
--tsconfig-raw='{...}' | --tsconfig-raw='{...}' | Inline tsconfig JSON |
--conditions=prod,foo | --conditions=prod,foo | Keep default conditions and add custom ones |
--main-fields=browser,main | --main-fields=browser,main | Same |
--resolve-extensions=.ts,.js | --resolve-extensions=.ts,.js | Same (RN .ios.ts etc.) |
--preserve-symlinks | --preserve-symlinks | Same |
--node-paths=... | --node-paths=... | Additional lookup paths for bare specifiers |
--charset=utf8 | --charset=utf8 | Same (preserve UTF-8) |
--charset=ascii | --ascii-only | ZNTC uses dedicated flag; escapes non-ASCII to \uXXXX |
--legal-comments=eof | --legal-comments=eof | Same (none/inline/eof/linked/external) |
--metafile=meta.json | --metafile=meta.json | Same |
--analyze | --analyze | Same (JSON now, tree format planned) |
--log-level=warning | --log-level=warning | Same (silent/error/warning/info/debug) |
--log-limit=10 | --log-limit=10 | Same |
--line-limit=80 | --line-limit=80 | Same (wraps long output lines at safe token boundaries) |
--ignore-annotations | --ignore-annotations | Ignore pure/sideEffects annotations |
--allow-overwrite | --allow-overwrite | Input=output is blocked by default; this flag explicitly permits it |
--watch | --watch or -w | Same |
--serve | --serve | Same (--port supported) |
esbuild Build API migration
Section titled “esbuild Build API migration”// esbuildimport * as esbuild from 'esbuild';await esbuild.build({ entryPoints: ['src/index.ts'], bundle: true, outdir: 'dist', format: 'esm', minify: true,});
// ZNTC — almost identicalimport { build } from '@zntc/core';await build({ entryPoints: ['src/index.ts'], bundle: true, outdir: 'dist', format: 'esm', minify: true,});esbuild plugin migration
Section titled “esbuild plugin migration”ZNTC native plugins use the esbuild-style setup(build) structure directly. Return value keys (path/contents) are identical.
// esbuild pluginconst myPlugin = { name: 'my-plugin', setup(build) { build.onResolve({ filter: /^virtual:/ }, (args) => ({ path: args.path, namespace: 'virtual', })); build.onLoad({ filter: /.*/, namespace: 'virtual' }, (args) => ({ contents: 'export default 42', loader: 'js', })); },};
// ZNTC plugin — same esbuild style (use path prefix instead of namespace)import type { ZntcPlugin } from '@zntc/core';
const myPlugin: ZntcPlugin = { name: 'my-plugin', setup(build) { build.onResolve({ filter: /^virtual:/ }, (args) => ({ path: '\0' + args.path, })); build.onLoad({ filter: /^\0virtual:/ }, (args) => ({ contents: 'export default 42', })); },};If you want to use Rollup/Vite-style plugins (resolveId/load/transform) as-is, wrap them with vitePlugin().
import { vitePlugin } from '@zntc/core';
export default defineConfig({ plugins: [ vitePlugin({ name: 'virtual-loader', resolveId(source) { if (source.startsWith('virtual:')) return '\0' + source; return null; }, load(id) { if (id.startsWith('\0virtual:')) return { code: 'export default 42' }; return null; }, }), ],});Unsupported esbuild options
Section titled “Unsupported esbuild options”| esbuild option | Alternative |
|---|---|
--mangle-props=<regex> | Not supported (mangling limited to --minify-identifiers on internal names) |
--mangle-cache=<path> | Not supported |
--mangle-quoted | Not supported |
--analyze (tree format) | --analyze JSON + /analyze/ visualization. CLI tree format planned |
--servedir=<path> | --serve <dir> (positional arg) |
--bundle=false (off by default) | Same default. ZNTC transpiles only without --bundle |
--splitting=false | Off by default. No flag means default |
--tree-shaking=false | Not supported. Per-package --external can reduce bundle scope |
--color=true|false | Not supported. Auto-detected from terminal |
--log-override:X=Y | Not supported. Only --log-level |
--supported:bigint=false | Not supported. Use --target for global control |
--reserve-props=<regex> | Not supported |
Migrating from Vite
Section titled “Migrating from Vite”Vite is a combination of dev server + production bundler (Rollup/Rolldown). ZNTC is a standalone bundler and doesn’t replace all Vite features.
Vite production build replacement
Section titled “Vite production build replacement”# Vitevite build
# ZNTCzntc --bundle src/main.ts --outdir dist --format=esm --splitting --minify --sourcemapvite.config.ts mapping
Section titled “vite.config.ts mapping”export default defineConfig({ build: { outDir: 'dist', minify: true, sourcemap: true, rollupOptions: { external: ['react', 'react-dom'], output: { globals: { react: 'React', 'react-dom': 'ReactDOM' }, }, }, },});
// ZNTC CLI equivalent// zntc --bundle src/main.ts --outdir dist --minify --sourcemap --external react --external react-dom \// --global:react=React --global:react-dom=ReactDOMRollup output.globals maps to ZNTC CLI --global:<specifier>=<global> or defineConfig({ globals: { react: "React" } }). Used when IIFE/UMD output needs to substitute external specifiers with global variables.
Vite plugin → ZNTC plugin
Section titled “Vite plugin → ZNTC plugin”Vite/Rollup plugin hooks (resolveId/load/transform) work when wrapped with vitePlugin(). Return value keys follow the Rollup convention ({ id, code }).
import { defineConfig, vitePlugin } from '@zntc/core';import fs from 'node:fs';
export default defineConfig({ plugins: [ vitePlugin({ name: 'svg-loader', load(id) { if (id.endsWith('.svg')) { const svg = fs.readFileSync(id, 'utf8'); return { code: `export default ${JSON.stringify(svg)}` }; } return null; }, }), ],});To write native-style plugins, use setup(build) { build.onLoad(...) }.
Vite feature mapping
Section titled “Vite feature mapping”| Vite feature | ZNTC equivalent |
|---|---|
vite (dev server) | zntc dev (HTML/env/public prepare + HMR/Fast Refresh + CSS-only HMR) |
vite build | zntc build, or zntc --bundle <entry> --outdir dist --splitting --minify --sourcemap for library builds |
vite preview | zntc preview dist |
import.meta.env.MODE | App mode auto-loads .env*; CLI bundles can use --define:import.meta.env.MODE=\"production\" |
import.meta.env.DEV | App mode injects dev/build mode automatically; CLI bundles can use --define:import.meta.env.DEV=true |
.env / .env.production auto-load | Supported in app mode (--env-dir, --env-prefix) |
import.meta.glob | Supported (Vite-compatible eager / import options) |
import.meta.hot | Supported (--serve --bundle) |
import.meta.url | Supported (ESM standard) |
@vitejs/plugin-react | --jsx=automatic (automatic runtime built-in) |
@vitejs/plugin-react Fast Refresh | Built-in HMR (React Refresh) |
@vitejs/plugin-vue | Not supported (details + workarounds) |
@vitejs/plugin-legacy | Partial via --target=es5 etc. |
CSS Modules (.module.css) | Supported in app mode. Provides default exports and valid named exports |
CSS @import | Built-in Lightning CSS or --loader:.css=text |
PostCSS (postcss.config.js) | Supported in app mode. zntc dev watches PostCSS dependencies and sends CSS-only HMR |
| Sass/Less/Stylus | Sass/SCSS is supported in app mode. Less/Stylus are not supported |
public/ static directory | Supported in app mode (--public-dir) |
HTML entry (index.html) | Supported in app mode (--entry-html) |
| SPA fallback | zntc preview --spa-fallback |
resolve.alias (object) | --alias:name=target or defineConfig({ alias: { ... } }) |
resolve.alias (array, RegExp) | defineConfig({ alias: [{ find: /^@\//, replacement: "./src/" }] }) (build() only, not in buildSync) |
resolve.conditions | conditions: ["prod", "foo"] or --conditions=prod,foo |
optimizeDeps (pre-bundling) | Not needed (handled during bundling) |
ssr / SSR build | Not supported |
worker.format | new Worker(new URL("./w.ts", import.meta.url)) is auto-detected and bundled as a standalone IIFE. The Vite worker.format knob itself is not supported |
| Rollup plugin compat | resolveId/load/transform hooks compatible |
Migrating from webpack
Section titled “Migrating from webpack”webpack configuration is complex, but ZNTC handles most of it via CLI options.
webpack.config.js → ZNTC CLI
Section titled “webpack.config.js → ZNTC CLI”module.exports = { entry: './src/index.ts', output: { path: 'dist', filename: 'bundle.js' }, resolve: { extensions: ['.ts', '.tsx', '.js'] }, module: { rules: [ { test: /\.tsx?$/, use: 'ts-loader' }, { test: /\.css$/, use: ['style-loader', 'css-loader'] }, { test: /\.svg$/, type: 'asset/resource' }, ], }, optimization: { minimize: true },};
// ZNTC equivalent// zntc --bundle src/index.ts -o dist/bundle.js --minify --loader:.svg=file --loader:.css=textwebpack loaders → ZNTC loaders/plugins
Section titled “webpack loaders → ZNTC loaders/plugins”| webpack loader | ZNTC equivalent |
|---|---|
ts-loader / babel-loader | Not needed. ZNTC handles TS/JSX directly |
@swc/swc-loader / esbuild-loader | Not needed. Replaced by ZNTC |
css-loader + style-loader | --loader:.css=text or built-in Lightning CSS post-processing |
file-loader / asset/resource | --loader:.png=file |
url-loader / asset/inline | --loader:.png=dataurl |
raw-loader / asset/source | --loader:.txt=text |
svg-loader / @svgr/webpack | --loader:.svg=text/file/dataurl or plugin |
json-loader | --loader:.json=json (built-in default) |
sass-loader / less-loader / stylus-loader | Sass/SCSS is supported in app mode. Pre-compile Less/Stylus |
postcss-loader | Supported in app mode. Use a plugin or pre-processing for library bundling |
html-loader | App mode rewrites index.html; --loader:.html=text can stringify imports |
worker-loader | Not needed. new Worker(new URL("./w.ts", import.meta.url)) auto-detected and bundled as a standalone IIFE |
thread-loader | Not needed. ZNTC has built-in parallel pipeline (--jobs=N) |
cache-loader | Not needed. Uses .zig-cache / module-level cache |
webpack plugins → ZNTC equivalents
Section titled “webpack plugins → ZNTC equivalents”| webpack plugin | ZNTC equivalent |
|---|---|
DefinePlugin | --define:KEY=VALUE |
ProvidePlugin | --inject:./shim.js |
IgnorePlugin | --external <pkg> or --block-list=<pattern> |
resolve.fallback: { fs: false, crypto: 'crypto-browserify' } | fallback: { fs: false, crypto: 'crypto-browserify' } (applied only when normal resolution fails) |
BannerPlugin | --banner:js=... |
SplitChunksPlugin | --splitting (automatic) |
MiniCssExtractPlugin | Built-in Lightning CSS post-processing (separate CSS chunks) |
HtmlWebpackPlugin | App mode handles index.html plus %ENV%/asset rewriting |
CopyWebpackPlugin | Not supported. Per-asset copy via --loader:.svg=copy |
TerserPlugin | --minify built-in |
CssMinimizerPlugin | Handled by Lightning CSS post-processing |
CompressionPlugin (gzip/brotli) | Not supported. Handle in post-build |
webpack.ContextReplacementPlugin | Not supported |
| Module Federation | Not supported |
| DllPlugin / DllReferencePlugin | Not supported |
Unsupported webpack features
Section titled “Unsupported webpack features”| webpack feature | Alternative |
|---|---|
require.context | Supported (require.context(dir, deep, regex) — resolved via plugin onResolveContext hook) |
Lazy chunk (import(/* webpackChunkName: "x" */ ...)) | Dynamic import itself supported. Magic comments are not |
webpack.config.js function / multi-config | Not supported. Single-export zntc.config.ts |
devServer.proxy | Supported (--proxy /api=http://localhost:3000) |
| Dev server overlay | Supported (build/runtime error overlay + source map remap) |
Persistent cache (cache.type: 'filesystem') | Not needed. Built-in cache |
| Stats JSON | --metafile=meta.json provides similar info |