Боилерплейт ASP.NET Core 2 с React, Redux и плюшками

в 7:57, , рубрики: .net, asp.net core, node.js, ReactJS, redux, server-side javascript, ssr, TypeScript, webpack

Боилерплейт ASP.NET Core 2 с React, Redux и плюшками - 1
      Настал, наконец, тот момент, когда я могу представить вам боилерплейт 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

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js