Що таке .NET Blazor та як його «готувати»
Всім привіт. Мене звуть Владислав Фурдак, в .NET-розробці я вже 13 років. Маю нагороду Microsoft MVP, займаюсь як розробкою, так і менторінгом розробників та підбором персоналу для компаній. Якщо щось з цього вам цікаве — пишіть мені.
Останній великий проєкт, в якому я брав участь, це SaaS/Portal, у якому клієнтська частина написана повністю на wasm Blazor. В статті я опишу свій досвід роботи з цим фреймворком та поділюся думками з приводу того, які його сильні та слабкі сторони, яку нішу він займає на ринку та в яких випадках його краще використовувати. До речі, я є автором NuGet-пакету для більш зручного роутінгу у wasm Blazor застосунках.
Як з’явився Blazor та що це таке
Blazor — це буквально C# у браузері. Як це можливо, запитаєте ви? Для початку згадаємо, що таке WebAssembly. WASM — це технологія, яка вперше з’явилася у браузерах у 2017 році. Суть технології — віртуальна машина, яка виконує web assembler в ізольованому середовищі, по суті, своєрідний аналог контейнеризації, де висока продуктивність досягається шляхом того, що інструкції, написані на інших мовах програмування, дуже просто скомпілювати у швидкісний код на клієнтській машині. Для запуску коду в WASM необхідно скомпілювати застосунок, написаний на C++, Go або Rust, у формат *.wasm. У WebAssembly є можливість викликати функції, написані на JavaScript, для виконання в контексті вікна браузера. Суто технічно, це є виклик спеціальних методів в dotnet.js, які потім викликають методи браузера.
І починаючи з цього моменту в гру вступає Mono runtime, яке було скомпільоване під Wasm, а отже може бути запущене в ізольованому середовищі Wasm. А якщо у нас є .NET у браузері та можливість програмної взаємодії з API браузера, то логічним виглядає розробка front-end фреймворку на C# для реалізації цієї можливості. Таким фреймворком і став Blazor.
Концептуальна схема:
Старші та молодші брати Blazor’а
Раніше робилося багато спроб створити можливість програмування на чомусь, окрім JavaScript/HTML, для створення інтерактивних застосунків у браузері.
Платформа |
Рік випуска |
Нативна підтримка в браузері |
Вимагає для запуску |
Відкрита розробка |
Контриб’ютори |
Java Applets |
1995 |
❌ |
JVM plugin |
❌ |
Sun |
ActiveX |
1996 |
✔️, IE |
ActiveX |
❌ |
Microsoft |
XUL |
1999 |
✔️, Mozilla |
❌ |
Mozilla | |
Java Web Start |
2001 |
❌ |
JVM plugin |
❌ |
Sun |
Flash/Flex |
2004 |
❌ |
Flash player plugin |
❌ |
Macromedia/Adobe |
Silverlight |
2007 |
❌ |
Silverlight plugin |
❌ |
Microsoft |
XBAP |
2008 |
❌ |
.NET 3 framework, windows |
❌ |
Microsoft |
JavaFX |
2008 |
❌ |
JVM plugin |
❌ |
Sun |
Yew, Percy |
2018 |
✔️, Mozilla Chrome Safari Edge |
WebAssembly |
✔️ |
Open source community |
Uno |
2018 |
✔️, Mozilla Chrome Safari Edge |
WebAssembly |
✔️ |
Nventive / Open source community |
Blazor |
2019 |
✔️, Mozilla Chrome Safari Edge |
WebAssembly |
✔️ |
Microsoft |
Flutter on Web |
2021 |
✔️, Mozilla Chrome Safari Edge |
WebAssembly |
✔️ |
|
Чому Blazor є найуспішнішою спробою з усіх вищезазначених для .NET-розробника:
- Базується на W3C стандарті WebAssembly.
- Розробка базується на HTML, на відміну від XAML в Uno або Dart у Flutter.
- Має нативну підтримку майже у всіх сучасних браузерах і не потребує розширень, що залежать від конкретних виробників.
- Має потужну базу розробників — будь-яка людина, яка знає C#, може писати на Blazor. Популярність C# є значно вищою за Dart(Flutter) та Rust(Yew, Percy).
- Використовує всю потужність екосистеми .NET.
- Підтримує майже всі топові UI-бібліотеки, які стали стандартом для Rich UI застосунків. Наприклад: DevExpress, Telerik, MaterialUI (MudBlazor)
До речі, ось динаміка Google Trends за запитом «Blazor». Хоча абсолютна величина у таких фреймворків, як Angular, вища, однак Blazor демонструє зростання тренду. На відміну від Angular, де тренд знижується.
Чи є Blazor конкурентом React або Angular
Скажу одразу — Blazor не є конкурентом для React, Angular чи Vue. Неправильно порівнювати його з цими фреймворками. Blazor вирішує дуже нішеву задачу — надати розробникам на C# сучасніший і гнучкіший спосіб реалізації вебзастосунків замість MVC або WebForms.
Блейзор програє |
Блейзор на одному рівні |
Блейзор перемагає |
|
|
|
То ж, резюмуючи, Blazor ідеально підходить під:
- Написання фронт-енду з використанням складних готових UI-пакетів, типу DevExpress.
- Написання прототипів вебзастосунків зусиллями непрофільних фронт-енд розробників.
- Написання SPA, яке не потребує складних взаємодій з browser API. До яких можна віднести, наприклад, динамчіну поведінку елементів, що зав’язана на їхній розмір, взаємодію з Audio/Video API. Всю цю функціональність необхідно буде огортати в окремі компоненти, ізолювати та підключати JS-код.
- Створення intranet-застосунків, що не будуть залежати від мережевих затримок, використання Blazor Server. Це дозволить не реалізовувати API-шар між фронт-ендом та бек-ендом. Хоча є навіть кейси реалізації публічних маркетплейсів на blazor server.
- Кросплатформенну розробку під MAUI Hybrid app, або ж винесення спільної кодобази для web & mobile app.
Blazor погано підходить під:
- Розробку дуже динамічних вебзастосунків на кшталт google maps.
- Розробку унікальних UI-механік, що все одно будуть потребувати імпорту великої кількості JS-бібліотек. Простіше взяти React із тисячами готових пакетів.
- Розробку вебзастосунків, що зав’зязані на browser API.
- Створення публічних вебсайтів або порталів. Якщо оберете wasm — буде довго завантажуватись, якщо оберете server — вам знадобиться багато RAM на сервері та можливі проблеми з UX.
Це не значить, що ви не зможете релізувати ці типи проєктів на Blazor, однак вам знадобиться більше зусиль, ніж це було б на інших фреймворках.
(Не) очевидні особливості розробки на Blazor
Які є real-world приклади, де Blazor вимагає взаємодії з JavaScript?
Кейс 1. Робота с SessionStorage або LocalStorage. Бо це BrowserAPI. Але існує nuget пакет для цього, щоб ізолювати взаємодію з JS. Якщо цікаво дізнатись як це працює — почитайте код самого пакету.
Кейс 2. Реалізація динаміки для вікна чату. Уявіть, що у вас є вікно чату, як в Скайпі, де вам прийшло багато нових меседжів, і потрібно перемотати зону з меседжами на їхній початок.
Тут вирішуються три задачі:
- Перемотка скролу в контейнері чату до верху нових меседжів.
- Встановлення сепаратора між новими та старими меседжами.
- Додавання JS-обробників, що будуть слідкувати та підгружати історичні меседжі автоматично. По суті реалізація Infinite scroll або нескінченного скролу, як у Facebook чи Instagram.
Приклад такої імплементації можете побачити тут, в методі Initialize(), що викликається при відкритті сторінки з чатами.
JS, що викликається із Blazor-застосунка, можна знайти тут.
Кейс 3. Інтеграція 3rd-party JS-віджетів. Наприклад, Google ReCaptcha або мерчант платіжної системи. Якщо пощастить, то буде існувати готовий пакет по типу цього. А якщо не пощастить, то обгортку доведеться писати самому. Але сказати, що всі віджети покриті пакетами під Blazor, я не можу.
Кейс 4. Написання свого кастомного рекордера Audio поверх API MediaRecorder чи, наприклад, WebRTC-стрімів.
Найбільша технічна конфа ТУТ!🤌
Задача реальна, але доволі заплутана, якщо частина функціональності буде в Blazor, а частина в JS.
Неочевидними тут є платформенні обмеження. Наприклад, в Safari, окрім того, що кастування типів працюють інакше, ніж в Chrome, існує обмеження на програмне відтворення аудіо.
Якщо ви використовуєте UI-пакет на кшталт MudBlazor і прописали на обробник кнопки JS Js Interop виклик play для Audio, то нічого не запуститься і ви навіть не побачите помилки.
Все тому, що згідно з полісі Apple відтворити аудіо може лише пряма взаємодія з юзером. Тобто обробник повинен явно бути навішений на кнопку. Але в нашому випадку ми робимо програмний виклик за допомогою Js interop із C# WebAssembly в JS.
TODO list: старт нового проєкту на Blazor
Про що треба подумати при старті нового проєкту на Blazor.
Модель виконання коду
У вас є такі варіанти. Не буду переказувати цей гарний документ, але скажу, що варіанти є такі:
WASM. Ваш апплікейшн ізольований в браузері клієнта. Поводить себе як класичний вебап, написаний на клієнтському фреймворку. Комунікує з вебсервером теж типовими методами: http-виклики, socket-з’єднання. Підходить, якщо у вас велика кількість клієнтів. Mono runtime завантажується лише раз та кешується на клієнті. Середній розмір одноразового завантаження збірок для пустого проєкту близько 8 мб. Якщо ви будете мати великий проєкт на практиці, це буде близько 12 мб зіпсованого трафіку. В епоху сучасного інтернету фактично ніщо порівняно з десятками мегабайтів асетів.
Blazor Server. В цьому випадку Mono-runtime не завантажується на клієнт. Завантажується лише JS-бандл, розмір якого менше мегабайту. Під взаємодію з користувачем створюється серверна сесія. Після взаємодії з інтерфейсом івенти відправляються на сервер, там обробляються та повертаються у вигляді патчу розмітки на клієнт. У випадку нестабільного з’єднання така модель буде мати купу лагів. Плюс вона потребує більшої кількості пам’яті на сервері. З плюсів — вам не потрібно писати API-layer та початкове завантаження трошки швидше.
Blazor Hybrid. Можливіть давати Blazor-застосунку доступ до API девайса. Це зручно для побудови Blazor MAUI Hybrid app з використанням WebView. Для кращого розуміння цих механізмів краще подивитись мануал.
Модель рендерингу компонентів
Модель рендерингу компонентів це не те, про що я писав вище. Але це застосовується для Interactive Server чи Auto, бо для чистого wasm-застосунку застосовується тільки Interactive WebAssembly до компонента за замовчуванням.
Якщо коротко:
Назва режиму |
Опис |
Інтерактивність |
Static Server |
Компоненти рендеряться на сервері та відправляються клієнту як статичний HTML. Інтерактивність відсутня. Це підходить для контенту, який не потребує взаємодії з користувачем після завантаження сторінки. |
❌ |
Interactive Server |
Компоненти рендеряться на сервері з інтерактивністю, забезпеченою через SignalR. Це означає, що взаємодія користувача відправляється на сервер, де обробляється логіка, і оновлення відправляються назад клієнту в реальному часі. |
✔️ |
Interactive WebAssembly |
Компоненти виконуються на клієнті в браузері за допомогою WebAssembly. Увесь необхідний код завантажується в браузер, що дозволяє застосунку працювати автономно від сервера після початкового завантаження. |
✔️ |
Interactive Auto |
Спочатку відпрацьовує як Interactive Server, потім продовжує працювати як Interactive Client. Це можна порівняти з SSR в сучасних фулстек-фреймворках на кшталт NextJs. |
✔️ |
Взагалі в Blazor можлива комбінація різних моделей рендерингу. Щось можна рендерити на сервері, робити «hydration» на клієнті. Або стрімити компонент поступово, що робить Blazor ще більш схожим на механіку роботи NextJs.
UI kit та побудова бібліотеки компонентів
1. Якщо ви використовуєте унікальний дизайн, наприклад намальований у Figma, вам ще до старту розробки бажано погодити, як ви будете його імплементувати. Продумати breakpoints для responsive UI (наприклад, дефолтні брейкпойнти MudBlazor відрізняються від дефолтних брейкпойнтів bootstrap). Починати проєкт краще зі створення component library, списку кастомізованих чи розроблених з нуля компонентів, що є незалежними та їх можливо перевикористовувати по всьому проєкту. Для хостінга компонентів є BlazingStory, ще є порт Storybook з React.
2. Якщо ви вирішили використовувати MudBlazor — мені не подобаються структурні елементи по типу MudPaper, MudGrid, MudCard. Писати структуру проєкту краще на Flexbox або CssGrid. Це швидше та гнучкіше для оргізації лейаута.
3. Використовуйте css-ізоляцію в Blazor для локальних модифікацій глобальної теми. Це доволі зручно.
4. Одразу визначтесь, чи потрібна вам підтримка нативних елементів для мобільної версії сайту. Наприклад, в MudBlazor її немає по дефолту.
Вибір патерна організації стейту
Для Blazor є нативним патерн MVVM, бо компоненти підтримують прив’язку даних з коробки.
Є декілька способів організувати код компонентів:
- Inline.
- Помістити в кінець файлу в @code.
- Написати в об’єкті представлення через розширення класу-компоненту.
- Написати окрему ViewModel, де буде інкапсульована логіка View.
Якщо ваш проєкт трохи більше за персональний сайт-визитівку, я б рекомендував відокремлювати View-моделі. Ось приклад, як можна організувати абстракції для View-моделей для вашого проєкту.
Далі ви можете робити View-моделі всередині інших View-моделей. Організовуючи зв’язок через events. Я вважаю цей спосіб найпростішим, щоб відокремити логічний зв’язок компонентів від зв’язку між views.
Але якщо у вас надскладний стан, який можна порівняти зі стейт-машиною, і MVVM виглядатиме занадто заплутано через недетермінований стейт, варто розглянути імплементацію патерна FLUX (Redux) за допомогою пакета Fluxor. Цей підхід з однонапрямленим потоком даних мав велику популярність в React.
Вибір додаткових налаштувань компіляції
- RunAOTCompilation — AOT-компіляція дозволяє зробити заздалегідь побудову платформо-залежного коду, без необхідності кожен раз запускати JIT-компілятор в рантаймі. Це може дати деякий буст перфомансу, якщо у вас імплементовані якісь алгоритми на .NET.
- WasmStripILAfterAOT — Видаляє
IL-код після AOT-компіляції, що робить бандл меншим. - WasmEnableSIMD — робить можливим використання SIMD-інструкцій для процесора. Якщо коротко — це може дати буст перфомансу на нових платформах та зламати виконання на старих. Як показали тести, додає зайвих 100 кб в бандл.
Більш детальний опис цих та інших налаштувань можете знайти тут.
Система роутінгу
Як на мене, дефолтна система роутінгу, що зав’язана на строкових константах в директивах компонентів, не дуже зручна, бо ендпойнти можуть змінюватись. Також слід продумати поверення назад в рамках SPA, якщо ви, наприклад, маєте декілька сторінок, з яких можна потрапити на сторінку оплати, а потім вам потрібно повернутись назад.
Всі ці ситуації я проробив в модернізації системи роутингу для wasm Blazor аплкейшенів в Blazor.Nexus-пакеті. Все одно для більш-менш складного застосунку вам доведеться все це вигадувати з нуля.
Хоча пакет в alpha-версії, але їм вже можна користуватись. Має покриття Unit-тестами. Альфу я залишив тому, що потрібно проробити деякі едж-кейси.
Інтернаціоналізація (i18n) та локалізація (l10n)
Підхід за замовчуванням у Blazor, як і у будь-яких .NET-стек застосунках, це використання тих самих resx-файлів. Підхід перевірений роками девелопмента на asp.net mvc / web-forms.
Більш просунутий підхід — використання Portable Object файлів. На жаль, найвідоміший SDK для роботи з PO, Orchard Core Localization, не працює у wasm-середовищі. Лише у Blazor Server або у звичайному asp.net back-end. Приклади, як з цим працювати, можна знайти тут. Однак може бути воркераунд — ви можете передавати переклади по API з сервера.
Для wasm-Blazor застосунку можна розглянути легковесний пакет Toolbelt.Blazor.I18nText.
Більше про інтернаціоналізацію та локалізацію в Blazor можете почитати тут.
У проєкті, де я брав участь, ми використовували вже готову пропрієтарну бібліотеку, що робить кодогенерацію із нашого власного формату перекладів. Вона була написана компанією клієнта для себе, для всіх своїх застосунків.
Бібліотеки та залежності
Попереджу вас, що є бібліотеки, що написані під Blazor та є покинутими. Або автори багато часу не приділяють їх розвитку і вони роками не мають чейнжів. Я б рекомендував дуже детально обирати, що тягнути в проєкт, та якщо це щось невеличке — краще написати самому.
Доволі великий список бібліотек можна знайти тут.
На чому стартувати новий проєкт, якщо не впевнені? Я пропоную такий стек:
- MudBlazor, доволі універсальна та безкоштовна бібліотека, непогано кастомізується.
- Blazored.LocalStorage / SessionStorage — вам знадобиться для зберігання стейту в браузері.
- Auto-generated HTTP clients. Якщо це не Server-side Blazor, та якщо у вас бек має OpenAPI-специфіцкаію, то ви можете згенерувати C# API клієнти. Бекенд не обов’язково повинен бути на C#, але використання C# здається більш зручним у цьому випадку. Для C# бекенду це буде NSwag.
Також в проєкт є сенс додати якийсь білдер JS, наприклад, Vite чи Webpack, якщо планується написання JS. Білд JS можна імплементувати як окремий білд-степ MSBuild’a через csproj, наприклад, як тут.
Створення PWA-застосунку
Якщо вам потрібно створити PWA-застосунок, є гайд, як це зробити. Також зверніть увагу на пакет для його автоматичного оновлення.
Тестування компонентів
Для компонентного тестування своїх компонентів можете використовувати bUnit — a testing library for Blazor components | bUnit.
Приклади проєктів на Blazor
- actual.chat — месенджер, що написаний на Blazor Server.
- Damselfly — проєкт з відкритим кодом на wasm Blazor, фотобібліотека та фотопроцесор.
- eatinglove.com — Поратал, що написаний на wasm Blazor.
- kant2002.github.io/KyivMachine — Емулятор ОЕМ «Київ», яка була піонерською машиною для Европи, і, мабуть, першою у світі мала явно пророблену концепцію вказівників як частин мови програмування, або як тоді казали — автопрограмування.
Україномовний код застосунку можете знайти тут.
Підсумки
Blazor не є срібною пулею. Раджу зважити pros & cons при старті нового проєкту на цьому фреймворку. Технологія є доволі нішевою, однак вже давно у статусі production-ready.
А якщо ви шукаєте однодумців, ментора чи просто любите технічні або кар’єрні дискусії, то далі буде корисна інформація.
Я засновник одного із найбільших .NET ком’юніті України — UA .NET Community . Вступайте, обмінюйтесь досвідом у френдлі атмосфері, задавайте питання.
Додавайтесь до мене в LinkedIn, щоб бути у курсі останніх новин.
Також я є адміном єдиного в Україні ком’юніті Blazor-розробників.
І якщо ви дуже досвідчений розробник, технічний лідер чи CTO або архітектор, то вступайте в українське ком’юніті Dot Net Tech Leads. Нас вже 150 людей, обговорюємо останні технологічні тренди, архітектурні челленджі. В нас є декілька сабтредів:
- з підготовки до інтрев’ю в FAAANG/MANGA;
- з розвитку своїх IT-продуктів;
- спільнота контриб’юторів StereoDB;
- спільнота бажаючих виступити на конференції DotNetTechConf UA, де я допомагаю спікерам, навіть без досвіду, зробити гарну технічну доповідь.
Дякую за рев’ю та доповнення статті Андрію Курдюмову.
Всім спасибі :)
29 коментарів
Додати коментар Підписатись на коментаріВідписатись від коментарів