Введение
Если вы Web-разработчик и ведете разработку для браузера, то вы точно знакомы с JS, который может исполняться внутри браузера. Существует мнение, что JS не сильно подходит для сложных вычислений и алгоритмов. И хотя в последние годы JS cделал большой рывок в производительности и широте использования, многие программисты продолжают мечтать запустить системный язык внутри браузера. В ближайшее время игра может поменяться благодаря WebAssembly.
Microsoft не стоит на месте и активно пытается портировать .NET в WebAssembly. Как один из результатов мы получили новый фреймворк для клиенской разработки — Blazor. Пока не совсем очевидно, сможет ли Blazor за счет WebAssembly быть быстрее современных JS — фреймворков типа React, Angular, Vue. Но он точно имеет большое преимущество — разработка на C#, а так же весь мир .NET Core может быть использован внутри приложения.
Компиляция и выполение C# в Blazor
Процесс компиляции и выполнения такого сложного языка как C# — это сложная и трудоемкая задача. А можно ли внутри браузера скомпилировать и выполнить С#?
— Это зависит от возможностей технологии (а точнее, ядра). Однако в Microsoft, как оказалось, уже все подготовили для нас.
Для начала создадим Blazor приложение.
После этого нужно установить Nuget — пакет для анализа и компиляции C#.
Install-Package Microsoft.CodeAnalysis.CSharp
Подготовим стартовую страницу.
@page "/"
@inject CompileService service
<h1>Compile and Run C# in Browser</h1>
<div>
<div class="form-group">
<label for="exampleFormControlTextarea1">C# Code</label>
<textarea class="form-control" id="exampleFormControlTextarea1" rows="10" bind="@CsCode"></textarea>
</div>
<button type="button" class="btn btn-primary" onclick="@Run">Run</button>
<div class="card">
<div class="card-body">
<pre>@ResultText</pre>
</div>
</div>
<div class="card">
<div class="card-body">
<pre>@CompileText</pre>
</div>
</div>
</div>
@functions
{
string CsCode { get; set; }
string ResultText { get; set; }
string CompileText { get; set; }
public async Task Run()
{
ResultText = await service.CompileAndRun(CsCode);
CompileText = string.Join("\r\n", service.CompileLog);
this.StateHasChanged();
}
}
Для начала надо распарсить строку в абстрактное синтаксическое дерево. Так как в следующем этапе мы будем компилировать Blazor компоненты — нам нужна самая последняя (LanguageVersion.Latest
) версия языка. Для этого в Roslyn для C# есть метод:
SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(code, new CSharpParseOptions(LanguageVersion.Latest));
Уже на этом этапе можно обнаружить грубые ошибки компиляции, вычитав диагностику парсера.
foreach (var diagnostic in syntaxTree.GetDiagnostics())
{
CompileLog.Add(diagnostic.ToString());
}
Далее выполняем компиляцию Assembly
в бинарный поток.
CSharpCompilation compilation = CSharpCompilation.Create("CompileBlazorInBlazor.Demo", new[] {syntaxTree},
references, new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
using (MemoryStream stream = new MemoryStream())
{
EmitResult result = compilation.Emit(stream);
}
Следует учесть, что нужно получить references
— список метаданных подключенных библиотек. Но прочитать эти файлы по пути Assembly.Location
не получилось, так как в браузере файловой системы нет. Возможно, есть более эффективный способ решения этой проблемы, но цель данной статьи — концептуальная возможность, поэтому скачаем эти библиотки снова по Http и сделаем это только при первом запуске компиляции.
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
references.Add(
MetadataReference.CreateFromStream(
await this._http.GetStreamAsync("/_framework/_bin/" + assembly.Location)));
}
Из EmitResult
можно узнать была ли успешной компиляция, а так же получить диагностические ошибки.
Теперь нужно загрузить Assembly
в текущий AppDomain
и выполнить скомпилированный код. К сожалению, внутри браузера нет возможности создавать несколько AppDomain
, поэтому безопасно загрузить и выгрузить Assembly
не получится.
Assembly assemby = AppDomain.CurrentDomain.Load(stream.ToArray());
var type = assemby.GetExportedTypes().FirstOrDefault();
var methodInfo = type.GetMethod("Run");
var instance = Activator.CreateInstance(type);
return (string) methodInfo.Invoke(instance, new object[] {"my UserName", 12});
На данном этапе мы скомпилировали и выполнили C# код прямо в браузере. Программа может состоять из нескольких файлов и использовать другие .NET библиотеки. Разве это не здорово? Теперь идем дальше.
Компиляция и запуск Blazor компонента в браузере.
Компоненты Blazor — это модифицированные Razor
шаблоны. Поэтому чтобы скомпилировать Blazor комопнент, нужно развернуть целую среду для компиляции Razor шаблонов и настроить расширения для Blazor. Нужно установить пакет Microsoft.AspNetCore.Blazor.Build
из nuget. Однако, добавить его в наш проект Blazor не получится, так как потом линкер не сможет скомпилировать проект. Поэтому нужно его скачать, а потом вручную добавить 3 библиотеки.
microsoft.aspnetcore.blazor.build\0.7.0\tools\Microsoft.AspNetCore.Blazor.Razor.Extensions.dll
microsoft.aspnetcore.blazor.build\0.7.0\tools\Microsoft.AspNetCore.Razor.Language.dll
microsoft.aspnetcore.blazor.build\0.7.0\tools\Microsoft.CodeAnalysis.Razor.dll
Создадим ядро для компиляции Razor
и модифицируем его для Blazor, так как по умолчанию ядро будет генерировать код Razor страниц.
var engine = RazorProjectEngine.Create(BlazorExtensionInitializer.DefaultConfiguration, fileSystem, b =>
{
BlazorExtensionInitializer.Register(b);
});
Для выполнения не хватает только fileSystem
— это абстракция над файловой системой. Мы реализовали пустую файловую систему, однако, если вы хотите компилировать сложные проекты с поддержкой _ViewImports.cshtml
— то нужно реализовать более сложную структуру в памяти.
Теперь сгенерируем код из Blazor компонента C# код.
var file = new MemoryRazorProjectItem(code);
var doc = engine.Process(file).GetCSharpDocument();
var csCode = doc.GeneratedCode;
Из doc
можно также получить диагностические сообщения о результатах генерации C# код из Blazor компонента.
Теперь мы получили код C# компонента. Нужно распарсить SyntaxTree
, потом скомпилировать Assembly, загрузить её в текущий AppDomain и найти тип компонента. Так же как в предыдущем примере.
Осталось загрузить этот компонент в текущее приложение. Есть несколько способов, как это сделать, например, создав свой RenderFragment
.
@inject CompileService service
<div class="card">
<div class="card-body">
@Result
</div>
</div>
@functions
{
RenderFragment Result = null;
string Code { get; set; }
public async Task Run()
{
var type = await service.CompileBlazor(Code);
if (type != null)
{
Result = builder =>
{
builder.OpenComponent(0, type);
builder.CloseComponent();
};
}
else
{
Result = null;
}
}
}
Заключение
Мы скомпилировали и запустили в браузере Blazor компонент. Очевидно, что полноценная компиляция динамического кода C# прямо внутри браузера может впечатлить любого программиста.
Но тут следует учитывать такие "подводные камни":
- Для поддержки двунаправленного биндинга
bind
нужны дополнительные расширения и библиотеки. - Для поддержки
async, await
, аналогично подключаем доп. библиотеки - Для компиляции связанных Blazor компонентов потребуется двухэтапная компиляция.
Все эти проблемы уже решены и это тема для отдельной статьи.
Комментарии (44)
engineX
20.12.2018 23:17+1Новый дивный мир, в котором можно выбрать язык программирования для frontend все ближе! :-)
Ne4to
21.12.2018 02:20Занимался последнюю неделю написанием небольшой внутренней админки на Blazor. Есть сложности с отладкой, но целом впечатления сугубо положительные. Вот тут рассказывается об альтернативных моделях хостинга приложения, например Blazor + Electron. Очень интересно во что это в итоге вырастет.
SamProf Автор
21.12.2018 10:53Я заметил, что отлаживать и разрабатывать проще в server-side режиме. Переключать их просто, а отладка и запуск приложения пока быстрее именно в полноценном .NET Runtime.
codecity
21.12.2018 03:25сможет ли Blazor за счет WebAssembly быть быстрее современных JS — фреймворков типа React, Angular, Vue
Да ладно, быстрее. Будет шикарно, если просто не будет жутких тормозов при открытии страницы. Так как сейчас работает — смысла нет, я уже забыл что открывал. Ваша демка загружалась секунд 30 — это не приемлемо для использования — просто баловство.
Если кардинально ничего не поменяется — то просто поделка для галочки, не пригодная для реального использования.ARad
21.12.2018 08:01Несмотря на большую задержку загрузки в СВОИХ областях вполне будет востребовано. К тому же при достаточной популярности браузеры решат проблему начальной загрузки популярных WebAssembly библиотек. Не так сложно это сделать.
codecity
21.12.2018 12:08Несмотря на большую задержку загрузки в СВОИХ областях вполне будет востребовано.
В таком виде — только для локальных сетей разве что. Если есть вариант доступа через. моб. сети, где скорость плавает — то использовать крайне не комфортно.
Будем надеяться что это просто недоработка.engineX
21.12.2018 20:25На Google I/O 2018 был доклад о том, что AutoCAD сделал веб версию на WebAssembly. Они просто смогли скомпилировать и переиспользовать ядро своей системы, написанной на C/C++. Не знаю сколько в итоге длится первый запуск и как много он весит, но мне кажется это пример приложения, загрузку которого можно и подождать ради возможности работать из браузера.
Ascar
21.12.2018 08:43Так автор упомянул как происходит загрузка сборок, судя по путям это с его же сервера тянется. Оптимизация этой загрузки в будущем очевидна.
codecity
21.12.2018 12:11Оптимизация этой загрузки в будущем очевидна.
Каким образом? Устанавливать сборки вместе с браузером? Или же хранение сборок в локальном кеше браузера?
Не первое вряд ли пойдут производители браузеров. На второе — нет гарантии что пользователь уже посещал сайты на .Net Core — такие будут ждать 30 сек. и не дождуться.
Желательная скорость загружки — до 0.8 сек для среднего интернет-канала. Терпимая — до 2-3 сек.Ascar
22.12.2018 15:45Как я и сказал загрузка была с того же сервера, библиотеки можно залить в облачное хранилище. Еще формат библиотек, их можно сжать и объединить в одну.
codecity
22.12.2018 15:57Боюсь, пока бесперспективно.
Если сделают компиляцию в нейтивный код, чтобы страница загружалась хотя бы 1 секунду — тогда можно будет вести речь о конкуренции Angular/React. Желательно быстрее. Пока же это, быть может, подойдет для внутренних/локальных сайтов, которыми пользуются работники одного предприятия в добровольно-принудительном порядке (им за это деньги платят).
Solexid
21.12.2018 09:52Первая загрузка — 8 секунд. Последуйщие — мгновенно. Так что тут просто загрузка с сервера.
codecity
21.12.2018 12:14Первая загрузка — 8 секунд.
У меня 30 сек., так как сейчас живу на даче и интернет не шибко быстрый. Но при посещении других сайтов — никакого дискомфорта не испытываю — остальные сайты загружаются мгновенно.
Но и 8 сек. — это жуткий дискомфорт. Не требуется сверх-скорость, но ждать более 2-3 сек. — не приемлемо.engineX
21.12.2018 20:08+1Хочу отметить, что приведенный в статье пример — это не пример использования Blazor как такогового, а пример того, как выполнить компиляцию Blazor «проекта» на стороне клиента.
Microsoft.CodeAnalysis.* (считай целый компилятор) самые тяжелые библиотеки в этом примере.codecity
21.12.2018 21:27А не попадался ли вам какой-нибудь сайт Blazor без этих Microsoft.CodeAnalysis.*? Реально ли его использовать или пока это просто эксперимент?
engineX
21.12.2018 22:05Blazor, как фреймворк для разработки приложений, выполняемых на стороне браузера — это определенно эксперимент.
Это признают даже в Microsoft. Именно по этой причине в новую версию .NET Core 3.0 войдет не Blazor, а Razor Components (server-side Blazor).
Причина все таже — даже при условии, что вам не нужны тяжеловесы аля Microsoft.CodeAnalysis.* для обычных приложений и использования умного mono linker, который вырезает весь неиспользуемый код, размер минимального приложения довольно велик.
Один из первых «осмысленных» демо примеров был FlightFinder Может появилось что-то более интересное.
SamProf Автор
21.12.2018 10:57В статье я указал что я сам не на 100% уверен в этом, однако предпосылки в этом есть. Microsoft везде пишет, что это до сих пор экспериментальный фреймворк. Т.е. много проблем еще не решено. На текущий момент они используют Mono-Linker для уменьшения размера dll-к. Но все равно они остаются полноценными CLR — Dll-ками. Обещают, что позже будет компиляция в нативный WASM, соответственно и скорость может быть значительно ваше.
А пока — можно только гадать, что будет дальше.
stopwaiting
21.12.2018 14:40Ура! +1 способ исправить
CORS header 'Access-Control-Allow-Origin' does not match…
А вообще-то немного не по себе от того что мой браузер тихо молча скачал dll и запустил код который потенциально может содержать что угодно включая свежайшие эксплоиты.
Прощайте сообщения «нужно обновить флеш плеер». Mono cможет сама себя обновить.SamProf Автор
21.12.2018 14:43Вы немного не верно понимаете технологию. Файтически они поднимают внутри браузера полноценную .NET CLR Runtime. И интерпретатор DLL. Но они исполняются внутри виртуальной машины WASM. Т.е. никакого вмешательства в дела ОС быть не может.
Но баги… конечно могут быть всегда.
А Cors — это не проблема, а защита. И она останеться, потому что все запросы из WASM полностью подчиняются правилам браузера.
Whuthering
21.12.2018 16:19+1… скачал dll и запустил его в виртуальной машине, которая, в свою очередь, работала в другой виртуальной машине, которая, в свою очередь, сидит в sandbox'e процесса браузера.
Что, согласитесь, совсем не то, что и загрузка бинаря адресном пространстве браузера со всеми его правами.
Rast1234
21.12.2018 18:26Он не выполнит произвольную DLL. Только ту, в которой есть .net assembly и которая собственно сможет выполниться в .net машине, которая запущена в браузерной песочнице.
devlev
21.12.2018 17:05+1Автору спасибо за статью. Blazor реально крут. Но — но медленный. Холодный старт 15мб и 12сек времени это на гарантированной скорости в 100мб/с и среднем железе. Тоже пробовал написать свой Hello World и был немного разочарован скоростью. Как верстальщику мне жутко еще не понравилось real-time обновление страниц при небольших изменениях — порядка 8-10 секунд (тестил в студии, готовый пример, возможно это можно как-то оптимизировать). Ну и даже Hello World уже прилично весит. В целом, как мне кажется, технология действительно имеет место быть, но пока ее можно использовать в каких то узкоспециализированных проектах, в которых скорость загрузки не важна, а нужно выжать максимум из возможностей браузеров. Может какие то игры делать на этой платформе оправдано. SPA с быстрым холодным стартом точно мимо
SamProf Автор
21.12.2018 18:03На одной из конф слышал, что они планируют уменьшить размер базового кода для продкашена до 1мб.
Что уже будет очень неплохо как мне кажется. Сейчас конечно да, печально.
Sioln
21.12.2018 18:22+1Silverlight, вид сбоку. Будут пытаться ужаться, используя минификаторы, вырезая неиспользуемый код из сборок.
Главное, чтобы после того, как MS в очередной раз закопает стюардессу, разработчики не остались без работы и с нерелевантным опытом.
kuber
21.12.2018 18:45Нет, это не Silverlight. Тут весь смысл в том, что WebAssembly разрабатывает не Microsoft, а сообщество.
staticlab
22.12.2018 15:58WebAssembly напрямую не связан с Blazor API.
kuber
23.12.2018 09:33Именно так. Он связан через Mono т.е. в принципе любой желающий может взять и разработать свой Blazor не приложив для этого титанический усилий.
staticlab
23.12.2018 11:27Усилия будут. В случае с флешем и сильверлайтом рантайм устанавливается на компьютер один раз, а здесь нужно тащить его с собой. Плюс, пока из wasm напрямую не доступен Web API придётся организовывать интероп с JS, а это отдельные накладные расходы и довольно много кода. Чтобы всё это минимизировать, нужно потрудиться.
kuber
23.12.2018 11:50Конечно будут, но не титанические. jQuery же тащат с собой и не жалуются и не просят его встроить в браузер. Продукт ещё даже не вышел, поэтому не надо смотреть на текущий момент. Главное это идея. Они за год настолько сильно изменили Blazor, что очень интересно посмотреть что с ним будет через год, другой.
kuber
21.12.2018 18:26Уже пару месяцев работаем с Blazor. Пытаемся понять подойдёт ли он нам. Ощущения очень хорошие. Проблемы со скоростью первого запуска есть, но в десктоп браузере они не так заметны, да и надо понимать, что он ещё не вышел.
Также в сторону wasm уже идёт c++ и go.
Конечно, надо ещё доработать процесс отладки. Немного огорчило, что нельзя напрямую из клиента обратится в WCF сервис.
EvilBeaver
22.12.2018 03:08Аааа!!! Где вы раньше были?! Я как раз сейчас с двухэтапной компиляцией мучаюсь. Компоненты, объявленные в компилируемой же сборке упорно не подхватываются даже при двухэтапной сборке. Ощущение, что addTagHelper не видит саму сборку. Че делать и куда копать не понял пока...
justboris
23.12.2018 12:45+1А может кто-то из уважаемой аудитории разъяснить, чем Blazor так хорош и почему его не ждет судьба GWT?
Ответ «потому что WebAssembly» ситуацию не проясняет, потому что одной этой составляющей для успеха маловато.
HerrDirektor
Уболтали, таки попробую еще до НГ эту штуку.
kuber
Лично я могу рекомендовать каждому c# разработчику попробовать Blazor. Мы уже некоторое время с ним работаем и ещё ни одной строки на JS не написали. А приложение уже кое-что умеет.
Ascar
Без js целиком не получится, надо хотя бы будет имплементить через JSRuntime.Current.InvokeAsync базовые функции типа alert, да и от кучи полезных библиотек не отказаться никак.
kuber
Пока что получается. А так я с вами согласен на данный момент библиотек не много, но и jQuery не сразу всеми плагинами оброс. Так что это дело времени.