Интегрируем SSR в Laravel 9 + Vue 3 + Vite проект
Большую массу PHP-программистов пугает тема SSR, ведь данная технология сильно сопряжена с JavaScript. Многие программисты рекомендуют использовать Node.js и готовые SSR фреймворки в обход любимому PHP. И да, справедливости ради, уйти от PHP в данном случае и правда хороший вариант, хотя не все хотят это делать по каким-либо причинам. Так вот, данная статья призвана показать, как с помощью PHP и Node.js, внутри Laravel, реализовать полноценный SSR как в каком-нибудь готовом Node.js фреймворке по типу Nuxt.js или Quasar. Сходите за чаем, будет весело (автору статьи).
Некоторые моменты будут показаны очень упрощено и, возможно, неказисто. Сделано это для упрощения статьи, но если захотите что-то обсудить или спросить, смело пишите свои комментарии.
Что такое SSR
SSR – это технология, которая позволяет исполнять на сервере JavaScript, а не в браузере, как это обычно происходит. SSR легко реализуется с помощью Node.js, так как там внутри есть все необходимые инструменты. Можно, конечно, обойтись без Node.js и вырвать из его исходников V8 (движок JavaScript на С++), но это уже совсем безбашенный вариант.
Если интересно почитать про SSR подробнее, то переходите по ссылке на нашу тематическую статью.
Перед стартом
Автор надеется что у Вас глобально установлен composer, PHP и Node.js. Автор использует PHP 8.1.1, Composer 2.4.4, Node 16.15.0. В статье используется Laravel 9, Vue 3 и Vite 3.0.0.
Подготовка
Для начала, давайте создадим Laravel проект:
composer create-project laravel/laravel example-app
Теперь мы можем перейти в проект:
cd example-app
И запустим его с помощью команды:
php artisan serve
Хотя, запускать его нам пока не надо, но можете это сделать для проверки.
Теперь нам надо установить laravel/ui пакет, для того, чтобы потом добавить Vue.js в проект. Выполните команду:
composer require laravel/ui
После этого нам надо добавить, через artisan, уже непосредственно Vue.js:
php artisan ui vue
Скорее всего, у Вас проект теперь использует Vue 3, а не Vue 2. А также Vite, а не Webpack, но это все не страшно, мы были к этому готовы. У нас будет Vue 3, а значит и возможность использовать composition API.
Теперь смотрите очень внимательно и выполняйте все действия правильно. Сначала установим пакеты, которые прилетели к нам в проект после установки прошлого пакета из composer. Для этого выполните команду:
npm install
Теперь устанавливаем Vue Router:
npm install vue-router@4
Далее, нам надо создать что-то вроде простого базового проекта на Vue.js. Для этого поместите следующее содержимое в resources/views/welcome.blade.php (старое удалите):
Создайте в папке /resources/js файл router.js со следующим содержимым:
Создайте в папке /resources/js папку pages и внутри нее два компонента Home.vue и About.vue с таким содержимым:
Теперь, создайте в папке /resources/js App.vue с таким содержимым:
Тут как раз мы уже используем подход с setup, а значит можем использовать composition API (ну это так, для справки).
И последний шаг, в папке /resources/js есть файл app.js, замените его содержимое на это:
Когда Вы все сделали, запускайте команду:
npm run build
Потом, запускайте сервер с помощью:
php artisan serve
В браузере у Вас должен запуститься наш проект по адресу localhost:8000.
Можете посмотреть как работает router, нажимать на кнопку с переменной count. В целом этого хватит, теперь можно настраивать SSR.
Ах да, если Вам не хватает привычной команды watch, можно добавить в раздел scripts в package.json свою команду с подобным эффектом:
vite build --watch
Пример:
Добавляем SSR
Если Вы откроете исходный код нашего проекта сейчас, то не увидите текста из компонентов:
Пора исправлять это гнусное недоразумение. Для этого мы напишем свой модуль на Node.js, который будет делать SSR. После этого из PHP мы будем вызывать Node.js и делать магию (надеемся чай уже выпит, далее потребуется что-то покрепче).
В папке /resources/js создайте файл server-app.js с таким содержимым:
Это копия нашего файла app.js, только настроенная под исполнение на сервере. Вместо createApp мы используем createSSRApp из библиотеки Vue.js. Функция renderToString из vue/server-renderer – это готовая функция, которая будет рендерить наше приложение на сервере. Подробнее можете почитать тут, в документации Vue.js по SSR.
A в асинхронную функцию main мы все обернули для того, чтобы использовать await в router и renderToString. Это обязательно, если router не отработает (не будет готов), то Вы никогда не отрисуете нужный компонент по нужному маршруту. А если не дождетесь ответа от renderToString, то и не увидите готовое приложение. И да, в нашем случае функция main автоматически вызывается, когда будет обращение к файлу.
Обратите внимание на строчку 9, там есть переменная requestUrl, которую мы заполняем из process.argv. Переменная процесса всегда доступна в Node.js, оттуда можно взять полезные вещи. Например, когда мы делаем так:
node index.js test
В process.argv[2] будет лежать test. Вот таким же способом из PHP мы будем передавать URL адрес, на которой совершился переход.
Ну и на 19 строчке есть process.stdout.write(), таким способом, мы отдадим результат из скрипта, который вызовем из shell_exec() в PHP. К слову, можно просто console.log() сделать в Node.js, тогда PHP тоже увидит результат.
Теперь, нам надо внести правки в router.js, который мы сделали пару минут назад. Измените его содержимое на следующее:
Здесь мы добавили изменения в начале файла – на 1, 3 и 4 строчке. Мы проверяем выполняется ли сейчас код на сервере, и если ответ утвердительный, то вместо createWebHistory используем createMemoryHistory. То есть, храним нужные маршруты в оперативной памяти, а не в истории браузера.
Вообще, внутри Node.js нет переменных window и document. Вместо window есть global, хотя ее содержимое даже близко к window не стоит. Ну а последней – document, нет и в помине, ибо это переменная дает доступ к API DOM дерева. Как Вы уже догадались, DOM дерева в Node.js нет.
Теперь идем в корень проекта, и в файле vite.config.js убираем строку:
vue: 'vue/dist/vue.esm-bundler.js',
К сожалению, это какое-то странное поведение Vite 3 и Vue 3, если Вы не уберете эту строчку, то Vite не увидит библиотеку vue/server-renderer. К слову, собрать приложение дальше нам это не помешает.
Теперь, там же, добавьте две строчки в раздел laravel() под переменной input:
ssr: 'resources/js/server-app.js',
ssrOutputDirectory: 'bootstrap/ssr',
Должно получиться так:
Переменная ssrOutputDirectory по умолчанию и так все отправит в bootstrap/ssr, но если Вы захотите, можете переопределить папку куда полетят собранные файлы (но пока это не делайте). Теперь модернизируем команду build в package.json. Давайте добавим туда сборку SSR сервера:
vite build && vite build --ssr
Теперь нам осталось модернизировать welcome.blade.php и web.php (пути в Laravel). Добавьте этот код в routes/web.php (прошлый можете удалить):
И в resources/views/welcome.blade.php удалите секцию:
<div id="app"></div>
И добавьте следующий код:
Должно получиться как-то так:
Строку:
/Users/maximkolmogorov/.nvm/versions/node/v16.15.0/bin/node
Измените на свой путь до Node.js. Вообще, обычно это:
/usr/local/bin/node
Но у автора по этому пути лежит Node.js 12, а нужно не меньше 16.15. Если что, можете установить нужную версию через NVM. Узнать версию Node.js у себя можно с помощью:
node -v
А путь до нее:
npm config get prefix
Главное к результату добавьте в конце /bin/node.
Как видно по коду, мы запускаем Node.js сервер из PHP с помощью shell_exec(). Почему именно shell_exec, а не exec? Просто exec вернет только последнюю строку вывода, а shell_exec вернет все. Хотя, внутри Node.js мы возвращаем все через process.stdout.write(), и результат и так будет в одной большой строке, но если бы мы где-то выше использовали еще и console.log() (то есть, создали бы две строки), то shell_exec наш обязательный выбор.
Как видите, вторым аргументом мы передаем наш собранный SSR сервер, а третий аргумент – это URL адрес, который мы пихаем в router.js. Главное в web.php дублировать все маршруты, которые есть в нашем Vue.js приложении.
Теперь собираем все наше добро командой:
npm run build
И запускаем наш Laravel проект:
php artisan serve
Переходим на какую-нибудь страницу, открываем полный исходный код и видим наш долгожданный контент:
Итог
Думаем слова излишни. Если Вы дочитали до этого момента (не перематывая), то, скорее всего, передумали внедрять SSR таким способом. Все таки, это довольно болезненный процесс, при котором надо неплохо владеть PHP, Node.js, инструментом сборки и фреймворком, который нужно рендерить (в нашем случае Vue.js). Автор статьи настоятельно рекомендует использовать готовые SSR фреймворки на базе Node.js: Nuxt.js, Quasar – для Vue.js, Next.js, Gatsby – для React.
Под капотом у готовых инструментов для SSR в PHP (по типу spatie/laravel-server-side-rendering) внутри лежит подобная реализация и подход к работе с Node.js сервером и рендерингом контента. Вам в любом случае придется писать Node.js сервер. Ну и данный плагин не протестирован вместе с Vite, у автора статьи не получилось подружить его вместе с ним. Видимо, если захочется использовать его, придется мигрировать с Vite на Webpack в рамках Laravel. А новые версии Laravel идут вместе с Vite, и выбора между ним и Webpack пока не предусмотрено. Хотя, в том же Vue CLI можно выбирать.
Ну и напоследок, Вы же понимаете, что если отойти от реализации автора и придумать что-то другое, где программист будет в нескольких разных местах (в рамках одного PHP процесса) вызывать shell_exec(), то это чревато потерей производительности. Посмотрите на картинку выше. Все таки, SSR крайне тяжелая операция, которую лучше не использовать больше одного раза и не трогать лишний раз цепочку Node.js > V8 > HTML в одном проекте при выполнении PHP скрипта.
Спасибо за внимание, если понравилось… то круто. Хорошего дня!
Оставьте комментарий