Настал, наконец, тот момент, когда я могу представить вам боилерплейт React Core Boilerplate (GitHub, Visual Studio Marketplace), или, иными словами, готовый шаблон проекта на ASP.NET Core.
Интересно? Добро пожаловать под кат.
Основные элементы шаблона:
- ASP.NET Core 2 — собственно, основа.
- TypeScript — на нём написан фронтенд.
- React + Server-side rendering (SSR), далее — библиотека, работающая, как на стороне клиента, так и сервера с помощью NodeServices.
- React Helmet — плагин, позволяющий управлять title и meta тегами, работает также вместе с SSR.
- Redux — контейнер состояний приложения.
- SASS — CSS препроцессор стилей.
- Webpack 4 — сборщик модулей.
- Axios — обеспечивает изоморфные fetch-запросы.
- ts-nameof — позволяет с помощью выражений на TypeScript получать путь к вложенным объектам, аля Html.NameFor в Razor; работает вкупе с сериализотором форм NSerializeJson.
Свои разработки, написанные на TypeScript и исправленные мною форки:
- domain-wait — схож с domain-task, но позволяет использовать async/await. Предназначен для ожидания серверным рендерингом fetch-запросов.
- NVal — валидатор форм (приближенный по API с jquery.validation).
- NVal-Tippy — дополнение к NVal, позволяющий с помощью js делать всплывающие ошибки валидации в тултипах.
- NSerializeJson — сериализатор форм в JSON с настройками типов (схожий с serializeJSON).
- bootstrap3-native — исправленный форк native, позволяющий работать с элементами bootstrap 3.
Цель проекта
Цель данного проекта — поддержать разработчиков данного стека: дать возможность сразу приступить к разработке проектов, не заморачиваясь с решением проблем совместимостей технологий в стеке, их настройкой. Поскольку проект создан "для людей" и содержит, на мой взгляд, интуитивно понятную архитектуру, он также будет полезен и новичкам в обучении React, т.к. не содержит проблем "из коробки" для построения приложений.
Детали
С чего всё начиналось
Я — фуллстек разработчик. Когда-то любил писать фронтенд на jQuery — от разработки плагинов до сложных сайтов. Однако в один прекрасный день стало ясно: поддержка сайтов на этой библиотеке тем труднее, чем больше проект. Тогда мною было решено заняться изучением других инструментов для разработки фронтенда. Angular мне не понравился из-за своей толстой абстракции и, предварительно изучив достоинства и недостатки каждого фреймворка и библиотеки, я сделал выбор в пользу React, в котором привлекло то, что HTML и TypeScript можно писать код в одном месте (.tsx-файле), не переключаясь между HTML и файлами скриптов.
Со временем я полюбил React. Но, признаться честно, поначалу приходилось применять jQuery и в React из-за плагинов, которые его требуют. А затем, когда я пытался освоить серверный рендеринг из-за известных проблем SPA и SEO, я узнал, что jQuery либо нельзя с ним подружить, либо это будет костыльно (через jsdom, т.к. требуется объект window). Я знал, что этих jQuery-плагинов мне будет не хватать. И с тех пор я начал разрабатывать плагины на "Vanilla JS", которые чем-то схожи по API с плагинами, работающими на jQuery.
Серверный рендеринг
Несколько месяцев назад Microsoft выпустила обновление для Visual Studio 2017, в котором был шаблон проекта ASP.NET Core 2 + TypeScript + React + Redux + Webpack с серверным пререндером с помощью NodeServices. Изучая шаблон, я потратил немало времени, чтобы подключить туда SASS через WebPack и обновить последний, поскольку, изучая документацию, форумы и StackOverflow, я не находил нужных мне ответов по настройке этого стека, в то время, как документация была, в основном, по стеку с Angular. Спустя некоторое время Microsoft заменила этот шаблон на create-react-app без TypeScript и серверного рендеринга. Судя по комментариям в интернетах, многих других, как и меня, это смутило. Однако у меня сохранился тот шаблон, и я решил из него сделать свой с блекджеком и плюшками, разобравшись с ним и решив все проблемы разом.
P.S.: Недавно читал, что существуют, так называемые, противники изоморфного подхода к разработке приложений. А как же SEO? Или вы предлагаете рендерить всё платными сторонними сервисами? Насколько я знаю, гугл не всегда может индексировать толстого клиента без пререндера. Ну да ладно.
Техническая часть
React, React Router, Redux
- Обе библиотеки обновлены до последней версии, кроме react-router-redux, с ней что-то не так.
- Добавлены примеры их совместного использования, в том числе использование Route вкупе с разными Layout (компонент AppRoute).
WebPack и модули
- Ввиду своей особой структуры конфигурации WebPack, а, именно, его разделения на серверную и клиентскую часть, для подключения загрузчиков и обновления WebPack пришлось применить смекалку: поставить заглушку для SASS в серверной конфигурации WebPack, чтобы собирать стили только в клиентской части (style-loader требует наличие window). А если использовать Extract CSS плагин (ныне MiniCssExtractPlugin для WebPack 4), тогда, при обновлении файлов, не работал Hot Module Reload (HMR). Поскольку изначально в шаблоне от Microsoft были конфигурации для старых версий библиотек и самого WebPack, многое пришлось перепиливать.
- Были добавлены алиасы часто используемых путей в WebPack и tsconfig.json.
- awesome-typescript-loader заменён на babel-loader.
NodeServices
Документации по NodeServices было мало для решения каких-то проблем: то HMR отваливался, то что-то не работало. Однако, проявив терпение, спустя полтора месяца, наконец-таки, у меня получилось разрешить критические моменты:
⬝ Вместо атрибута "asp-prerender-module" для DIV-элемента во View Razor'a, пришлось инжектить в него ISpaPrerender:
@inject Microsoft.AspNetCore.SpaServices.Prerendering.ISpaPrerenderer prerenderer
и, выполнять вручную скрипт:
var prerenderResult = await prerenderer.RenderToString(%путь к скрипту загрузки серверного рендера%, customDataParameter: data);
Затем, из объекта prerenderResult можно вытаскивать отрендеренный HTML React'a и React Helmet в блоки BODY/DIV и HEAD, соответственно.
⬝ NPM-пакет domain-task не позволял использовать async/await и инструментов для fetch-запросов кроме isomorfic-fetch и fetch из этого же npm-пакета. Это исправлено мною в пакете domain-wait. Теперь fetch-запросы писать приятнее, особенно, используя axios.
Архитектура приложения
Из личного опыта внес некоторые наработки, например, во все методы сервисов (кроме авторизации) принимать ServiceUser и возвращать объект Result с результатом или ошибками. Оба таких объекта + имитация сервиса авторизации уже заложена в боилерплейт. Я противник Identity, да и авторизация не всем подойдёт. Так что, ничего лишнего, да и имитация авторизации легко выпиливается (AccountService, Middleware, ControllerBase).
- Два React'овских Layout — гостевой и для авторизованных пользователей.
- Подключены и написанные мною плагины, которые доступны в NPM и активно используются. Теперь легко интегрировать формы с валидацией, и вытаскивать из них JSON данные. Всё это демонстрируется в примере, находящемся в шаблоне.
- На стороне frontend'a все fetch-запросы инкапсулированы в сервисы, которые ещё и изоморфные, т.е. могут работать и на стороне сервера.
- В проекте приведён пример того, как, на мой взгляд, должно выглядеть приложение: легко читаемые и интуитивно понятные структура приложения и код.
| .gitignore
| AppSettings.cs
| appsettings.Development.json
| appsettings.json
| Constants.cs # Константы, содержат ключи от куки для фейковой авторизации.
| package.json # Файл NPM.
| Program.cs # Входная точка приложения.
| ReactSSR.WebApp.csproj # Файл проекта Visual Studio 2017.
| README.md
| Startup.cs # Содержит настройки приложения и middleware фейковой авторизации.
| tsconfig.json # Файл конфигурации TypeScript.
| webpack.config.js # Содержит конфигурации WebPack для сборки серверного и клиенсткого бандлов.
| webpack.config.vendor.js # Содержит конфигурации WebPack для серверного и клиентского Vendor-бандлов.
|
+---ClientApp
| | boot-client.tsx # Входная точка для рендеринга фронтенда в браузере.
| | boot-server.tsx # Входная точка для рендеринга фронтенда на стороне сервера.
| | configureStore.ts # Конфигурация хранилищ Redux.
| | global.d.ts # Глобальные определения модулей и типов TypeScript для фронтенда (например, ts-nameof, *.png, и т.д.)
| | Globals.ts # Инкапсулирует изоморфное состояние приложения (например, данные об авторизации).
| | routes.tsx # Настройки маршрутизации для фронтенда.
| | Ui.ts # Включает хелперы для UI (например, всплывающие подсказки).
| | utils.ts # Содержит полезные методы.
| |
| +---components # Компоненты (не страницы).
| | +---person
| | | PersonEditor.tsx # Компонент, входящий в состав примера.
| | |
| | ---shared # Общие компоненты.
| | AppRoute.tsx # Компонент для построения маршрутов с более, чем одним лэйаутом.
| | ErrorBoundary.tsx # Компонент, основанный на паттерне "error boundary". При обёртке в него, помогает отлавливать ошибки в других компонентах.
| | Footer.tsx # Футер для авторизованной зоны.
| | Loader.tsx # Компонент, содержащий индикатор загрузки.
| | PagingBar.tsx # Переключатель страниц.
| | TopMenu.tsx # Верхнее меню для авторизованной зоны.
| |
| +---images
| | logo.png # Логотип бойлерплейта.
| |
| +---layouts # Слои (зоны).
| | AuthorizedLayout.tsx
| | GuestLayout.tsx
| |
| +---models # Модели TypeScript, используемые в приложении.
| | ILoginModel.ts
| | IPersonModel.ts
| | IServiceUser.ts
| | ISessionData.ts
| | Result.ts # Реализация паттерна "Result".
| |
| +---pages # Страницы приложения.
| | ExamplePage.tsx
| | HomePage.tsx
| | LoginPage.tsx
| |
| +---services # Изоморфные JS-сервисы, инкапсулирующие логику для работы с запросами.
| | AccountService.ts # JS-сервис фейковой авторизации.
| | PersonService.ts # JS-сервис - пример.
| | ServiceBase.ts # Базовый абстрактный TS-класс для построения изоморфных JS-сервисов.
| |
| +---store # Хранилища Redux.
| | index.ts # Определения для хранилищ Redux.
| | LoginStore.ts
| | PersonStore.ts # Хранилище для примера.
| |
| ---styles
| authorizedLayout.scss # Стили для авторизованной зоны.
| guestLayout.scss # Стили для гостевой зоны.
| loader.scss # Стили для индикатора загрузки.
| main.scss # Общие стили.
| preloader.css # Стили для первоначального индикатора загрузки.
|
+---Controllers
| AccountController.cs # Контроллер фейковой авторизации.
| ControllerBase.cs # Инкапсулирует свойства и настройки для фейковой авторизации.
| MainController.cs # Контроллер входной точки.
| PersonController.cs # Контроллер-пример..
|
+---Extensions
| ServiceCollectionExtensions.cs # Инкапсулирует методы, позволяющие определять Lazy DI контейнеры.
|
+---Infrastructure # Папка, содержащая основные модели инфраструктуры приложения.
| Result.cs # Реализация паттерна "Result" на стороне сервера.
| ServiceBase.cs # Базовый класс для всех сервисов, реализующих паттерн Facade/Фасад.
|
+---Models
| LoginModel.cs # Модель для фейковой авторизации.
| PersonModel.cs # Модель для примера.
| ServiceUser.cs
| SessionData.cs # Модель данных изоморфной сессии.
|
+---Services # Содержит сервисы, реализующие паттерн "Facade".
| AccountService.cs # Сервис фейковых аккаунтов.
| PersonService.cs # Сервис-пример.
|
+---Views
| | _ViewImports.cshtml
| | _ViewStart.cshtml
| |
| +---Main
| | Index.cshtml # Входная точка приложения, содержащая корневой контейнер, в который рендерится фронтенд.
| |
| ---Shared
| Error.cshtml
|
---wwwroot # Корневая папка, в которую будут собираться бандлы.
favicon.ico
На этом всё. Всем спасибо за внимание и, конечно же, Happy Coding!
Автор: Trixon