Static Build

A storefront typically pulls in dozens of small .js and .css files: styling libraries, stylesheets, scripts for individual modules. Each file is a separate request to the server. A bundle merges them into a single file, and this happens right when you save in the development environment — no external bundlers, config files, or separate build commands needed.

The Bundle Line

Open a .js or .css file in the development environment. Above the editor you’ll see two lines: a blue one — the file description, and a grey one — the parameters. The parameters specify which bundles this file will be included in:

base.js: 4

To the left of the colon is the bundle name, to the right is the priority. A single file can belong to multiple bundles, listed separated by commas:

base.js: 4, print.css: 2

Priority is optional: without it, the file gets 0. The bundle name becomes the name of the output file, so the extension is specified directly in it — base.js, melbis.css, melbis.auth.js. The name may contain Latin letters, digits, dots, and underscores; all other characters are stripped — in particular the hyphen, so melbis-auth.js will become melbisauth.js.

What Can Be Bundled

Any .js or .css file inside a template group can be included in a bundle — both from the shared statics/ directory and from individual module directories:

templates/default/
    statics/
        base/bootstrap.js               base.js: 2
        base/bootbox.js                 base.js: 4
        melbis/main.js                  melbis.js: 1
    units/
        melbis_cataloge/scripts.js      melbis.js: 10
        melbis_base_page/scripts.js     melbis.js: 15

That’s the whole point: a module keeps its script alongside its own templates, where it’s easy to edit and find, while the code arrives at the storefront in a shared file, adding no extra requests.

Build Output

The assembled file is placed in the statics/ directory of the same template group, with the prefix bundle.:

templates/default/statics/bundle.melbis.js

A rebuild is triggered on every save of a file that has the bundle line filled in, and it rebuilds all bundles in the group from scratch.

A composition report is prepended to the file:

/*       Melbis Shop auto bundle report       */
/*         Create: 2026-07-17 15:39:56        */

/*   #1    main.js       32 ln    1 kb    /templates/default/statics/melbis/main.js            */
/*   #10   scripts.js    57 ln    1 kb    /templates/default/units/melbis_cataloge/scripts.js  */
/*   #15   scripts.js    77 ln    3 kb    /templates/default/units/melbis_base_page/scripts.js */

It shows what was included and in what order — this is the first place to look when it’s unclear whose code ran first.

Bundling is concatenation only. No minification, transpilation, or path rewriting inside CSS takes place: files are joined in priority order as-is. If you need minified vendor code, put the already-minified version of the file into the bundle.

File Order

Files are sorted by priority in ascending order. The numbers themselves are arbitrary — only their relative values matter, so it’s convenient to leave gaps so you can insert files between existing ones later.

Priorities are a handy way to separate logical groups. For example, in the demo store, files with priority below 10 are base files from statics/, and above 10 are module scripts: this ensures libraries are guaranteed to load before the code that depends on them. This is not a platform rule, just a way to keep things organized.

Do not assign the same priority to files whose order matters. Sorting is done by number only, and files with equal values are placed in the order the engine traversed the metadata directory — which cannot be predicted.

Including in a Template

<link rel="stylesheet" type="text/css" href="{PATH}/statics/bundle.base.css">
<link rel="stylesheet" type="text/css" href="{PATH}/statics/bundle.melbis.css?{BUILD}">

<script defer src="{PATH}/statics/bundle.base.js"></script>
<script defer src="{PATH}/statics/bundle.melbis.js?{BUILD}"></script>

{BUILD} is the project build number, a system tag available in any template. It acts as a version marker: as long as the number hasn’t changed, the browser serves the file from its cache, but as soon as the number increases, it downloads the file again.

It changes in two ways: manually — via the MELBIS_BUILD parameter in the “Design → Installation” dialog (see “Configuration”), or automatically — via a button in the development environment that enables incrementing MELBIS_BUILD on every file save.

You can add ?{BUILD} to any bundle. Vendor libraries change infrequently, so they are often included without the marker — but that’s a matter of preference, not a requirement.

Removing a File from a Bundle

To remove a file from a bundle, clear the bundle name from the parameters line and save the file: it will be excluded from the bundle on the next build.

If the last file is removed from a bundle, the bundle itself remains on disk. There is nothing left to build it from, so the engine simply leaves it untouched — the previous statics/bundle.name file stays in place and continues to be served to the storefront. The same applies when renaming a bundle: a file with the new name will appear, but the old one will remain. Such files must be deleted manually.

It is best to delete and rename static files using the development environment’s file tree: that way, bundle metadata is moved or removed along with the file. If you delete a file outside the IDE — for example, via FTP — its metadata will remain, and the next build will report a Can't include file to bundle error.


File Transformer

A bundle only concatenates. If code also needs to be minified, obfuscated, or otherwise transformed, there is a separate mechanism for that — the file transformer, Ctrl+B or the button on the editing panel.

Here’s how it works: the environment sends the filename and the current contents of the editor to the server — including unsaved edits — the server calls your transformer script via HTTP, and whatever it returns completely replaces the text in the editor. The result is not saved anywhere automatically: review what you got and save the file the usual way.

Configuration

The script address is set in “Editor Settings → Optimization → File Transformer”:

This is a development environment setting, not a project setting: it is not stored in config.json, and each developer configures it on their own machine.

The script is called by the server, not the application — just like scheduler tasks. That’s why the address uses localhost: the server calls itself, and the transformer works immediately, without any domain configuration. If you move the script to a separate host, the address must be reachable from the store’s web server, not from the developer’s workstation.

What the Script Receives

A standard POST request with two fields:

Field Contents
name the filename — the extension makes it easy to decide how to handle it
content the current contents of the editor

It should return the processed text in the response body — no wrappers, headers, or JSON. A response with a status code of 400 or higher will be shown as an error by the environment, and the editor contents will not be changed.

Example: minify.php

The demo store includes a ready-made transformer — a JS and CSS minifier. It’s a plain PHP file in the project root; the engine plays no part in it.

It starts with a check that the request came from the server itself:

// The transformer is called by the engine, so only local requests are allowed
$ip = $_SERVER['REMOTE_ADDR'] ?? '';
if ( $ip !== '127.0.0.1' && $ip !== '::1' )
{
    header('HTTP/1.1 403 Forbidden');
    die('Only local allowed');
}

Then comes the actual transformation:

switch ( pathinfo($_POST['name'], PATHINFO_EXTENSION) )
{
    case 'js':
        $data = array('input' => $_POST['content']);
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, 'https://www.toptal.com/developers/javascript-minifier/api/raw');
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data));
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 60);
        echo curl_exec($ch);
        curl_close($ch);
        break;

    case 'css':
        // Same thing, but calling a CSS minifier
        break;

    default:
        echo $_POST['content'];
}

Branching by extension is not a platform requirement, but a convenient convention: a single address handles all file types. The default branch returns the content as-is, so pressing Ctrl+B on an unknown file type won’t cause any harm; it’s worth keeping this rule in your own transformer.

The internals can be anything: minification, obfuscation, auto-formatting, calling a third-party service or a local utility. The platform doesn’t know or check what exactly happens — it only cares about the text in the response.

Don’t remove the local-call check. The transformer lives in the site root, accepts arbitrary text, and runs on your server — without it, anyone on the internet could invoke it. The same technique is used by cron modules, which have a ready-made CronLocalOnly() method for this purpose (see “Task Scheduler”).