头图

Author: Ma Kunle (Kun Wu)

Since its debut in 2015, Flutter has matured for many years and has been widely used in Internet companies such as Ali, Meituan, and Pinduoduo. 90+% of new businesses on ICBU Ali sellers are developed using Flutter, and the ICBU client development team has many Flutter developers.

The early experimental version of Flutter for Web (FFW) was released in 2019. At that time, many interested students were already investigating it. At that time, it was not suitable for use in the production environment due to many problems just released. In March of this year (2021), Flutter 2.0 was released, and FFW officially entered the stable branch.

The foreign trade information section of Ali sellers is mainly developed with Flutter. In the goal of this fiscal year, the promotion of foreign trade information outside the app is an important part of open source drainage. Information promotion outside the app requires a content-bearing web page, and the requirements for the web page are as follows:

  • Replicate the UI and functions of the relevant pages on the App side (mainly including a custom HTML parsing and rendering engine written by dart) [main workload]
  • Quick launch
  • App-side function synchronization

Due to the lack of support from the front-end classmates, to complete this page, the classmates on the app side can only invest by themselves. After some consideration, we chose FFW for the following reasons:

  • The cost of switching to the front-end technology stack Rax is slightly higher, and at the same time, it takes more time to copy the function of the target page
  • Using most of the code on the FFW target page can reuse the ready-made dart code on the end
  • The Flutter technology stack on the app side covers a wide range of students

After the above reflections, the FFW pit-filling journey was officially started.

Demo

At present, the FFW related pages of Ali sellers have been launched. Since the release of FFW, the problem of large js files has always existed. In theory, it will greatly affect the page loading experience. In the actual test, it was observed that the loading experience on PC and mobile devices is acceptable, and it runs very well. Smooth, the relevant Demo is as follows:

Problem overview

It is relatively simple to create an FFW project. Flutter switches to the stable version, and then runs the command flutter create xxxProject to enter the project and click to run a Demo project to run it. To apply FFW to an actual project, you need to consider engineering issues and how to integrate into Ali's system, such as: how to publish, how to manage and control the development process, how to request interfaces, etc. The summary is as follows:

The above are the problems encountered in the minimal closed-loop practice of FFW open source drainage by Ali sellers. In addition, the problems to be built by FFW are as follows:

Engineering Fundamentals

The following is an explanation of the causes and solutions of engineering fundamental problems in minimal closed-loop practice.

environment and reuse

Referring to the development of Flutter on the App side, in FFW, we must first consider which version of Flutter to choose, and then consider how to reuse the existing Flutter code.

Flutter version selection

The version selection problem occurs because the Flutter versions of FFW and Flutter for App (FFA) cannot be unified. The Flutter version required by FFW is 2.0+, and the current Flutter version in our App is 1.X+. It will take an uncertain time to upgrade to the 2.0+ version. After some consideration, our current FFW and FFA selection versions are as follows:

FFA: hummer/release/v1.22.6.3          -- UC的Hummer分支
FFW: origin/flutter-2.8-candidate.9     -- 官方分支

FFW does not use the stable version because on the recently released iOS 15, the FFW page will be stuck due to a webGL problem. The fix for this problem has been integrated into the candidate version. (The latest stable version is 2.10.0, the problem has been solved)

code reuse

The issues to be considered when FFA code is reused in FFW are divided into two parts:

  • Dart code reuse
  • Platform-related plug-in capability reuse

Dart code reuse

FFW requires the dart version corresponding to Flutter 2.0+ version to be 2.12. This version of dart introduces the sound null safety feature. The Flutter version used on FFA is 1.+ version. The dart corresponding to the version has not yet introduced null safety. At the same time, the old and new versions of the dart library code in Flutter cannot be mixed and compiled, so the existing App-side code library cannot be seamlessly reused at present. It can only be reused by modifying the existing code. The main points of code modification are:

  • For nullable variables, add ? after the type
User? nullableUser;
  • Use ? or ! when manipulating nullable variables
nullableUser?.toString();   // 空安全,如为空不会出现NPE
nullableUser!.toString();   // 强制指定非空,如为空会报错
  • Optional parameter @required annotation replaced with required reserved word
/// 老版本
User({
  @required this.name,
  @required this.age,
});

/// 新版本
User({
  required this.name,
  required this.age,
});

After these three steps of modification, the low version code can basically be compiled and passed in the new version. In addition, there will be some API changes due to version upgrades, which also need to be modified accordingly, such as:

/// 老版本
typedef ValueWidgetBuilder<T> 
  = Widget Function(BuildContext context, T value, Widget child);

/// 新版本
typedef ValueWidgetBuilder<T> 
  = Widget Function(BuildContext context, T value, Widget? child);

This type of problem is the majority in API changes and is relatively simple to fix. In addition, there is another type of change. For example, in the abstract class TextSelectionControls, the number of method parameters such as handleCut has changed:

/// 老版本
void handleCut(TextSelectionDelegate delegate) {...}

/// 新版本
void handleCut(TextSelectionDelegate delegate, 
               ClipboardStatusNotifier? clipboardStatus) {...}

This type of change needs to be modified according to the actual situation, the difficulty is medium, and the newly added parameters are highly unlikely to be used.

Platform related plugins

Platform-related plug-ins will call the native capabilities. To use the plug-ins in FFA on FFW, you need to implement the corresponding capabilities for the plug-ins on the Web platform, which will be explained in the js calling section below. If the library in pub.dev is used, and the library meets the following conditions, the corresponding version can be used directly:

  • The codebase has a web version
  • There are releases that support Null safety (Web support will also support this)
Web version supportedSupport null safety

release system

After the local Demo project is created and run successfully, there are several issues to consider:

  • How to manage the process from development to release
  • How to publish the page to the online public network to be accessible

    • How to package and build
    • how to publish

For the management and control of the development to release process, refer to the front-end selection of DEF platform (Alibaba's internal front-end development and release platform) to manage by creating a WebApp, which will not be described in detail here. For page publishing, the content involved is as follows:

Engineering build

There are two ways to build FFW. Not all of the built products need to be streamlined in the application; in addition, some additional processing of the product is required to release the product on the DEF platform. In the construction, the main consideration is how to build. The optional commands for FFW compilation and construction are as follows:

/// canvaskit方式渲染
flutter build web --web-renderer canvaskit
  
/// html方式渲染
flutter build web --web-renderer html

The difference between the two commands is how the target page is rendered. The official Flutter explanation of the difference between the two methods is as follows:

In summary:

  • Html method: The page uses the basic elements of Html to render, the advantage is that the page resource file is small;
  • CanvasKit method: It uses WebAssembly technology and has better performance, but because WebAssembly-related wasm files need to be loaded, 2.+ MB of resource files need to be loaded, which is more suitable for scenarios with higher page performance requirements.

The comparison of the two methods of resource loading in the empty project is as follows. Based on the consideration of page size and page performance, we choose to use html.

Html wayCanvasKit way

Product streamlining and handling

For a newly created project, the compiled product is located in the ./build/web directory with the following structure:

build
└── [ 384]  web
    ├── [ 224]  assets
    │   ├── [   2]  AssetManifest.json
    │   ├── [  82]  FontManifest.json
    │   ├── [740K]  NOTICES
    │   └── [  96]  fonts
    │       └── [1.2M]  MaterialIcons-Regular.otf
    ├── [ 917]  favicon.png
    ├── [6.5K]  flutter_service_worker.js
    ├── [ 128]  icons
    │   ├── [5.2K]  Icon-192.png
    │   └── [8.1K]  Icon-512.png
    ├── [3.6K]  index.html           【发布保留】
    ├── [1.2M]  main.dart.js         【发布保留】
    ├── [ 570]  manifest.json
    └── [  59]  version.json

The functions and descriptions of each directory and file are as follows:

  • assets: resource files such as pictures, fonts, etc., corresponding to the assets configured in the yaml file. If the pictures are configured on TPS in FFW and IconFont is not used, this directory is not required;
  • favicon.png: the icon of the page, not required when using TPS resources;
  • flutter_service_worker.js: Control page loading, reload, shutdown, etc. during local debugging, not required for publishing;
  • icons: icon resource, it is not necessary to publish to TPS;
  • index.html: page entry file, the main job is to introduce main.dart.js and some other resources, similar to the shell project of App, needed;
  • main.dart.js: The compiled product of dart in the project, required;
  • manifest.json: The page is used as the configuration of the webapp, but it is not required;
  • version.json: build information, optional.

In the actual release, only index.html and main.dart.js are required to be built. For each iteration, only main.dart.js is required when the "shell project" is not changed.

After selecting the desired product, some processing of these two files is required before the DEF platform is released:

  • The reference to main.dart.js in html is replaced with the cdn address of the corresponding iteration (spliced according to the iteration number and publishing environment);
  • Modify the <base> tag in html, refer to https://docs.flutter.dev/development/ui/navigation/url-strategies#hosting-a-flutter-app-at-a-non-root-location ;
  • Remove comments in js and html files (def release gatekeeper check);
  • Replace the ?? operator in js (this operator reports an error in the DingTalk H5 container);
  • Move index.html and main.dart.js to the product folder on the DEF platform.

page publishing

On the DEF platform, after the product file is processed, the js and html files will be published to the corresponding cdn, and the html will be deployed to a specific address:

Advance:

on-line:

For the online environment index.html content is as follows:

<!DOCTYPE html>
<html>
 <head> 
  <!-- 发布到域名的二级目录时使用 --> 
  <base href="/content_page/" /> 
 </head> 
 <body> 
  <!-- 替换为 main.dart.js 相应的 cdn 地址 --> 
  <script type="text/javascript" src="https://g.alicdn.com/algernon/alisupplier_content_web/1.0.10/main.dart.js"></script>  
 </body>
</html>

At this point, we can use the page deployment address to access our target page. If the page is opened at one time, and there is no need to perform multiple page jumps internally, the publishing work is completed at this step. If it involves multiple page jumps, you also need to publish the relevant content to your own domain name. The simpler way is to configure redirection. In addition, you can also directly refer to the product:

code debugging

After the basic link runs through, the development of the requirements can be carried out. The more important environment in the development process is the debugging of the code. FFW can be debugged in Chrome in an App-like way and has a better experience. After setting a breakpoint in the IDE in the Debug environment, you can debug the breakpoint in the IDE, view the breakpoint in Chrome, and even see the dart code in Chrome. Taking VSCode as an example, the debugging process and experience are as follows:

Start Flutter debugging

Breakpoints visible in VSCode and Chrome

Ability support

After entering the actual development, it needs the support of capabilities such as routing and interface requests. The first is page routing and address.

Page Routing and Addressing

When multiple pages appear in FFW applications, or parameters need to be passed through Http links, corresponding routing configuration is required. Similar to FFA, you can configure the corresponding Route in the root MaterialApp, and then use Navigator.push to jump or open the page directly through the page address. The following code can realize name jump and page address jump:

/// MaterialApp 配置
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      onGenerateRoute: RouteConfiguration.onGenerateRoute,
      onGenerateInitialRoutes: (settings) {
        return [RouteConfiguration.onGenerateRoute(RouteSettings(name: settings))];
      },
    );
  }
}
/// 命名路由配置
class RouteConfiguration {
  static Map<String, RouteWidgetBuilder?> builders = {
    /// 页面A
    '/page_a': (context, params) {
      return PageA(title: params?['title']);
    },

    /// 页面B
    '/page_b': (context, params) {
      return PageB(param1: params?['param1'], param2: params?['param2']);
    },
  };

  static Route<dynamic> onGenerateRoute(RouteSettings settings) {
    return NoAnimationMaterialPageRoute(
      settings: settings,
      builder: (context) {
        var uri = Uri.parse(settings.name ?? '');
        var route = builders[uri.path];

        if (route != null) {
          return route(context, uri.queryParameters);
        } else {
          return CommonPageNotFound(routeSettings: settings);
        }
      },
    );
  }
}

After the configuration is complete, you can jump on the page, or jump directly through the browser address:

  • In-app jump: After the configuration is complete, you can jump to the target page through the Navigator inside the app
/// Navigator 跳转页面 B
Navigator.of(context).restorablePushNamed('/page_b?param1=123¶m2=abc');
  • Address jump: Enter the address of the page in the browser address bar to jump to the page
/// 页面 B 访问地址
https://xxx.xx/#/page_b?param1=123¶m2=abc

Note: The above address jump method requires that the UrlStrategy of FFW is the hash tag method (the default UrlStrategy).

Native for the web platform - JS call

By using repositories such as pub.dev, various capabilities can be easily used in FFW. For capabilities that are not available in the warehouse, expansion should be considered. On FFA, you can use native capabilities through plug-ins, and on FFW, you can use js capabilities through extensions. Through the ability to call js, the massive technical accumulation in the front end can be applied to FFW.

The dart in FFW will eventually be compiled into js, and it should be possible to use js naturally in FFW. In order to support the call of js in dart, dart officially released the js library. It is very convenient to call js in dart by using the annotations in the library.

For example, when you need to call the alert method, define as follows:

/// 文件:js_interface.dart

/// 调用js方法的工具类库,需在 dependencies 中引入 js 库
@JS()
library lib.content;

import 'package:js/js.dart';

/// alert 弹窗
@JS('alert')
external void jsAlert(String msg);

Then introduce js_interface.dart where alert is needed and call the jsAlert method:

import 'package:mtest/js_interface.dart';

...
jsAlert('测试消息');
...

For more usage, see the description of the js library in pub.dev: https://pub.dev/packages/js . After getting through the ability of js, many of the next problems are easily solved.

Mtop interface

In view of the construction of the existing Mtop (a gateway used by Ali App) on the App side, if the existing Mtop can be called in FFW, it will reduce a lot of workload. For this, the ability to add Mtop calls to FFW is required, which requires two parts of work:

  • FFW side supports Mtop call
  • The server supports Mtop calls in H5 mode

FFW supports Mtop

The ability of mtop can be introduced in FFW by calling the ability of mtop.js. The overall process is as follows:

1. Introduce mtop.js into index.html

<script src="//g.alicdn.com/mtb/lib-mtop/2.6.1/mtop.js"></script>

2. Define the interface file js_mtop_interface.dart

@JS()
library lib.mtop;

import 'package:js/js.dart';
import 'package:js/js_util.dart';
import 'dart:convert';
import 'dart:js';

/// mtop请求的参数
@anonymous
@JS()
class MtopParams {
  external String get api;
  external String get v;
  external dynamic get data;
  external String get ecode;
  external String get dataType;
  external String get type;
  external factory MtopParams({
    required String api,
    required String v,
    required dynamic data,
    required String ecode,
    required String dataType,
    required String type,
  });
}

/// lib.mtop 请求函数
@JS('lib.mtop.request')
external dynamic _jsMtopRequest(MtopParams params);

/// dart map 转为 js 的 object
Object mapToJSObj(Map<String, dynamic> a) {
  var object = newObject();
  a.forEach((k, v) {
    var key = k;
    var value = v;
    setProperty(object, key, value);
  });
  return object;
}

/// mtop js 请求接口
Future mtopRequest(String api, Map<String, dynamic> params, String version, String method) {
  var jsPromise = _jsMtopRequest(
    MtopParams(
      api: api,
      v: version,
      data: mapToJSObj(params),
      ecode: '0',
      type: method,
      dataType: 'json',
    ),
  );
  return promiseToFuture(jsPromise);
}

/// 返回结果解析使用
@JS('JSON.stringify')
external String stringify(Object obj);

3. Make the mtop interface call

import 'package:mtest/mtop/js_mtop_interface.dart';

...
try {
   var res = await mtopRequest(apiName, params, version, method);
   print('res $res');
} catch (err) {
   data = stringify(err);
}

4. Parsing result: The result returned by the interface request is a jsObject, which can be converted into json through the js method JSON.stringify and used at the dart level

String jsonStr = stringify(res);

Server H5 Mtop Configuration

After accessing mtop.js in FFW, the target mtop interface needs to be processed accordingly before it can be called:

  • mtop releases h5 version
  • Applying to configure a CORS domain name whitelist

RBI

The same as the mtop request, the js library of Golden Arrow can be introduced into FFW for management. The process is as follows:

1. Index.html introduces the js file

<script type="text/javascript" src="https://g.alicdn.com/alilog/mlog/aplus_v2.js"></script>

2. Define the interface file js_goldlog_interface.dart

@JS()
library lib.goldlog;

import 'package:js/js.dart';

/// record 函数
@JS('goldlog.record')
external dynamic _jsGoldlogRecord(String t, String e, String n, String o, String a);

void goldlogRecord(String logkey, String eventType, String queryParams) {
  _jsGoldlogRecord(logkey, eventType, queryParams, 'GET', '');
}

3. Dot call

import 'package:mtest/track/js_goldlog_interface.dart';

...
goldlogRecord(logkey, eventType, queryParams);
...

After that, you can configure the corresponding points on the log platform.

monitor

The monitoring capability access is relatively simple. Here, select arms (application real-time monitoring service) and directly introduce arms in index.html. The process is as follows:

1. Introduce relevant libraries in index.html

<script type="text/javascript">
    var trace = window.TraceSdk({
      pid: 'as-content-web',
      plugins: [
        [window.TraceApiPlugin, { sampling: 1 }],
        [window.TracePerfPlugin],
        [window.TraceResourceErrorPlugin]
      ],
    });
    // 启动 trace 并监听全局 JS 异常,debug时问题暂时注释
    trace.install();
    trace.logPv();
</script>

2. Relevant configuration on the arms platform

Note: trace.install() will cause the page not to be displayed in the Debug environment, which can be disabled in the Debug environment.

Optimized and Compatible

After completing the above basic capacity building, FFW can basically meet the development of simple needs. In addition to requirements development, issues such as experience and compatibility need to be considered.

Load optimization

One of the problems that FFW has had since its release is the package size. For an empty helloworld project, the size of a single js package is 1.2 MB (before uncompressed), which may take a few seconds to load when the network is not good on mobile devices. To improve the page loading experience, consider doing the following:

Wait for process optimization

The FFW page is blank before the js is loaded, giving people a feeling that the page is stuck. For this reason, a loading animation can be added before the js is loaded so that the page will remain blank. Referring to the effective method on the App, you can insert the skeleton screen before the data is loaded. The implementation is as follows:

 <iframe src="https://g.alicdn.com/algernon/alisupplier_content_web/0.9.1/skeleton/index.html" id="iFrame" frameborder="0" scrolling="no"></iframe>
  <script>
    function setIframeSize() {
      <!-- 骨骼屏尺寸设置,占满全屏 -->
    }
    function removeIFrame() {
      var iframe = document.getElementById("iFrame");
      iframe.parentNode.removeChild(iframe);
    }
    setIframeSize();
</script>

  <!-- load 完成之后移除骨骼屏 -->
  <script type="text/javascript" src="https://g.alicdn.com/algernon/alisupplier_content_web/1.0.10/main.dart.js" 
    onload="removeIFrame()"></script>

TODO JS unpacking & optimization

The optimization of the waiting process can improve the waiting experience to a certain extent. The symptoms are not the root cause. If you want to load quickly, you need to make the loading resources small. For multi-page applications, you can split the entire main.dart.js into multiple small packages. , which is gradually loaded in the process of use. At present, it is known that Meituan has corresponding technologies, but the implementation details are unknown and need to be studied. Please refer to https://github.com/flutter/flutter/issues/46589

Compatibility problems

Similar apps will have experience problems on different devices, and FFW pages will have compatibility problems in different H5 containers. In our practice, different H5 containers are recorded as follows:

White screen problem in DingTalk H5 container:

  • The ?? syntax is not supported, it will be solved after replacement
  • The FFW product js contains a large number of try{}finally{} no catch operations, and an error will be reported in the DingTalk H5 container. When packaging, use the script to replace it uniformly.

White screen problem in WeChat H5 container:

  • Remove MaterialIcons and use images instead

The page stuck problem on iOS 15:

iOS compatibility issues:

  • Clickable RichText, after setting the underline attribute, the link following the picture will be blocked. No solution has been found yet, so you can only use the underline that comes with RichText first.
  • The clickable RichText automatically scrolls when clicked. The verification is caused by the InteractiveSelectionu property. After setting it to false, the performance is the same as that of Android.

other problems

In addition to the compatibility problems of H5 containers, some problems with FFW itself are encountered in practice, which are recorded as follows:

provider library problem:

  • The toString() method of the ProviderNotFoundException class in the provider library /lib/src/provider.dart contains a huge error description String. The compiled js syntax of the String will be wrong, and it can be deleted after deletion.

JsonConverter problem:

  • JsonConverter().convert will report an error when running, use it with caution, you can convert dart array to js array manually

Content of TODO

In current practice, only a small closed-loop construction available for business has been completed. There are still many TODO contents in FFW, as follows:

Project build:

  • DEF cloud construction: When trying to install the Flutter environment on the DEF cloud construction platform, the request for content outside Ali will be 403, and there are many content in Flutter that need to be pulled online, such as the content in the packages in the root directory of Flutter, which is currently built locally. to be solved;
  • mtop access during local debug: mtop request needs to configure CORS whitelist and the port needs to be 80. The local debug uses ip and the port is a random number. When it is forcibly set, it is not authorized to operate. Currently, only the http server can be run locally after the host is set. Debug in chrome, breakpoint debug to be resolved.

basic functions:

  • Video and audio playback capabilities to be studied

Compatible and optimized

  • js package split loading to be studied
  • Custom font file optimization to be studied

Imagine:

  • Dynamic Flutter in App: Replace the Flutter page in the App with FFW to make a dynamic solution similar to weex
  • App WebAppization: Apps implemented by Flutter can be converted into WebApps at low cost through FFW, solving problems such as apps without a Mac version

pay attention to [Alibaba Mobile Technology] WeChat public account, 3 mobile technology practice & dry goods every week for you to think about!


阿里巴巴终端技术
336 声望1.3k 粉丝

阿里巴巴移动&终端技术官方账号。