Frontend inside Bitrix. Using and writing extensions + SPA application in Vue or React

In this article we will look at the correct approach to Frontend development in Bitrix. Namely, the division into extensions that we can connect at any time. + Let's write an SPA application in Vue and React.

PS this article will turn out to be very complex, but I will try to write it as simply and clearly as possible without missing important points.


Simple extension

Hidden text

Extensions are needed to divide JavaScript into modules and use them when we need them. Conventionally, you have any library. But you don't want to load it everywhere.

On the one hand, you can simply connect the script where you need it using the

First, you should find out where to store our extensions, otherwise the Bitrix API will not work. They should be located in the local / js / extensions folder / your_extensions folder.

The folder with extensions can be called anything you want, and without it you won’t be able to create your own extension. Again, the nesting can be large. For example, within one extension there may be another. Separate from the parent or a child that, when connected, will load the code of the parent or another extension.

Folder structure

Folder structure

In order for our extension to be connected, we must have a configuration file (config.php) inside.

For now, we will describe a simple connection of css and js files.

<?if (!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED !== true) die();

return [
	'css' => './style.css',
	'js' => './script.js',
];

Next, let's style our extension a little.

.hello-world {
    font-size: 40px;
    font-style: italic;
    color: blueviolet;
}

Let's write some simple JavaScript code.

document.addEventListener("DOMContentLoaded", () => {
  document.querySelector("body").innerHTML = "<h1 class="hello-world">Hello world from ext</h1>";
});

And then we open the file where we are going to call our extension and connect it.

<?require($_SERVER["DOCUMENT_ROOT"]."/bitrix/header.php");
Bitrix\Main\UI\Extension::load("myextensions.myext");

We get this result:

index.php - the page on which you connected your extension

index.php - the page on which you connected your extension

Now let's learn how to runtime our extensions. To do this, delete the line with the connection of our extension.

<?Bitrix\Main\UI\Extension::load("myextensions.myext");

And remove the DOM tree loading tracking listener to make it look like this.

document.querySelector("body").innerHTML =
  "<h1 class="hello-world">Hello world from ext</h1>";

Next, open the console on our page and write the following code.

BX.Runtime.loadExtension('myextensions.myext')

And after executing this line, we will again see the result of our extension.

The result of dynamic loading of the extension.

The result of dynamic loading of the extension.

More details about extension runtimes can be found in the documentation: https://dev.1c-bitrix.ru/api_help/js_lib/runtime/Runtime_loadExtension.php

Now let's learn how to connect dependencies.


Connecting dependencies

For example, we divided the functionality and wrote a whole common module that will be used for another extension.

So let’s say we made our own API / Library / Dependency, it doesn’t matter.

As an example, I will make a set of functions that will be used for other extension modules.

I will place these functions in utils.

function sum(a, b) {
  return a + b;
}

function multiplication(a, b) {
  return a * b;
}

And in the first extension I will connect the dependencies in config.php.

<?if (!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED !== true) die();

return [
	'css' => './style.css',
	'js' => './script.js',
	'rel' => [
		'myextensions.utils'
	]
];

Next, I can use my functions inside the first extension.

console.log(sum(1, 2));
console.log(multiplication(2, 2));
Execution result

Execution result

Now we’ll make a nested extension into the parent that will inherit from it, but before that we’ll write the result of executing the functions into a constant.

const sumResult = sum(1, 2);
const multiplicationResult = multiplication(2, 2);

This is what the structure of the child extension will look like.

File structure

File structure

Next is the configuration.

<?if (!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED !== true) die();

return [
	'js' => './script.js',
	'rel' => [
		'myextensions.myext'
	]
];

And the script itself.

console.log(sumResult);
console.log(multiplicationResult);

You will also see the result in the console, and now let’s move on to the extension build tool from the Bitrix developers.


Bitrix CLI

@bitrix/cli — console tool for Bitrix developer. The main goal is to simplify and automate front-end development for projects on 1C-Bitrix: Site Management And Bitrix24.

This quote is taken from the documentation: https://dev.1c-bitrix.ru/learning/course/index.php?COURSE_ID=43&LESSON_ID=12435&LESSON_PATH=3913.3516.4776.3635.12435

Overall, it’s not the most convenient thing, but you can work with it.

Hidden text

Again, many may write:
For what? Why? Why can’t you use other collectors, collect everything in a bundle and connect the extension. Well, or just throw out Bitrix as an API and write the front normally.

I’ll answer right away that this can be done and preferably if you don’t want to suffer with setting up and adding npm packages to the collector.

Again, Bitrix CLI is more like a + for those who want to get a certificate. Well, for fans of beaters, of course.

First, as indicated in the documentation, you need to install npm or yarnwell, you can figure this out yourself, information about installation is easy to find.

For bitrix cli to work inside our extension we need to create a configuration file bundle.config.js

File structure

File structure

Next, add the following code to bundle.config.js

module.exports = {
  input: "./script.js",
  output: "./dist.js",
};

In this code, we accept script.js and at the output we get our assembled script.js - the so-called bundle, for now we will not bother with names and folders.

In the terminal, go to local and write the command

bitrix build

Before doing this, I removed all the code in script.js and we should have gotten something like this

/* eslint-disable */
(function (exports) {
	'use strict';



}((this.window = this.window || {})));
//# sourceMappingURL=dist.js.map

Well, if you didn’t remove the code from script.js, then your code should also be there.

Next, let's look at some properties from the documentation and find out how they work.

Hidden text
// Неймспейс, в который будут добавлены все экспорты из файла, 
// указанного в input. Например, 'BX.Main.Filter'
namespace: string,

This option adds an object corresponding to the namespace string to the window; the nesting of the object is indicated through a dot.

/* eslint-disable */
this.BX = this.BX || {};
(function (exports) {
	'use strict';



}((this.BX.MyExt = this.BX.MyExt || {})));
//# sourceMappingURL=dist.js.map
plugins: {
		// Переопределяет параметры Babel.
		// Можно указать собственные параметры Babel
		// https://babeljs.io/docs/en/options
		// Если указать false, то код будет собран без транспиляции
		babel: boolean | Object,
		
		// Дополнительные плагины Rollup, 
		// которые будут выполняться при сборке бандлов 
		custom: Array<string | Function>,
          
        //В документации данное свойство не добавили
        //Данная опция позволяет разрешать использование плагинов или отключать
        //По дефолту false
        resolve: Boolean
	},

This option will be extremely useful if you are building, for example, React or Vue applications.

// Включает или отключает минификацию. 
// По умолчанию отключено. 
// Может принимать объект настроек Terser:
// false — не минифицировать (по умолчанию)
// true — минифицировать с настройками по умолчанию 
// object — минифицировать с указанными настройками 
minification: boolean | object,

Everything is clear from the description. I recommend using this property for production.

You can learn the remaining properties from the documentation or from a video on this topic. Again, everything is easy to Google, I just looked at those properties that will actually be used frequently.

Thanks to Bitrix CLI we can use ES6 Modules in developing our extensions.

Simple examples with imports do not make sense; better yet, I’ll show you the SPA assembly in Vue.


SPA on Vue

It’s worth clarifying a couple of points right away, but for those who are not interested, look further.

Hidden text

First we will use the integrated Vue by the Bitrix team since the CLI works with it and allows us to use it to the maximum.

For those who don’t like this option, they can put their own Vue inside with Typescript and anything else, and simply build their application without the Bitrix CLI, and then in config.php we specify the path to our assembled bundle.

The same applies to React, since the bitra collector is very difficult to configure for this, and it is also not clear how (documentation, as always, is missing).

In the future, I will show how I assembled React and based on this you will be able to do other things.

To create a SPA in Vue, you first need to import it (I will use Vue3)

import { createApp } from "ui.vue3";

The import logic here is exactly the same as when connecting an extension. And also during assembly, our config.php will be supplemented with the necessary dependencies automatically.

Let's start by writing a simple application without components.

import { createApp } from "ui.vue3";

document.addEventListener("DOMContentLoaded", () => {
  createApp({
    data() {
      return {
        counter: 1,
      };
    },
    mounted() {
      setInterval(() => {
        this.counter++;
      }, 1000);
    },
    template: `<div>{{ counter }}</div>`,
  }).mount("#root");
});

Since placing the layout inside is not very convenient, I recommend that people who use VSCode install this extension to make their code easier to work with and more readable, or other similar plugins.

https://marketplace.visualstudio.com/items?itemName=pushqrdx.inline-html

Next, we’ll put it all into a component and then do the routing.

import { createApp } from "ui.vue3";
import App from "./components/App";

document.addEventListener("DOMContentLoaded", () => {
  createApp(App).mount("#root");
});

The article is already getting quite long, so I’ll just show the structure and each file.

Structure of our SPA

Structure of our SPA

Index.js

import { createApp } from "ui.vue3";
import { createRouter, createWebHashHistory } from "ui.vue3.router";

import Test from "./pages/Test";
import About from "./pages/About";
import App from "./App";

const router = createRouter({
  routes: [
    {
      path: "/",
      component: Test,
    },
    {
      path: "/about",
      component: About,
    },
  ],
  history: createWebHashHistory(),
});

document.addEventListener("DOMContentLoaded", () => {
  createApp(App).use(router).mount("#root");
});

App.js

import About from "./pages/About";
import Test from "./pages/Test";

export default {
  components: { About, Test },
  template: /*html*/ `<div>
        <nav>
            <ul>
                <li><router-link to="/">Test</router-link></li>
                <li><router-link to="/about">About</router-link></li>
            </ul>
        </nav>
        <main>
            <router-view />
        </main>
    </div>`,
};

Test.js

export default {
  data() {
    return {
      name: "Тестовая страница",
    };
  },
  template: /*html*/ `
    <div>
      <h1>{{ name }}</h1>
    </div>
  `,
};

About.js

import Counter from "../components/Counter";

export default {
  name: "About",
  components: {
    Counter,
  },
  template: /*html*/ `
    <div>
        <h2>Счетчик на странице About</h2>
        <Counter/>
    </div>
  `,
};

Counter/index.js and style.css

import "./style.css";

export default {
  name: "Counter",
  data() {
    return {
      count: 0,
    };
  },
  methods: {
    reset() {
      this.count = 0;
    },
  },
  mounted() {
    setInterval(() => this.count++, 300);
  },
  template: /*html*/ `
    <div>
      <button class="button-counter" @click="reset">reset count</button>

      counter : {{ count }}
    </div>
  `,
};
.button-counter {
    background-color: blueviolet;
    color: white;
}

Next, we launch the collector. And we see the following result.

Hidden text

you can also write bitrix build -w

and it will track changes to your files and reassemble them every time you save.

And now our routing is ready. It was possible to cram more work here with Pinia or Vuex out of the box, but I think it’s not difficult to figure it out on your own.

Our simple SPA application is ready, and we have not lost the opportunity to use JS from Bitrix, we can also use Ajax to components, modules, and other Bitrix libraries.

But again, this is not the best option, since we work inline, and we won’t be able to build a React application via the Bitrix CLI, but we can do it smarter.

Building React inside Bitrix

And now everything will be very simple and familiar for Frontend developers.

We go to the terminal, go to the directory where our extensions are stored and simply write npx create-react-app [ur project name] [template]

After that

  1. Let's go to our project and run the build command, in my case it will be npm run build.

  2. Next, we create config.php inside.

  3. And we launch and connect our extension to the page.

  4. And we check the result.

config.php

config.php

What should have happened.

What should have happened.

You will only have a problem with the paths to the image, but this is not a problem)))

In the same way, you can work with absolutely any application, in addition, until the Bitrix developers deliver the normal settings for their collector, the data approach will be optimal, since Frontend developers will work comfortably in their environment, and will even be able to use JS from Bitrix if they receive data from Bitrix controllers/modules/components

Is it possible to configure React build via Bitrix CLI?

In theory, yes, I +- managed to configure it for React, and it even collected something, but as soon as I imported useState from React, it immediately somehow refused to build my application, in general, I don’t see anything wrong with building it first any other collector, and then just connect the files.

Therefore, at the moment there are only two approaches. We are not considering the third option of discarding Bitrix as an API because the requirements are different, and the problem needs to be solved somehow.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *