Flutter如何将代码显示到界面上呢?

描述

前言

如何优雅的将项目中的代码,亦或是你的 demo 代码展示到界面上?本文对使用简单、便于维护且通用的解决方案,进行相关的对比和探究

为了节省大家的时间,把最终解决方案的相关接入和用法写在前面

预览代码

快速开始

接入:pub,github

 

dependencies:
  code_preview: ^0.1.5

 

用法:CodePreview,提供需要预览的 className,可自动匹配该类对应的代码文件

本来想把写法简化成传入对象,但是因为一些原因无奈放弃,改成了 className

具体可以参考下面 Flutter Web中的问题模块的说明

 

import 'package:code_preview/code_preview.dart';
import 'package:flutter/material.dart';

class Test extends StatelessWidget {
  const Test({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return const CodePreview(className: 'Test');
  }
}

 

使用效果:flutter_smart_dialog

flutter

配置代码文件

因为原理是遍历资源文件,所以必须将需要展示的代码文件或者其文件夹路径,定义在 assets 下,这步操作,为大家提供了一个自动化的插件解决

强烈建议需要展示到界面的代码,都放在统一的文件夹里管理

展示界面的代码需要在 pugspec.yaml 中的 assets 定义

flutter

如果代码预览的文件夹,分级复杂,每次都需要定义路径实在麻烦

提供一个插件:Flutter Code Helper

安装:Plugins 中搜索 Flutter Code Helper

flutter

pugspec.yaml 中定义下需要自动生成文件夹的路径,文件夹随便套娃,会自动帮你递归在 assets 下生成

不需要自动生成,可:不写该配置,或者配置空数组(auto_folder: [])

 

code_helper:
  auto_folder: [ "assets/", "lib/widgets/" ]

 

说明下:上面的插件是基于 RayC 的 FlutterAssetsGenerator 插件项目改的

看了下 RayC 的插件代码和相关功能,和我预想的上面功能实现有一定出入,改动起来变动较大

想试下插件项目的各种新配置,直接拉到最新

后期如果想到需要什么功能,方便随时添加

所以没向其插件里面提 pr,就单独新开了个插件项目

高级使用

主题

提供俩种代码样式主题

日间模式

 

CodePreview.config = CodePreviewConfig(codeTheme: CodeTheme.light);

 

flutter

夜间模式

 

CodePreview.config = CodePreviewConfig(codeTheme: CodeTheme.dark);

 

flutter

注释解析

你可以使用如下的格式,在类上添加注释

key 的前面必须加 @,举例(@title,@xxx)

key 与 value 的之间,必须使用分号分割,举例(@xxx: xxx)

value 如果需要换行,换行的文案前必须加中划线

 

/// @title:
///  - test title one
///  - test title two
/// @content: test content
/// @description: test description
class OneWidget extends StatelessWidget {
  const OneWidget({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return const Placeholder();
  }
}

 

然后可以从 customBuilder 的回调获取 param 参数,param 中拥有 parseParam 参数

获取取得上面注释的数据:param.parseParam ['title'] 或者 param.parseParam ['***']

获取的 value 的类型是 List,可兼容多行 value 的类型

customBuilder 的用法

codeWidget 内置的代码预览布局,如果你想定义自己预览代码的布局,那就可以不使用 codeWidget

一般来说,可以根据注释获取的数据,结合 codeWidget 嵌套来自定义符合要求的布局

param 中含有多个有用内容,可自行查看

 

import 'package:code_preview/code_preview.dart';
import 'package:flutter/material.dart';

class Test extends StatelessWidget {
  const Test({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return CodePreview(
      className: 'OneWidget',
      customBuilder: (Widget codeWidget, CustomParam? param) {
        debugPrint(param?.parseParam['title'].toString());
        debugPrint(param?.parseParam['content'].toString());
        debugPrint(param?.parseParam['description'].toString());
        return codeWidget;
      },
    );
  }
}

 

目前内部预览的布局,会自动去掉类上的注释,如果想保留注释,可自行匹配下

 

 CodePreview.config = CodePreviewConfig(removeParseComment: false);

 

几种代码预览方案

FlutterUnit 方案

FlutterUnit 项目也是自带代码预览方案,这套方案是比较特殊方案

大概看了下,整个 FlutterUnit 的数据都是基于 flutter.db,该文件里面就有相关 demo 的文本信息

所有的 demo 也是单独存在一个叫 widgets 的项目中

所以大概可以猜测出

应该会有个 db 的辅助工具,会去扫描 widgets 的项目中的 demo 代码

将他们的文本信息都扫描出来,然后解析上面的注释等相关信息,分类存储到数据库中,最后生成 db 文件

flutter

映射表,宿主可以通过 db 中的组件类名,从这里拿到 demo 效果实例

flutter

总结

整套流程看下来,实现起来的工作量还是有点大的

db 辅助工具的编写

文本注释相关解析规则

如何便捷的维护 db 文件(辅助工具是否支持,生成后自动覆盖宿主 db 文件)

不同平台 db 文件的读取和相关适配

优点

因为扫描工具不依赖 Flutter 相关库,预览方案可以快速的移植到其它编程语言(compose,SwiftUI 等)

具备高度自定义,因为是完全独立的第三方扫描工具,可以随性所欲的定制化

缺点

最明显的缺点,应该就是稍微改下 demo 代码,就需要三方工具重新生成 db 文件(如果三方工具实现的是 cli 工具,可以将扫描生成命令和 push 等命令集成一起,应该可以比较好的避免该问题)

build_runner 方案

build_runner 是个强大代码自动生成工具,根据 ast 语法树 + 自定义注解信息,可以生成很多强大的附属代码信息,例如 json_serializable 等库

所以,也能利用这点自定义类注解,获取到对应的整个类的代码信息,在对应附属的 xx.g.dart 文件中,将获取的代码内容转换成字符串,然后直接将 xx.g.dart 文件的代码字符串信息,展示到界面就行了

优点

可以通过生成命令,全自动的生成代码,甚至将整个预览 demo 的映射表都可以自动配置完成

可以规范的通过注解配置多个参数

缺点

因为 build_runner 需要解析整个 ast 语法树,一旦项目很大之后,解析生成的时间会非常非常的长!

因为现在很多的这类库都是依赖 build_runner,所以跑自动生成命令,会导致巨多 xx.g.dart 文件被改动,极大的增加 cr 工作量

资源文件方案

这应该最常用的一种方案

在 pubspec.yaml 中的 assets 中定义下我们代码文件路径

 

flutter:
  assets:
    - lib/widgets/show/

 

然后用 loadString 获取文件内容

 

final code = await rootBundle.loadString('lib/widgets/show/custome_dialog_animation.dart');

 

flutter

优点

侵入性非常低,不会像 build_runnner 方案那样影响到其它模块

便于维护,如果 demo 预览代码被改变了,打包的时候,资源文件也会生成对应改变后的代码文件

缺点

使用麻烦,使用的时候需要传入具体的文件路径,才能找到想要的代码资源文件

需要反复的在 pubspec.yaml 中的 assets 里面定义文件路径

资源文件方案优化

上面的三种方案各有优缺点,明确当前的诉求

目前是想写个简单的,通用的,仅在 Flutter 中实现代码预览方案

要求使用简单,高效

维护简单,多人开发的时候不会有很大成本

FlutterUnit 方案:实现起来成本较大,且多人开发对单个 db 文件的维护很可能会有点问题,例如:更新代码的时候,db 文件忘记更新

build_runner 方案:生成时间是个问题,还有很对其他类型 xx.g.dart 文件产生影响也比较麻烦

资源文件方案:整体是符合预期的,但是使用时候,需要传入路径和 pubspec.yaml 中反复定义文件路径,这是俩个很大痛点

结合实现成本和诉求,选择资源文件方案,下面对其痛点进行优化

使用优化

Flutter 的编译产物中,有个相当有用的文件:AssetManifest.json

AssetManifest.json 文件里面,有所有的资源文件的路径,然后就简单了,我们只需要读取该文件内容

 

final manifestContent = await rootBundle.loadString('AssetManifest.json');

 

获取到所有的路径之后,再结合传入的类名,读取所有路径的文件内容,然后和传入的类名做正则匹配就行了

稍微优化

将传入的类名,转换为下划线名称和所有路径名称做匹配,如果能匹配上,再进行内容匹配,匹配成功后就返回该文件的代码内容

如果上述匹配失败,就进行兜底的全量匹配

优化前

 

import 'package:code_preview/code_preview.dart';
import 'package:flutter/material.dart';

class Test extends StatelessWidget {
  const Test({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return const CodePreview(path: 'lib/widgets/show/custome_dialog_animation.dart');
  }
}

 

优化后

 

import 'package:code_preview/code_preview.dart';
import 'package:flutter/material.dart';

class Test extends StatelessWidget {
  const Test({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return const CodePreview(className: 'CustomDialogAnimation');
  }
}

 

一般来说,我是统一配置预览 demo 和 className,这样比较好对照

flutter

路径定义优化

本来是想在 pubspec.yaml 的 assets 里面直接写通配符定义全路径,然后悲剧了,它不支持这种写法

 

flutter:
  assets:
    - lib/widgets/**/*.dart

 

GG,只能想其他办法了,想了很多方法都不行,只能从外部入手,用 idea 插件的形式,实现自动化扫描生成路径

安装:Plugins 中搜索 Flutter Code Helper

flutter

pugspec.yaml 中定义下需要自动生成文件夹的目录,文件夹随便套娃,会自动帮你递归在 assets 下生成

不需要自动生成,可:不写该配置,或者配置空数组(auto_folder: [])

 

code_helper:
  auto_folder: [ "assets/", "lib/widgets/" ]

 

Flutter Web 中的问题

魔幻的 runtimeType

flutter web 的 release 模式中

dart2js 会压缩 JS,这样会使得类型名被改变

例如:dart 中的 TestWidgetFunction 类的 runtimeType,可能会变成 minified:Ah,而不是 TestWidgetFunction!

为啥需要压缩呢?压缩名称可以使得编译器将 JavaScript 体积缩小 3 倍 +;精确等效语义和性能 / 代码大小之间的权衡,Dart 明显是选择了后者

这种情况只会在 Flutter Web 的 release 模式下发生,其他平台和 Flutter web 的 Debug | Profile 模式都不会有这种问题;所以说 Xxx.runtimeType.toString,并不一定会得到预期内的数据。。。

解决思路

将压缩类型 minified:Ah 恢复成 Test

将获取的 Test 字符串使用相同算法压缩成 minified:Ah

如有知道如何实现的,务必告诉鄙人

下面从压缩级别调整的角度,探究是否可解决该问题

dart2js 压缩说明

注:flutter build web 默认的是 O4 优化级别

O0: 禁用许多优化。

O1: 启用默认优化 (仅是 dart2js 该命令的默认级别)

O2: 在 O1 优化基础上,尊重语言语义且对所有程序安全的其他优化(例如缩小)

备注:使用 - O2, 使用开发 JavaScript 编译器编译时,类型的字符串表示不再与 Dart VM 中的字符串表示相同

O3: 在 O2 优化基础上,并省略隐式类型检查。

注意:省略类型检查可能会导致应用程序因类型错误而崩溃

O4: 在 O3 优化基础上,启用更积极的优化

注意:O4 优化容易受到输入数据变化的影响,在依赖 O4 之前,需测试用户输入中的边缘情况

下面是 flutter 新建项目,未做任何改动,不同压缩级别的 js 产物体积

 

# main.dart.js: 7.379MB
flutter build web --dart2js-optimization O0 
# main.dart.js: 5.073MB
flutter build web --dart2js-optimization O1
# main.dart.js: 1.776MB
flutter build web --dart2js-optimization O2
# main.dart.js: 1.716MB
flutter build web --dart2js-optimization O3
# main.dart.js: 1.687MB
flutter build web --dart2js-optimization O4

 

总结

预期用法

为什么想使用对象?因为当对象名称改变时,对应使用的地方,可以便捷观察到需要改变

可以使用传入的对象实例,在内部使用 runtimeType 获取类型名,再进行相关匹配

 

CodePreview(code: Test());

 

但是

综上可知,使用 flutter build web --dart2js-optimization O1 编译的 flutter web release 产物,能够使得 runtimeType 的语义和 Dart VM 中字符串保持一致

但是该压缩级别下的,js 体积过于夸张,务必会对加载速度产生极大影响,可想而知,在复杂项目中的体积增涨肯定更加离谱

对于想要用法更加简单,使用低级别压缩命令打包的想法需要舍弃

用法不得已做妥协

 

CodePreview(className: "Test");

 

这是个让我非常纠结的思路历程

最后

到这里也结束了,自我感觉,对大家应该能有一些帮助

一般来说,大部分团队,都会有个自己的内部组件库,因为 Flutter 强大的跨平台特性,所以就能很轻松的发布到 web 平台,可以方便的体验各种组件的效果,结合文章中的代码预览方案,就可以更加快速的上手各种组件用法了~





审核编辑:刘清

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分