Создание и настройка проекта 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'ом, в какой последовательности и принципе.