Завершение курса + всякие полезности

Ну вот наш курс и подошёл к завершению.

Надеюсь, вам было интересно поработать с Vesp и вы узнали для себя что-то новое и даже интересное.

В этой заключительной заметке я хочу сделать краткий обзор PHP и JS библиотек, которые я использую в своих проектах. Возможно, они сэкономят вам время при разработке.

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

PHP

Начнём с box/spout - это очень шустрая библиотека для работы с XLSX, ODS и CSV. Я наткнулся на неё, когда искал экономичное решение для экспорта больших объёмов информации, и она с ними отлично справилась.

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

ramsey/uuid я использую для удобной генерации uuid моделей, когда мне не нужно, чтобы пользователи могли перебирать данные из контроллеров по id. например, на bezumkin.ru все картинки выводятся именно по uuid, подбором ничего не открыть.

Обратите внимание, что uuid бывает нескольких видов, если вам нужен именно случайный uuid - то это 4я версия.

peppeocchi/php-cron-scheduler уже используется в Vesp по умолчанию, но я про него ничего не рассказывал. Там всё просто - прописываете в crontab сервера файл core/cli/cron.php и добавляете скрипты для выполнения, рядом лежит пример для удаления старых сессий.

phpmailer/phpmailer для отправки почты, вместе с pelago/emogrifier, который разбирает стили письма из шапки и вставляет в теги, чтобы все почтовые клиенты их нормально понимали.

А для шаблонов писем я использую, понятное дело fenom/fenom.

Если будет интерес, могу написать подробнее про подключение подобных библиотек к проекту.

Еще вам может понадобиться установить doctrine/dbal, если вы будете писать миграции, которые меняют тип колонок в таблицах методом ->change(), например со string на text. У меня такое иногда случается.

Этого набора библиотек мне хватает для реализации 90% проектов. В остальных случаях нужно подключить какой-нибудь сервис для оплаты или что-то подобное. Но всё это делается по одной схеме - ищем подходящую библиотеку, читаем документацию, подключаем.

Javascript

При выборе JS решения для Vesp я советую руководствоваться следующими правилами:

  • в первую очередь проверяем официальные модули для Nuxt
  • если ничего нет, ищем нужное на npmjs
  • предпочтение отдаём модулям Nuxt, а затем интеграциям с Vue
  • если совсем ничего нет - берём решения написанные на чистом JS, по возможности без зависимостей
  • И никакого jQuery!

vue-meta уже интегрирован в Nuxt и доступен на всех страницах для выставления title и прочей работы с мета-тегами. Именно этот модуль мы используем в админке для указания названий страниц.

Я не уделял на курсе внимания SEO оптимизации, но думаю вы и сами легко с этим разберётесь, потому что модуль позволяет всё настроить в лучшем виде - просто читайте документацию.

@nuxtjs/sitemap - генерация карты сайта. Отлично работает как со статическими сайтами, так и с динамическими маршрутами на тысячи страниц. Просто пишете функцию с запросом в API для получения этих страниц, и модуль генерирует по ним карту.

Пример с bezumkin.ru:

Config.sitemap = {
  hostname: env.SITE_URL,
  gzip: true,
  exclude: ['/admin/**', '/user/**', '/service/**'],
  routes: async () => {
    const routes = []
    const [{data: sections}, {data: topics}] = await Promise.all([
      axios.get(env.API_URL + 'web/sections', {params: {sort: 'views_count', dir: 'desc'}}),
      axios.get(env.API_URL + 'web/topics', {params: {sort: 'id', dir: 'desc'}}),
    ])

    sections.rows.forEach((section) => {
      routes.push({
        url: '/sections/' + section.alias,
        lastmod: section.updated_at || section.created_at,
      })
    })
    topics.rows.forEach((topic) => {
      routes.push({
        url: (topic.section.lessons ? '/lessons/' : '/sections/') + topic.section.alias + '/' + topic.id,
        lastmod: topic.updated_at || topic.created_at,
      })
    })

    return routes
  },
}

@nuxtjs/pwa поможет вам превратить ваш сайт в PWA, который можно устанавливать на телефон. Всё работает хорошо по умолчанию, нужно только указать свою иконку. Этот модуль уже подключен по умолчанию в Vesp.

@nuxtjs/svg позволяет легко импортировать SVG файлы в любом виде, хоть голым текстом, хоть компонентом для вставки в шаблон. Просто посмотрите примеры в документации, это крайне полезный модуль для вёрстки.

@nuxtjs/markdownit позволит вам удобно загружать на страницы какие-то статичные документы, вроде пользовательского соглашения в формате Markdown. Понятное дело, работать можно не только с файлами, а рендерить вообще любой markdown в html через функцию $md.render().

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

glightbox я использую для вывода увеличенных картинок при клике, например прямо здесь - на bezumkin.ru.

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

bootstrap-vue/nuxt - лучшая интеграция Bootstrap 4 с 85+ компонентами на все случаи жизни. Лично я очень ценю возможность самостоятельного подключения и модификации Bootstrap в стили проекта, с импортом только нужного, что облегчает размер собранного проекта.

Там есть и ленивая загрузка картинок, и прогресс-бары и еще много чего еще - просто внимательно посмотрите документацию, она очень подробная.

К сожалению, на Vue 3 и Bootstrap 5 они переходить не торопятся, поэтому я уже присматриваю другие решения на будущее.

@fortawesome/vue-fontawesome - мои любимые иконки, которые постоянно развиваются и обновляются. В последнее время, наверное, даже слишком бурно, что иногда приводит к небольшим проблемам. В любом случае, богаче набора иконок так удобно интегрированных в Nuxt найти не получится.

Заключение

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

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

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

Завершение курса означает только окончание написания новых заметок, но я продолжаю отвечать на ваши вопросы в комментариях!

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

Буду очень рад вашим отзывам о курсе в комментариях. Что понравилось, что не очень, о чём еще нужно написать в будущем за рамками курса.

← Предыдущая заметка
Запуск в продакшн
Следующая заметка →
Отправка писем
Комментарии (59)
bezumkinВасилий Наумкин
12.07.2022 06:28

Насчёт древовидного - это всё делается через рекурсивный вызов одного компонента, никакой уникальности именно для Vesp нет. Принцип ровно тот же, что и в MODX, да и вообще в любой системе.

Например, комментарии на bezumkin.ru:

  • базовый компонент CommentsTree получает комменты в "плоском" виде, где есть id и parent_id
  • строит по этому parent_id дерево комментариев
  • вызывает в своём шаблоне форму редактирования и компонент оформления ветки CommentsThread с передачей ему комментариев
  • тот оформляет первый уровень комментариев, и смотрит, есть ли у них потомки.
  • если есть, перебирает их и внутри вызывает сам себя, то есть, делает рекурсию

CommentsTree:

<template>
  <div id="topic-comments" ...>
    <comments-thread
      :comments="commentsTree"
      :topic="topic"
      ...
    />

    <comments-form v-if="..." />
  </div>
</template>

CommentsThread:

<template>
  <div class="comments-thread">
    <div v-for="comment in comments" ...>
      ...

      <comments-form v-if="showForm(comment)" ... />
      <comments-thread
        v-if="comment.children.length"
        :comments="comment.children"
        :topic="topic"
        ...
      />
    </div>
  </div>
</template>

Нужно будет прописать логику вывода формы комментирования в нужном месте, и методы сохранения, редактирования и удаления комментриев. Лично я всё это держу в родительском компоненте CommentsTree, а действия из вложенных CommentsThread передаю событиями this.$emit('action', data)

Примерно такая же логка будет и с любыми другими рекурсивными данными, вроде вложенного меню.

И возможно я прошу многого, но хотелось бы понимать как пишутся умные фильтры, по типу mSearch2

Постараюсь написать про это заметку, как будет время.

bezumkinВасилий Наумкин
12.07.2022 20:40

Всё как обычно, в самом начале файла конфига

import axios from 'axios'
bezumkinВасилий Наумкин
16.08.2022 14:50

Курс замечательный

Ура!

Хотелось бы статейку про правильную интеграцию JS в VESP.

Модули Nuxt ставятся парой строк, там вообще никаких вопросов нет. Большинство популярных компонентов Vue про Nuxt или в курсе, или имеют issues про это на Github.

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

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

bezumkinВасилий Наумкин
18.09.2022 06:26

Vesp расширяет и возвращает конфиг из @vesp/frontend. То есть, должно быть так:

import {Config, findEnv, loadEnv} from '@vesp/frontend'

// ... всякие другие настройки

Config.plugins = ['@/src/site/plugins/menu']

// ...

return Config

А если ты делаешь export default {...}, то просто игнорируешь все настройки по умолчанию и возвращаешь только 1 плагин, без остальных настроек - и тогда система не может даже найти директорию с исходниками, потому и просить её создать.

bezumkinВасилий Наумкин
20.08.2022 12:00

Очень даже установлены - просто их тянет зависимостями сам Vesp.

bezumkinВасилий Наумкин
20.08.2022 12:49

А там PHP зависимости от Composer!

bezumkinВасилий Наумкин
21.08.2022 03:27

Только сейчас заметил, что у тебя путь от корня сайта.

Такого быть не должно, директория core находится на одном уровне с www - корнем сайта. Ты видимо случайно в ней composer как-то запустил или что-то такое.

Вот правильная www - там только api.php и никаких директорий.

bezumkinВасилий Наумкин
22.08.2022 00:13

А, тогда все верно!

bezumkinВасилий Наумкин
31.08.2022 10:51

Это 2 разных тега:

Несмотря на 2 тега, физически файл грузится только 1 раз.

Стоит отметить, что мы этими вопросами вообще не заведуем и не управляем - Nuxt сам собирает файлы, минимизирует и подключает оптимальным способом.

bezumkinВасилий Наумкин
01.09.2022 08:31

Не знаю, судя по Composition API оно вообще для Vue 3. Да и Laravel там, наверное, не просто так.

Ты лучше скажи, зачем оно вообще тебе нужно?

Это же админка, чтобы что-то админить - а Vesp по умолчанию админить нечего, кроме юзеров. Всё нужно писать, а в процессе написания заодно и админку сделаешь на Bootstrap.

В общем, я ни разу ничего подобного не использовал и зачем оно нужно не в курсе.

bezumkinВасилий Наумкин
04.09.2022 14:08

2. Это никакая не главная проблема, да и вообще не проблема. Просто не все JS компоненты в курсе, что они могут быть запущены без браузера, на сервере - и могут быть не готовы к подобному.

Например, какой-нибудь lightbox при инициализации проверяет ширину экрана. Но на сервере

  • никакого экрана нет
  • лайтбокс там никто запускать и не будет

Поэтому такой компонент на сервере просто не нужно запускать, и нет проблем.

3. Ты путаешь https://getbootstrap.com и https://bootstrap-vue.org

Первый - собственно SASS\CSS стили и правила оформления, а второй - это набор компонентов, который реализует разный функционал на Vue, используя стили Bootstrap. Типа у открытой модалочки одни классы, а у закрытой другие - и меняет их Vue.

Нужные стили я подключаю в assets/scss/index.scss, а компоненты Bootstrap Vue в nuxt.config.js, чтобы работал tree shaking

Если я вдруг забуду подключить стили - компоненты всё равно работать будут, просто не будет оформления.

Точно так же стили Bootstrap используются и в React у компонентов React Bootstrap.

Ну и насчёт Сhart.js ты уже догадался, что это оригинальная библиотека и её адаптация для Vue. Как правило адаптации пишут сторонние разработчики, но вот например у Sortable.JS, который мы использовали - куча поддерживаемых фреймворков от основной команды.

bezumkinВасилий Наумкин
26.09.2022 10:15

Смотри на их сайте, только обрати внимание, что мы используем те, которые solid и free.

https://fontawesome.com/search?o=r&m=free&s=solid

bezumkinВасилий Наумкин
27.09.2022 17:58

Так надо ж подключить в конфиге nuxt.

Импортируются только явно указанные иконки, для уменьшения размера итогового приложения.

bezumkinВасилий Наумкин
11.11.2022 13:08

Конечно! Нужно только:

  1. Доустановить варианты, которых тебе не хватает. Для платных нужны особые настройки.
  2. Указать тип явно через массив <fa :icon="['fal', 'cart-shopping']" />

Могут быть fal (light), far (regular), fad (duo), fas (solid) и fab (brands)

bezumkinВасилий Наумкин
12.11.2022 08:58

а вот таких пакетов нет:

Они доступны только за деньги, я же дал ссылку на документацию.

bezumkinВасилий Наумкин
14.11.2022 00:22

component: ['fa', 'fab'],

Это откуда взялось? Там строка, а не массив, ничего менять не нужно - посмотри документацию.

И еще, admin/nuxt.config.js по другому написано:

Это просто функции объединения для объектов и массивов из библиотеки lodash, чтобы добавить свои настройки к тем, которые уже есть в стандартном Vesp.

bezumkinВасилий Наумкин
26.12.2022 12:20

Что еще нужно добавить или отнять, чтобы в админке появилась рабочая панель редактора?

Не знаю, ни разу этим компонентом не пользовался. Но могу предположить, что навешивать его нужно на сырой <textarea, а не на vue компонент <b-form-textarea

И еще, как сделать отформатированный текст из базы данных

Вся фишка Markdown именно в том, что текст в БД хранится без HTML, а рендерится перед выводом на экран. Вот тебе его и нужно пропустить через markdownit или что-то подобное. У него в примерах есть такое, попробуй:

<template>
  <div v-html="$md.render(твоя-переменная-с-markdown-текстом)"></div>
</template>
bezumkinВасилий Наумкин
26.12.2022 15:50

А я себе свой редактор написал с помощью textarea-editor, где могу прописать любую логику при нажатиях на кнопки и шорткаты.

bezumkinВасилий Наумкин
24.01.2023 03:46

Ты всё еще не перестроился мысленно на Webpack. Не нужно подключать готовый скрипт как на обычном сайте, нужно его импортировать в свой код.

import GLightbox from 'glightbox'
import 'glightbox/dist/css/glightbox.min.css'

export default {
  data() {
    return {
      files: [
        // ...
      ],
  },
  // ...
  methods: {
    onShowImage(e) {
      const elements = []

      let startAt = 0
      const thumbnails = e.currentTarget.closest('.images')
      if (thumbnails) {
        const links = thumbnails.querySelectorAll('a')
        links.forEach((a) => {
          elements.push({href: a.href, type: 'image'})
        })
        startAt = elements.findIndex((i) => i.href === e.currentTarget.href)
      } else {
        elements.push({href: e.currentTarget.href, type: 'image'})
      }

      GLightbox({elements, startAt}).open()
    },
  },
}

И навесить обработчик на ссылки с картинками:

<template>
  <div class="images">
    <a v-for="file in files" :key="file.id" :href="$image(file)" @click.prevent="onShowImage">
      <img :src="$image(file, {w: 200, h: 150, crop: 'fit'})" alt="" />
    </a>
  </div>
</template>

Код неточный, могут быть ошибки и очепятки.

bezumkinВасилий Наумкин
05.02.2023 03:52

Как-то проморгал твой комментарий, сорян.

Проблема в том, что ты запускаешь Nuxt в режиме серверного рендера, а плагины типа lightbox на сервере не работают. При импорте подобных дополнений на сервере будет ошибка window is not defined, что логично - никакого window там нет.

Поэтому нужно вынести подключение Glightbox в клиентский плагин, то есть плагин с суффиксом .client.

import GLightbox from 'glightbox'
import 'glightbox/dist/css/glightbox.min.css'

export default (ctx, inject) => {
  inject('glightbox', (params ={}) => {
    return new GLightbox(params)
  })
}

Затем полключить его в nuxt.config.js:

Config.plugins = ['@/plugins/glightbox.client.js']

И дальше можно использовать this.$glightbox на всех страницах:

mounted() {
  this.$glightbox({
    selector: '.glightbox',
    touchNavigation: true,
    loop: true,
    autoplayVideos: true,
  })
},
bezumkinВасилий Наумкин
05.02.2023 15:17

Да, я забыл суффикс в своем примере - так что ты все правильно сделал.

Пример поправил.

bezumkinВасилий Наумкин
23.05.2023 03:51

Я использую PHPMailer для работы с почтой, шаблоны оформляю на Fenom. Делаю один основной, а разные варианты писем его просто расширяют.

Постараюсь написать заметку про это, с готовым кодом - но обещать не могу, занят на работе.

bezumkinВасилий Наумкин
28.05.2023 03:37
bezumkin
Василий Наумкин
09.04.2024 01:45
Ошибка 500 Это не похоже на ошибку Nginx, это скорее всего ошибка PHP - надо смотреть его логи. Во...
futuris
Futuris
04.04.2024 05:56
Я просто немного запутался. Когда в абзаце &quot;Vesp/Core&quot; ты пишешь про &quot;новый trait Fil...
bezumkin
Василий Наумкин
20.03.2024 18:21
Volledig!
Андрей
14.03.2024 10:47
Василий! Как всегда очень круто! Моё почтение!
russelgal
russel gal
09.03.2024 17:17
А этот стоило написать хотя бы затем, чтобы получить комментарий от юзера, который ничего не писал ...
inetlover
Александр Наумов
27.01.2024 00:06
Василий, спасибо! Извини, тупанул.
bezumkin
Василий Наумкин
22.01.2024 04:43
Давай-давай!
bezumkin
Василий Наумкин
24.12.2023 11:26
Спасибо!
bezumkin
Василий Наумкин
27.11.2023 02:43
Ура!
bezumkin
Василий Наумкин
25.11.2023 08:30
Vesp тянет 2 зависимости: vesp-frontent для фронта и vesp-core для бэкенда. Их можно обновлять, но э...