Создание и настройка проекта React + Webpack с нуля до SSR
Хочу вам показать наглядный пример и инструкцию того, как можно самому с «чистого листа» сконфигурировать Webpack для React и Server Side Render'а без каких-либо бойлерплейтов, вроде create-react-app.
Некоторое время назад я начал заниматься созданием сайтов на сей чудесном view-фреймворке и по ходу дела сталкивался со многими проблемами, начиная от того, как правильно и практично реализовать конфиг Webpack'а и как готовить SSR совместно с TypeScript. Было прочитано кучу мануалов, пользовательских решений в гитхабе и прочего, что новичка (да даже иногда и опытных разработчиков) может ввести в ступор. Конечно, можно использовать create-react-app и потом «костылить» eject'ы (расширение базовой конфигурации), обмазывая все готовыми плагинами, но ведь мы хотим держать весь проект под своим видением, не так ли? Да и просто будет полезно понять весь принцип «приготовления» приложения «от» и «до».
Маленькое предисловие:
1) На клиенте используем Node JS 13й версии. К сожалению, на момент написания статьи (сентябрь 2020 г.) на >=14 версии не работает Webpack Dev Server (далее WDS) (никак не обновят в нем Chokidar).
2) В проекте будем использовать: Webpack, TypeScript (далее TS), React, React Router, Stylus, ExpressJS.
3) Постараюсь описать каждый установленный npm-пакет и мало мальски важные, на мой взгляд, параметры в Webpack'е.
Вся инструкция предназначена для программистов, которые уже имели опыт с выше описанными технологиями. Расписывая все с самой базы — не хватит и дня на чтение.
Проект основан почти на «голом» React'е, без всяких Redux'ов, MobX'ов, Helmet'ов и тэдэ. Я хочу продемонстрировать принцип работы сборки и запуска проекта на сервере. Все остальное, надеюсь, сможете отредактировать в дальнейшем под себя.
Итак, начнем с инита npm:
Можете и без ключа yes, чтобы предварительно занести нужную информацию в package.json.
Установим все необходимое для Webpack'a и чтобы он мог собирать TS:
- webpack — сборщик собственной персоной
- webpack-cli — позволяет работать с Webpack'ом из консоли
- webpack-dev-server — небольшой сервер для разработки
- webpack-notifier — будет показывать нам уведомления, если вдруг что-то где-то сломаем
- typescript — компилятор TS-кода
- ts-loader — инжектор TS'а в Webpack
- clean-webpack-plugin — при каждой сборке автоматически очищает папку для итоговых файлов
- uglifyjs-webpack-plugin — для продовой версии будет сжимать и минифицировать JS-файлы
Для тех, кто не так давно работает с npm: с параметром --save-dev устанавливаем пакеты, которые нам нужны именно для разработки (webpack, typescript, например), с --save пакеты которые пойдут в сборку (react, react-dom, fontawesome).
Установим React с роутером:
- react — view-фреймворк
- react-dom — плагин, который будет конвертировать React DOM в html-структуру
- react-router-dom — роутер, который интегрируется через React DOM
Для TS нам надо установить типизацию React, чтобы компилятор понимал с какими данными работает и было удобнее писать код в редакторе:
Устанавливаем CSS-препроцессор (я использую Stylus), с которым будем взаимодействовать и пара плагинов, которые наши стили «доведут до ума» перед продом:
- stylus — препроцессор
- stylus-loader — инжектор Stylus'а в Webpack
- css-loader — позволит нам работать в стилях с @import как import/require в JS
- style-loader — встраивает наши стили в DOM
- autoprefixer@9.8.6 — в релизной сборке будет добавлять префиксы к некоторым стилям (например, -webkit-transition). 10я версия в данном кейсе пока что не работает.
- postcss-csso — очень крутой плагин, который минифицирует CSS
- postcss-loader — инжектор PostCSS'а в Webpack
Все нужные плагины для разработки установили, далее немного разберемся с архитектурой наших файлов.
Создадим в корне проекта папку src со всеми исходниками:
Настраиваем TS. Особо сильно в конфигурацию упарываться не будем, пока просто укажем стоковые параметры для компиляции React со строгими правилами. За всей документацией можно сходить на официальный сайт: https://www.typescriptlang.org/docs
В корне проекта создаем файл tsconfig.json:
Сделаем внешний конфиг-файл с модулями и правилами для Webpack. Внешний нужен для того, что мы будем использовать его для 2х конфиг-файлов Webpack'a: клиент и сервер, но это чуть позднее. Документация https://webpack.js.org/concepts/
В корне проекта создаем webpack.config.js:
Создаем в корне проекта файл клиентской конфигурации для Webpack'а webpack.client.js:
Осталось немного. Добавим в корень проекта конфиг PostCSS postcss.config.js:
И добавим скрипты для сборки в package.json с донастройкой PostCSS.
В секцию scripts добавляем команды:
Для Autoprefixer'а в корень json'а добавим параметр, для каких браузеров добавлять префиксы:
Супер! Наш сборщик полностью готов собирать проект. Наполним src/Client.tsx тестовым контентом:
Не забываем, что у нас еще есть index.html для WDS src/Html/Browser.html
Выполняем в консолях npm run watch и видим результат
Берем тестовый React-контент с GitHub'а, где я сделал страницы с роутингом и стили. Кстати, заметьте, стили я подключаю через require в самом компоненте (в методе render для классовых компонентов). Это позволяет в head добавлять стили только отображаемых компонентов, что снижает нагрузку на парсинг и рендер.
Представим, что наше приложение полностью написано и готово к релизу. Приступим к Server Side Render части.
Для работы SSR нам нужны следующие npm-пакеты:
- express — Node JS сервер
- @types/express — TS тип для ExpressJS
- mini-css-extract-plugin — плагин, который выгрузит стили не в JS-файлы, а отдельный CSS. Если понадобится, вы можете вывести его в серверную часть.
- webpack-node-externals — плагин, позволяющий исключать из сборки node_modules по дефолту
Выполняем
В webpack.config.js добавим:
В массив модулей добавляем новый объект для компиляции отдельного изоморфного CSS-файла:
И также добавим для него PostCSS для production-режима:
Казалось, мы могли бы все это запустить напрямую в ExpressJS, но нам надо все приложение прогнать через TS.
Создадим в корне проекта отдельный Webpack-конфиг для компиляции серверной части webpack.server.js:
Создадим наш энтрипоинт для сборки сервера src/Server.tsx:
1) «Обертку» HTML'а теперь генерируем динамически.
2) Обратите внимание на StaticRouter. ExpressJS будет автоматически генерировать роутер для отдачи статичного HTML с сервера.
3) В тело документа напрямую «опрокидываем» наш энтрипоинт приложения.
4) GET-метод реализуем через Node JS Streams.
Создадим для начала HTML в src/Html/Server.tsx:
У компонента есть аргумент scripts. ExpressJS собирает из /dist/assets все JS-файлы и явно указывает на них.
Кстати, все ваши meta-теги, ссылки на фавиконки надо указывать в этом файле. Browser.html нужен только для WDS'а.
Нам осталось только немного модифицировать src/Client.tsx для условного определения методов рендера:
https://ru.reactjs.org/docs/react-dom.html#render
https://ru.reactjs.org/docs/react-dom.html#hydrate
Вот тут могут быть «запуточки». Пояснение: когда запускаем наше приложение через WDS, сначала входим в src/Html/Browser.html, потом подгружаем JS и отрабатывает метод render, т.к. в div#root ничего нет. Инитим BrowserRouter и далее App. Когда же запускается React c ExpressJS сервера, в div#root уже присутствует контент: определенная страница или 404я — отрабатывает метод hydrate.
С ExpressJS алгоритм такой:
Строим основное тело HTML-документа из файла src/Html/Server.tsx ->
Определяем StaticRouter с нужными параметрами и контекстом ->
Рендерим главный компонент App ->
Отдаем страницу
Когда запускаем React на клиентской стороне с ExpressJS сервера:
Тело HTML уже получили ->
Рендерим динамический контент через метод hydrate
Добавим в package.json команду для сборки и запуска сервера:
Все, теперь наше приложение готово к запуску. Предварительно поставьте нужный порт для сервера в src/Server.tsx (в примере 3000).
Сервер стартовал и можем протестировать работу по адресу http://localhost:3000
Также вместо node вы можете использовать nodemon.
Надеюсь, данная статья помогла вам понять, как правильно и удобно сплитовать конфиги Webpack и как работает SSR в связке с React'ом, в какой последовательности и принципе.
Cannot find module 'webpack-cli/bin/config-yargs'
like
А можно пояснить, как обрабатывается css на сервере? Есть файл main.css, но его нет в head документа. Его содержимое вставляется в тегах style в head документа.
main.css вы можете вставить в head-документа (/src/Html/Server.tsx), чтобы поисковые боты могли для себя проанализировать страницу. При открытии сайта на стороне клиента стиль удалить из head, т.к. будет динамическая подгрузка стилей от активных компонентов. Делается это через ванильные методы JS для работы с DOM.
Метод топорный, но рабочий.
Если углубляться в ускорение загрузки страниц, надо делать динамику стилей и на стороне сервера в зависимости от компонентов, которые на запрашиваемой странице.