Frontend: Старый

Рекомендации по встраиванию приложения на Vue.js в старый frontend

Если требуется расширить старый функционал на фронте, есть две альтернативы:

  • писать новый код, используя фрейморки, доступные в контексте того места, где происходит доработка. 
  • загрузить bundle с функционалом, написанным на актуальном фреймворке и собранным webpack-ом

Поскольку в нашем проекте в большинстве случаев фронт делался давно, написанный в контексте старых фрейворков код автоматически становится неподдерживаемым и требует рефакторинга. Поэтому предлагается использовать второй способ создания нового функционала.

Порядок действий

  • В контексте (том месте старого кода, где будет происходить обращение к новому функционалу, к примеру, открытие popup-а) формируется js-объект с данными, необходимыми для работы нового функционала. Объект присваивается глобальному объекту window с использованием доступных "старых" фреймворков:
window.pageData = {
   // пример с необходимыми данными
   serviceId: $('#service').val(), // <-- смотрите, это же jQuery!
   orderNum: document.querySelector('#orderNum').value // контекстно-независимое обращение к DOM-элементу (Vanilla JS)
}
  • На странице со старой версткой, на которой будет отображаться новый модуль, добавляется точка монтирования <div id="your-module-container"></div>
  • Создается новый модуль webpack
    • создается файл в каталоге frontend/webpack/modules с соответствующим вашему модулю именем (можно скопировать любой из имеющихся, будут отличаться только пути). 
    • заранее указываются корректные пути: 
      relativeAppFilePath - адрес до главного файла вашего модуля, который будет располагаться в frontend/embeddedApps
      relativeOutputPath - каталог с бандлами (сборками) - /www/Resource/<ваш_модуль>
    • В webpack.config.js добавляется новый элемент: 
      require(__dirname+'/frontend/webpack/modules/<ваш_модуль>.module.js')
  • Создается главный файл модуля 
'use strict';

import Vue from 'vue'; // уже можно использовать любой компонент в т.ч. Vue-файлы
import YourModuleView from 'views/YourModule/YourModuleView.vue';

// главное приложение модуля
const YourNewModuleApp = {
   mountPoint: '#your-module-container', // точка сборки

   // инициализация модуля
   init: function() {
      new Vue({
         components: {
            YourModuleView
         }, 
         data: function() {
            // получение нужных данных из глобального объекта
            orderId: window.pageData.orderId, 
            serviceId: window.pageData.serviceId
         },
         // передача параметров вниз по иерархии
         template: '<your-module-view v-bind:order-num="orderNum" v-bind:service-id="serviceId"></your-module-view>',
      }).$mount(this.mountPoint);
   }
}

if (document.querySelector(YourNewModuleApp.mountPoint)) {
   // в случае обнаружения точки монтирования запускается инициализация модуля
   YourNewModuleApp.init();
}
  • Сборка и генерация бандла: npm run build
  • В контексте старого кода вызываем новый скрипт сборки. Сделать это можно разными способами:
    • вставкой тэга скрипт: <script src="/Resource/<your-module>/bundle.js?app.vers=<?=WEBAPP_VERSION?>"></script>. Скрипт стоит вставлять после блока с инициализацией необходимых переменных.
    • $.getScript('/Resource/<your-module>/bundle.js') в случае, если доступен jQuery 
  • В случае, если требуется callback из нового модуля в старый, стоит использовать технологию postMessage https://developer.mozilla.org/ru/docs/Web/API/Window/postMessage Это, кстати, позволит использовать iframe для отображения нового модуля.
// старый код, обработка события

window.addEventListener("messageFromNewModule", onReceiveMessageFromNewModule);

function onReceiveMessageFromNewModule(stringPayload) {
   // операции со старым кодом, выполняемые после получения события из нового модуля
}


// новый код, публикация события

methods: {
   postMessageToExternalApp: function() {
      window.postMessage("messageFromNewModule", '{"result":true}');
   }
}

Ниже приводится пример кода, демонстрирующий, как поддерживать старый код.

<?php
// когда в одном языке используется другой язык, это гарантированно неподдерживаемый код:
if ($value === 1) {
    $js = ' if( !confirm("' . MyMessageEnum::ANSWER . '") ){ $("#btnClose").click(); }';
}
if ($value > 1) {
    $js = ' $("#agreementStatus").val("' . MyServiceEnum::STATUS . '"); ';
    $js .= ' alert("' . MyServiceEnum::STATUS_TEXT . '"); ';
}

?>
<script>
    <?=$js;?>

    // более-менее поддерживаемый вариант:

    var value = <?=$value;?>;
    var answer = "<?=MyMessageEnum::ANSWER;?>";
    var status = <?=MyServiceEnum::STATUS;?>;
    var statusText = "<?=MyServiceEnum::STATUS_TEXT;?>";

    if (value === 1 && !confirm(answer)) {
        $("#btnClose").click();
    }
    if (value > 1 && !confirm(message)) {
        $("#agreementStatus").val(status);
        alert(statusText);
    }
</script>

Таким образом, мы стараемся не ухудшать старый код.

 

Создание модульности

Сейчас старый код представляет собой зачастую скрипты, "заточенные" под конкретную страницу. При этом идут прямые обращения к объектам, существование которых в скрипте не проверяется, например, функции из других js-скриптов. Функции часто перечислены без какой либо логической связности (к примеру: www/js/bodyDivS2.js - смена бланков, сохранение услуги, загрузка инфы по заказу; логика выбора услуги размазана по нескольким скриптам).

Предлагается следующая стратегия рефакторинга этих файлов: 

1. Создание прослойки кода, закрывающей прямые вызовы старых функций. Прослойка будет представлять из себя некие локальные модули, объявляемые вначале каждого старого js-файла с предварительной проверкой на существование. Лучше всего использовать понятное название модуля, соответствующее бизнес-процессу (например, работа с заказом должны быть заключена в модуль window.Order{}). В некоторых случаях, когда трудно определить принадлежность кода к модулю, допускается давать название, одноименное файлу: для www/js/bodyDivS2.js -> window.BodyDivS2{}

2. В каждом таком модуле должна появиться функция, вызывающая первоначальную функцию (либо, если ничего не мешает, можно переместить только тело функции, а старое объявление функции удалить - понятно, что это возможно, только когда старая функция нигде не вызывается). Вероятно, что мы столкнемся с дублированием функционала в разных файлах с незначительными различиями. В этом случае, собираем все в один модуль и делим на мелкие методы, которые можно повторно вызвать. 

3. После появления обертки, обращение из других файлов должно происходить к ней. При этом, обязательно требуется выполнить проверку на существование объекта модуля.

// -- Module.js
window.Module: { 
   moduleFunc: function() { 
     oldFunction(); 
   } 
} 

// --- Module1.js 
if (typeof window.Module === 'undefined') { // проверяем на существование 
   window.Module = { 
      moduleFunc: function() {} 
   } 
} 

Module1: { 
   someFunc: function() { 
      // обращение к функции внешнего модуля window.Module.moduleFunc(); 
   } 
} 

4. После того, как код примет вид небольших модулей с перекрестными вызовами, следует начать создание приложений, собираемых в webpack bundle. Для этого следует сгруппировать все модули, созданные в старых файлах, внутри одного или нескольких независимых приложениях на основе логической связности кода. Старый код придется почистить от проверок на существование модулей, и заменить их на импорты.

5. Следует стараться максимально отказываться от использования старых фреймворков, таких как jQuery. Для работы с DOM на старых страницах (поиск узла, назначение событий на input и т.д.) предлагается следующий способ: вынести работу с элементами на странице в отдельный локальный модуль, внутри которого реализовать минимальный набор функций для работы с DOM-ом (аналоги $.get(), $().click(), итд). Так, чтобы в коде других модулей не встречались прямые вызовы jQuery. Позже, в этом модуле взаимодействие с элементами на странице будет проще переделать под новый фреймворк или совсем избавиться в пользу Vue.js

6. Стараться использовать функции обратного вызова при обращении к внешним модулям. К примеру, частая история - показ загрузчика (www/js/loading/Loading.js). Когда процесс начинается в одном файле (напр. клик по услуге), вызывается лоадер, затем вызываются внешние функции из других файлов, и где-то там лоадер прячется (в теле последней функции в стеке). Так вот, показывать и прятать лоадер нужно в одном и том же месте, а вызов внешних функций должен сопровождаться функцией обратного вызова, тело которой будет располагаться там же, где и начался весь процесс. Но стоит следить за их вложенностью, чтобы не попасть в "Ад обратных вызовов". 


someFunction: function() { 
   Loader.show(); 
   ExternalModule.func1(a, b, function() { 
      // обратный вызов 
      Loader.hide(); 
   }); 
}

 


21.12.2010 12:33

Комментарии

Сделайте начальству гостевую книгу!
Bold |

Copyrights © 2007 - 2026 YaPro.Ru