Practice of Flutter Web in Meituan Takeaway

 

In the multi-form business scenario, how to ensure the consistency of multi-end experience is a more concerned direction in the field of front-end technology. The Meituan Food Delivery front-end technical team explored cross-terminal (App\PC\H5) solutions based on Flutter Web, and truly realized "Write Once & Run AnyWhere". This article is a summary of the team's practical experience, I hope it can be helpful or inspiring for everyone.

1. Background

1.1 Business background

Merchant-end business form of Meituan takeaway

The business of Meituan takeaway merchants is centered on millions of merchants. It provides a series of functions such as transaction fulfillment, operation, advertising, marketing, etc. on PC and App, and there are often H5 scenarios (such as takeaway college, merchant community, marketing, etc.). Activities, etc.). In this multi-form business scenario, how to ensure the consistency of multi-terminal experience and how to improve the efficiency of multi-terminal iteration has always been the focus of business-side production and research.

1.1.1 Ensure the consistency of multi-end experience

Due to the different terminal capabilities, there is a big difference in the performance of the business between the App and the Web. For example, the App comes with an animation transition, but the implementation cost in the Web is higher, and this part is often downgraded and discarded. Features. In addition, even if we can use the company's internal Roo, MTDUI and other multi-end UI component libraries to smooth out the UI differences on each side as much as possible, it is difficult to achieve a perfect consistent experience because the component libraries are implemented differently on each side.

1.1.2 Improve the efficiency of multi-end iteration

Due to the different technical systems of each terminal, the requirements involving multiple terminals often require different development and testing teams to complete the development, joint debugging, testing, and online processes, occupying huge resources. When the teams cannot support in parallel, it may even lead to The entire business delivery cycle is lengthened. Although cross-platform solutions such as React Native and Flutter solve part of the reuse problem, it is obviously not enough in the merchant business scenario. Our goal is to achieve full platform (Android, iOS, PC, H5) reuse , Maximize the efficiency of multi-end iteration.

1.2 Technical background

1.2.1 Flutter's reserves at Meituan takeaway merchants

MTFlutter is a company-level Flutter R&D ecosystem built by Meituan Waimai. Its architecture is shown in the following figure:

MTFlutter architecture diagram

As shown in the figure, MTFlutter has covered a complete set of closed-loop research and development, debugging, testing, release, online operation and maintenance, and project management. At the same time, it has implemented dynamic solutions to support the company's multiple business development. Under the trend of big front-end integration, Meituan takeaway merchants continue to explore better multi-end reuse solutions. Through the construction of MTFlutter ecology, the current Flutter technology stack has covered more than 90% of the business in the merchant app, and it also has Flutter development. The ability of students is more than 90%. Therefore, under the premise of sufficient technical "reserve", we can explore the reuse of all platforms (Android, iOS, PC, H5) based on Flutter.

1.2.2 Flutter Web support

In 2018, Google released the Flutter Web Beta version for the first time, aiming to further realize the vision of a code and multi-terminal operation. At present, Flutter Web has been officially integrated into the Master. During the effort of countless engineers, Flutter Web has been able to provide a more unified interactive behavior and visual experience with Flutter Natvie.

Flutter Native VS Flutter Web

As can be seen from the above figure, the overall architecture of Flutter Web and Flutter Native are similar. The two share the Framework layer (green part), and provide a collection of Material/Cupertino theme Widgets that include animation, gestures, basic Widget classes, and most applications. The difference is: Flutter Web rewrites the dart:ui layer (yellow part), uses DOM and Canvas to align the UI rendering capabilities of Flutter Native, so that the UI written by Flutter can be displayed normally on modern browsers.

In addition, thanks to dart2js, a well-established tool, Dart logic can be easily converted to JavaScript and then run normally on the Web.

2. Challenges

In summary, we explore cross-terminal (App\PC\H5) solutions based on Flutter Web, and truly realize "Write Once & Run AnyWhere". Of course, the challenges are also huge, mainly reflected in the fact that Flutter and MTFlutter do not have sufficient Web support at this stage.

2.1 Status of Flutter Web

Google's official current work on Flutter Web is mainly focused on the alignment of dart:ui (Web), and there are relatively few issues related to engineering and performance, such as:

  • The Flutter Web build product is relatively rudimentary. It simply outputs static resources such as main.dart.js (1.1M, not Gzip) and pictures. It lacks optimization work such as JS unpacking, file Hash, and resource uploading CDN, which greatly affects the page performance. Loading performance.

  • Because Flutter Web itself implements a set of page scrolling mechanism, position information is frequently calculated during page scrolling, causing the content of the scrolling area to be recreated, and ultimately resulting in poor page scrolling performance.

2.2 Status of MTFlutter

Although MTFlutter has done a lot of customization and optimization at the Flutter Native level, the construction on Flutter Web has just started, and the specific manifestations are as follows:

  • MTFlutter's existing basic dependencies such as: Request (request encapsulation), Router (routing), buried point, container bridge, front-end monitoring, have not yet supported the implementation in the Web.

  • MTFlutter has implemented a complete Flutter Module packaging release process, but it does not support the construction and deployment of the Web.

Three, the overall design

MTFlutter architecture diagram

The above picture is the MTFlutter + Web architecture diagram. It can be seen from the picture that the Flutter Web page needs to meet the production requirements, and there is still a lot of work (shown in the yellow part of the above picture), mainly including:

  • Expand the support of basic dependencies (such as Request, Router, embedded point, etc.) on the Web side.

  • Improve engineering construction, such as: static resource optimization, construction and deployment automation.

  • In-depth scrolling performance and page loading performance optimizations enable Flutter Web to meet basic production requirements.

Four, detailed design

4.1 Infrastructure construction

The basic development dependencies of enterprise-level applications (such as: request library, routing library, embedded point library, etc.) need to be rebuilt in Flutter with Dart. Time cost, compatibility, risk, etc. are uncontrollable. MTFlutter is a Plugin developed based on the original Native foundation, so it does not support the web side. This chapter will expand and introduce how to extend MTFlutter's basic reliance on the Web side in a smooth and insensitive way.

4.1.1 Flutter Package sub-platform programming

In Flutter, you can create modular code that is easy to share by using Package. The official strongly recommends the use of Package form to manage various tools and methods. In the official definition, Package includes the following two categories:

  1. Dart Package : A general package written in Dart, some of which may contain specific functions that depend on the Flutter framework, and the scope of its use is limited to Flutter, such as path .

  2. Plugin Package : Use Dart to write API special Dart Package implemented by multiple platforms. Plugin Package can write plug-in packages for Android (using Kotlin or Java), iOS (using Swift or Objective-C), Web, macOS, Windows or Linux or any combination thereof.

The following respectively introduces how to program by platform in these two types of packages.

(1) Dart Package

Dart Package is written in pure Dart, so most of the code can be directly compiled by dart2js to code that can be run on the Web platform, but some libraries involving Native capabilities (such as  dart:io ) cannot be translated, so it needs to be performed on the platform. Compatible methods, the following introduces two solutions for sub-platform programming in Dart Package.

Code level sub-platform

For code-level sub-platforms, we can use a constant kIsWeb provided by the Flutter SDK. The method of use is as follows:

Looking at the source code, we can see that the reason kIsWeb can be used to determine the Web platform is that JavaScript does not support integers. In the Web environment, Dart's double and int are supported by objects of the same type, and the floating-point number "0.0" is equal to an integer. "0", this is not the case for Dart code running on AOT or VM.

import 'package:flutter/foundation.dart';
if (kIsWeb) {
  print('Web 端')
} else {
  print('其他端');
}

File level sub-platform

For file-level sub-platforms, we use conditional import and export, and the specific usage of conditional export is as follows:

// tool.dart
export 'src/tool_native.dart' // 兜底导出,即没有命中条件时导出的文件
  if (dart.library.html) 'src/tool_web.dart'; // web 端导出的文件,该文件中可以使用 dart:html,也可以通过判断 dart.library.js 导出 Web 端文件。
// 引入 tool.dart
import 'package:tool/tool.dart';
void main() {
  print('import tool');
}

Conditional import is similar to conditional export, just change export to import. This is also a very practical method of sub-platform programming in business development.

(2) Plugin Package

Plugin Package (hereinafter referred to as Plugin) on Android and iOS platforms is implemented through MethodChannel to deliver messages at the UI layer and Platform layer to achieve specific platform support. The official documentation also fully introduces the specific implementation methods on Android and iOS platforms. And examples, the implementation of the Web platform has been introduced less. To sum up, the difference between the implementation of the Web platform and the Native platform is mainly concentrated in the following two points.

First of all, the recommended method of Web Plugin is not to implement it in its platform-specific JS language, but to implement it through Dart Library or Package. For the existing JS SDK that is already available or the need to use a lot of JS to implement functions, the official package is provided: The js package calls Javascript to realize the interaction with Javascript.

Secondly, the Web Plugin does not pass messages by registering MethodChannel. Flutter can directly call  the Flutter Web Plugin class written in the officially designated form ( Federated Plugin ).

The following figure fully shows the overall architecture of a Plugin:

Flutter Plugin architecture diagram

4.1.2 Infrastructure construction

On the whole, the basic dependencies of MTFlutter are developed and maintained in the form of Plugin. In order to handle the common logic in dependencies and improve the scalability of Plugin, MTFlutter Plugin adds a common logic processing layer on top of the Flutter Plugin architecture (the native implementation layer of each platform and the Plugin Interface layer), and finally exposed to the user is the Plugin API layer Provided interface. The architecture diagram of MTFlutter Plugin is as follows:

MTFlutter Plugin architecture diagram

In terms of implementation details, due to the differences between the types of dependencies in the project, the handling of dependencies is also slightly different. The following describes the corresponding solutions for dependencies with different characteristics.

(1) Each platform realizes scenes that can be aligned on the Web side, such as a buried point library

The embedded library uses the SDK provided by the company on both the Native side and the Web side. It has a natural consistency in API design. Therefore, we are fully capable of aligning all interfaces at the Plugin Interface layer. The upper-level business logic only needs to be on-demand. Just do some compatibility processing. The overall design idea of ​​the web-side extension of the buried point library is as follows:

  1. Directly import the Script script in the web/index.html file of the business project and initialize it (note: the location of the imported Script must be placed in front of main.dart.js).

  2. With the help of package:js library to call the embedded point JS SDK, align the API of Flutter embedded point library, and realize the web-side support of Flutter Plugin. The detailed architecture diagram is shown in the following figure:

Buried point library architecture diagram

(2) Each platform implements scenarios that cannot be aligned on the Web side, such as routing libraries

The MTFlutter routing library is a brand new routing system maintained at the bottom of Native. It relies on native support to provide powerful customized functions. However, on the web side, these cannot achieve 100% support at the native implementation layer of each platform. Since the MTFlutter Plugin ultimately exposes the Plugin API, we chose to directly align the Plugin API to implement the support of the routing library on the Web side (with the help of Flutter Navigator, dart:html to complete the extension in pure Dart language), the detailed architecture is shown in the following figure:

Routing library architecture diagram

(3) The web side needs to use a large number of JS to realize the function of the dependent library, such as the request library

Since a large amount of business processing logic (such as interceptors, exception reporting, etc.) is uniformly encapsulated in existing Web requests, the cost is still high if Dart is used to implement it again. Want to reuse the original request library based on Axios (JS request library) encapsulation is equivalent to let Plugin's Web platform use JS language. The interaction between Dart and JS is through package:js for interface calls, so we use Dart to align the corresponding APIs in the public logic processing layer. The detailed architecture diagram is shown in the following figure:

Request library architecture diagram

4.2 Performance optimization

In conventional Web projects, in order to ensure better page loading and rendering performance, we need to do a lot of work in the processing of static resource files, such as resource file hashing, CDNization, on-demand loading processing, etc. It can be pre-processed through build tools such as Webpack and Rollup.

However, in Flutter Web, these preprocessing operations are currently not officially supported. The reason is that there is only one command exposed to us by Flutter flutter build web, which makes us unable to directly perform more fine-grained personalization. If we want Flutter Web to meet the standards of enterprise applications, we need to explore the operating principles of the Flutter SDK in a deeper level. Below we list the current performance problems and their solutions.

4.2.1 Current performance issues

Google officially has done few things to optimize the performance of Flutter Web. The compiled and output pages have major performance problems, which are mainly reflected in the following two aspects:

  1. The first screen rendering time is long . Even after using FutureBuilder to split the business code into xxx.part.js, the volume of main.dart.js remains at 1.1M. The loading and parsing time of a single file is too long, and static resources lack CDN support, which will inevitably affect the rendering time of the first screen.

  2. The scrolling performance is poor. Flutter Web itself implements a set of page scrolling mechanism. During the page scrolling process, Canvas is frequently created, which eventually leads to scrolling performance problems and even page crashes.

Through the display of the browser network monitoring situation in the following figure, the above problems can be clearly reflected:

Browser network monitoring

Memory usage during page scrolling

In order to solve the above performance problems, we explored the Flutter SDK compilation process and summarized the overall process from Flutter business code to web products. The detailed process is shown in the following figure:

Compilation process

From the process, we can see that Flutter currently only supports Dart-->JS conversion and UI layer alignment on the Web side. There is not much work done in engineering and performance optimization.

Therefore, we must solve the above performance problems to ensure that our business can be delivered normally. Through careful analysis and sorting of the compilation process, we customize the Flutter SDK before the AOT product is generated, and perform load performance optimization and memory performance optimization respectively. The contents of these two parts are introduced below.

Flutter SDK customized process

4.2.2 Load performance optimization

Run flutter build webafter the command, the main static resources we have are: master file main.dart.js (1.1M), business code xxx.part.js each page (after use FutureBuilder), image files. Applying these resources directly to the project will encounter the following problems:

  1. The function cannot be updated in time : the browser's caching of the file with the same name may cause the program code to not be updated in time or execution disorder.

  2. The first screen rendering performance is poor : the main.dart.js file is too large, and the single file loading and parsing time is too long, which will inevitably affect the rendering time of the first screen.

  3. Cannot use CDN : Flutter only supports the relative path loading method, and cannot use CDN domain names other than the current domain name, resulting in the inability to enjoy the advantages of CDN.

For this reason, in the loading part, we have added the following three optimizations to the Flutter SDK to meet the standards of online operation. The optimization steps are shown in the following figure:

Optimization steps

Resource file hashing

Except for the web/index.html file, we have to hash all referenced files. The modification of build_system/web.dart is carried out according to the following steps:

  1. Traverse the product catalog and build a ResourceMap.

  2. Calculate the hash value of each file separately.

  3. Name the new file name-[hash].xxx.

  4. Modify the reference relationship of the new file name in the corresponding file.

Large file fragmentation

After Flutter Web is compiled, it will generate the main file main.dart.js with a volume of 1.1M (about 400K after Gzip), which has a great impact on the loading performance of the page. To this end, we split the code and use the browser’s feature of parallel loading of multiple files to effectively improve the page’s loading performance.

DETAILED DESCRIPTION steps of: main.dart.jssplit into multiple plain text file Dart side, the front end by way of XHR parallel load and sequentially assembled into Javascript code in the <script> tag in order to achieve parallel load segment file.

After hashing and sharding, the reference relationship of static resources

CDNization of resource files

Due to the different Flutter Web resource reference mechanism, even if the relative path of the file is replaced with the absolute path with the CDN domain name in the process of hashing the resource file, the CDN resource cannot be loaded. At the same time, the local test found that the loading logic of pictures and Javascript resources is not the same. Therefore, the respective loading logic should be optimized separately.

  • Picture : After a lot of reading and sort out the source code, we find that the picture requested URL will first read the metalabel assetBasevalues stitching URL path to access resources in accordance spliced URL. Currently, the project web/index.htmltemplate file and no metalabels, so there will be a request based on the relative path. The solution is in the compilation process, according to a request to increase the environment metalabel, and the contentset of CDN path.

  • JavaScript processing : In order to solve the problem of load picture resource file, although we increased assetBasethe metalabel, but found that xxx.part.jsthe file is still loaded with the current domain name, we can see the logic load and pictures of resource loading Javascript resources vary. For main.dart.jssource code analysis, we found that the request xxx.part.jsof the domain name contains depends on main.dart.jsthe content of Scriptthe label srcattribute. By js_helper.dartdynamically compiled, we read srcattribute revised to read window.assetBasethis global variable ( metatag assetBasevariable value after processing) to achieve xxx.part.jsfile CDN load.

4.2.3 Scrolling performance optimization

When a scrollable area appears on the page, a large number of Canvas will be created every time the page is scrolled. Using Safari's Canvas analysis tool, we found that the root cause of the problem is that Flutter will frequently create Canvas in the scrolling area during page scrolling. The Canvas memory created each time ranges from 10 to 70M. The more scrolled content, The memory usage will be larger, so that after scrolling a few frames, the memory usage will exceed the browser's threshold.

Canvas occupancy is shown in the Safari graphics tool

Flutter has a ReusablePool concept for Canvas management. In the initial process, a certain number of Canvas will be created. For the parts that have not changed during page interaction, the Canvas that has been cached in the pool will be used first in order to save memory. Because Flutter Web itself implements a set of page scrolling mechanism, position information is frequently calculated during page scrolling, causing the content of the scrolling area to be recreated. This is why Canvas is created every time you scroll.

The solution we designed is to modify the FlutterSDK and define a threshold during the scrolling process. When the scrolling height is within the threshold range, we will cache the current Canvas. Such selective creation and destruction of Canvas can effectively alleviate memory pressure, thereby improving page scrolling performance.

After optimization, the process of browser creation and destruction of Canvas

4.3 Build and deploy

4.3.1 Docker image customization

Because the installation steps of the MTFlutter Web environment are relatively fixed, and the entire installation process takes a long time (> 80s). Therefore, by customizing it as a Docker image and integrating it into Talos, the Flutter Web compilation phase can eliminate the installation process and effectively improve the construction efficiency. For the detailed process of Docker image customization and release, please refer to the official document , so I won't go into details in this article. The Dockerfile used to customize the Flutter Web image is as follows:

FROM $BaseImage \# 继承基础镜像
RUN apt-get update
RUN apt-get install rubygems -y
RUN gem install flutter-cli
RUN flutter-cli install
ENV PATH="/$User/.flutter_sdk/bin:${PATH}"
ENV PUB\_HOSTED\_URL="https://xxx.com" \# 私有pub服务
ENV FLUTTER\_STORAGE\_BASE_URL="https://storage.flutter-io.cn"
RUN ~/.flutter_sdk/bin/flutter config --enable-web

4.3.2 Continuous Delivery and Deployment

In order to achieve continuous delivery and deployment, we have established the release pipeline of Flutter Web in Talos (Meituan's internal front-end continuous delivery solution):

Talos release pipeline

It can be seen that the installation process of the MTFlutter Web environment has been eliminated in the pipeline. The important nodes in the existing pipeline are introduced as follows:

  • Flutter-Web-Build uses Docker's built-in MTFlutter to compile web.

  • Flutter-Web-Publish is responsible for uploading the compiled product to the Meituan resource storage server.

5. Achievements display

5.1 Effect display

We took the lead in landing Flutter Web at Meituan Takeaway Merchant Academy (a platform that helps merchants learn takeaway operations knowledge, industry development and platform strategies in the form of articles, videos, etc., it has strong communication attributes and has external delivery scenarios). , Now take the business college video content page as an example to compare the display effects of Flutter Native and Flutter Web:

Flutter Native

Flutter Web

It can be seen that the interaction and visual experience of the two are highly consistent, which not only ensures that the business is close to the Native experience in the App, but also greatly improves the experience consistency between the Web and Flutter Native.

5.2 Page load performance

As mentioned above, we implemented a series of resource optimization methods for Flutter Web, which greatly improved the page load performance. Among them, the page load time was roughly  reduced from 1300ms (TP50) to 580ms (TP50) , more performance indicators The data is shown in the figure below:

A 7-day performance trend chart

It can be seen that the performance index data gap between Flutter Web and existing Web projects is small, which can meet daily business requirements. However, there is still a lot of room for optimization of loading performance data, and we will continue to explore it.

5.3 Scrolling performance

For scrolling optimization, we modify the Flutter SDK so that Canvas does not need to be created repeatedly when the page is scrolled, but is cached. This greatly saves memory overhead (after optimization, the page memory occupancy is stable at about 100M, which is no different from regular Web pages), and at the same time, scrolling performance is improved to a certain extent. Take the business college article content page as an example to compare and optimize the rolling FPS before and after optimization:

FPS before optimization

FPS after optimization

It can be seen that the scrolling performance of Flutter Web pages has been greatly improved, which is sufficient to cope with most business scenarios. However, because the location information is frequently calculated during the scrolling process of the Flutter Web page, certain problems will still be exposed in complex business scenarios (such as a large number of animations on the page). Therefore, further optimization of scrolling performance will also be the focus of our future work.

5.4 Business iteration efficiency

Based on the team's construction of Flutter Web engineering capabilities and Flutter's good cross-platform features, the implementation of Flutter Web's demand for the revision of Meituan Takeaway Merchant College has greatly improved the iterative efficiency, and the estimated human efficiency has increased by more than 40%. The calculation formula is:

Among them, E represents the improvement of human efficiency, Ci refers to the time spent on compatibility and adaptation, and Np represents the number of cross-end services. At present, the Meituan Takeaway Merchant Academy has completed multiplexing on both Native and H5, and subsequent requirements on the PC side During the alignment, the efficiency improvement value will be enlarged, and it is expected that the efficiency improvement will reach more than 60%. At the same time, we will promote and apply in more businesses to improve the iterative efficiency of the overall business.

6. Summary and Outlook

To sum up, the diverse business forms and sufficient technical "reserves" of Meituan takeaway merchants make it possible to realize multi-terminal reuse based on Flutter. Flutter Web has also achieved phased results in the business of Meituan Food Delivery Merchant Academy, achieving the consistency of experience on the App and H5 sides, and laying a solid foundation for the subsequent promotion of more business lines to achieve App-Web integration.

It is foreseeable that multi-terminal reuse based on Flutter Web is bound to effectively shorten the project delivery cycle. But because our page loading performance and scrolling performance are still not perfect enough to deal with more complex business scenarios, we still have a lot of work:

  • Page scrolling performance optimization: Due to the layout difference between Flutter and the Web, dart:ui (Web) is also subject to the layout constraints of Flutter Native. How to break such constraints is the key to solving the scrolling performance problem.

  • Page loading performance optimization: The current page loading performance still has a lot of room for optimization. Flutter needs to be compiled intervention and optimization (such as separating main.dart.js on demand) to reduce the size of the resource package and effectively improve the page loading performance.

  • Flutter Web infrastructure : perfect and optimize development, debugging, compilation, construction, and deployment links, so that new and old projects can quickly access Flutter Web.

  • Flutter Web reuse on the PC side : Work with the UED team to formulate PC and App adaptation specifications. At the same time, based on the powerful capabilities of Dart2js and dart:ui (Web), it realizes the abstraction of logic, completes the adaptation of components and modules, and achieves improved Maximum efficiency

  • Follow up with the official trend of Flutter: The release of Flutter 2.0 stabilizes the support for the Web. At the same time, the Canvaskit compilation mode is adopted by default, which greatly improves the page scrolling performance. However, because the canvaskit.wasm file is too large (2.5M), which reduces the loading performance, it is still not recommended to use Canvaskit directly on the Web side. However, the official promise to optimize the overall performance in 2021 is still worth looking forward to, and we will also maintain follow-up and communication.

We will continue to do more explorations and attempts based on Flutter Web. If you are also interested in Flutter Web, you are welcome to leave a message or give suggestions in the comment area at the end of the article, thank you very much.

read more

---

Frontend  | Algorithm  | Backend  |  Data   

Security  |  Android  |  iOS   |  Operation and Maintenance  |  Testing

----------  END  ----------

Job Offers

Meituan Waimai has long-term recruitment of senior/senior engineers and technical experts on Android, iOS, and FE. Interested students are welcome to submit their resumes to: [email protected] (please indicate the subject of the email: Meituan Takeaway Technical Team).

Maybe you still want to watch

  |  Exploration and Practice on Flutter Packet Size Management

  |  Meituan takeaway Flutter dynamic practice

  |  The principle of Flutter and the practice of Meituan

Guess you like

Origin blog.csdn.net/MeituanTech/article/details/114994309