<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="ru">
    <title>san-smith</title>
    <subtitle>Разработчик: Dart и Flutter, руководство командой, Rust и симуляции.</subtitle>
    <link rel="self" type="application/atom+xml" href="https://sansmith.dev/atom.xml"/>
    <link rel="alternate" type="text/html" href="https://sansmith.dev"/>
    <generator uri="https://www.getzola.org/">Zola</generator>
    <updated>2026-07-01T00:00:00+00:00</updated>
    <id>https://sansmith.dev/atom.xml</id>
    <entry xml:lang="ru">
        <title>Миграция с Flutter Web на Jaspr: наш опыт</title>
        <published>2026-07-01T00:00:00+00:00</published>
        <updated>2026-07-01T00:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sansmith.dev/posts/from-flutter-web-to-jaspr/"/>
        <id>https://sansmith.dev/posts/from-flutter-web-to-jaspr/</id>
        
        <content type="html" xml:base="https://sansmith.dev/posts/from-flutter-web-to-jaspr/">&lt;p&gt;&lt;img src=&quot;https://sansmith.dev/posts/from-flutter-web-to-jaspr/kpdv.webp&quot; alt=&quot;flutter vs. jaspr&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;-- Открывай &lt;a rel=&quot;external&quot; href=&quot;https://dart.dev/&quot;&gt;dart.dev&lt;/a&gt;! -- Обычная Димина напористость сегодня была приправлена доброй горстью нетерпеливости. -- Открыл? Теперь листай вниз! Что ты видишь в правом нижнем углу? &lt;br/&gt;
-- Ссылки всякие: на Github там, Twitter и т.п. -- в растерянности я пытался угадать, на что именно нужно обратить внимание. &lt;br/&gt;
Кажется, Дима по ту сторону звонка схватился за голову. &lt;br/&gt;
-- Видишь баннер &quot;Built with Jaspr&quot;?! &lt;br/&gt;
Я молча кивнул и кликнул по баннеру.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Уже несколько лет мы занимаемся развитием клиентского приложения на Flutter: начинали с мобильных платформ, когда веб и десктоп вышли в бету -- портировали его и на эти платформы. Последние два года PWA крутится в проде и в некоторых регионах является самой популярной платформой для нашего приложения.&lt;/p&gt;
&lt;p&gt;Недавно мы попробовали мигрировать наше приложение с Flutter Web на Jaspr -- под катом расскажем о том, почему мы это сделали, с какими трудностями столкнулись и какие выводы сделали.&lt;/p&gt;
&lt;h2 id=&quot;chto-za-jaspr-takoi&quot;&gt;Что за Jaspr такой?&lt;/h2&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https://jaspr.site/&quot;&gt;Jaspr&lt;/a&gt; — это современный веб-фреймворк с открытым исходным кодом, написанный на языке Dart и предназначенный для создания быстрых, производительных веб-сайтов и веб-приложений.&lt;/p&gt;
&lt;p&gt;Главная идея Jaspr заключается в том, чтобы перенести компонентный подход и реактивную модель разработки из Flutter в нативный веб. В отличие от классического Flutter Web, который рендерит интерфейс на Canvas, Jaspr генерирует стандартный HTML и CSS, как это делают React, Vue или Angular.&lt;/p&gt;
&lt;p&gt;Фреймворк поддерживает рендеринг на стороне сервера (SSR), генерацию статических сайтов (SSG) и классические одностраничные приложения (SPA).&lt;/p&gt;
&lt;p&gt;Очень рекомендую посмотреть сайт проекта, там наглядно показано, чем Jaspr похож на Flutter, а в чём они отличаются.&lt;/p&gt;
&lt;h2 id=&quot;chto-ne-tak-s-flutter-web&quot;&gt;Что не так с Flutter Web?&lt;/h2&gt;
&lt;p&gt;Для нас главная ахиллесова пята приложений на Flutter Web -- долгий холодный старт. Дело в том, что PWA, написанное на Flutter Web, должно загрузить canvaskit.wasm (несколько МБ) прежде, чем сможет нарисовать первый кадр. При хорошем интернете это не проблема, но в регионах, где нестабильный 3G -- это максимум, на который можно рассчитывать, становится больно. Первый запуск занимает несколько секунд, при плохом раскладе -- несколько десятков.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://sansmith.dev/posts/from-flutter-web-to-jaspr/flutter-timeline.png&quot; alt=&quot;flutter app timeline&quot; /&gt;&lt;/p&gt;
&lt;details&gt;
  &lt;summary&gt;Угнать за 6 секунд&lt;/summary&gt;
&lt;blockquote&gt;
&lt;p&gt;Почти 6 секунд потребовалось, чтобы загрузить необходимые ресурсы до первого запроса к API. Да, скорость интернета в данном случае была паршивой, но ведь пользователям не интересны причины.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/details&gt;
&lt;p&gt;Аналитика показала, что пользователь не готов столько ждать -- если загрузка длилась более 6 секунд, то мы теряем потенциального клиента (напомню, речь о первом старте приложения).&lt;/p&gt;
&lt;p&gt;Ещё одно неудобство для конечных пользователей -- приложение ведёт себя не как &quot;обычный&quot; сайт: нельзя воспользоваться поиском по странице, не работает выделение текста (без дополнительных приседаний со стороны разработчиков) и т.п. Особенно это становится заметно в десктопном варианте, где пользовательский опыт существенно отличается от мобильного.&lt;/p&gt;
&lt;p&gt;Вот тут-то на сцену и выходит Jaspr: с его рендером в нативный DOM, в теории, можно избежать загрузки лишнего рантайма и бонусом получить привычный для пользователя &quot;обычный сайт&quot;.&lt;/p&gt;
&lt;h2 id=&quot;jaspr-proof-of-concept&quot;&gt;Jaspr: Proof of Concept&lt;/h2&gt;
&lt;p&gt;Основное наше клиентское приложение имеет внушительную (по меркам Flutter проектов) кодовую базу и включает в себя работу с SignalR с кастомными протоколами, нетривиальную логику подписок на события и т.д. Объём передаваемых по сокетам данных может измеряться мегабайтами и вся эта красота должна оперативно обновлять ряд экранов в рантайме.&lt;/p&gt;
&lt;p&gt;Для проверки концепции я выбрал один из самых нагруженных экранов, для которого как раз и требовалась корректная работа с SignalR -- благо модульная структура нашего приложения позволяла.&lt;/p&gt;
&lt;p&gt;Jaspr не может рисовать виджеты Flutter, поэтому план был таким:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Вынести в чисто дартовые пакеты общий код (работа с сетью, бизнес-логика и т.п.);&lt;/li&gt;
&lt;li&gt;Перевести &quot;старые&quot; Flutter модули на использование этих дартовых пакетов;&lt;/li&gt;
&lt;li&gt;Реализовать Jaspr версии &quot;старых&quot; модулей и собрать Jaspr приложение.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://sansmith.dev/posts/from-flutter-web-to-jaspr/package-separation.webp&quot; alt=&quot;Схема разделения пакетов&quot; /&gt;&lt;/p&gt;
&lt;details&gt;
  &lt;summary&gt;Немного о структуре проекта&lt;/summary&gt;
&lt;blockquote&gt;
&lt;p&gt;На самом деле у нас не одно приложение, а несколько. И собираются они из нескольких десятков пакетов -- такой подход позволяет, с одной стороны, изолировать код, а с другой -- использовать его повторно.
Наши пакеты, за редким исключением имеют одинаковую структуру:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;my_package/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;├──&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; lib/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;│&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;   ├──&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; src/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;│&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;   │&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;   ├──&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; api/&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;          #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; работа с сетью, DTO&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;│&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;   │&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;   ├──&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; domain/&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;       #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; бизнес-логика -- репозитории, сервисы&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;│&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;   │&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;   ├──&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; models/&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;       #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; доменные модели данных&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;│&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;   │&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;   └──&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; presentation/&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; экраны, виджеты, утилиты для представления&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;│&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;   └──&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; my_package.dart&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;└──&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; pubspec.yaml&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;api&lt;/code&gt;, &lt;code&gt;domain&lt;/code&gt; и &lt;code&gt;models&lt;/code&gt;, как правило, не требуют зависимости от Flutter SDK. Их можно безболезненно вынести в отдельный Dart пакет, мы договорились к таким пакетам добавлять суффикс &lt;code&gt;shared&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/details&gt;
&lt;p&gt;Вечером я расчехлил Cursor и взялся за работу. Довольно быстро появились первые инсайты:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Наш код имел гораздо большую связанность, чем я предполагал;&lt;/li&gt;
&lt;li&gt;Зависимость на Flutter SDK имели даже те пакеты, которым, казалось бы, никакой Flutter не нужен;&lt;/li&gt;
&lt;li&gt;Web платформа имеет ряд своих особенностей (ладно, это я знал и раньше, но никогда не сталкивался в таких масштабах);&lt;/li&gt;
&lt;li&gt;Я совершенно не разбираюсь в современной веб вёрстке.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Стало очевидно, что требуемый объём работ сильно больше, чем я ожидал первоначально.
Для proof of concept пришлось сузить вводные: пока плюём на работоспособность Flutter версии, заменяем моками или упрощёнными реализациями ряд компонентов. Оставляем только основной сценарий: инициализация -&amp;gt; подключение к SignalR -&amp;gt; подписка на события -&amp;gt; отрисовка данных на экране с real time обновлениями.&lt;/p&gt;
&lt;p&gt;Спустя несколько часов и полностью выжженные лимиты 20-долларовой подписки Cursor, мой прототип был готов. Визуально экран был очень далёк от flutter оригинала, линтер ругался как ненормальный на полученный код, тесты были сломаны, а diff включал в себя больше тысячи изменённых файлов -- Bitbucket был просто не в состоянии показать их все.&lt;/p&gt;
&lt;p&gt;Но этот прототип работал. И работал хорошо: за вычетом внешнего вида, экран вёл себя ровно так, как и требовалось.&lt;/p&gt;
&lt;p&gt;И как же быстро он загружался! После Flutter PWA это был как глоток свежего воздуха -- холодный старт занимал 1-1,5 секунды, а после включения экспериментальной компиляции в WASM так и вовсе измерялся в миллисекундах.&lt;/p&gt;
&lt;p&gt;Да, конечно, прототип был сильно меньше оригинального Flutter приложения, но он показал очень хороший потенциал.&lt;/p&gt;
&lt;p&gt;Мы обсудили с ребятами результаты, прикинули возможные подводные камни, подготовили демо и пошли защищаться перед бизнесом.&lt;/p&gt;
&lt;h2 id=&quot;jaspr-mvp&quot;&gt;Jaspr: MVP&lt;/h2&gt;
&lt;p&gt;Бизнес очертил перечень модулей, которые необходимы в MVP, чтобы эксперимент считался успешным и дал вводные: команда из двух с половиной флаттеристов (с учётом отпусков), срок -- квартал.&lt;/p&gt;
&lt;p&gt;К этим вводным мы добавили свои:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;пишем максимально приближенный к Flutter реализации код, чтобы уменьшить сложность перехода флаттеристов;&lt;/li&gt;
&lt;li&gt;не используем условные импорты (у Jaspr не может быть io платформ);&lt;/li&gt;
&lt;li&gt;стили компонентов описываем в отдельных CSS/SCSS файлах, не вмешивая сырые строки со стилями в dart коде.&lt;/li&gt;
&lt;/ul&gt;
&lt;details&gt;
&lt;summary&gt;Про стили&lt;/summary&gt;
&lt;p&gt;Jaspr из коробки &lt;a rel=&quot;external&quot; href=&quot;https://docs.jaspr.site/concepts/styling&quot;&gt;предоставляет&lt;/a&gt; несколько подходов к стилизации компонентов. Но мы пошли своим путём не из-за их фатального недостатка: на встрече с бизнесом прозвучало &lt;em&gt;пожелание&lt;/em&gt; сделать так, чтобы что-то из проделанной работы можно было использовать с условным React -- если результаты будут признаны неудовлетворительными.
С паршивой овцы -- хоть шерсти клок, ага.
Портировать вёрстку показалось делом неблагодарным, а вот CSS файлы вполне могли бы использоваться как есть.&lt;/p&gt;
&lt;/details&gt;
&lt;p&gt;Поэтому нам пришлось портировать некоторые используемые пакеты на Jaspr: например, с опорой на пакет &lt;a rel=&quot;external&quot; href=&quot;https://pub.dev/packages/elementary&quot;&gt;&lt;code&gt;elementary&lt;/code&gt;&lt;/a&gt; мы запилили свой &lt;a rel=&quot;external&quot; href=&quot;https://pub.dev/packages/jaspr_elementary&quot;&gt;&lt;code&gt;jaspr_elementary&lt;/code&gt;&lt;/a&gt;. Также потребовалось создать несколько локальных утилит, вроде генератора CSS стилей с watch режимом.&lt;/p&gt;
&lt;details&gt;
&lt;summary&gt;Про генерацию стилей&lt;/summary&gt;
&lt;p&gt;Допустим мы хотим создать компонент-контейнер &lt;code&gt;ListGroup&lt;/code&gt;. Создаём файл стилей &lt;code&gt;list_group.scss&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;scss&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;list-group&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;  display&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; flex&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;  flex-direction&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; column&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;  align-items&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; stretch&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;  width&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 100&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;%&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;  box-sizing&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; border-box&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;  border-radius&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; var&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;--radius-m&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 16&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;px&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;  background-color&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; var&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;--component-base-card-background&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;  overflow&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; hidden&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;  &amp;amp;&lt;/span&gt;&lt;span&gt; &amp;gt; &lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;not&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;last-child&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;    border-bottom&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 1&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;px&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; solid&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; var&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;--component-divider-low&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Выходные псевдоклассы будут иметь вид:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;css&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;_72db67ef_list_group__list-group&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;  display&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; flex&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;  flex-direction&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; column&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;  align-items&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; stretch&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;  width&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 100&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;%&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;  box-sizing&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; border-box&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;  border-radius&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; var&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;--radius-m&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 16&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;px&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;  background-color&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; var&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;--component-base-card-background&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;  overflow&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; hidden&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;_72db67ef_list_group__list-group&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; &amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; :&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;not&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;last-child&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;  border-bottom&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 1&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;px&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; solid&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; var&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;--component-divider-low&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Хэш-префикс в начале зависит только от пути к исходному файлу, за счёт чего гарантируется уникальность имён псевдоклассов. Это также позволяет обойтись без шума в диффах -- хэш будет одинаковым на любой платформе у всех разработчиков.&lt;/p&gt;
&lt;p&gt;Помимо выходных &lt;code&gt;*.css&lt;/code&gt; генерируется ещё файл &lt;code&gt;*.styles.dart&lt;/code&gt; с константами псевдоклассов:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;dart&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;abstract&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; class&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; ListGroupStyles&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;  static&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; const&lt;/span&gt;&lt;span&gt; listGroup &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;_72db67ef_list_group__list-group&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Он нужен, чтобы в вёрстке использовать более удобные поля класса вместо строковых констант.
Кроме того, при перемещении файлов в другой каталог изменится только значение константы, а вёрстка останется неизменной.&lt;/p&gt;
&lt;p&gt;Теперь можно собрать всё вместе и использовать в Jaspr компоненте:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;dart&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;package:jaspr/dom.dart&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;package:jaspr/jaspr.dart&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;list_group.styles.dart&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; ListGroup&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; extends&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; StatelessComponent&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;  final&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; List&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;Component&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; children&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;  const&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; ListGroup&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;    super&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    required&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;children&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  })&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;  @override&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;  Component&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; build&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;BuildContext&lt;/span&gt;&lt;span&gt; context) &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; div&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    children&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    classes&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; ListGroupStyles&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;listGroup&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  )&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;И остался последний элемент системы: watch процесс, который отслеживает изменения в &lt;code&gt;*.scss&lt;/code&gt; файлах, вызывает для них инкрементальную компиляцию и генерацию новых &lt;code&gt;*.css&lt;/code&gt; и &lt;code&gt;*.styles.dart&lt;/code&gt; файлов. А затем копирует выходные &lt;code&gt;.css&lt;/code&gt; файлы в каталог &lt;code&gt;web&lt;/code&gt;: &lt;code&gt;jaspr serve&lt;/code&gt; подхватит изменение веб-ассетов и обновит страницу.&lt;/p&gt;
&lt;p&gt;Благодаря этому инструменту вёрстка компонентов стала почти такой же удобной, как и во Flutter.&lt;/p&gt;
&lt;/details&gt;
&lt;p&gt;В целом план реализации MVP практически совпадал с оным для прототипа:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Выносим общий код в Dart пакеты;&lt;/li&gt;
&lt;li&gt;Мигрируем на них Flutter приложение;&lt;/li&gt;
&lt;li&gt;Реализуем Jaspr версию приложения.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Только теперь без срезанных углов. Мы составили примерную диаграмму Ганта и, засучив рукава, взялись за работу.&lt;/p&gt;
&lt;p&gt;Первые месяц-полтора мы сосредоточились на распутывании наших зависимостей, выделении общего кода и и подготовки инструментов. Показывать бизнесу в тот период было практически нечего, но зато улучшили качество кода в целом, плюс получили новые инсайты, улучшающие и Flutter приложение. Пожалуй, именно в это время мы сполна ощутили профиты от использования модульности и Clean Architecture подхода.&lt;/p&gt;
&lt;p&gt;И да -- оказывается, мы очень недурно проектируем и пишем Dart код! Большая часть написанного нами без сучка, без задоринки было готово работать с Jaspr. Пользуясь случаем, передаю ребятам привет -- вы очень крутые разработчики!&lt;/p&gt;
&lt;p&gt;Затем мы приступили к портированию экранов. И опять же, значительная часть логики UI (которая в Flutter версии у нас хранилась в WidgetModel) прямо кусками переносилась в ComponentModel компонентов Jaspr -- тут хорошо сыграло решение с портированием Elementary.&lt;/p&gt;
&lt;p&gt;Кстати, хозяйке на заметку: если Cursor сдобрить несколькими специфичными для Jaspr правилами и скиллами, то он очень неплохо готовит Jaspr компоненты на основе референса Flutter виджетов. Это сильно сократило время реализации.&lt;/p&gt;
&lt;p&gt;И если к концу первого месяца даже у разработчиков были сомнения, что мы уложимся в дедлайн, то к концу второго месяца стало очевидно -- MVP будет готов. Возможно с некоторыми отличиями от макетов, но в функциональном смысле готов на 100%.&lt;/p&gt;
&lt;h2 id=&quot;rezul-taty-i-vyvody&quot;&gt;Результаты и выводы&lt;/h2&gt;
&lt;p&gt;Результат полностью оправдал наши ожидания: несмотря на то, что MVP был сильно больше прототипа, холодный старт сохранился на уровне 1-1,5 секунд (без компиляции в WASM).&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://sansmith.dev/posts/from-flutter-web-to-jaspr/jaspr-mvp-timeline.png&quot; alt=&quot;jaspr app timeline&quot; /&gt;&lt;/p&gt;
&lt;details&gt;
  &lt;summary&gt;Есть только миг&lt;/summary&gt;
&lt;blockquote&gt;
&lt;p&gt;Чуть больше секунды на холодный старт до первого запроса к API. При тех же условиях версия на Flutter Web давала 6 секунд.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/details&gt;
&lt;p&gt;Бонусом мы неплохо отрефакторили код, а ряд идей, появившихся в процессе работы над Jaspr версией, были портированы и во Flutter приложение.&lt;/p&gt;
&lt;p&gt;Мы сознательно упростили себе жизнь и использовали client mode, но Jaspr умеет и в SSR. А значит потенциально можно добиться ещё более лучших результатов.&lt;/p&gt;
&lt;details&gt;
  &lt;summary&gt;Почему не SSR?&lt;/summary&gt;
&lt;blockquote&gt;
&lt;p&gt;Как-то так получилось, что среди наших флаттеристов не было никого с опытом в веб-разработке. С учётом сжатых сроков и новой для нас технологии мы решили, что погружаться в дебри SSR было бы нерационально. Но в дальнейшем мы всё-таки планируем его использовать.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/details&gt;
&lt;p&gt;При этом важно отметить сроки реализации: по нашим оценкам разработка аналогичного React приложения заняла бы кратно больше времени -- это как раз тот случай, когда кросс-платформенность и переиспользование кода даёт огромный прирост.&lt;/p&gt;
&lt;p&gt;Несколько выводов, которые мы сделали по результатам этого эксперимента:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Чистая архитектура и модульность -- это реально очень важно! Не срезайте углы, потом может стать больно;&lt;/li&gt;
&lt;li&gt;ИИ-инструменты хорошо помогают тем, кто их настраивает под проект. У остальных -- жрёт токены;&lt;/li&gt;
&lt;li&gt;Jaspr -- действительно неплохая альтернатива Flutter Web для ряда сценариев;&lt;/li&gt;
&lt;li&gt;Если вы работаете с вебом, вам не помешает иметь веб-разработчика в команде.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;В целом Jaspr очень крутой проект, который позволяет Flutter разработчикам создавать веб-приложения с использованием имеющегося опыта. Но нужно трезво подходить к выбору технологий -- Jaspr (как и Flutter Web) хорошо подходит, если у вас есть сильная Dart команда и вы планируете переиспользовать большую часть кода.&lt;/p&gt;
</content>
        
    </entry>
    <entry xml:lang="ru">
        <title>Как мы WASM в PWA на Flutter прикручивали</title>
        <published>2023-07-20T00:00:00+00:00</published>
        <updated>2023-07-20T00:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://sansmith.dev/posts/flutter-wasm/"/>
        <id>https://sansmith.dev/posts/flutter-wasm/</id>
        
        <content type="html" xml:base="https://sansmith.dev/posts/flutter-wasm/">&lt;p&gt;— Парни, у нас PWA тормозит! — в голосе Димы чувствовались нотки интриги.&lt;/p&gt;
&lt;p&gt;Вообще-то мы разрабатываем на Flutter кроссплатформенное приложение для мобильных устройств, но коль уж фреймворк позволяет, на сдачу запустили и веб-версию. Поначалу с PWA мы отхватили немало проблем, но со временем большую часть из них победили. Только вот производительность (из песни слов не выкинешь) так и осталась ахиллесовой пятой приложения — даже на достаточно мощных устройствах нет-нет, да проскакивали микрофризы.&lt;/p&gt;
&lt;p&gt;Новостью это не было ни для нас, ни для Димы, поэтому причина озвучивания этого факта ни с того, ни с сего была не ясна.&lt;/p&gt;
&lt;p&gt;Вдоволь насладившись нашими удивлёнными взорами, Дима продолжил:&lt;/p&gt;
&lt;p&gt;— Нет, вы не поняли. У нас приложение очень тормозит — слово &quot;очень&quot; он произнёс с акцентом на &quot;о&quot;, ещё и протянув его пару секунд, чтобы подчеркнуть, насколько всё плохо. — Пользователи жалуются, что после авторизации может зависнуть на несколько секунд, иногда до десяти.&lt;/p&gt;
&lt;p&gt;Вот это уже было неожиданностью. Разработчики сдержанно загудели, обсуждая возможные причины.&lt;/p&gt;
&lt;p&gt;Дима подождал, когда схлынут самые жаркие споры и уточнил:&lt;/p&gt;
&lt;p&gt;— Что думаете делать?&lt;/p&gt;
&lt;p&gt;— А что тут думать, профилировать надо — высказал витавшую в воздухе мысль Саша.&lt;/p&gt;
&lt;h2 id=&quot;profiliruem&quot;&gt;Профилируем&lt;/h2&gt;
&lt;p&gt;У Flutter есть довольно неплохие инструменты разработчика, которые включают в том числе и профилировщик. Однако Саша знал, что в мобильных приложениях проблема с лагом была выражена не так сильно, как в PWA, поэтому и профилировать решил с помощью инструментов разработчика Chrome.&lt;/p&gt;
&lt;p&gt;Дело это нехитрое: запускаем веб версию приложения в режиме профилирования, открываем инструменты разработчика в браузере, переходим на вкладку &quot;Perfomance&quot; и в нужном месте включаем запись. После прохождения интересующего сценария в приложении останется остановить запись и подождать, когда Chrome подготовит данные.&lt;/p&gt;
&lt;p&gt;В нашем случае источник проблемы был найден довольно быстро: им оказалась функция из сторонней библиотеки, которая занимается длительными математическими вычислениями. Классическая числодробилка вроде нахождения числа Фибоначчи или факториала.&lt;/p&gt;
&lt;p&gt;Выглядела эта функция примерно так:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;dart&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; _onPressed&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;  setState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; _inProgress &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; true&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;  // Вызов функции, которая долго вычисляется&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  _result &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; fib&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;_value)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;  setState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; _inProgress &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; false&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Вызов этой функции в вебе измерялся секундами, а бедный пользователь всё это время с тоской и тревогой наблюдал застывший экран.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://sansmith.dev/posts/flutter-wasm/image.png&quot; alt=&quot;Alt text&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Хорошо, что во Flutter есть волшебная функция &lt;code&gt;compute&lt;/code&gt;! С её помощью можно вынести долгие вычисления в отдельный изолят, а когда вычисления будут завершены — положить их в нужное место. Понятно дело, что магия в этом мире работает плохо и получить искомое значение быстрее не получится, но хотя бы можно не блокировать основной поток. В этом случае пользователь вместо унылого застывшего экрана будет видеть экран с весёлым и задорным лоадером.&lt;/p&gt;
&lt;p&gt;Саша уже умел пользоваться функцией &lt;code&gt;compute&lt;/code&gt;, поэтому приступил к переписыванию кода.&lt;/p&gt;
&lt;h2 id=&quot;ispol-zuem-compute&quot;&gt;Используем compute&lt;/h2&gt;
&lt;p&gt;Функция &lt;code&gt;compute&lt;/code&gt; принимает два аргумента: вычисляемую функцию и её аргумент, а возвращает объект &lt;code&gt;Future&lt;/code&gt;. С помощью метода &lt;code&gt;then&lt;/code&gt; этого объекта мы можем присвоить нужное значение нашим переменным, когда вычисления будут закончены.&lt;/p&gt;
&lt;p&gt;После переработки наш метод принял следующий вид:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;dart&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; _onPressed&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;  setState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; _inProgress &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; true&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;  // Вызов функции, которая долго вычисляется&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;  compute&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;(n) &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; fib&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;n)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; _value)&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;then&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;(value) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    _result &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span&gt; value&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;    setState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; _inProgress &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; false&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  })&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Получилось неплохо! — подумал довольный собой Саша, после того как запустил приложение на десктопе (обычно при разработке он запускал приложение на своём горячо любимом линуксе — так было быстрее). А чего бы ему не быть довольным? — кнопки нажимаются, лоадеры задорно крутятся и в нужный момент меняются на задуманные виджеты. Никаких тебе фризов.&lt;/p&gt;
&lt;p&gt;Мысль о фризах напомнила Саше о лежащем в холодильнике мороженом. &quot;За хорошую работу не грех себя порадовать вкусняшкой — особенно в такую жару&quot;, подумал Саша и сходил за холодной сладостью.&lt;/p&gt;
&lt;p&gt;Однако после запуска веб-версии приложения мороженое пришлось отставить в сторону: там лаги и фризы будто бы не знали, что им следовало исчезнуть. Профилировщик говорил то же, что и раньше: вы долго считаете какую-то дрянь.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://sansmith.dev/posts/flutter-wasm/image-1.png&quot; alt=&quot;Alt text&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Если что-то ведёт себя не так, как ты ожидаешь, то скорее всего ты чего-то не знаешь об этом самом чём-то. И действительно, если обратиться к статье &lt;a rel=&quot;external&quot; href=&quot;https://dart.dev/language/concurrency#concurrency-on-the-web&quot;&gt;об изолятах&lt;/a&gt; на сайте языка Dart, то можно обнаружить такое сообщение:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;All Dart apps can use &lt;code&gt;async-await&lt;/code&gt;, &lt;code&gt;Future&lt;/code&gt;, and &lt;code&gt;Stream&lt;/code&gt; for non-blocking, interleaved computations. The Dart web platform, however, does not support isolates.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Перефразируя классиков, можно сказать, что товарищ &lt;code&gt;compute&lt;/code&gt; в вебе нам совсем не товарищ.&lt;/p&gt;
&lt;p&gt;Как оказалось, мы были не единственными, кто столкнулся с проблемой производительности при работе с данной библиотекой: в репозитории обнаружился &lt;a rel=&quot;external&quot; href=&quot;https://github.com/bcgit/pc-dart/issues/113&quot;&gt;issue&lt;/a&gt; двухгодичной давности, в котором обсуждалась эта проблема. Авторы библиотеки честно сказали, что для веба ничего сделать нельзя, разве что переписать её на &lt;a rel=&quot;external&quot; href=&quot;https://webassembly.org/&quot;&gt;WASM&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Мысль о реализации нужной функциональности для веба на технологии WebAssembly показалась интересной, ведь это стильно-модно-молодёжно, да и команда Flutter/Dart недавно рассказывала, что работает над этой технологией.&lt;/p&gt;
&lt;h2 id=&quot;prikruchivaem-wasm&quot;&gt;Прикручиваем WASM&lt;/h2&gt;
&lt;p&gt;Беглый поиск, однако, показал, что для Dart эта технология пока не готова к продакшену: поддерживают её только последние версии Chrome и Firefox, а Safari не поддерживает вовсе (и похоже не собирается).&lt;/p&gt;
&lt;p&gt;С другой стороны, некоторые языки программирования уже умеют работать с данной технологией (например, &lt;a rel=&quot;external&quot; href=&quot;https://www.rust-lang.org/what/wasm&quot;&gt;Rust&lt;/a&gt;). Саша не был знатоком Rust, но этого и не требовалось: используемая функция наверняка уже был реализована для этого языка, нужно только было скомпилировать её в &lt;code&gt;wasm&lt;/code&gt; и научить PWA использовать этот wasm-файл.&lt;/p&gt;
&lt;h3 id=&quot;shag-pervyi-sobiraem-wasm-modul&quot;&gt;Шаг первый: собираем wasm модуль&lt;/h3&gt;
&lt;p&gt;Одна из приятных особенностей работы с экосистемой Rust — это хорошая документация. О том как подготовить Rust проект для компиляции в wasm очень подробно описано &lt;a rel=&quot;external&quot; href=&quot;https://developer.mozilla.org/en-US/docs/WebAssembly/Rust_to_wasm&quot;&gt;здесь&lt;/a&gt;. Важный момент, который там описан, это установка &lt;code&gt;wasm-pack&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Создадим новый Rust проект:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;cargo&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; new&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-lib&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; fib_wasm&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Теперь нужно добавить в файл &lt;code&gt;Cargo.toml&lt;/code&gt; новую зависимость:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;toml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;dependencies&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;wasm-bindgen&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;0.2&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Именно библиотека &lt;code&gt;wasm-bindgen&lt;/code&gt; возьмёт на себя всю работу по подготовке Rust кода в wasm: для этого используется атрибут &lt;code&gt;#[wasm_bindgen]&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;use&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; wasm_bindgen&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;prelude&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;wasm_bindgen&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;pub&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; fn&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; fib&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;n&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; u32&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; -&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; u32&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    if&lt;/span&gt;&lt;span&gt; n&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; &amp;lt;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 2&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        n&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;        fib&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;n&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 2&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; +&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; fib&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;n&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Чтобы собрать проект в wasm, нужно выполнить команду:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;wasm-pack&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; build&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-target&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; web&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;После этого в каталоге с проектом появится папка &lt;code&gt;pkg&lt;/code&gt;, в которой нас интересуют файлы &lt;code&gt;&amp;lt;project_name&amp;gt;.js&lt;/code&gt; и &lt;code&gt;&amp;lt;project_name&amp;gt;_bg.wasm&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Осталось придумать, как научить PWA использовать получившийся wasm файл.&lt;/p&gt;
&lt;h3 id=&quot;shag-vtoroi-prikruchivaem-wasm-k-html&quot;&gt;Шаг второй: прикручиваем wasm к html&lt;/h3&gt;
&lt;p&gt;Для начала давайте положим полученные на предыдущем шаге файлы куда-нибудь поближе к &lt;code&gt;index.html&lt;/code&gt; (можно просто в каталог &lt;code&gt;web&lt;/code&gt;) и добавим в этот файл следующие строки:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;html&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  &amp;lt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; type&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; defer&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    import&lt;/span&gt;&lt;span&gt; init&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span&gt; fib&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; from&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;./fib_wasm.js&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;    init&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;then&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      window&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;fib&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;n&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; fib&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;n&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  &amp;lt;&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;script&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Здесь мы добавили к объекту &lt;code&gt;window&lt;/code&gt; нашу функцию, чтобы иметь возможность достучаться до неё из js кода.&lt;/p&gt;
&lt;p&gt;Теперь необходимо подготовить две реализации вызова искомой функции: для веб-платформы и остальных, а затем, с помощью условного импорта обратиться к нужной реализации.&lt;/p&gt;
&lt;p&gt;В нашем случае это будут файлы &lt;code&gt;fib.dart&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;dart&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;///&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; Сторонняя функция, которая долго вычисляется&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; fib&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;int&lt;/span&gt;&lt;span&gt; n) &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; switch&lt;/span&gt;&lt;span&gt; (n) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;      &amp;lt;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 2&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;span&gt; n&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      _ &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; fib&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;n &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 2&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; fib&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;n &lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;И &lt;code&gt;fib_web.dart&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;dart&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;// ignore: avoid_web_libraries_in_flutter&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;dart:js&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;///&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; Сторонняя функция, которая долго вычисляется&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; fib&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;int&lt;/span&gt;&lt;span&gt; n) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;  return&lt;/span&gt;&lt;span&gt; context&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;callMethod&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;fib&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; [n])&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Здесь мы обращаемся к той самой функции, которую добавили к объекту &lt;code&gt;window&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Осталось внести изменения в наш исходный проект:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;dart&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;fib.dart&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; if&lt;/span&gt;&lt;span&gt; (dart.library.html) &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;fib_web.dart&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Теперь можно и посмотреть, насколько быстрее стал наш код:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://sansmith.dev/posts/flutter-wasm/image-2.png&quot; alt=&quot;Alt text&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Ну что же, неплохо! В данном случае вместо 6 секунд результат был получен за 0,945 секунды. В зависимости от особенностей вычисляемой функции, результаты могут отличаться в большую или меньшую сторону: например, в одной нашей задаче время изменилось с 1,5-8 секунд до 0,05-0,09 секунды (sic!).&lt;/p&gt;
&lt;p&gt;В веб этот код всё ещё выполняется в основном потоке, т.е. по-прежнему может вызывать фризы, но хотя бы на меньшее время.&lt;/p&gt;
&lt;h2 id=&quot;zakliuchenie&quot;&gt;Заключение&lt;/h2&gt;
&lt;p&gt;Flutter — хорошая технология, которая действительно позволяет создавать крутые кроссплатформенные приложения. Но как и у любой технологии, у неё есть ограничения (особенно на не мобильных платформах). Однако технологии не стоят на месте и иногда, если ваше приложение споткнулось, ему могут помочь коллеги по цеху. Такие как Rust и WebAssembly.&lt;/p&gt;
&lt;p&gt;Исходники проекта можно посмотреть на &lt;a rel=&quot;external&quot; href=&quot;https://github.com/san-smith/flutter-with-wasm&quot;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;P.S. Все события вымышлены, любые совпадения с реальными людьми случайны. За время написания статьи ни одно мороженое не пострадало.&lt;/p&gt;
</content>
        
    </entry>
</feed>
