Компиляция и запуск C# и Blazor внутри браузера

Введение



Если вы 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 компонентов потребуется двухэтапная компиляция.

Все эти проблемы уже решены и это тема для отдельной статьи.


GIT


Demo

Поделиться публикацией

Комментарии 61

    +3
    Уболтали, таки попробую еще до НГ эту штуку.
      0

      Лично я могу рекомендовать каждому c# разработчику попробовать Blazor. Мы уже некоторое время с ним работаем и ещё ни одной строки на JS не написали. А приложение уже кое-что умеет.

        0
        Без js целиком не получится, надо хотя бы будет имплементить через JSRuntime.Current.InvokeAsync базовые функции типа alert, да и от кучи полезных библиотек не отказаться никак.
          0

          Пока что получается. А так я с вами согласен на данный момент библиотек не много, но и jQuery не сразу всеми плагинами оброс. Так что это дело времени.

      0
      Одним словом свой рантайм в webassembly и все с этим сопутствующие вещи…
        +2
        Новый дивный мир, в котором можно выбрать язык программирования для frontend все ближе! :-)
          +1
          Занимался последнюю неделю написанием небольшой внутренней админки на Blazor. Есть сложности с отладкой, но целом впечатления сугубо положительные. Вот тут рассказывается об альтернативных моделях хостинга приложения, например Blazor + Electron. Очень интересно во что это в итоге вырастет.
            0
            Я заметил, что отлаживать и разрабатывать проще в server-side режиме. Переключать их просто, а отладка и запуск приложения пока быстрее именно в полноценном .NET Runtime.
            0
            сможет ли Blazor за счет WebAssembly быть быстрее современных JS — фреймворков типа React, Angular, Vue

            Да ладно, быстрее. Будет шикарно, если просто не будет жутких тормозов при открытии страницы. Так как сейчас работает — смысла нет, я уже забыл что открывал. Ваша демка загружалась секунд 30 — это не приемлемо для использования — просто баловство.

            Если кардинально ничего не поменяется — то просто поделка для галочки, не пригодная для реального использования.
              0
              Несмотря на большую задержку загрузки в СВОИХ областях вполне будет востребовано. К тому же при достаточной популярности браузеры решат проблему начальной загрузки популярных WebAssembly библиотек. Не так сложно это сделать.
                0
                Несмотря на большую задержку загрузки в СВОИХ областях вполне будет востребовано.

                В таком виде — только для локальных сетей разве что. Если есть вариант доступа через. моб. сети, где скорость плавает — то использовать крайне не комфортно.

                Будем надеяться что это просто недоработка.
                  0
                  На Google I/O 2018 был доклад о том, что AutoCAD сделал веб версию на WebAssembly. Они просто смогли скомпилировать и переиспользовать ядро своей системы, написанной на C/C++. Не знаю сколько в итоге длится первый запуск и как много он весит, но мне кажется это пример приложения, загрузку которого можно и подождать ради возможности работать из браузера.
                0
                Так автор упомянул как происходит загрузка сборок, судя по путям это с его же сервера тянется. Оптимизация этой загрузки в будущем очевидна.
                  0
                  Оптимизация этой загрузки в будущем очевидна.

                  Каким образом? Устанавливать сборки вместе с браузером? Или же хранение сборок в локальном кеше браузера?

                  Не первое вряд ли пойдут производители браузеров. На второе — нет гарантии что пользователь уже посещал сайты на .Net Core — такие будут ждать 30 сек. и не дождуться.

                  Желательная скорость загружки — до 0.8 сек для среднего интернет-канала. Терпимая — до 2-3 сек.
                    0
                    Как я и сказал загрузка была с того же сервера, библиотеки можно залить в облачное хранилище. Еще формат библиотек, их можно сжать и объединить в одну.
                      0
                      Боюсь, пока бесперспективно.

                      Если сделают компиляцию в нейтивный код, чтобы страница загружалась хотя бы 1 секунду — тогда можно будет вести речь о конкуренции Angular/React. Желательно быстрее. Пока же это, быть может, подойдет для внутренних/локальных сайтов, которыми пользуются работники одного предприятия в добровольно-принудительном порядке (им за это деньги платят).
                        0
                        Еще рано говорить о перспективности, подождем первую версию.
                  0
                  Первая загрузка — 8 секунд. Последуйщие — мгновенно. Так что тут просто загрузка с сервера.
                    0
                    Первая загрузка — 8 секунд.

                    У меня 30 сек., так как сейчас живу на даче и интернет не шибко быстрый. Но при посещении других сайтов — никакого дискомфорта не испытываю — остальные сайты загружаются мгновенно.

                    Но и 8 сек. — это жуткий дискомфорт. Не требуется сверх-скорость, но ждать более 2-3 сек. — не приемлемо.
                      +1
                      Хочу отметить, что приведенный в статье пример — это не пример использования Blazor как такогового, а пример того, как выполнить компиляцию Blazor «проекта» на стороне клиента.
                      Microsoft.CodeAnalysis.* (считай целый компилятор) самые тяжелые библиотеки в этом примере.
                        0
                        А не попадался ли вам какой-нибудь сайт Blazor без этих Microsoft.CodeAnalysis.*? Реально ли его использовать или пока это просто эксперимент?
                          0
                          Blazor, как фреймворк для разработки приложений, выполняемых на стороне браузера — это определенно эксперимент.

                          Это признают даже в Microsoft. Именно по этой причине в новую версию .NET Core 3.0 войдет не Blazor, а Razor Components (server-side Blazor).
                          Причина все таже — даже при условии, что вам не нужны тяжеловесы аля Microsoft.CodeAnalysis.* для обычных приложений и использования умного mono linker, который вырезает весь неиспользуемый код, размер минимального приложения довольно велик.

                          Один из первых «осмысленных» демо примеров был FlightFinder Может появилось что-то более интересное.
                    0
                    В статье я указал что я сам не на 100% уверен в этом, однако предпосылки в этом есть. Microsoft везде пишет, что это до сих пор экспериментальный фреймворк. Т.е. много проблем еще не решено. На текущий момент они используют Mono-Linker для уменьшения размера dll-к. Но все равно они остаются полноценными CLR — Dll-ками. Обещают, что позже будет компиляция в нативный WASM, соответственно и скорость может быть значительно ваше.
                    А пока — можно только гадать, что будет дальше.
                    0
                    Ура! +1 способ исправить
                    CORS header 'Access-Control-Allow-Origin' does not match…

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

                    Прощайте сообщения «нужно обновить флеш плеер». Mono cможет сама себя обновить.
                      +2
                      Вы немного не верно понимаете технологию. Файтически они поднимают внутри браузера полноценную .NET CLR Runtime. И интерпретатор DLL. Но они исполняются внутри виртуальной машины WASM. Т.е. никакого вмешательства в дела ОС быть не может.
                      Но баги… конечно могут быть всегда.

                      А Cors — это не проблема, а защита. И она останеться, потому что все запросы из WASM полностью подчиняются правилам браузера.
                        +1

                        … скачал dll и запустил его в виртуальной машине, которая, в свою очередь, работала в другой виртуальной машине, которая, в свою очередь, сидит в sandbox'e процесса браузера.
                        Что, согласитесь, совсем не то, что и загрузка бинаря адресном пространстве браузера со всеми его правами.

                          0

                          А потом переписать Electron-приложения на Blazor :)

                          0

                          Он не выполнит произвольную DLL. Только ту, в которой есть .net assembly и которая собственно сможет выполниться в .net машине, которая запущена в браузерной песочнице.

                            0
                            CORS разрешается вроде как на стороне сервера…
                            +1
                            Автору спасибо за статью. Blazor реально крут. Но — но медленный. Холодный старт 15мб и 12сек времени это на гарантированной скорости в 100мб/с и среднем железе. Тоже пробовал написать свой Hello World и был немного разочарован скоростью. Как верстальщику мне жутко еще не понравилось real-time обновление страниц при небольших изменениях — порядка 8-10 секунд (тестил в студии, готовый пример, возможно это можно как-то оптимизировать). Ну и даже Hello World уже прилично весит. В целом, как мне кажется, технология действительно имеет место быть, но пока ее можно использовать в каких то узкоспециализированных проектах, в которых скорость загрузки не важна, а нужно выжать максимум из возможностей браузеров. Может какие то игры делать на этой платформе оправдано. SPA с быстрым холодным стартом точно мимо
                              0
                              На одной из конф слышал, что они планируют уменьшить размер базового кода для продкашена до 1мб.
                              Что уже будет очень неплохо как мне кажется. Сейчас конечно да, печально.
                              +1

                              Silverlight, вид сбоку. Будут пытаться ужаться, используя минификаторы, вырезая неиспользуемый код из сборок.


                              Главное, чтобы после того, как MS в очередной раз закопает стюардессу, разработчики не остались без работы и с нерелевантным опытом.

                                0

                                Нет, это не Silverlight. Тут весь смысл в том, что WebAssembly разрабатывает не Microsoft, а сообщество.

                                  0

                                  WebAssembly напрямую не связан с Blazor API.

                                    0

                                    Именно так. Он связан через Mono т.е. в принципе любой желающий может взять и разработать свой Blazor не приложив для этого титанический усилий.

                                      0

                                      Усилия будут. В случае с флешем и сильверлайтом рантайм устанавливается на компьютер один раз, а здесь нужно тащить его с собой. Плюс, пока из wasm напрямую не доступен Web API придётся организовывать интероп с JS, а это отдельные накладные расходы и довольно много кода. Чтобы всё это минимизировать, нужно потрудиться.

                                        +1

                                        Конечно будут, но не титанические. jQuery же тащат с собой и не жалуются и не просят его встроить в браузер. Продукт ещё даже не вышел, поэтому не надо смотреть на текущий момент. Главное это идея. Они за год настолько сильно изменили Blazor, что очень интересно посмотреть что с ним будет через год, другой.

                                          0

                                          Кто jQuery тащит?

                                            +1

                                            Странный вопрос. Сайты, которые его используют.

                                              0

                                              Но большинство из его фич давно встроены в браузеры...

                                                0
                                                Вообще то все его фичи были в браузерах изначально.
                                                  0

                                                  Не было querySelector, единого XMLHttpRequest/fetch и элементов ФП (.map, .forEach).

                                0

                                Уже пару месяцев работаем с Blazor. Пытаемся понять подойдёт ли он нам. Ощущения очень хорошие. Проблемы со скоростью первого запуска есть, но в десктоп браузере они не так заметны, да и надо понимать, что он ещё не вышел.
                                Также в сторону wasm уже идёт c++ и go.
                                Конечно, надо ещё доработать процесс отладки. Немного огорчило, что нельзя напрямую из клиента обратится в WCF сервис.

                                  0
                                  Вы используете Blazor для публично доступного сайта или для внутреннего (корпоративного) приложения?
                                    0

                                    Для внутреннего и только в целях тестирования возможностей.

                                  0

                                  Аааа!!! Где вы раньше были?! Я как раз сейчас с двухэтапной компиляцией мучаюсь. Компоненты, объявленные в компилируемой же сборке упорно не подхватываются даже при двухэтапной сборке. Ощущение, что addTagHelper не видит саму сборку. Че делать и куда копать не понял пока...

                                    +2
                                    А может кто-то из уважаемой аудитории разъяснить, чем Blazor так хорош и почему его не ждет судьба GWT?

                                    Ответ «потому что WebAssembly» ситуацию не проясняет, потому что одной этой составляющей для успеха маловато.
                                      0
                                      Остается язык на котором ведется разработка, там Java, тут шарп.
                                        0
                                        В сообществе есть запрос на свержение монополии JavaScript и весьма своеобразной экосистемы, образовавшейся вокруг него, проекты, подобные Blazor, дают надежду.

                                        Касаемо GWT: он стар. Когда он вышел, в нем еще не было потребности, а сейчас её уже нет. Кроме того, это не флагманский проект Google, что наводит пессимизм. GWT Гуглу просто не нужен, в то время, как Blazor явно станет частью ASP.NET, а это ключевой продукт Майкрософт. Нет сомнений, что, в случае возникновения спроса, они в него вложатся.
                                          0

                                          Отвечу сразу вам и Ascar, потому что называется одно и то же преимущество – возможность не писать на ненавистном Javascript


                                          По своему опыту работы с GWT, в более-менее больших приложениях, абстракция все равно протекает и приходится вставлять вставки «нативного» JS.


                                          В этом отношении мне больше нравится подход Rust или Kotlin, где есть нормальная интеграция с веб-платформой, не требующая кучи трюков для вызова браузерного API.


                                          В Blazor я вижу как раз-таки GWT-стиль — скрыть браузерную сущность за толстым слоем абстракций, которые по-любому не смогут поспевать за развитием веб-технологий. И это меня настораживает

                                            0
                                            Blazor — это фреймворк а-ля Angular, Kotlin — это еще один язык, транспилируемый в JavaScript. У них разные назначения и вектора развития.
                                              0

                                              Тем самым вы подтвердили мои опасения. GWT тоже фреймворк, и он не смог угнаться за конкурентами.
                                              Аргумент «это будет часть ASP, а значит Майкрософт его не бросит» не убедителен, потому что у Майкрософта уже есть Typescript

                                                0
                                                Дался Вам этот GWT. GWT был внутренней разработкой Google, которую они низвестно зачем выложили в Open Source. Он просто не успел набрать обороты, как хайп был уже на стороне NodeJS и «супергероического» AngularJS. Обратите внимание, что AngularJS — разработка Google же, Google вобще любит конкурировать сама с собой.

                                                Стратегически, МС нужны PWA-приложения, причем именно на .NET-стеке, TypeScript вообще к .NET отношения не имеет.
                                                  0
                                                  Дался Вам этот GWT

                                                  Потому что эти проекты похожи как родные братья, только с разницей в 10 лет. И мотивация к появлению одна и та же: «что угодно, лишь бы не JS»


                                                  GWT был внутренней разработкой Google, которую они низвестно зачем выложили в Open Source

                                                  Я знаю несколько проектов на GWT вне Google, так что рынок был. Sencha GWT для кого-то же создали? Через 10 лет про Blazor тоже так говорить будут: «экспериментальная разработка, непонятно зачем выложенная в OpenSource»?


                                                  хайп был уже на стороне NodeJS и «супергероического» AngularJS

                                                  Я собственноручно переводил сборку фронтенда с Maven на Grunt. Однозначно получилось удобнее, никакого «ради хайпа» там не было. GWT тоже пробовали, оказалось не так удобно.

                                                    0
                                                    И мотивация к появлению одна и та же: «что угодно, лишь бы не JS»

                                                    Ну нет же, в 2006 году JavaScript почти всех устраивал, а GWT был слишком большой пушкой, доступной, к тому же, только для Java-проггеров. Он и сейчас, в 2018-м слишком большой.

                                                    Sencha GWT для кого-то же создали?

                                                    Дя себя они его создали. Платные навороченные компонетны не очень-то подходят для публичных сайтов. Пиплу за глаза хватало jQuery.

                                                    GWT тоже пробовали, оказалось не так удобно.

                                                    Вот, и все остальные так же думали.

                                                    Через 10 лет про Blazor тоже так говорить будут: «экспериментальная разработка, непонятно зачем выложенная в OpenSource»?

                                                    В мире Java основной фреймворк — это Spring. GWT вообще ни с какого боку. С точки зрения проггера — а нафик мне это надо? А какие дает преимущества? А кто будет поддерживать? А не будет ли тормозить? А откуда брать специалистов? Мотивы Google не понятны совершенно. В мире ASP.NET никто не спрашивает — вот вам Blazor, мы его добавим в версии 3.0 (пока только серверные компоненты) — пользуйтесь. Цели — PWA и экспансия .NET за пределы Windows — и аудитория вполне прозрачны и понятны. Удастся ли реализовать задуманное? Время покажет.
                                                      0

                                                      Аргумент «GWT был примочкой сбоку, а Blazor встраивается в основной стек веб-разработки на .Net» принимается с таким подходом может что и выйдет

                                          0
                                          Blazor может взлететь, а может о нем все забудут через год, другой. Это не столь важно.
                                          Ничто не вечно.
                                          Важно, что постоянно появляются попытки подвергнуть сомнению монополизацию JS в браузерах. Монополия это практически всегда плохо. Мы, как разработчики должны только радоваться данным попыткам, потому что в итоге мы выиграем в любом случае.
                                          В сторону WebAssembly уже идут: Java, C++, Go, C# и это только о которых я знаю.
                                            0

                                            Комментарии к статье о поддержки WebAssembly в ещё один язык:


                                            • как здорово, давайте свергать монополию JS

                                            Комментарии к статье о программировании IoT на node.js


                                            • все плохо, зачем Javascript лезет не в свою нишу, нужно использовать правильные интрументы.

                                            Либо я что-то не так понимаю, либо здесь двойные стандарты.


                                            Кроме того, монополия на DOM API все ещё в руках JS, так что в итоге Blazor все равно частично транслируется в JS со всеми вытекающими

                                              0
                                              Конечно, сегодня все так, как вы пишите, но есть проблема — существует завтра. Нет смысла в таких обсуждениях. Не нравится не используйте. Я начал использовать и получил кайф.
                                                0
                                                Никаких двойных стандартов. Посыл один — «никто не любит JavaScript, надо его выпилить». Отсюда и «долой с бэкенда» (node.js) и «долой с фронтенда» (WebAssembly)

                                                Это не наброс на JS, это я просто про отсутствие двойного стандарта.
                                                  0
                                                  Так стало понятнее, хоть и радостнее ситуацию не делает

                                          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                          Самое читаемое