Google Flutter — это отличный кросс-энд фреймворк, который работает не только на платформах Android и iOS, но также поддерживает веб-приложения и настольные приложения. Мини-программы — очень важная технологическая платформа в Китае, и мы думали о том, можем ли мы расширить Flutter до мини-программ? Наша команда открыла исходный код раньшеПроект Алита, Alita может конвертировать код React Native и запускать его на платформе апплета WeChat. Вдохновленные этим, мы считаем, что Flutter, который также является декларативной средой пользовательского интерфейса, также может работать на платформе апплета.
Итак, мы запустилипроект с открытым исходным кодом flutter_mp. Возьмите апплет WeChat в качестве примера, но на данном этапе проект flutter_mp все еще находится на ранней экспериментальной стадии, и многие функции все еще изучаются и планируются.Вы можете в любое время следить за нашим последним прогрессом на Github или участвовать в совместной разведке проекта.
Введение
Хотя есть еще много не доделанных функций, поговорим о принципе реализации всего flutter_mp. Из соображений экономии места ниже мы кратко опишем несколько важных частей flutter_mp.
Давайте посмотрим на фактический эффект flutter_mp:
Пример официального макета версии Flutter
Сконвертируйте и запустите эффект на апплете через flutter_mp
Обработка декларативного пользовательского интерфейса
Flutter — это декларативная среда пользовательского интерфейса. Декларативному пользовательскому интерфейсу нужно только описать структуре, как выглядит пользовательский интерфейс, не заботясь о конкретных деталях реализации среды. В частности, для Flutter описание пользовательского интерфейса верхнего уровня обрабатывается базовым графическим движком Skia. , который является собственным Flutter, Замена обработки на html/css/canvas — это flutter_web, а flutter_mp исследует обработку этих описаний пользовательского интерфейса в апплете класса.
Рассмотрим простейший пример
var x = 'Hello World'
Center(
child: Text(x)
);
Для приведенной выше структуры пользовательского интерфейса нам нужно использовать только следующую структуру в файле wxml апплета, и все будет в порядке.
// wxml部分
<Center>
<Text>{{x}}</Text>
</Center>
// js 部分
Component({
data: {
x: 'Hello World'
}
})
Хотя фактическая структура намного сложнее, чем в приведенном выше случае, из приведенного выше простого примера мы знаем, что необходимо сделать как минимум две вещи:
Нам нужно сгенерировать соответствующий файл шаблона wxml апплета в соответствии с кодом Flutter. Соберите данные, необходимые для рендеринга wxml, и поместите их в поле данных компонента апплета.
генерация структуры wxml
Мы знаем, что небольшие программы не могут динамически управлять узлами, а структура wxml должна быть сгенерирована заранее, поэтому перед тем, как Flutter запустит маленькую программу, будет этап компиляции и упаковки, который будет проходить через код Dart. Сгенерируйте файлы wxml в соответствии с определенными правилами (этап компиляции также сделает еще одну важную вещь, о которой будет сказано ниже — скомпилирует Dart в js).
В частности, мы сначала переработаем исходный код Dart в поддающуюся анализу структуру AST, которая представляет собой древовидное представление исходного кода. Затем мы глубоко просматриваем структуру дерева синтаксиса AST, чтобы сгенерировать целевой wxml Весь процесс выглядит следующим образом:
Сложность построения структуры wxml заключается в следующем: Flutter — это не только декларативный пользовательский интерфейс, но и «пользовательский интерфейс значений», что такое «пользовательский интерфейс значений»? Проще говоря, Flutter рассматривает UI как обычное значение, похожее на строку или число, поскольку это обычное значение, оно может участвовать во всех процессах управления, что может быть возвращаемым значением функции или функции. , и т.д. Хотя wxml апплета также является декларативным пользовательским интерфейсом, это не «пользовательский интерфейс значений».Wxml больше похож на шаблон и более статичен. Как выразить динамический «пользовательский интерфейс значений» с помощью статического wxml, является ключом к построению структуры wxml.
посмотреть пример
Widget getX() {
if (condition1) {
return Text('Hello');
} else if (condition2) {
return Container(
child: ...
);
} else if (condition3) {
return Center(
child: ...
);
}
...
}
Widget x = getX();
Center(
child: x // < --- 如何处理这里的 x??
);
Здесь child: x x - это динамическое значение, и его конкретное значение необходимо определить во время выполнения. Это может быть любой виджет. Как быть с этим динамическим x на статическом wxml? Вдохновленный фреймворком Alita, он в основном основан на динамизме шаблона апплета (атрибут шаблона is может принимать переменные значения). Есть следующие шаги:
- Во-первых, при обходе исходной структуры AST Dart каждый независимый и полный фрагмент «значения пользовательского интерфейса» соответствует шаблону wxml, такому как пользовательский интерфейс в getX выше.
<template name="template001">
<text>Hello</text>
</template>
<template name="template002">
<Container>...</Container>
</template>
<template name="template003">
<Center>...</Center>
</template>
- При обнаружении динамического значения, такого как x, заполнитель шаблона будет сгенерирован фиксированным.
<template name="template004">
<Center>
<template is="{{templateName}}" data="{{...templateData}}"/>
</Center>
<template name="template003">
- На этапе выполнения он будет основан на getX.
Результат работы функции определяет «значение пользовательского интерфейса» сопоставления x. Если условие 1 в getX истинно, то значением templateName здесь является template001. Для конкретных работ по расчету и сбору данных обратитесь к процессу «Сбор данных для рендеринга» ниже. Видно, что то, как flutter_mp обрабатывает «пользовательский интерфейс значений», полностью относится к Alita.
Сбор данных рендеринга
Генерация структуры wxml завершается на этапе компиляции, в отличие от нее, данные рендеринга являются информацией времени выполнения, которую можно изменить в любой момент в соответствии с setState. Итак, как мы собираем необходимые данные рендеринга?
Если мы по-прежнему будем следовать архитектурной схеме Flutter, то будет сложно вставить собранные нами функции-ловушки. Кроме того, эта архитектура Flutter слишком тяжела для небольших программ. Процессы в красной рамке на рисунке ниже не подходят для рендеринг небольших программ. Наконец, потому что окончательный код будет преобразован в js, а многие библиотеки, на которые опирается Flutter, не поддерживают преобразование js, например dart:ui и так далее.
Поэтому мы реализовали минималистскую версию апплета Flutter mini_flutter. Во время компиляции мы заменим все ссылки на библиотеку Flutter на mini_flutter. Mini_flutter существует только на этапе рендеринга на приведенном выше рисунке. Индивидуальный рендеринг постоянно собирает информацию о виджетах на время выполнения. Наконец, создается структура JSON описания пользовательского интерфейса. Эта структура содержит упомянутые выше templateName и templateData. Описание пользовательского интерфейса будет получено апплетом более низкого уровня и использовано для отображения пользовательского интерфейса апплета. Схема архитектуры выглядит следующим образом:
Dart/JS: преобразование и взаимодействие
Язык разработки Flutter — Dart, а рабочая среда апплета — браузер, поэтому нам также нужно скомпилировать Dart в код JavaScript.
Это также упоминается на этапе компиляции и упаковки выше.В этом процессе в основном используется инструмент dart2js, предоставленный Dart.Однако для небольшой программной среды сгенерированный код js все еще необходимо адаптировать.Кроме того, хотя это весь код JS , Среда выполнения js, сгенерированная dart2js, и собственная js апплета изолирована, то есть они не могут совместно использовать переменные, методы и т. д., и каждый из них выполняется в своем собственном «домене».
Это порождает две проблемы:
- Инициализация виджета или обновление setState, как передать сгенерированное описание пользовательского интерфейса JSON в «домен» апплета?
- Все соответствующие обратные вызовы и события рендеринга происходят в «домене» апплета Как передать эту информацию в Dart?
Подводя итог: как Dart (который в конечном итоге скомпилируется в JS) взаимодействует с собственным JS апплета?
Решение этой проблемы в основном с помощью dart:js, package:js этих двух библиотек:
Дротик операция JS
import 'package:js/js.dart';
@JS("JSON.stringify")
external stringify(String str);
Таким образом, когда код Dart вызывает метод stringify, он фактически выполняется.window.JSON.stringify
метод
JS операция Дарт
// dart注册
void main() {
context['dartHi'] = () {
print('dart hi!');
};
}
// js 调用
window.dartHi()
Вот лишь краткое описание взаимодействия между Dart и JS.Кроме того, поскольку операционной средой апплета является среда браузера после кастрации, реализация flutter_mp немного отличается.
Короче говоря, Dart и JS совместимы, что открывает среду Flutter верхнего уровня и среду апплета нижнего уровня.
система компоновки
Система компоновки Flutter отличается от CSS, но похожа на CSS.
На этапе рендеринга, упомянутом выше, эквивалентный стиль CSS будет создан в соответствии с атрибутами макета, категориями и ограничениями виджета. Обратите внимание, что ограничения границ здесь зависят от контекста. Например, фактический размер контейнера без ширины и высоты связан не только с дочерними элементами, но и с граничными ограничениями, переданными родительским элементом.Это на самом деле довольно проблематично.Можем ли мы выразить атрибуты виджета и граничные ограничения Flutter полностью в CSS?Все еще ищете эффективное решение.
Суммировать
Как и в случае с flutter_web, невозможно полностью передать все функции Flutter в апплет, в общем, мы считаем, что он должен быть частью страницы, а некоторые функции должны выполняться в апплете, поэтому имеет смысл использовать flutter_mp.
Как упоминалось ранее, flutter_mp все еще находится на очень ранней стадии.Если вам нужно добиться сквозной разработки небольших программ в производственной среде, рекомендуется использовать наш зрелый проект RN to small program.Alita.