Наш путь от Symfony к Vue - часть 2

В части 1 этой статьи мы подробно рассказали, почему мы решили переместить все веб-приложения Mr Jeff из Symfony в стек внешнего интерфейса Javascript, почему мы выбрали Vue и как мы планировали выполнить миграцию с помощью Encore. В этой новой главе сначала мы увидим, как мы установили и настроили Encore в соответствии с нашими потребностями, затем создали простейшее приложение Vue, чтобы проверить, что все работает, и, наконец, как мы успешно переписали одно из наших представлений Symfony как работающее приложение Vue. внутри нашего сайта.

Настоящее путешествие начинается здесь - перед нами разворачивается океан, полный ветров, штормов и кракенов. Нам понадобятся отважные моряки, идите с нами!

Раскладывание парусов: настройка Encore

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

Как всегда, первый шаг - установка вещей. Вот все пакеты, которые нам понадобятся сейчас:

$ yarn add --dev @symfony/webpack-encore
$ yarn add --dev vue vue-loader@^14 vue-template-compiler
$ yarn add --dev sass-loader node-sass

Затем мы создаем webpack.config.js в корне проекта и указываем конфигурацию Webpack, которую мы хотим для нашего проекта. Encore предоставляет нам отличные однострочные элементы для многих распространенных фрагментов конфигурации Webpack, что упрощает настройку большей части рабочего процесса. Итак, начнем с простого:

var Encore = require('@symfony/webpack-encore');
Encore
  .setOutputPath('web/build/')
  .setPublicPath('/build')
  .addEntry('app', './vue/src/main.js')
  .cleanupOutputBeforeBuild()
  .enableSourceMaps(!Encore.isProduction())
  .enableVersioning(Encore.isProduction())
  .enableVueLoader()
  .enableSassLoader();
module.exports = Encore.getWebpackConfig();

Достаточно просто - на данный момент все, что мы сделали, это скопировали из нескольких разделов документации Encore. Мы только скорректировали путь для нашей точки входа, так как мы хотели назвать нашу папку проекта vue вместо assets. Между прочим, в этой папке vue будет жить весь наш код Vue, и мы поместили его на верхний уровень проекта Symfony, но вы можете разместить его где угодно.

Эта точка входа в основном означает «Эй, Encore, перейдите к этому файлу, импортируйте и обработайте все, что вы там найдете, упакуйте его в файл с именем app и бросьте в только что установленный нами выходной каталог». Если мы хотим иметь несколько приложений (что мы и сделаем позже), нам просто нужно добавить здесь больше точек входа. Отлично!

Кроме того, остальная часть файла читается очень легко - мы просто просим Webpack очищать нашу выходную папку перед каждой новой сборкой, сообщая ему включить исходные карты и управление версиями ресурсов, а также включить загрузчики Vue и Sass, которые мы установили ранее. Ах, какое удовольствие иметь возможность просто отбросить эти несколько строк вместо того, чтобы часами бороться с вызывающими кошмар объектами конфигурации Webpack! 🙌

Переживание шторма: наше первое приложение на Vue

Давайте продолжим. Следующим шагом является создание той main.js точки входа, на которую мы только что указали: мы должны импортировать библиотеку Vue и компонент Vue, а затем смонтировать компонент в элементе HTML. Должно получиться так:

import Vue from 'vue'
import App from './components/App'
new Vue({
  el: '#app',
  template: '<App/>',
  components: { App },
});

Конечно, нам также понадобится этот компонент приложения, поэтому давайте создадим в нем папку components и файл App.vue. Наш первый компонент Vue!

<template>
    <h1>Look ma! A Vue component!</h1>
</template>
<script>
</script>
<style lang="scss">
</style>

И наконец, поскольку мы включили управление версиями ресурсов, нам нужно указать Symfony на файл манифеста, который Webpack создаст для нас. Итак, мы открываем config.yml и добавляем эту строку:

framework:
  assets:
    json_manifest_path: '%kernel.project_dir%/web/build/manifest.json'

(Последняя строка должна быть в одну строку, она просто не умещается в ширину Medium. Проверьте ссылку здесь.)

Так что это должно сработать! Скажем Encore обработать все это и сгенерировать наше приложение с помощью этой команды:

$ yarn encore production

Если в папке web/build появилось несколько новых файлов, ура, это сработало! Но подождите, мы еще не видим наше приложение… 🤔 Ах, конечно, нам нужно перейти к нашему шаблону и указать, где оно должно отображаться. Мы будем использовать свежий новый шаблон, но вы можете выбрать любой элемент в любом шаблоне:

{% extends '::layout.html.twig' %}
{% block title %}Test page{% endblock %}
{% block stylesheets %}
    <link rel="stylesheet" href="{{ asset('build/app.css') }}"/>
{% endblock %}
{% block content %}
    <div id="app"></div>
{% endblock %}
{% block javascripts %}
    <script src="{{ asset('build/app.js') }}"></script>
{% endblock %}

Пустой <div> имеет тот же идентификатор, который мы выбрали в файле main.js, а эти файлы app.css и app.js генерируются Encore, когда мы запускали команду сборки.

Обновите и БУМ! Если содержимое нашего компонента находится на экране, значит, все прошло нормально. Наш шаблон визуализировал элемент #app и загрузил файл CSS, который пуст, поскольку мы еще не добавили никаких стилей, и файл JS, который запускает весь необходимый код для создания нашего приложения и помещения его в элемент #app, просто как мы указали ранее.

Появляется кракен: наше первое НАСТОЯЩЕЕ приложение Vue

Итак, теперь, когда наша установка работает, давайте создадим реальный пример. В нашем проекте первое место, где мы хотели представить Vue, - это экран настроек пользователя - он простой, но требует серьезных изменений, поэтому звучит как идеальный тестовый пример для начала перехода. Итак, вместо того, чтобы возиться с и без того запутанными шаблонами Twig и сценариями jQuery, мы избавимся от всего этого и заменим его новым блестящим приложением Vue.

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

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

var Encore = require('@symfony/webpack-encore');
Encore
  // ...
  .addEntry('app', './vue/src/main.js')
  .addEntry('pageSettings', './vue/src/pageSettings.js')
  // ...

Теперь нам нужен pageSettings.js, который импортирует Vue и компонент PageSettings и создаст приложение. Он очень похож на тот, который мы использовали для тестирования:

import Vue from 'vue'
import PageSettings from './components/PageSettings'
new Vue({
  el: '#page-settings',
  template: '<PageSettings/>',
  components: { PageSettings },
});

Конечно, нам также понадобится этот PageSettings.vue компонент, который будет содержать фактический макет и логику страницы. Это могло быть что-то вроде этого:

<template>
  ...
  <form class=”form” @submit.prevent=”changePassword”>
    <div class="form-group">
     <label>Current password</label>
     <input type="password" v-model="passwordCurrent">
    </div>
    <div class="form-group">
      <label>New password</label>
      <input type="password" v-model="passwordNew">
    </div>
    <div class="form-group">
      <label>Repeat password</label>
      <input type="password" v-model="passwordRepeat">
    </div>
    <button type="submit" :disabled="!isFormValid">Save</button
  </form>
  ...
</template>
<script>
import UserService from ‘@/services/UserService’
export default {
  computed: {
    isFormValid () {
      return ... // Just a bunch of form validation logic here
    }
  },
  methods: {
    changePassword () {
      const data = {
        currentPassword: this.passwordCurrent,
        password: this.passwordNew
      }
      UserService.updatePassword(this.userId, data)
        .then(res => {
          // Handle OK response
        })
        .catch(err => {
          // Handle error response
        })
    }
  },
  created () {
    this.userId = sessionStorage.getItem(‘userId’)
  },
  data () {
    return {
      userId: null,
      passwordCurrent: ‘’,
      passwordNew: ‘’,
      passwordRepeat: ‘’
    }
  }
}
</script>
<style lang="scss">
</style>

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

Здесь есть одна интересная деталь: наша конечная точка API для изменения пароля пользователя требует, чтобы мы передали ей идентификатор пользователя, но, поскольку наше представление больше не обрабатывается Symfony, у нас нет доступа к нему. Итак, вопрос в том, как мы можем передать некоторые исходные данные из Symfony в наши приложения Vue? Есть несколько способов сделать это, но для этого варианта использования мы выбрали один из самых простых - использование хранилища браузера. Как видите, в хуке created () (все о хуках Vue здесь) мы читаем идентификатор пользователя из хранилища сессий. Мы увидим, как мы его там сохранили, в следующем фрагменте кода.

Наконец, нашему новому приложению нужно место для жизни. Мы перейдем к нашему settings.html.twig шаблону, поцарапаем там большую часть кода Twig и jQuery и заменим его ссылками на файлы CSS и JS, которые Encore создаст для нас. Это должно выглядеть примерно так:

{% extends '::base_layout.html.twig' %}
{% block title %}Settings{% endblock %}
{% block stylesheets %}
  <link rel="stylesheet" href="{{ asset('build/pageSettings.css') }}"/>
{% endblock %}
{% block content %}
  <div id="page-settings"></div>
{% endblock %}
{% block javascripts %}
  <script type="text/javascript">
    sessionStorage.setItem('userId', '{{ user.id }}');
  </script>
  <script src="{{ asset('build/pageSettings.js') }}"></script>
{% endblock %}

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

Остальная часть шаблона точно такая же, как и тестовый, который мы делали раньше, поэтому, если мы снова запустим нашу команду сборки:

$ yarn encore production

Бум! Если мы теперь перейдем к маршруту для нашей страницы настроек, наше блестящее новое приложение Vue полностью заменит старое представление!

Слишком много гигантских щупалец: базовой настройки обычно недостаточно

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

Как уже упоминалось, этот подход к созданию одного независимого приложения для каждого представления отлично работал с парой представлений, которые мы использовали для нашего пилотного проекта: с очень простой страницей настроек, которую мы показали здесь, и новой функцией, для которой требовался только список, раздел с подробными сведениями об элементе, и несколько вызовов API. Пилот прошел отлично, и решение о Vuefying все было одобрено, но мы также обнаружили несколько признаков того, что текущая настройка может быть улучшена. Нужно ли нам поддерживать контроллер Symfony, шаблон и точку входа для каждого создаваемого приложения Vue, если все они почти идентичны? Как мы можем добавить конфигурации Webpack, которых нет в Encore? Что, если несколько наших представлений должны передавать или получать одни и те же исходные данные? Как мы можем использовать наши переменные env Symfony во Vue, если они существуют только на стороне сервера? Можем ли мы использовать возможности горячей перезагрузки, которые предлагает Webpack? И если этот локальный сервер с горячей перезагрузкой создает свой собственный localhost, как мы можем получить к нему доступ, если мы работаем в контейнерах Docker?

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

Да, нам нужно разобраться с еще некоторыми файлами конфигурации, прежде чем мы сможем полностью сосредоточиться на миграции наших приложений. Но не бойтесь, самая сложная часть путешествия позади - впереди только спокойная вода и голубое небо. Эй!

Обновление: Часть 3 уже вышла!

Следите за новостями до конца истории! Мы подробно расскажем, как мы добавили vue-router после того, как количество приложений Vue начало расти, и как мы настроили нашу конфигурацию Encore, чтобы улучшить рабочий процесс разработки и иметь возможность работать с Docker, плагинами Babel и другими инструментами. Остров сокровищ ждет, оставайтесь с нами!