Configuration
@utoo/pack can be configured with utoopack.json, utoopack.config.mjs, utoopack.config.js, or the Node API.
Configuration Files
utoopack.json
{
"$schema": "@utoo/pack/config_schema.json",
"mode": "production",
"entry": [
{
"import": "./src/index.ts",
"html": {
"template": "./index.html"
}
}
],
"output": {
"path": "./dist",
"filename": "[name].[contenthash:8].js",
"chunkFilename": "[name].[contenthash:8].js",
"clean": true
}
}Use utoopack.json when your config is fully JSON-serializable. Use utoopack.config.* when you want type inference or JS-based composition. The config-file form can also carry runtime-only UserConfig fields such as processEnv, watch, dev, buildId, tracing, packPath, projectPath, and rootPath.
Top-level Options
| Option | Description |
|---|---|
mode | Build mode: development or production |
target | Target environment |
entry | Entry points, HTML generation, and library output |
output | Output path, filename templates, public path, asset copying |
resolve | Aliases and extension resolution |
define | Build-time constant replacement |
provider | Auto-provide modules, similar to Webpack ProvidePlugin |
externals | Keep dependencies out of the bundle |
module | Custom module rules and loaders |
styles | CSS Modules, Less, Sass, PostCSS, Emotion, styled-components, styled-jsx |
images | Asset inlining behavior |
react | JSX runtime and React transform options |
mdx | Rust MDX transform options |
reactCompiler | Rust React Compiler options |
optimization | Minification, tree shaking, split chunks, shared runtime, import transforms |
devServer | Host, port, HTTPS, HMR, lazy compilation, browser console forwarding, proxy |
server | Server entries, Server Functions, and server output |
sourceMaps | Enable source maps |
stats | Emit build stats |
persistentCaching | Reuse cache across runs |
cacheDirectory | Directory for the persistent cache, lock, and trace files |
turbopackMemoryEviction | Memory eviction mode for the persistent cache |
turbopackGc | Garbage collection of unreachable tasks |
nodePolyfill | Polyfill Node.js built-ins for browser builds |
swcPlugins | Custom SWC plugins |
pluginRuntimeStrategy | Loader / plugin process model |
experimental | Compatibility entry for reactCompiler and swcPlugins |
Core Configuration
mode and target
Build mode affects optimizations and defaults.
{
"mode": "production",
"target": "chrome >= 80"
}| Value | Description |
|---|---|
development | Faster builds, better debugging, HMR enabled |
production | Optimized output, minification, tree shaking |
target is a Browserslist query. Omit it for a browser build. When the first resolved distribution is node, for example "node 20" or the shorthand "node", the build targets the Node.js platform.
entry
Define application entry points, HTML generation, and library output.
{
"entry": [
{
"name": "main",
"import": "./src/index.ts",
"library": {
"name": "MyBundle",
"export": ["default"]
},
"html": {
"template": "./index.html",
"filename": "index.html",
"title": "My App",
"inject": "body",
"scriptLoading": "module"
}
}
]
}output
Configure output path, file naming, static copying, and runtime asset URLs.
{
"output": {
"path": "./dist",
"type": "standalone",
"filename": "[name].[contenthash:8].js",
"chunkFilename": "[name].[contenthash:8].js",
"cssFilename": "[name].[contenthash:8].css",
"cssChunkFilename": "[name].[contenthash:8].css",
"assetModuleFilename": "assets/[name].[contenthash:8][ext]",
"publicPath": "/",
"crossOriginLoading": "anonymous",
"copy": ["./public"],
"chunkLoadingGlobal": "TURBOPACK",
"clean": true,
"entryRootExport": "AppExports"
}
}| Option | Description |
|---|---|
path | Output directory |
type | Output type: standalone or export |
filename | Entry chunk filename template |
chunkFilename | Non-entry chunk filename template |
cssFilename | Main CSS filename template |
cssChunkFilename | CSS chunk filename template |
assetModuleFilename | Asset filename template |
publicPath | Public URL prefix for assets; also supports runtime and auto |
crossOriginLoading | Cross-origin attribute for lazy chunks |
copy | Copy static files into the output directory |
chunkLoadingGlobal | Global variable used by the runtime to load chunks |
clean | Clean output directory before build |
entryRootExport | Expose entry exports on window / globalThis |
Use publicPath: "runtime" to read the runtime prefix from globalThis.publicPath, or publicPath: "auto" to infer it from the currently loaded script URL.
copy accepts source path strings or { "from": "./public", "to": "static" } objects; to defaults to the source path.
In production, client JS and CSS chunks default to short content-hashed names using Turbopack’s 13-character hash, while development builds keep readable names. Explicit output.filename, output.chunkFilename, and output.cssFilename templates take precedence; use "filename": "[name].js" when consumers need stable entry filenames.
define
Build-time variable replacement.
{
"define": {
"process.env.NODE_ENV": "\"production\"",
"__VERSION__": "\"1.0.0\""
}
}provider
Automatically inject modules, similar to Webpack ProvidePlugin.
{
"provider": {
"$": "jquery",
"Buffer": ["buffer", "Buffer"]
}
}externals
Exclude dependencies from the bundle.
{
"externals": {
"react": "React",
"react-dom": "ReactDOM",
"lodash-es": {
"root": "_",
"type": "global"
}
}
}Externals support simple string globals, script, commonjs, esm, global, and promise forms, plus more advanced subpath configuration.
resolve
Configure module resolution. resolve.alias rewrites an import request to another module or path, using Turbopack-style alias rules .
{
"resolve": {
"alias": {
"react": "preact/compat",
"@/*": "./src/*",
"legacy/": "./src/legacy/",
"runtime/*": ["./src/runtime/*", "./fallback/runtime/*"]
},
"extensions": [".ts", ".tsx", ".js", ".jsx"]
}
}resolve.alias accepts this shape:
type ResolveAlias =
Record<string, string | string[] | Record<string, string | string[]>>Matching rules:
| Alias | Matches | Does not match |
|---|---|---|
"foo": "./src/foo" | import "foo" | import "foo/bar" |
"foo/*": "./src/foo/*" | import "foo/bar" and deeper subpaths | import "foo" |
"foo/": "./src/foo/" | Same as "foo/*": "./src/foo/*" | import "foo" |
Use * when you want to carry the matched subpath into the replacement. The * can match across path separators, so "@/*": "./src/*" resolves imports such as @/components/Button.
Alias values can be:
| Value | Behavior |
|---|---|
string | Rewrite to a single package or path |
string[] | Try multiple candidates in order |
| condition object | Pick a value by condition, commonly browser, with default as the fallback |
Relative path values are resolved from the project root, for example "./src/foo". Absolute paths inside the project are normalized to project-relative paths. Avoid aliases that point outside the project directory.
import { defineConfig } from '@utoo/pack';
export default defineConfig({
resolve: {
alias: {
// Exact replacement
lodash: 'lodash-es',
// Subpath replacement
'@/*': './src/*',
// Directory shortcut
'components/': './src/components/',
// Multiple candidates
'runtime/*': ['./src/runtime/*', './fallback/runtime/*'],
// Conditional alias
env: {
browser: './src/env.browser.ts',
default: './src/env.node.ts',
},
},
extensions: ['.ts', '.tsx', '.js', '.jsx'],
},
});Migrating from Webpack alias
The biggest difference is subpath matching. In Webpack, an alias such as foo: path.resolve(__dirname, 'src/foo') is commonly used to cover both foo and foo/bar. In Utoopack, a plain key is exact. Add /* or use the trailing slash shortcut when you want subpath imports.
| Webpack | Utoopack |
|---|---|
foo: path.resolve(__dirname, 'src/foo') for import "foo" | "foo": "./src/foo" |
foo: path.resolve(__dirname, 'src/foo') for import "foo/bar" | "foo/*": "./src/foo/*" or "foo/": "./src/foo/" |
'@': path.resolve(__dirname, 'src') for import "@/Button" | "@/*": "./src/*" |
react: 'preact/compat' | "react": "preact/compat" |
Webpack’s $ suffix marks an exact alias, for example foo$: './src/foo'. Utoopack does not need that suffix because exact matching is the default:
{
"resolve": {
"alias": {
"foo": "./src/foo"
}
}
}This only affects:
import "foo"It does not affect:
import "foo/bar"To cover subpaths, use:
{
"resolve": {
"alias": {
"foo/*": "./src/foo/*"
}
}
}Or the directory shortcut:
{
"resolve": {
"alias": {
"foo/": "./src/foo/"
}
}
}module
Define loader-based module rules. Rule objects can include loaders, condition, and type.
{
"module": {
"rules": {
"*.md": ["raw-loader"],
"*.svg": [
{
"loaders": [
{
"loader": "@svgr/webpack",
"options": {
"icon": true
}
}
]
}
]
}
}
}styles
Configure built-in style processing.
{
"styles": {
"autoCssModules": true,
"cssModules": {
"localIdentName": "[local]__[hash:base64:6]"
},
"postcss": {},
"less": {
"loader": "less-loader",
"implementation": "less"
},
"sass": {
"implementation": "sass"
},
"inlineCss": {
"injectType": "styleTag"
},
"emotion": {
"autoLabel": "dev-only"
},
"styledComponents": {
"displayName": true,
"ssr": true
},
"styledJsx": {
"useLightningcss": true
}
}
}Use styles.cssModules.localIdentName to customize the CSS Modules class-name template. Use styles.less.loader to select a custom Less loader; implementation selects the Less implementation, and additional fields are forwarded to the loader.
Inline styles.postcss plugins run after plugins from a discovered postcss.config.* file in the same PostCSS pass.
styles.postcss.implementation
styles.postcss.implementation accepts an absolute path to the PostCSS module as a string, or null. When omitted or set to null, Utoopack resolves postcss at runtime. Use a JavaScript config to resolve a specific installed implementation:
import { createRequire } from 'node:module';
import { defineConfig } from '@utoo/pack';
const require = createRequire(import.meta.url);
export default defineConfig({
entry: [{ import: './src/index.ts' }],
styles: {
postcss: {
implementation: require.resolve('postcss'),
},
},
});This field selects the implementation and is not forwarded to PostCSS plugins.
images
Configure image inlining.
{
"images": {
"inlineLimit": 8192
}
}react
React transform options.
{
"react": {
"runtime": "automatic",
"importSource": "react",
"absoluteSourceFilename": false
}
}mdx
mdx enables the Rust MDX transform. It accepts a boolean or an object that configures compilation, the JSX runtime, and the MDX syntax mode.
{
"mdx": {
"development": false,
"jsx": false,
"jsxRuntime": "automatic",
"jsxImportSource": "react",
"providerImportSource": "@mdx-js/react",
"mdxType": "gfm"
}
}mdxType supports commonmark and gfm. Use "mdx": true to enable the default configuration.
React Compiler and SWC plugins
reactCompiler accepts a boolean or an object with compilationMode and target for the Rust React Compiler. compilationMode supports infer, annotation, and all; target supports React 17, 18, and 19 and defaults to 19. React 17 and 18 require react-compiler-runtime.
{
"reactCompiler": {
"compilationMode": "infer",
"target": "19"
},
"swcPlugins": [
["swc-plugin-name", {}]
]
}experimental.reactCompiler and experimental.swcPlugins remain supported. Top-level reactCompiler takes precedence over experimental.reactCompiler; swcPlugins from the top level and experimental are merged.
optimization
{
"optimization": {
"minify": true,
"extractComments": true,
"treeShaking": true,
"moduleIds": "deterministic",
"splitChunks": {
"js": {
"minChunkSize": 50000,
"maxChunkCountPerGroup": 40,
"maxMergeChunkSize": 200000
}
},
"cssChunking": "graph",
"modularizeImports": {
"lodash": {
"transform": "lodash/{{member}}",
"preventFullImport": true
}
},
"packageImports": ["package-name"],
"transpilePackages": ["shared-ui"],
"removeConsole": {
"exclude": ["error"]
},
"concatenateModules": true,
"removeUnusedExports": true,
"removeUnusedImports": true
}
}| Option | Type | Description |
|---|---|---|
moduleIds | named | deterministic | Module ID generation strategy |
noMangling | boolean | Disable local-name minifier mangling and production export-name mangling |
compress | boolean | object | Control compression; object options include passes, sequences, keepClassnames, and keepFnames |
minify | boolean | Minify output code |
extractComments | boolean | Extract legal comments to [file].LICENSE.txt when minifying library output |
treeShaking | boolean | Remove unused code |
inferModuleSideEffects | boolean | Infer side-effect-free modules from source when package metadata does not declare sideEffects; defaults to true |
splitChunks | object | Configure size and count limits for js / css chunks |
cssChunking | boolean | string | object | CSS chunk grouping algorithm |
modularizeImports | object | Transform package imports with templates, similar to babel-plugin-import |
packageImports | string[] | Optimize package entry points with many exports |
transpilePackages | string[] | Transpile selected dependencies |
removeConsole | boolean | object | Remove console calls, with optional methods retained through exclude |
concatenateModules | boolean | Concatenate modules to reduce chunk count; the CLI enables it by default in production |
sharedRuntime | boolean | Share one runtime asset across production browser application entries; defaults to false |
removeUnusedExports | boolean | Remove unused exports; defaults off in development and on in production |
removeUnusedImports | boolean | Remove unused imports; defaults off in development and on in production |
nestedAsyncChunking | boolean | Enable nested async chunks |
wasmAsAsset | boolean | Inline WASM into the bundle; defaults off and emits static assets instead |
optimization.sharedRuntime
Set optimization.sharedRuntime: true to emit one browser runtime asset shared across production application entries. Each entry keeps its own small bootstrap script. Sharing reduces duplicated runtime code and lets separate pages reuse the cached runtime.
{
"mode": "production",
"entry": [
{
"name": "main",
"import": "./src/index.ts",
"html": {
"template": "./index.html",
"filename": "index.html"
}
},
{
"name": "admin",
"import": "./src/admin.ts",
"html": {
"template": "./admin.html",
"filename": "admin.html"
}
}
],
"optimization": {
"sharedRuntime": true
}
}The option defaults to false and is ignored in development, Node.js, and library builds. For a single entry, separating the runtime adds a request compared with the default embedded runtime.
Generated HTML automatically includes the entry bootstrap followed by the shared runtime. If you maintain HTML yourself, enable stats: true and load JavaScript assets in the order returned by stats.entrypoints[entry].assets, including the bootstrap before the runtime. Preserve that order when adding script tags.
optimization.inferModuleSideEffects
When a package does not declare sideEffects metadata, Utoopack can infer side-effect-free modules from their source and use that information for tree shaking. This option defaults to true. Set optimization.inferModuleSideEffects: false to disable source-based inference.
optimization.packageImports
optimization.packageImports optimizes named imports from packages that export many modules. After adding a package, application code can keep importing from the package root while Utoopack loads only the modules that are actually used, reducing module parsing and compilation overhead in development and production builds. The option accepts a string[] and behaves similarly to Next.js optimizePackageImports.
{
"optimization": {
"packageImports": ["package-name"]
}
}Utoopack’s built-in list is maintained with reference to Next.js, but the DEFAULT_OPTIMIZE_PACKAGE_IMPORTS implementation is the source of truth. It currently contains 74 packages or package subpaths:
[
"lucide-react", "date-fns", "lodash-es", "ramda",
"react-bootstrap", "ahooks", "@ant-design/icons",
"@headlessui/react", "@headlessui-float/react",
"@heroicons/react/20/solid", "@heroicons/react/24/solid",
"@heroicons/react/24/outline", "@visx/visx", "@tremor/react",
"rxjs", "@mui/material", "@mui/icons-material", "recharts", "react-use",
"effect", "@effect/schema", "@effect/platform",
"@effect/platform-node", "@effect/platform-browser", "@effect/platform-bun",
"@effect/sql", "@effect/sql-mssql", "@effect/sql-mysql2", "@effect/sql-pg",
"@effect/sql-sqlite-node", "@effect/sql-sqlite-bun",
"@effect/sql-sqlite-wasm", "@effect/sql-sqlite-react-native",
"@effect/rpc", "@effect/rpc-http", "@effect/typeclass",
"@effect/experimental", "@effect/opentelemetry",
"@material-ui/core", "@material-ui/icons", "@tabler/icons-react", "mui-core",
"react-icons/ai", "react-icons/bi", "react-icons/bs", "react-icons/cg",
"react-icons/ci", "react-icons/di", "react-icons/fa", "react-icons/fa6",
"react-icons/fc", "react-icons/fi", "react-icons/gi", "react-icons/go",
"react-icons/gr", "react-icons/hi", "react-icons/hi2", "react-icons/im",
"react-icons/io", "react-icons/io5", "react-icons/lia", "react-icons/lib",
"react-icons/lu", "react-icons/md", "react-icons/pi", "react-icons/ri",
"react-icons/rx", "react-icons/si", "react-icons/sl", "react-icons/tb",
"react-icons/tfi", "react-icons/ti", "react-icons/vsc", "react-icons/wi"
]Packages configured through optimization.packageImports are merged with this built-in list and de-duplicated.
optimization.cssChunking
optimization.cssChunking supports true, loose, and graph. loose (and true) uses the default grouping algorithm. graph uses graph-based grouping and can be configured with estimated request cost and weight distribution.
{
"optimization": {
"cssChunking": {
"type": "graph",
"requestCost": 100000,
"weightDistribution": 0.1
}
}
}false, strict, and { "type": "strict" } are not currently supported by Utoopack and cause the build to fail.
devServer
Configure the development server, HMR, on-demand compilation, browser console forwarding, and HTTP proxies.
{
"devServer": {
"hot": true,
"dynamicHmrChunkLists": true,
"lazyDynamicImports": true,
"browserToTerminal": "warn",
"host": "0.0.0.0",
"port": 3000,
"https": false,
"proxy": [
{
"context": ["/api"],
"target": "http://localhost:7001",
"changeOrigin": true,
"pathRewrite": {
"^/api": ""
}
}
]
}
}| Option | Type | Default | Description |
|---|---|---|---|
hot | boolean | true in the dev server | Enable Hot Module Replacement |
dynamicHmrChunkLists | boolean | false | Register HMR chunk lists as dynamic chunks load; requires hot: true |
lazyDynamicImports | boolean | false | Compile client import() targets when the browser first requests them; development only |
browserToTerminal | boolean | "error" | "warn" | false | Forward browser console output to the development terminal |
host | string | "localhost" | Host to bind |
port | number | 3000 | Preferred port; an available port is selected if it is in use |
https | boolean | false | Enable HTTPS; a self-signed certificate may be generated |
proxy | object[] | No proxy rules | HTTP proxy rules |
devServer.lazyDynamicImports
lazyDynamicImports defers compiling the targets of client import() calls until the browser first requests them. It only applies in development and defaults to false. This can reduce the modules compiled at startup; the first request for an imported module waits for its compilation.
{
"devServer": {
"hot": true,
"dynamicHmrChunkLists": true,
"lazyDynamicImports": true
}
}Pair this option with dynamicHmrChunkLists: true and keep hot: true so modules loaded on demand continue receiving HMR updates. Enabling lazyDynamicImports does not automatically enable either HMR option.
devServer.browserToTerminal
Use "error" to forward errors, "warn" to include warnings, or true to forward all supported console output. The default is false. Error-level output also includes failed assertions, uncaught errors, and unhandled Promise rejections.
{
"devServer": {
"browserToTerminal": "warn"
}
}devServer.proxy
Each proxy rule supports:
| Field | Description |
|---|---|
context | Path prefix or prefixes to match; a value starting with ^ is treated as a regular expression |
target | Target server URL |
pathRewrite | Object of regex-to-replacement pairs where the first match wins, or a (path) => string function in JS configs |
changeOrigin | Rewrite the Host header to match the target; defaults to true |
secure | Verify the target’s TLS certificate; set false to accept self-signed certificates |
server
server configures server entries, Server Functions boundaries, server chunk output, and server-only resolve / externals overrides. entry accepts a single entry string or an array of named entries. The first item in an entry array is the primary server runtime and receives Server Functions.
{
"server": {
"entry": [
{
"name": "server",
"import": "./src/server.ts"
},
{
"name": "admin-server",
"import": "./src/admin.server.ts"
}
],
"resolve": {
"alias": {
"@/*": "./src/*"
}
},
"externals": {
"pg": {
"root": "pg",
"type": "commonjs"
}
},
"function": {
"clientProxy": "./src/transport.ts",
"serverRegister": "./src/register.ts"
},
"output": {
"path": "./dist/server",
"filename": "[name].[contenthash:8].js",
"chunkFilename": "chunks/[name].[contenthash:8].js"
}
}
}For a single entry, use the shorthand "entry": "./src/server.ts". The clientProxy module must export createServerReference(actionId, name), and the serverRegister module must export registerServerReference(action, actionId, name).
server.resolve is applied only to server builds: its alias entries override matching top-level resolve.alias entries, and its extensions replace resolve.extensions when set. server.externals replaces the top-level externals map for server entries and Server Functions when provided; omit it to keep using top-level externals, or set it to {} to disable externals for server builds.
Advanced Options
| Option | Type | Description |
|---|---|---|
sourceMaps | boolean | Enable source maps |
stats | boolean | Enable build statistics output |
nodePolyfill | boolean | Polyfill Node.js built-ins for browser |
persistentCaching | boolean | Enable persistent build cache; enabled by default |
cacheDirectory | string | Persistent cache, lock, and trace directory; defaults to .turbopack, relative to projectPath |
turbopackMemoryEviction | boolean | auto | full | Persistent-cache memory eviction; defaults to auto; false disables it and true is equivalent to full |
turbopackGc | boolean | Collect unreachable tasks during persistent-cache snapshots; defaults to false |
swcPlugins | [string, any][] | Custom SWC plugins and their options |
pluginRuntimeStrategy | workerThreads | childProcesses | Run loaders / plugins in worker threads or child processes |
experimental | object | Compatibility entry for reactCompiler and swcPlugins |
Cache directory and task garbage collection
cacheDirectory sets the directory used for persistent cache data, lock files, and trace files. It defaults to .turbopack. Relative paths are resolved from projectPath; absolute paths are also supported. Add a custom cache directory to .gitignore.
{
"persistentCaching": true,
"cacheDirectory": ".cache/utoopack",
"turbopackMemoryEviction": "auto",
"turbopackGc": true
}turbopackGc defaults to false and does not read the TURBO_ENGINE_GC environment variable. Garbage collection runs during persistent-cache snapshots, so setting persistentCaching: false prevents it from running in the normal build and development workflow.
In development with read/write persistent caching, setting turbopackMemoryEviction: false disables garbage collection even if turbopackGc: true; the backend emits a warning. Enable persistent caching and use turbopackMemoryEviction: "auto" or "full" when enabling garbage collection.
Config-file runtime fields
These UserConfig fields can be used in utoopack.config.* to control the build process.
| Option | Type | Description |
|---|---|---|
processEnv | Record<string, string> | Environment variables used during compilation |
watch | object | File-watching options |
dev | boolean | Run in development mode |
buildId | string | Current build ID |
tracing | boolean | Show compilation progress; enabled by default. Rust tracing logs are controlled by RUST_LOG |
packPath | string | Absolute path to @utoo/pack |
projectPath | string | Project path |
rootPath | string | Root path, which may differ from the project path in a monorepo |
watch supports enable, pollIntervalMs, ignored, and nodeModulesRegexes. node_modules is ignored by default. Use !node_modules/<regex> entries in ignored, or package-name regular expressions in nodeModulesRegexes, to include selected dependencies in watching.
import { defineConfig } from '@utoo/pack';
export default defineConfig({
entry: [{ import: './src/index.ts' }],
watch: {
enable: true,
pollIntervalMs: 500,
ignored: ['node_modules'],
nodeModulesRegexes: ['rc-.*', '@rc-component/.*'],
},
});Programmatic API
build()
const { build } = require('@utoo/pack');
await build({
config: {
mode: "production",
entry: [{ import: "./src/index.ts" }],
output: { path: "./dist" }
}
});build(options, projectPath?, rootPath?) and serve(options, projectPath?, rootPath?, serverOptions?) accept either BundleOptions, which is the config object plus runtime fields such as processEnv, watch, dev, buildId, tracing, and packPath, or a WebpackConfig in compatibility mode. When rootPath is omitted it is discovered from the project path. serverOptions for serve() supports port, hostname, https, logServerInfo, selfSignedCertificate, and an onReady callback that fires once the first build is written and the server is listening.
For the full configuration schema, see config_schema.json .