Pull to refresh

Blazor: Техническое введение

Reading time10 min
Views79K
Original author: Steve Sanderson
Сегодня команда ASP.NET анонсировала, что проект Blazor был перемещён в репозиторий организации ASP.NET. Мы начинаем стадию эксперимента, чтобы понять сможем ли мы развить Blazor в поддерживаемый продукт. Это большой шаг вперёд!

image

Что такое Blazor? Это фреймворк для браузерных приложений, написанный на .NET и запускающийся с помощью WebAssembly. Он даёт вам все преимущества богатых современных одностраничных приложений (SPA), позволяя при этом использовать .NET от начала и до конца, вплоть до общего кода на сервере и клиенте. В посте с анонсом подробно описаны основные случаи применения, сроки и так далее.

В этом посте я хочу поглубже поговорить о технических деталях для тех, кому интересно как же это работает.

Запуск .NET в браузере


Первый шаг для построения SPA-фреймворка на .NET это каким-то образом получить возможность запускать .NET код в браузере. Наконец-то, это может быть сделано с использованием открытых стандартов и работать в любом браузере (без всяких плагинов), благодаря WebAssembly.

На данный момент WebAssembly поддерживается всеми основными браузерами, в том числе и мобильными. Это компактный байткод-формат, оптимизированный для уменьшения объема скачиваемых данных и ускорения исполнения. Несмотря на то, что многие разработчики могли бы так подумать, WebAssembly не привносит никаких новых проблем безопасности, так как это не обычные бинарные файлы (вроде x86/x64) — это новый формат, содержащий байткод, который может делать только то же самое, что и JavaScript.

Так как же он позволяет нам запускать .NET? Всё благодаря тому, что команда Mono добавила поддержку WebAssembly в свой проект. Если вы пропустили новости, то проект Mono стал частью Microsoft в 2016 году. Mono это официальный .NET рантайм для клиентских платформ (таких как нативные мобильные приложения и игры). WebAssembly это просто ещё одна клиентская платформа, поэтому вполне разумно, что Mono должно на ней работать.

Моно может запускаться на WebAssembly в двух режимах: режиме интерпретации и AOT.

Интерпретация


В режиме интерпретации рантайм Mono компилируется в WebAssembly, но ваши .NET сборки — нет. Браузер загружает и запускает рантайм, который в свою очередь может загружать и исполнять стандартные .NET сборки (обычные .NET .dll файлы), собранные обычным .NET тулчейном.

Диаграмма показывающая режим интерпретации

Это похоже на то, как для обычной CLR основное ядро распространяется скомпилированным в нативный код, который затем загружает и исполняет .NET сборки. Единственное ключевое различие в том, что десктопная CLR активно использует JIT-компиляцию для ускорения исполнения, в то время как Mono на WebAssembly работает ближе к классической модели интерпретации.

Ahead-of-time (AOT) компиляция


В AOT режиме ваше .NET приложение превращается в чистые WebAssembly бинарники сразу при сборке. В рантайме не происходит никакой интерпретации — ваш код выполняется как обычный WebAssembly-код. Этот режим всё ещё требует загрузки некоторой части рантайма Mono (таких низкоуровневых .NET сервисов как, например, сборка мусора), но позволяет отказаться от таких компонентов как парсер .NET файлов.

Диаграмма показывающая режим AOT

Это похоже на то, как с незапамятных времён утилита ngen позволяет AOT-компиляцию .NET сборок в нативный машинный код, или на недавно появившийся полноценный нативный AOT .NET рантайм — CoreRT.

Режим интерпретации против AOT


Какой режим лучше? Мы пока что не знаем.

Однако мы знаем, что режим интерпретации даёт гораздо более быстрый процесс разработки, чем AOT. После изменения кода вы можете пересобрать его обычным .NET-компилятором и получить обновлённое приложение в браузере в считанные секунды. AOT-компиляция, в свою очередь, может занимать минуты.

Очевидная мысль — режим интерпретации будет основным для разработки, а AOT — для продакшена.

Но всё это может оказаться совсем не так, потому что режим интерпретации, к удивлению, гораздо быстрее, чем вы могли бы подумать. И мы слышали от ребят из Xamarin, которые используют .NET для нативных мобильных приложений, что обычные (не AOT) .NET сборки очень маленькие и хорошо поддаются компрессии, в отличие от AOT-сборок. Мы будем рассматривать оба варианта пока у нас не появится возможность объективно оценить разницу.

Blazor, SPA фреймворк


Возможность запустить .NET в браузере это хорошее начало, но этого недостаточно. Чтобы быть продуктивным разработчиком приложений вам нужен последовательный набор стандартных решений для стандартных проблем — таких как создание/переиспользование UI, управление состоянием, роутинг, юнит-тестирование, оптимизация сборки и так далее. Всё это должно быть спроектировано вокруг сильных сторон .NET и языка C#, позволяя извлечь максимум из существующей экосистемы .NET и поставляться вместе с первоклассной поддержкой инструментов, как этого ожидает .NET разработчик.

Blazor это всё вышеперечисленное. Он вдохновлён сегодняшними лучшими SPA фреймворками, такими как React, Vue и Angular, а также некоторыми UI стэками от Microsoft вроде Razor Pages. Наша цель — дать веб-разработчикам то, что максимально хорошо сочетается с .NET.

Компоненты


Во всех современных SPA фреймворках приложения построены из компонентов. Компонент обычно представляет из себя какой-то UI элемент: страницу, диалог, набор вкладок или форму. Компоненты могут вкладываться друг в друга, переиспользоваться и разделяться между проектами.

В Blazor компонент это .NET класс, который вы можете написать напрямую (то есть как C# класс) или, что более принято, в виде страницы разметки Razor (.cshtml файл).

Появившийся примерно в 2010 году Razor это синтаксис для комбинирования разметки с C# кодом. Он создан специально для продуктивности разработчика, позволяя вам переключаться между разметкой и C# безо всяких церемоний, с полной поддержкой intellisense. В примере ниже показан простой компонент диалога, описанный в Razor файле MyDialog.cshtml:

<div class="my-styles">
  <h2>@Title</h2>
  @RenderContent(Body)
  <button onclick=@OnOK>OK</button>
</div>

@functions {
    public string Title { get; set; }
    public Content Body { get; set; }
    public Action OnOK { get; set; }
}

Когда вы будете использовать этот компонент, инструментарий знает что вам подсказать:

Анимированная GIF показывающая поддержку компонентов Blazor в инструментах

Многие шаблоны проектирования могут быть построены на этом простом фундаменте, включая популярные паттерны из SPA фреймворков вроде компонентов с состоянием (stateful components), функциональных компонентов без состояния (stateless components) и компонентов более высокого порядка (higher-order components). Вы можете вкладывать компоненты друг в друга, процедурно генерировать их, разделять между библиотеками, запускать юнит-тесты без необходимости наличия браузера и, в целом, жить хорошей жизнью.

Инфраструктура


При создании нового проекта Blazor предложит основные сервисы, необходимые большинству приложений:

  • Шаблоны
  • Роутинг
  • Внедрение зависимостей
  • Ленивую загрузку (то есть загрузку частей приложения по мере необходимости)
  • Юнит-тестирование

Важный аспект архитектуры — всё это опционально. Если вы что-то не используете — это будет удалено из итогового билда при публикации.

Другой важный момент — только несколько самых низкоуровневых частей находятся в ядре фреймворка. К примеру, роутинг и система шаблонов не такие — они реализованы в «юзер-спейсе», то есть этот код может быть написан разработчиком приложения без использования каких либо внутренних API. Поэтому если вам не нравятся наш роутинг или система шаблонов — вы можете заменить их своими. Наш текущий прототип системы шаблонов представляет из себя около 30 строк кода на C#, так что вы легко сможете разобраться и переписать его если захочется.

Развёртывание


Очевидно, что значительная часть целевой аудитории Blazor это ASP.NET разработчики. Для них мы выпустим middleware для прозрачного хостинга UI на Blazor с такими дополнительными возможностями как пререндеринг на сервере.

Не менее важны для нас и разработчики пока совсем не использующие .NET. Чтобы Blazor был жизнеспособен для разработчиков, предпочитающих Node.js, Rails, PHP или любую другую серверную технологию, а то и вовсе пишущих serverless-приложения — мы абсолютно точно не будем требовать наличия .NET на сервере. Результат сборки Blazor-приложения — папка dist, в которой лежат только статические файлы. Мы можете раздавать их со страниц Github, из облачных хранилищ, через Node.js сервера и вообще через что угодно.

Общий код и netstandard


.NET standard это способ описать уровень возможностей, предоставляемых .NET рантаймом или требуемых .NET сборкой. Если ваш .NET рантайм поддерживает netstandard2.0 и ниже, и у вас есть сборка нацеленная на netstandard2.0 и выше, то вы сможете запустить эту сборку на этом рантайме.

Mono на WebAssembly будет поддерживать netstandard2.0 или более высокую версию (в зависимости от сроков выхода). Это означает, что вы сможете использовать свои .NET библиотеки и на бэкенде, и в браузерных приложениях. К примеру, у вас может быть проект с классами моделей бизнес логики — его можно будет использовать и на сервере, и на клиенте. И, конечно же, вы сможете скачивать пакеты из NuGet.

Однако не все .NET API имеют смысл в браузере. К примеру, вы не сможете слушать произвольный TCP сокет, так что System.Net.Sockets.TcpListener не будет делать ничего полезного. Также вы практически наверняка не должны использовать System.Data.SqlClient в браузерном приложении. И это не проблема, так как, во-первых, браузеры всё-таки поддерживают API, которые действительно нужны людям для создания веб-приложений, и во-вторых, у .NET standard есть механизм обработки для таких случаев. При вызове не применимых для конкретной платформы API базовая система классов (BCL) будет выбрасывать исключение PlatformNotSupported. В начале это может приводить к проблемам, однако со временем авторы NuGet-пакетов внесут изменения в свои библиотеки для поддержки разных платформ. Если .NET хочет двигаться в сторону самой бурно развивающейся платформы приложений в мире — это ступень, на которую придётся подняться.

Совместимость с JavaScript/TypeScript


Даже если вы пишете браузерное приложение на C#/F# — иногда бывает нужно подключить чужую JavaScript библиотеку или свой собственный код на JavaScript/TypeScript для вызова какого-нибудь нового браузерного API.

Это должно быть очень просто, так как стандарт WebAssembly спроектирован чтобы взаимодействовать с JavaScript (и это неудивительно) и мы можем легко использовать это в .NET коде.

Чтобы работать с чужими JavaScript библиотеками мы исследуем возможность использования определений типов TypeScript в C# коде с полным intellisense. Это сделает около 1000 самых популярных JS-библиотек очень простыми для интеграции.

Текущий подход для вызова чужих библиотек или вашего JS/TS кода из .NET это регистрация именованной функции в JS/TS файле. Например:

// Это JavaScript
Blazor.registerFunction('doPrompt', message => {
  return prompt(message);
});

… и затем создаём обёртку для вызова из .NET:

// Это C#
public static bool DoPrompt(string message)
{
    return RegisteredFunction.Invoke<bool>("doPrompt", message);
}

Подход с registerFunction имеет приятный бонус в виде хорошей работы с JavaScript-сборщиками вроде Webpack.

И, чтобы поберечь ваше время и нервы, команда Mono работает над библиотекой, которая пробросит стандартные браузерные API в .NET.

Оптимизация


Исторически .NET фокусировался на платформах, где размер приложения не такая уж большая проблема. Не имеет большой разницы весит ли ваше ASP.NET приложение 1МБ или 50МБ. Это средней степени проблема для десктопных или мобильных приложений. Но для браузеров размер загрузки очень критичен.

В защиту можно сказать, что .NET на WebAssembly скорее всего будет загружаться всего один раз. Ведь можно использовать стандартное HTTP кэширование (или даже модные штуки вроде service worker) чтобы гарантировать, что пользователь загрузит ядро рантайма только единожды. А если использовать CDN, то пользователь и вовсе может использовать результаты одной загрузки сразу в нескольких приложениях.

Всё это хорошо, однако я не думаю, что этого достаточно. Если рантайм будет весить 20МБ, то это всё равно слишком много, даже для единоразовой загрузки. Это же не браузерный плагин в конце концов — это обычное построенное по стандартам веб-приложение. Даже самая первая загрузка должна быть быстрой.

Поэтому мы прикладываем много усилий для уменьшения размера загрузки. Мы видим следующие 3 фазы оптимизаций:

1. Уменьшение рантайма Mono


Рантайм Mono содержит много специфичных для десктопа возможностей. Мы надеемся, что Blazor будет содержать урезанную версию Mono, которая существенно меньше, чем полный дистрибутив. При ручной попытке оптимизации я смог удалить около 70% из .wasm файла рантайма без нарушения работы базового приложения.

2. Уменьшение IL кода при публикации


Компоновщик (linker) .NET IL (основанный на компоновщике Mono) выполняет статический анализ, определяя какие части .NET-библиотек могут быть вызваны из вашего приложения, и удаляет всё остальное.

Это похоже на tree shaking в JavaScript, за разницей в том, что IL-компоновщик гораздо более точен и работает на уровне отдельных методов. Это позволяет убрать весь неиспользуемый код системной библиотеки, что даёт огромную разницу в большинстве случаев, часто уменьшая размер приложения ещё на 70+%.

3. Компрессия


Ну и наконец, самое очевидное — мы ожидаем что ваш сервер поддерживает HTTP-сжатие. Это обычно срезает ещё 75% объёма.

Конечно, веб-приложение на .NET никогда не будет таким же миниатюрным как простейшее приложение на React. Наша цель состоит в том, чтобы сделать его настолько небольшим, что обычный юзер на среднем подключении не заметит даже самой первой загрузки, не говоря уже о последующих загрузках из кэша.

Так и зачем это всё нужно?


Нравится это вам или нет, но веб-разработка сильно изменится в ближайшие несколько лет. WebAssembly позволит веб-разработчикам выбирать из гораздо большего списка языков и платформ, чем когда либо. И это хорошо — наш мир наконец-то взрослеет. Разработчики серверного ПО и нативных приложений всегда могли выбирать язык и парадигмы, которые лучше всего подходят для решения их проблем, соответствуют культуре команды и подкреплены имеющимися знаниями. Мечтаете писать функциональщину на Haskell или Lisp для вашего финансового приложения? Хотите немного низкоуровневого C? Вы Apple-разработчик и хотите продолжить использовать свои знания Swift? Всё это придёт в веб.

Не пугайтесь. Это не значит, что вам нужно будет знать все эти языки. Это значит, что все мы станем обычными разработчиками ПО. Ваши текущие знания программирования для браузеров по прежнему актуальны и ценны, но у вас появятся новые пути выразить свои идеи и больше точек соприкосновения с другими сообществами разработчиков.

И наша инициатива состоит в том, чтобы поставить .NET в авангард этого движения, а не тащиться позади, отставая на годы.

Текущий статус


Чувствуете желание попробовать? Притормозите — мы всё ещё на очень ранних стадиях проекта. Пока ещё ничего не готово для скачивания и многое из вышеописанного в процессе разработки. Большинство из вас должны просто расслабиться и подождать, первые пре-альфа билды появится примерно через месяц.

Помните, на данный момент Blazor — эксперимент для команды ASP.NET. Нам потребуется несколько месяцев чтобы понять сможем ли мы сделать из него полноценный, поддерживаемый продукт. Мы ещё ничего не обещаем, поэтому не надо строить свои бизнес-планы вокруг Blazor!

Если же вы сильно заинтересовались, то посмотрите в репозиторий, попробуйте собрать его, позапускать тесты и приходите пообщаться с нами. Можете даже поискать // TODO комментарии и прислать Pull Request, или поделиться идеей о клёвой фиче.


От переводчика


В заключение приведу несколько интересных ссылок:

Tags:
Hubs:
Total votes 38: ↑37 and ↓1+36
Comments46

Articles