Недавно на работе решили сделать сложный интерфейс для пользователя с кучей кнопок, чекбоксов, полей ввода и т.д. Естественно для таких вещей очень хорошо подходят фреймворки типа Vue.js, React и т.д. Выбор пал на Вью.
Но те кто работал с мудл знают как непросто устроена эта система. Поэтому пришлось повозиться с интеграцией фреймворка.
Подготавливаем инструменты сборки
Итак, самое главное что нам понадобится — это сборщик модулей, он же бандлер. Я выбрал webpack, просто потому, что знаком с ним.
Устанавливаем вебпак командой:
npm install --save-dev webpack
Лично я ставлю всё в корень проекта. Пока что не вижу смысла плодить node_modules в каждом плагине мудла.
Отлично, вебпак поставили, теперь давайте создадим плагин. Будем использовать локальный плагин, т.к. это проще для понимания, и по опыту большинство плагинов в мудле именно локальные.
Создание структуры плагина
В директории local создаём директорию vuetest. Создаём в ней необходимый минимум для плагина.
- version.php
- lang/en/local_vuetest.php
- index.php
- templates/main_layout.mustache
- styles.css
- amd
amd — особенно важная директория в нашем плагине, именно здесь хранится наш js код, который мы дальше будем загружать на странице.
Внедряем Vue.js
Теперь давайте создадим отдельную директорию, в которой будем вести все наши дела с Vue.js, такой подход позволит нам не засорять корневую директорию плагина специфичными файлами для Вью. Назовём нашу директорию Vue_project. И добавим туда две директории, src и assets.
В src будет храниться только программный код, т.е. файлы js и vue-шаблоны. Директория assets используется для статических файлов, например, картинок.
Итого, у нас на данный момент получается такая структура:
local/
└── vuetest/
├── amd
├── lang/
│ └── en/
│ └── local_vuetest.php
├── index.php
├── templates/
│ └── main_layout.mustache
├── Vue_project/
│ ├── assets
│ └── src
├── styles.css
└── version.php
В директории Vue_project/src создадим файл index.js. Этот файл будет отвечать за инициализацию Вью. Код файла, например, может быть таким:
import { createApp } from 'vue';
import App from './App.vue';
const app = createApp(App);
app.mount('#app');
Ну и добавим vue-шаблон App.vue в эту же директорию. Сделаем его предельно простым для наших целей.
<template>
<h1>Hello from Vue.js!</h1>
</template>
Настройка webpack
Хорошо, основу подготовили, теперь настало время настроить наш вебпак. Добавляем в директорию Vue_project файл webpack.config.js со следующим содержимым:
const path = require('path');
const { VueLoaderPlugin } = require('vue-loader');
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.min.js',
path: __dirname + '/../amd/build/',
publicPath: "/local/vuetest/amd/build/",
library: {
type: 'amd',
}
},
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader'
},
{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
},
{
test: /\.(png|svg|jpg|jpeg|gif)$/i,
type: 'asset/resource',
},
]
},
resolve: {
modules: [path.resolve(__dirname, '/../../node_modules'), 'node_modules'],
},
plugins: [
new VueLoaderPlugin()
],
devtool: 'source-map',
optimization: {
minimize: true,
}
};
Не будем подробно расписывать этот файл, думаю можно спокойно нагуглить, что значат все эти директивы. Остановимся лишь на ключевых моментах.
entry: './src/index.js',
output: {
filename: 'bundle.min.js',
path: __dirname + '/../amd/build/',
publicPath: "/local/vuetest/amd/build/",
library: {
type: 'amd',
}
},
Здесь мы берём наш главный файл отвечающий за приложение и компилируем из него минифицированный бандл. Обратите внимание, что бандл мы компилируем в amd-формат. Т.к. мудл работает с джаваскриптом в виде amd модулей. Этот бандл должен попасть в директорию local/vuetest/amd/build, что мы собственно и задаём в этом куске кода.
resolve: {
modules: [path.resolve(__dirname, '/../../node_modules'), 'node_modules'],
},
Поскольку у меня node_modules хранятся в корне проекта, этот кусок кода задаёт путь для поиска модулей ноды именно в корне.
Далее давайте создадим в директории Vue_project файл package.json со следующим содержимым:
{
"name": "vue_test",
"version": "1.0.0",
"description": "Vue Test",
"private": true,
"main": "webpack.config.js",
"scripts": {
"build": "webpack"
}
}
На данном этапе структура проекта будет выглядеть так:
local/
└── vuetest/
├── amd
├── lang/
│ └── en/
│ └── local_vuetest.php
├── index.php
├── templates/
│ └── main_layout.mustache
├── Vue_project/
│ ├── assets
│ ├── src/
│ │ ├── index.js
│ │ └── App.vue
│ ├── package.json
│ └── webpack.congif.js
├── styles.css
└── version.php
Так же нам надо установить сам vue и vue-loader. Пишем следующие команды:
npm i vue-loader
npm i vue@^3.2.13
Сборка и запуск проекта
Ну вот собственно и всё с джаваскриптом) Теперь переходим в консоли в директорию Vue_project и выполняем команду:
npm run build
Здесь нам могут предложить установить webpack-cli если у нас его нет. Соглашаемся.
Мы скомпилировали джаваскрипт в local/vuetest/amd/build/bundle.min.js и теперь займёмся его загрузкой на страницу.
Для этого нам надо добавить немного кода в файл /local/vuetest/index.php
<?php
require_once(__DIR__ . '/../../config.php');
$url = new moodle_url('/local/vuetest/index.php');
$PAGE->set_url($url);
$PAGE->set_pagelayout('custompage');
$PAGE->set_title(new lang_string('vuetest', 'local_vuetest'));
$PAGE->set_heading(new lang_string('vuetest', 'local_vuetest'));
require_login();
$context = context_system::instance();
$PAGE->set_context($context);
$renderer = $PAGE->get_renderer('local_vuetest');
$context = array(
'output' => $OUTPUT,
'mainblockhtml' => '<div id="app"></div>'
);
echo $renderer->render_from_template('local_vuetest/main_layout', $context);
$PAGE->requires->js_call_amd('local_vuetest/bundle');
echo $renderer->footer();
Здесь остановимся на трёх моментах.
Первое:
$renderer = $PAGE->get_renderer('local_vuetest');
Это наш собственный отрисовщик. Делаем его для лучшего контроля за отрисовкой UI.
Для этого создаём в корне плагина такую структуру папок classes/output.
Далее в classes/output создаём файл renderer.php со следующим содержимым:
<?php
namespace local_vuetest\output;
use theme_boost\output\core_renderer;
class renderer extends core_renderer
{
}
Это просто минимально необходимый код, что бы всё заработало. Можете расширять его потом как хотите.
Второе:
echo $renderer->render_from_template('local_vuetest/main_layout', $context);
Здесь мы сначала генерируем html на основе mustache шаблона, а затем выводим его в браузер. Но для этого нам надо его создать сначала)
Давайте это сделаем. У нас уже есть директория templates в корне плагина. А также в ней файл main_layout.mustache
Осталось добавить минимальный html необходимый для работы в этот файл:
{{> theme_boost/head }}
<body {{{ bodyattributes }}}>
{{> core/local/toast/wrapper}}
<div id="page-wrapper" class="d-print-block">
{{{ output.standard_top_of_body_html }}}
<div id="page" data-region="mainpage" data-usertour="scroller" class="px-0 drag-container">
<div class="container flex-grow-1 px-0">
<div id="topofscroll" class="main-inner px-0">
<div id="page-content" class="d-print-block bg-white bg-transparent">
<div id="region-main-box">
{{{ mainblockhtml }}}
</div>
</div>
</div>
</div>
</div>
{{{ output.standard_after_main_region_html }}}
</div>
</body>
</html>
Ну и третий момент:
$PAGE->requires->js_call_amd(‘local_vuetest/bundle’);
Здесь мы подключаем наш js-код с приложением Vue.
Итого, наша финальная структура проекта будет следующей
local/
└── vuetest/
├── amd
│ └── build
├── lang/
│ └── en/
│ └── local_vuetest.php
├── classes
│ └── output
│ └── renderer.php
├── index.php
├── templates/
│ └── main_layout.mustache
├── Vue_project/
│ ├── assets
│ ├── src/
│ │ ├── index.js
│ │ └── App.vue
│ ├── package.json
│ └── webpack.congif.js
├── styles.css
└── version.php
Фух, кажется на этом всё. Теперь можно перейти по адресу <ваш_адрес_мудл>/local/vuetest/index.php, что бы увидеть приветственную надпись «Hello from Vue.js!».
Итоги
Как видите подключение фронтент-фреймворка в мудл дело непростое и требует хорошего понимания работы самой системы и фронтенд технологий. А ведь это только начало. Дальше сама разработка приложения на Vue. Но это уже совсем другая история)
Если не хотите так сильно заморачиваться, лучше доверить разработку на мудл профессионалам, например, мне)