JS Extension
A JS extension is the primary unit of frontend code in Bitrix. It is a standalone module with source files, build configuration and a PHP manifest.
Creating
Create a new extension with chef create:
chef create ui.buttons # TypeScript (default)
chef create ui.buttons --tech js # JavaScriptChef resolves the extension name to a path and creates the directory in local/js/:
ui.buttons → local/js/ui/buttons/
my.feature → local/js/my/feature/
crm.kanban → local/js/crm/kanban/All necessary files will be created:
local/js/ui/buttons/
├── bundle.config.ts
├── config.php
├── src/
│ └── ui.buttons.ts
└── test/
├── unit/
│ └── ui.buttons.test.ts
└── e2e/
└── ui.buttons.spec.tsIf the project has a tsconfig.json, Chef will automatically create a TypeScript extension. If tsconfig.json is absent — JavaScript. You can explicitly specify via --tech ts or --tech js.
File Structure
Full extension structure with tests:
local/js/ui/buttons/
├── bundle.config.ts # Build configuration
├── config.php # PHP manifest (dependencies, assets)
├── src/
│ └── ui.buttons.ts # Entry point
├── dist/
│ ├── ui.buttons.bundle.js # Compiled JS (generated on build)
│ └── ui.buttons.bundle.css # Compiled CSS (generated on build)
└── test/
├── unit/ # Unit tests (Mocha + Chai)
│ └── ui.buttons.test.ts
└── e2e/ # E2E tests (Playwright)
└── ui.buttons.spec.tsThe extension name is derived from its path: local/js/ui/buttons/ → ui.buttons.
bundle.config.ts
Build configuration for the extension. Created in the root of the extension directory.
export default {
input: './src/ui.buttons.ts',
output: './dist/ui.buttons.bundle.js',
namespace: 'BX.UI.Buttons',
};JavaScript config is also supported:
bundle.config.js.
Splitting JS and CSS
By default, CSS is included in the JS bundle. To output CSS as a separate file:
export default {
input: './src/ui.buttons.ts',
output: {
js: './dist/ui.buttons.bundle.js',
css: './dist/ui.buttons.bundle.css',
},
namespace: 'BX.UI.Buttons',
};Full options reference — see bundle.config.
config.php
PHP manifest of the extension. Contains paths to compiled files and the list of dependencies.
<?php
if (!defined('B_PROLOG_INCLUDED') || B_PROLOG_INCLUDED !== true)
{
die();
}
return [
'js' => './dist/ui.buttons.bundle.js',
'css' => './dist/ui.buttons.bundle.css',
'rel' => [
'main.core',
],
'skip_core' => false,
];Chef automatically updates the rel array on build — it analyzes imports and writes dependencies. No need to update rel manually.
Loading on a Page
\Bitrix\Main\UI\Extension::load('ui.buttons');After loading, all extension exports are available via the namespace:
const button = new BX.UI.Buttons.Button({ text: 'Save' });
document.body.appendChild(button.render());Bundle Anatomy
The built bundle is an IIFE that extends the namespace object. Here is a simplified example for the ui.buttons extension with a dependency on main.core:
/* eslint-disable */
this.BX = this.BX || {};
this.BX.UI = this.BX.UI || {};
(function (exports, main_core) {
'use strict';
class Button {
constructor(options) {
this.node = main_core.Tag.render`<button>${options.text}</button>`;
}
render() {
return this.node;
}
}
exports.Button = Button;
}((this.BX.UI.Buttons = this.BX.UI.Buttons || {}), BX));this.BX.UI.Buttons— the namespace object, all exports go into itBX— the global namespace of themain.coredependency, passed as an IIFE argumentimport { Tag } from 'main.core'in source becomesmain_core.Tagin the bundle
Tests
Unit Tests
Run in a real browser via Playwright. Use Mocha + Chai.
TIP
mocha, chai and their types are included in Chef and used when running chef test. For IDE autocompletion and type checking, install them locally:
npm install --save-dev @types/mocha @types/chai// test/unit/ui.buttons.test.ts
import { it, describe } from 'mocha';
import { assert } from 'chai';
import { Button } from '../../src/ui.buttons';
describe('Button', () => {
it('should render node', () => {
const button = new Button({ text: 'OK' });
assert.ok(button.render() instanceof HTMLElement);
});
});E2E Tests
Use the Playwright Test API.
Without authentication — for public pages:
// test/e2e/ui.buttons.spec.ts
import { test, expect } from '@playwright/test';
test('button is visible on page', async ({ page }) => {
await page.goto('/my-page');
await expect(page.locator('.ui-btn')).toBeVisible();
});With automatic authentication — import from ui.test.e2e.auth:
// test/e2e/ui.buttons.spec.ts
import { test, expect } from 'ui.test.e2e.auth';
test('button is visible on page', async ({ page }) => {
// page is already authenticated
await page.goto('/my-page');
await expect(page.locator('.ui-btn')).toBeVisible();
});Running
chef test ui.buttons # All tests
chef test unit ui.buttons # Unit only
chef test e2e ui.buttons # E2E only
chef test ui.buttons --headed # With visible browser
chef test ui.buttons --debug # With DevTools and sourcemapsSee Testing for details.