Production Build
By default Chef builds extensions in dev mode. For production builds use the --production flag:
chef build ui.buttons --productionMode Comparison
| Dev (default) | Production | |
|---|---|---|
| Source maps | enabled | disabled |
| Minification | disabled | enabled (Terser) |
Vue __file | included | removed |
process.env.NODE_ENV | "development" | "production" |
Dev Mode
The default mode when running chef build. Optimized for development:
- Source maps — source maps are generated alongside the bundle (
.bundle.js.map). They allow debugging TypeScript code directly in browser DevTools. - No minification — code remains readable, errors are easy to locate.
- Vue
__file— Vue components include the path to the source file, helping Vue Devtools display component names.
chef build ui.buttons # dev mode
chef build ui.buttons -w # dev + watchProduction Mode
Optimized for deployment:
- Minification — code is compressed via Terser. Whitespace is removed, variable names are shortened, dead code is eliminated.
- No source maps — source maps are not generated, reducing file size.
- No Vue
__file— the source path is removed from Vue components, keeping the project structure private.
chef build ui.buttons --productionExample
Dev build:
✔ ui.buttons
└─ buttons.bundle.js 13.7 KBProduction build:
✔ ui.buttons
└─ buttons.bundle.js 5.9 KB (-7.8 KB)Environment Variables
Chef automatically replaces environment variables during build:
| Variable | Production | Development |
|---|---|---|
process.env.NODE_ENV | "production" | "development" |
import.meta.env.MODE | "production" | "development" |
import.meta.env.PROD | true | false |
import.meta.env.DEV | false | true |
Replacement happens statically at build time. This enables tree-shaking to remove dev-only code from npm packages (Lexical, React, Vue, etc.):
// This block will be completely removed in production builds
if (process.env.NODE_ENV !== 'production') {
console.warn('Debug info');
}Export Conditions
When importing npm packages, Chef respects export conditions from package.json. The appropriate module variant is selected based on the build mode:
- Dev — conditions
development,browser,import,default - Production — conditions
production,browser,import,default
This allows npm packages to provide different builds for development and production. For example, many libraries include additional checks and warnings in their development build that are absent from production.
Config Priority
If sourceMaps or minification are explicitly set in bundle.config, the config value takes priority over the build mode.
// bundle.config.ts
export default {
input: 'src/index.ts',
output: 'dist/index.bundle.js',
sourceMaps: true, // source maps will ALWAYS be generated, even with --production
};| Setting | Not set in config | Set in config |
|---|---|---|
sourceMaps | dev: true, prod: false | Config value |
minification | dev: false, prod: true | Config value |
Bulk Production Builds
The --production flag works with all extension selection methods:
chef build --production # All in current directory
chef build ui.* --production # By pattern
chef build ui.buttons main.core --production # Specific extensionsStandalone Build
By default Chef builds extensions as IIFE modules: dependencies on other Bitrix extensions are declared as external and loaded via the dependency system (rel in config.php). In standalone mode all dependencies are inlined directly into the bundle — the output is a single self-contained file.
When to Use
- The extension must work without the Bitrix dependency system
- You need a single file with no external dependencies (e.g. for embedding on external sites)
- You use npm packages that should be included in the bundle
Configuration
Add standalone: true to bundle.config.ts:
export default {
input: 'src/index.ts',
output: 'dist/index.bundle.js',
standalone: true,
};Dependency Remap
Sometimes a Bitrix extension is a type wrapper over an npm package. For example, ui.lexical provides types for lexical. In a standalone build you need the actual npm code, not the wrapper. Use remap for this:
export default {
input: 'src/index.ts',
output: 'dist/text-editor.bundle.js',
standalone: {
remap: {
// Bitrix extension → another Bitrix extension
'ui.type-only-dep': 'ui.forms',
// Bitrix extension → npm package (from node_modules of the specified extension)
'ui.lexical.core': { npm: 'lexical', from: 'ui.lexical' },
// Glob pattern → npm packages
'ui.lexical.*': { npm: '@lexical/*', from: 'ui.lexical' },
},
},
};Remap entry formats:
| Format | Example | Description |
|---|---|---|
string | 'ui.forms' | Replace with another Bitrix extension |
{ npm, from } | { npm: 'lexical', from: 'ui.lexical' } | Use npm package from node_modules of the specified extension |
Glob * | 'ui.lexical.*': { npm: '@lexical/*', ... } | Wildcard pattern — * is replaced with the matched part |
What Happens During Build
Normal mode (default):
src/index.ts
├── import { Loc } from 'main.core' → external (rel in config.php)
├── import { Button } from 'ui.buttons' → external (rel in config.php)
└── import { parse } from 'linkifyjs' → requires resolveNodeModules: trueResult: the bundle contains only the extension code, Bitrix dependencies are loaded separately. npm packages are inlined when resolveNodeModules: true is enabled.
Standalone mode:
src/index.ts
├── import { Loc } from 'main.core' → inlined into bundle
├── import { Button } from 'ui.buttons' → inlined into bundle
└── import { parse } from 'linkifyjs' → inlined from node_modulesResult: the bundle contains everything — Bitrix extensions and npm packages alike.
Example
// src/index.ts
import { Loc } from 'main.core';
import { parse } from 'linkifyjs';
export class LinkParser
{
parse(text: string): string[]
{
return parse(text).map(link => link.href);
}
}Normal build (with resolveNodeModules: true):
✔ vendor.link-parser
└─ link-parser.bundle.js 48.2 KB
rel: main.core ← Bitrix dependencies remain externalStandalone build:
✔ vendor.link-parser
└─ link-parser.bundle.js 93.7 KB
rel: (empty — all dependencies inside)INFO
The bundle size in standalone is noticeably larger — it includes all Bitrix dependencies (main.core, etc.) that are loaded separately in normal mode.
Mode Comparison
| Normal | Standalone | |
|---|---|---|
| Bitrix extensions | external (rel) | inlined |
| npm packages | inlined with resolveNodeModules: true | inlined automatically |
| Dependency CSS | loaded separately | merged into one file |
| Bundle size | minimal | maximal |
Dependencies in config.php | populated automatically | empty |
| Code duplication | none | possible |
Combining with Other Options
Standalone works with all other bundle.config options:
export default {
input: 'src/index.ts',
output: 'dist/index.bundle.js',
standalone: true,
namespace: 'BX.MyApp',
};It is also compatible with --production:
chef build vendor.my-app --productionIn this case the standalone bundle will also be minified.
Dependency CSS
In standalone mode Chef automatically collects CSS from all dependencies, including CSS-only extensions (without JS). Dependency CSS is merged with the extension's own CSS into a single file.
If dependencies contain assets (images, fonts), they are copied into subdirectories images/{extension-name}/ to avoid filename collisions between extensions.
Important
- Bundle size — in standalone mode all dependencies end up in one file. If the extension depends on large libraries (main.core, ui.vue3), the bundle size can grow significantly.
- Duplication — if both a standalone bundle and regular extensions with shared dependencies are loaded on the same page, the dependency code will be loaded twice.
- npm packages — in normal mode npm packages are only resolved with
resolveNodeModules: true, while Bitrix dependencies remain external. In standalone mode both npm packages and Bitrix dependencies are inlined automatically. - remap — if a dependency is a type wrapper over an npm package, use
remapto substitute it with the actual code. Both exact names and glob patterns are supported.