SPA на Vue в Bitrix
Мы будем делать SPA на Vue используя расшерения (extensions) и сборщик CLI. Кому этот вариант не подходить, может внутри поставить свой Vue, и уже без Bitrix CLI собирать свое приложение, а потом в config.php указать путь к собранному бандлу.
Простое приложение
Вызовим расширение на любой странице:
test/index.php<? require($_SERVER["DOCUMENT_ROOT"] . "/bitrix/header.php"); ?>
<?php \Bitrix\Main\UI\Extension::load(['hmarketing.vue']); ?>
<div id="root"></div>
<? require($_SERVER["DOCUMENT_ROOT"] . "/bitrix/footer.php"); ?>
Для создания SPA на Vue, сначала нужно импортировать Vue:
/local/js/hmarketing/vue/src/scripts.jsimport { createApp } from "ui.vue3";
Логика импортов тут точно такая же как при подключении расширения. При сборке наш config.php
дополниться нужными зависимостями автоматически.
Напишем для начала простое приложение без компонентов:
/local/js/hmarketing/vue/src/scripts.jsimport { createApp } from "ui.vue3";
document.addEventListener("DOMContentLoaded", () => {
createApp({
data() {
return {
counter: 1,
};
},
mounted() {
setInterval(() => {
this.counter++;
}, 1000);
},
template: `{{ counter }}`,
}).mount("#root");
});
Далее вынесем это все в компонент и добавим метод reset
:
/local/js/hmarketing/vue/src/scripts.jsimport { createApp } from "ui.vue3";
import App from "./components/App";
document.addEventListener("DOMContentLoaded", () => {
createApp(App).mount("#root");
});
/local/js/hmarketing/vue/src/components/App.vueexport default {
data() {
return {
count: 0,
};
},
name: "Counter",
methods: {
reset() {
this.count = 0;
},
},
mounted() {
setInterval(() => this.count++, 300);
},
template: `
<div>
<button class="button-counter" @click="reset">reset count</button>
counter : {{ count }}
</div>
`,
};
Приложение с роутингом и компонентами
SPA на Vue для Bitrix с использованием Bitrix CLI, в приложении есть возможность использовать JS от Bitrix, мы можем так же использовать аяксы к компонентам, модулям, и прочим библиотекам Битрикс.
Готовый код можно скачать в моем репозитории на GitFlic.
Структура расширения (extensions):
vue
основная папка расширенияdist
файлы сборки после Bitrix CLI-
-
App.js
основной компонент с роутингомCounter.js
компонент с счетчиком
-
About.js
страница о компанииWorkers.js
страница работников
scripts.js
основной файл скртптов расширенияstyles.scss
основной файл стилей расширения
-
bundle.config.js
config.php
/local/js/hmarketing/vue/src/components/App.jsimport About from "../pages/About";
import Workers from "../pages/Workers";
export default {
components: { About, Workers },
template: `<div>
<nav>
<ul>
<li><router-link to="/">Main</router-link></li>
<li><router-link to="/workers">Workers</router-link></li>
<li><router-link to="/about">About</router-link></li>
</ul>
</nav>
<main>
<router-view />
</main>
</div>`,
};
/local/js/hmarketing/vue/src/components/Counter.jsexport default {
data() {
return {
count: 0,
};
},
name: "Counter",
methods: {
reset() {
this.count = 0;
},
},
mounted() {
setInterval(() => this.count++, 300);
},
template: `
<div>
<button class="button-counter" @click="reset">reset count</button>
counter : {{ count }}
</div>
`,
};
/local/js/hmarketing/vue/src/pages/About.jsimport Counter from "../components/Counter";
export default {
data() {
return {
name: "Страница о компании",
};
},
name: "About",
components: {
Counter,
},
template: `
<div>
<h1>{{ name }}</h1>
<Counter/>
</div>
`,
};
/local/js/hmarketing/vue/src/pages/Workers.jsexport default {
data() {
return {
name: "Страница работников",
};
},
name: "Workers",
template: `
<div>
<h1>{{ name }}</h1>
</div>
`,
};
/local/js/hmarketing/vue/src/scripts.jsimport './styles.scss';
import { createApp } from "ui.vue3";
import { createRouter, createWebHashHistory } from "ui.vue3.router";
import Workers from "./pages/Workers";
import About from "./pages/About";
import App from "./components/App";
const router = createRouter({
routes: [
{
path: "/workers",
component: Workers,
},
{
path: "/about",
component: About,
},
],
history: createWebHashHistory(),
});
document.addEventListener("DOMContentLoaded", () => {
createApp(App).use(router).mount("#root");
});
/local/js/hmarketing/vue/src/styles.scss.button-counter {
display: inline-block;
box-sizing: border-box;
padding: 0 16px;
margin: 0 15px 15px 0;
outline: none;
border: none;
border-radius: 4px;
height: 30px;
line-height: 30px;
font-size: 12.5px;
font-weight: normal;
text-decoration: none;
vertical-align: top;
color: #55677d;
background-color: #dfe6ed;
cursor: pointer;
user-select: none;
appearance: none;
touch-action: manipulation;
overflow: hidden;
&:focus-visible {
box-shadow: 0 0 0 3px lightskyblue;
}
&:hover {
opacity: 0.88;
}
&:active {
line-height: 32px;
}
&:disabled {
pointer-events: none;
opacity: 0.65;
}
}
/local/js/hmarketing/vue/bundle.config.jsmodule.exports = {
input: "./src/scripts.js",
output: {
js: "./dist/script.bundle.js",
css: "./dist/style.bundle.css",
},
namespace: 'BX.HM',
browserslist: true,
plugins: {
resolve: true,
}
};
/local/js/hmarketing/vue/config.php<?php
if (!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED !== true) die();
return [
'css' => './dist/style.bundle.css',
'js' => './dist/script.bundle.js',
'rel' => [
'main.polyfill.core',
'ui.vue3',
'ui.vue3.router',
],
'skip_core' => true,
];