[Из песочницы] Бэкэнд в однофайловых компонентах VueJS

15 ИЮНЯ 2022

------------------------------------------------------------------------

Наш телеграм-канал: t.me/@mailsgun_online

Youtube: youtube.com/@mailsgun_online

------------------------------------------------------------------------

Однажды, читая документацию по Vue Loader, наткнулся на интересное нововведение в 15 версии. Речь идет о кастомных блоках, которые можно внедрить в однофайловые компоненты Vue. В примере показано, как можно получить доступ к содержанию этого блока непосредственно в компоненте. Я сначала как бы не придал особой ценности этой возможности, но потом подумал, хм…, а если туда запихнуть бэк связанным с этим куском фронта… И понеслось…

Бэк у меня на то время (год назад) был на php. Для начала, я решил посмотреть, как мой любимый редактор PhpStorm справится со вставкой кода php в этот блок. Как ни старался, речи о какой-либо подсветки кода и прочих автокомплитных функций не шло. Думаю, напишу ка я issue в тех поддержку JetBrains. Через некоторое время мне пришел отрицательный ответ, о какой-либо возможности это настроить, но прислали инструкцию как это настроить для javascript. Ну думаю ладно, идею всё равно надо попробовать реализовать. Ранее никогда мне не приходилось разрабатывать что-либо для Webpack’а. День изучал документацию, и за последующие два вечера разработал Loader и плагин. Всё это работало, но без элементарной подсветки синтаксиса в кастомных блоках .vue, код php приносил только боль…

Шло время. Потихоньку знакомясь с nodejs и следя за change логом изменений в новых версиях, нахождению полезных и готовых решений для себя, я начал понимать, что при выборе — на чем писать бэк, я буду использоваться все же ноду. Запуск несколько копий приложений на ноде и разруливание нагрузки на эти копии используя ngnix, давали лучшие результаты. Недавно вернулся к этой теме и доработал лоадер и плагин.

Начну с шаблона

Шаблон для бэкэнда

Шаблон представляет из себя заготовку, в которую должны попадать куски бэкэнда из кастомных блоков файлов vue. Всё это после обработки сохраняется в результирующем файле. Пример шаблона:

Шаблон бэкэнда

const WEB_PORT = 314;
const Koa = require('koa');
var Router = require('koa-router');
const app = new Koa();
var router = new Router();
app
    .use(router.routes())
    .use(router.allowedMethods());
const body = require('koa-json-body')({ limit: '10kb' });
app.listen(WEB_PORT);
app.context.db = require('../lib/db.js');
/*{{endpoints}}*/

/*{{endpoints}}*/ — это то место, куда будет вставляться код из кастомных блоков

Webpack loader

Код загрузчика

var loaderUtils = require("loader-utils");
var path = require('path');
const id = 'gavrilow_backend_plugin';
exports.default = function (source) {
    this.cacheable();
    // Отправляем данные далее следующему загрузчику
    // ВАЖНО!!! Отправляем пустую строку, иначе все что отправим попадет в конечную сбрку
    this.async()(null, '');
    // Удаляем все переносы строк. Их очень много.
    const _source = source.replace(/^n/img, '');
    // Путь к файлу в котором содержится Custom Block [blockType=backend]
    const file_path = this.resourcePath;
    // this._compiler - глобальный объект, который доступен из плагина
    if (this._compiler[id] === undefined)
        this._compiler[id] = {
            change: true,
            arr: []
        };
    var fp_exists = false;
    // Перебираем массив и ищем ранее добавленный код из Custom Blocks vue
    // Идентификатор блока - полный путь файлу.
    for (let i = this._compiler[id].arr.length - 1; i >= 0; i--) {
        if (this._compiler[id].arr[i].file_path === file_path) {
            fp_exists = true;
            // если нашли, то сравним с прошлой версией.
            if (this._compiler[id].arr[i].data !== _source) {
                // если есть изменения то сохраяем исменения в объект и для палагина выставляем флаг, что были изменения
                this._compiler[id].arr[i].data = _source;
                this._compiler[id].change = true;
            }
            break;
        }
    }
    if (fp_exists) return; // Если выше был заход в первое условие в цикле, то выходим
    // Добавлеме новый объект в массив, содержащий тест Custom Blocks и полный поуть к файлу
    // и сигнализируем флагом [ change = true ] для плагина что есть изменения.
    this._compiler[id].change = true;
    this._compiler[id].arr.push({
        file_path: file_path,
        data: _source
    });
};

В загрузчик к попадают на обработку файлы *.vue в которых содержатся кастомные блоки. Имя кастомного блока можно задать свое.

Webpack plugin

Код плагина

const fs = require('fs');
const util = require('util');
const readFile = util.promisify(fs.readFile);
const writeFile = util.promisify(fs.writeFile);
var footer_header_template;
class gavrilow_backend_plugin {
    constructor(options) {
        this.options = options;
        this.logMess = '';
    }
    endLog(){
               this.logMess = '------ gavrilow-backend-plugin ------------------------------------------------------------------n'
            +this.logMess;
        this.addLogMess('-------------------------------------------------------------------------------------------------');
        console.log(this.logMess);
        this.logMess = '';
    }
    addLogMess(mess){
        this.logMess += mess+'n';
    }
    async prepareTemplate(){
        try {
            if (footer_header_template === undefined) {
                let contents = await readFile(this.options.backend_template, "utf-8");
                footer_header_template = contents.split(/^/*+?{{.*endpoints.*}}+?*/$/img);
                if (footer_header_template.length !== 2) {
                    footer_header_template = undefined;
                    this.addLogMess('Не удалось найти точку вставки блоков.');
                    this.endLog();
                    return false;
                } else return true;
            } else return true;
        } catch (err) {
            footer_header_template = undefined;
            throw err;
        }
    }
    apply(compiler) {
        compiler.hooks.emit.tapAsync(
            'gavrilow_backend_plugin',
            (compilation, callback) => {
                callback();
                if (this.options.backend_template === undefined || this.options.backend_template === '') {
                    this.addLogMess('Необходимо создать и/или указать файл-шаблон для бэкэнда...');
                    this.endLog();
                    return;
                }
                if (this.options.backend_output === undefined || this.options.backend_output === '') {
                    this.addLogMess('Необходимо указать путь и имя js файла для бэкэнда...');
                    this.endLog();
                    return;
                }
                if (!compiler.gavrilow_backend_plugin) {
                    this.addLogMess('В Вашем проекте нет ни одной секции для бекенда [ <backend>...</backend> ].');
                    this.endLog();
                    return;
                }
                (async ()=>{
                    try {
                        // Подготваливаем шаблон
                        if (!await this.prepareTemplate())
                            return;
                        // Если загрузчик не выставил флаг сигнализирующий о каких-либо изменений
                        if (!compiler.gavrilow_backend_plugin.change) return; // Если ничего для бэка не поменялось
                        // сбрасываем флаг
                        compiler.gavrilow_backend_plugin.change = false;
                        if (compiler.gavrilow_backend_plugin.arr.length === 0) {
                            this.addLogMess('По какой-то причине нет данных из секции [ <backend>...</backend> ]');
                            this.endLog();
                            return;
                        }
                        this.addLogMess('Собираем beckend: "'+this.options.backend_output+'"n...');
                        // записываем все что выше /*{{endpoints}}*/ в шаблоне
                        var backend_js = footer_header_template[0]+"n";
                        // конкатенация кусков кода из Custom Blocks
                        for (let i = 0; i < compiler.gavrilow_backend_plugin.arr.length; i++) {
                            backend_js +=compiler.gavrilow_backend_plugin.arr[i].data+"n";
                            this.addLogMess('['+compiler.gavrilow_backend_plugin.arr[i].file_path+']');
                        }
                        // присоединяем все что ниже /*{{endpoints}}*/ в шаблоне
                        backend_js += footer_header_template[1];
                        // асинхронно записываем результат
                        await writeFile(this.options.backend_output, backend_js);
                    } catch (err) {
                        throw err;
                    } finally {
                        this.endLog();
                    }
                })();
            }
        );
    }
}
gavrilow_backend_plugin.loader = require.resolve('./loader');
module.exports = gavrilow_backend_plugin;

Плагин срабатывает по окончанию сборки проекта. Подготавливает шаблон разбивая его на 2 части: до /*{{endpoints}}*/ и после /*{{endpoints}}*/ Если был установлен флаг изменения массива лоадером, то происходит сборка конечного скрипта из всех доступных частей.

Как это всё попробовать

Проект залил на гитхаб.

Там же и описание настроек.

Теги:

Похожие публикации

Source: habr1

Ну и напоследок напоминаю вам о нашем новом проекте vkserfing bot.