Как добавить Vue.js в Moodle

Недавно на работе решили сделать сложный интерфейс для пользователя с кучей кнопок, чекбоксов, полей ввода и т.д. Естественно для таких вещей очень хорошо подходят фреймворки типа 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. Но это уже совсем другая история)

Если не хотите так сильно заморачиваться, лучше доверить разработку на мудл профессионалам, например, мне)

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *