Skip to content

bundle.config ​

Build configuration for an extension. Create bundle.config.ts in your extension directory:

ts
export default {
  input: './src/my.extension.ts',
  output: {
    js: './dist/my.extension.bundle.js',
    css: './dist/my.extension.bundle.css',
  },
  namespace: 'BX.MyExtension',
};

JavaScript configuration (bundle.config.js) is also supported.

Options ​

OptionTypeDescription
inputstringEntry point file (.ts, .js or .css)
outputstring | {js?, css?}Output bundle path(s)
namespacestringGlobal namespace for exports
concat{js?: string[], css?: string[]}Concatenate files in specified order
targetsstring | string[]Browser targets for transpilation
sourceMapsbooleanGenerate source maps
minificationboolean | objectTerser minification options
treeshakeboolean | string | objectRemove unused code. Accepts boolean, Rollup preset ('smallest', 'safest', 'recommended'), or TreeshakingOptions object (default: true)
pluginsPlugin[]Custom Rollup plugins
resolveNodeModulesbooleanResolve dependencies from node_modules
babelbooleanEnable/disable Babel transpilation (default: true)
standaloneboolean | objectStandalone build with inlined dependencies
protectedbooleanProtect from rebuilding
rebuildstring[]Rebuild dependent extensions
inlinestring[]Bundle the used code of other extensions in
transformClassesboolean | string[]Transpile classes — all (true) or by name
emitDeclarationbooleanGenerate .d.ts with namespace declarations (default: true)
safeNamespacesbooleanSafe access to dependency namespaces via optional chaining
bannerstring | {top?, bottom?}Comment at the top and/or the end of the JS bundle
cssImagesobjectCSS image processing options
baselinebooleanCheck web feature availability during build (default: true)

CSS-only Extensions ​

If an extension contains only styles without JavaScript logic, you can use a CSS file as the entry point:

ts
export default {
  input: './src/style.css',
  output: {
    css: './dist/my.extension.bundle.css',
  },
};

With CSS-only builds:

  • No JS bundle is created
  • Only css needs to be specified in output
  • No wrapper index.ts file with style imports is needed

CSS Image Processing ​

The cssImages option controls how images referenced via url() in CSS are processed.

By default, images under 14 KB are inlined as base64 (SVGs are optimized via SVGO). Larger images are copied to the output directory with relative paths.

ts
export default {
  input: './src/index.ts',
  output: './dist/my.bundle.js',
  cssImages: {
    type: 'copy',
  },
};

cssImages Options ​

OptionTypeDefaultDescription
type'inline' | 'copy''inline'inline — inline small files (base64/SVG), copy — copy all files
maxSizenumber14Size threshold in KB for inlining (only with type: 'inline')
outputstring—Output directory for copied files
absolutePathsbooleanfalseUse absolute paths for images instead of relative
excludestring[][]Glob patterns of images that are never inlined (paths relative to the extension root)

Controlling Inlining of Individual Images ​

To keep an image from being inlined and copy it to the output directory instead, add the ?no-inline marker to its URL. The ?inline marker does the opposite: it inlines the image regardless of maxSize, exclude and type: 'copy':

css
.photo {
  /* Copied even if smaller than maxSize */
  background-image: url('./images/photo.png?no-inline');
}

.logo {
  /* Inlined even if larger than maxSize */
  background-image: url('./images/logo.svg?inline');
}

The marker is removed from the resulting URL, other query parameters are kept: url('./images/icon.svg?v=2&no-inline') becomes url("images/icon.svg?v=2").

To exclude a group of files at once, list patterns in exclude:

ts
export default {
  input: './src/index.ts',
  output: './dist/my.bundle.js',
  cssImages: {
    exclude: ['./src/images/photos/**', '**/*.gif'],
  },
};

An SVG referenced with a fragment (url('./images/sprite.svg#icon')) is not inlined: the fragment points to a part of the file and would be lost once the file is embedded. Such a file is copied and the fragment is kept in the URL.

Priority order: marker in the URL → exclude → SVG fragment → maxSize and type.

Absolute Paths ​

With absolutePaths: true, chef automatically computes the extension's public path and generates absolute URLs in CSS:

ts
export default {
  input: './src/index.ts',
  output: './dist/my.bundle.js',
  cssImages: {
    absolutePaths: true,
  },
};

Result in CSS:

css
/* Instead of */
.icon { background: url(./images/icon.png); }

/* Becomes */
.icon { background: url(/bitrix/js/ui/buttons/dist/images/icon.png); }

The public path is determined automatically based on the extension's location (/bitrix/js/... for sources, /local/js/... for projects).

Plugins ​

The plugins option accepts an array of Rollup-compatible plugins. Plugins are added at the end of the build chain, after Chef's built-in plugins.

Installation ​

Install the plugin in your extension directory:

bash
cd /path/to/my.extension
npm install @rollup/plugin-alias

Usage ​

ts
import alias from '@rollup/plugin-alias';
import { resolve } from 'node:path';

export default {
  input: './src/index.ts',
  output: './dist/my.bundle.js',
  namespace: 'BX.My',
  plugins: [
    alias({
      entries: [
        { find: '@utils', replacement: resolve(import.meta.dirname, 'src/utils') },
      ],
    }),
  ],
};

Multiple Plugins ​

ts
import alias from '@rollup/plugin-alias';
import replace from '@rollup/plugin-replace';

export default {
  input: './src/index.ts',
  output: './dist/my.bundle.js',
  namespace: 'BX.My',
  plugins: [
    alias({
      entries: [
        { find: '@utils', replacement: './src/utils' },
      ],
    }),
    replace({
      __VERSION__: JSON.stringify('1.0.0'),
      preventAssignment: true,
    }),
  ],
};

Resolving node_modules ​

The resolveNodeModules option enables resolving dependencies from node_modules. By default, Chef treats all npm dependencies as external — they are not included in the bundle.

ts
export default {
  input: './src/index.ts',
  output: './dist/my.bundle.js',
  namespace: 'BX.My',
  resolveNodeModules: true,
};

When enabled:

  1. Install dependencies: npm install in the extension directory
  2. Dependencies from node_modules will be inlined into the bundle
  3. The bundle size will increase, but the extension becomes independent of npm

TIP

If you need full independence from Bitrix dependencies as well, use standalone mode.

Inlining Extensions ​

By default, an import of another Bitrix extension stays an external dependency: it is not included in the bundle, the extension is added to rel in config.php and is loaded on the page as a whole. The inline option lists extensions whose code is bundled in:

ts
export default {
  input: './src/index.ts',
  output: './dist/my.bundle.js',
  namespace: 'BX.My',
  inline: ['ui.icon-set.api.core', 'ui.bbcode.*'],
};
  • Only the code that is actually imported and used ends up in the bundle — the rest is removed during the build.
  • Inlined extensions are left out of rel. Their own dependencies stay external and go to rel unless they are listed in inline too.
  • CSS imported by the sources of an inlined extension goes to the CSS bundle.
  • Names accept glob patterns: * — one segment of the name, ** — any number of segments.
  • Sources of an inlined extension may be TypeScript, JavaScript or Flow, regardless of the current extension's language.
  • The chef test test bundle inlines the same extensions.

Limitations:

  • Only an extension with bundle.config and sources can be inlined. Without sources, the build fails with CF1016.
  • Inlined code is a separate copy. If the same extension is loaded on the page through rel of another dependency, its code runs twice: classes from different copies fail instanceof checks, and module-level state (registries, event subscriptions, caches) is not shared. Chef warns about it with CF1017.
  • Inlined code is not registered in the namespace of the source extension: for example, inlining ui.icon-set.api.core does not fill BX.UI.IconSet.
  • Only code unreachable from the used functions is removed. If a used class refers to the whole module (for example, iterates over all data sets), the whole module ends up in the bundle.
  • Changes in an inlined extension reach the bundle only after the current extension is rebuilt. To rebuild it automatically, add the current extension to rebuild of the inlined one.
  • Standalone mode inlines every dependency, inline is not needed there.

Standalone ​

A standalone build inlines all Bitrix dependencies and npm packages into a single bundle. See Standalone Build for details.

Simple form:

ts
export default {
  input: './src/index.ts',
  output: './dist/my.bundle.js',
  standalone: true,
};

With dependency remapping via remap:

ts
export default {
  input: './src/index.ts',
  output: './dist/my.bundle.js',
  standalone: {
    remap: {
      // Bitrix extension → another Bitrix extension
      'ui.type-only-dep': 'ui.forms',

      // Bitrix extension → npm package
      'ui.lexical.core': { npm: 'lexical', from: 'ui.lexical' },

      // Glob pattern
      'ui.lexical.*': { npm: '@lexical/*', from: 'ui.lexical' },
    },
  },
};

The key is a Bitrix extension name (or glob pattern), the value is:

  • string — name of another Bitrix extension whose code should be used instead
  • { npm, from } — npm package (npm) from node_modules of the specified extension (from)

In glob patterns * is replaced with the matched part of the name. For example, 'ui.lexical.*': { npm: '@lexical/*', from: 'ui.lexical' } turns ui.lexical.rich-text into @lexical/rich-text.

Disabling Babel ​

The babel option allows disabling Babel transpilation. This is useful when the code is already pre-built and doesn't need Babel processing.

ts
export default {
  input: './src/index.ts',
  output: './dist/my.bundle.js',
  namespace: 'BX.My',
  babel: false,
};

WARNING

Without Babel, the code won't be transpiled for target browsers. Only use this option if you're sure the input code is already compatible with the required browsers.

Class Transpilation ​

The transformClasses option enables transpiling ES classes into functions via Babel. This is required for compatibility with legacy code that extends classes using BX.merge, Object.assign, or other patterns incompatible with native classes.

Transpile all classes:

ts
export default {
  input: './src/index.ts',
  output: './dist/my.bundle.js',
  namespace: 'BX.My',
  transformClasses: true,
};

Transpile only specific classes:

ts
export default {
  input: './src/index.ts',
  output: './dist/my.bundle.js',
  namespace: 'BX.My',
  transformClasses: ['EventEmitter', 'BaseEvent'],
};

When an array of names is provided, all other classes remain native. This allows transpiling only the classes that actually need backward compatibility, preserving bundle size and performance.

Type Declarations ​

When building TypeScript extensions, Chef automatically generates a .d.ts file with ambient namespace declarations alongside the bundle. This enables IDE hints when accessing exports via namespace, e.g. new BX.UI.Bbcode.App().

dist/
├── app.bundle.js      # Compiled bundle (comments stripped)
└── app.bundle.d.ts    # Type declarations with JSDoc

JSDoc comments from source files are preserved in .d.ts for IDE hints, but stripped from the runtime bundle to reduce size.

Declaration generation only works for TypeScript extensions with a namespace other than window.

To disable:

ts
export default {
  input: './src/index.ts',
  output: './dist/my.bundle.js',
  namespace: 'BX.My',
  emitDeclaration: false,
};

Safe Namespaces ​

By default, if an extension depends on another extension that hasn't been loaded on the page, including the bundle will cause a fatal error — JavaScript cannot access a non-existent namespace (e.g., BX.Main.Core).

The safeNamespaces option enables optional chaining for dependency namespace references in the IIFE wrapper. If a dependency is missing, the extension receives an empty object instead of a fatal error. This allows you to check for the dependency in code and work with it conditionally:

js
if (BX.Main?.Core)
{
  // Dependency is loaded, safe to use
}
ts
export default {
  input: './src/index.ts',
  output: './dist/my.bundle.js',
  namespace: 'BX.My.Extension',
  safeNamespaces: true,
};

When enabled, dependency references in the IIFE wrapper use ?. and ??:

js
// Before:
})(this.BX.My.Extension = this.BX.My.Extension || {}, BX.Main.Core, BX.UI.Buttons);

// After:
})(this.BX.My.Extension = this.BX.My.Extension || {}, BX?.Main?.Core??{}, BX?.UI?.Buttons??{});

The extension's own namespace is not transformed — it is already safely initialized at the top of the bundle.

The banner option adds a comment to the JS bundle on every build — for example, a copyright notice, a license or other legal text.

A string is added to the top of the bundle:

ts
export default {
  input: './src/index.ts',
  output: './dist/my.bundle.js',
  banner: 'Copyright (c) Bitrix24',
};

To choose the position, pass an object with top (start) and bottom (end) fields. You can set either of them or both:

ts
export default {
  input: './src/index.ts',
  output: './dist/my.bundle.js',
  namespace: 'BX.My',
  banner: {
    top: 'Copyright (c) Bitrix24\nAll rights reserved',
    bottom: '// end of my.bundle',
  },
};

Result:

js
/* eslint-disable */
/*!
 * Copyright (c) Bitrix24
 * All rights reserved
 */
this.BX = this.BX || {};
(function (exports) {
  // ...
})(this.BX.My = this.BX.My || {});
// end of my.bundle
//# sourceMappingURL=my.bundle.js.map

How it works:

  • Plain text is wrapped into a legal comment /*! ... */ — minifiers and bundlers keep such comments if the bundle is processed again. Text that is already a comment (/*! ... */, /* ... */ or // lines) is inserted as is
  • The top banner goes right after the /* eslint-disable */ line, or at the very start when there is no such line (in a minified bundle). The bottom banner goes before the sourceMappingURL comment
  • The banner is added to the finished bundle: neither comment stripping in TypeScript builds nor minification removes it. With concat, it wraps the whole concatenated file. Plugins from plugins see the bundle with the banner in their generateBundle and writeBundle hooks
  • The source map is shifted by the added lines, so positions in the debugger stay correct
  • The banner is added to the JS bundle only; the CSS bundle is left unchanged

Environment Variables ​

Chef automatically replaces environment variables during build:

VariableProductionDevelopment
process.env.NODE_ENV"production""development"
import.meta.env.MODE"production""development"
import.meta.env.PRODtruefalse
import.meta.env.DEVfalsetrue

Replacement happens statically at build time. This enables tree-shaking to remove dev-only code from npm packages:

ts
if (process.env.NODE_ENV !== 'production') {
  console.warn('Debug info');
}

In chef build mode (no flags), variables are set to development. In chef build --production mode — to production.

Released under the MIT License.