前言
如何优雅的将项目中的代码,亦或是你的 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
配置代码文件
因为原理是遍历资源文件,所以必须将需要展示的代码文件或者其文件夹路径,定义在 assets 下,这步操作,为大家提供了一个自动化的插件解决
强烈建议需要展示到界面的代码,都放在统一的文件夹里管理
展示界面的代码需要在 pugspec.yaml 中的 assets 定义
如果代码预览的文件夹,分级复杂,每次都需要定义路径实在麻烦
提供一个插件:Flutter Code Helper
安装:Plugins 中搜索Flutter Code Helper
pugspec.yaml 中定义下需要自动生成文件夹的路径,文件夹随便套娃,会自动帮你递归在 assets 下生成
不需要自动生成,可:不写该配置,或者配置空数组(auto_folder: [])
code_helper: auto_folder: [ "assets/", "lib/widgets/" ]
说明下:上面的插件是基于 RayC 的 FlutterAssetsGenerator 插件项目改的
看了下 RayC 的插件代码和相关功能,和我预想的上面功能实现有一定出入,改动起来变动较大
想试下插件项目的各种新配置,直接拉到最新
后期如果想到需要什么功能,方便随时添加
所以没向其插件里面提 pr,就单独新开了个插件项目
高级使用
主题
提供俩种代码样式主题
日间模式
CodePreview.config = CodePreviewConfig(codeTheme: CodeTheme.light);
夜间模式
CodePreview.config = CodePreviewConfig(codeTheme: CodeTheme.dark);
注释解析
你可以使用如下的格式,在类上添加注释
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
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 文件
映射表,宿主可以通过 db 中的组件类名,从这里拿到 demo 效果实例
总结
整套流程看下来,实现起来的工作量还是有点大的
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');
优点
侵入性非常低,不会像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,这样比较好对照
路径定义优化
本来是想在pubspec.yaml的assets里面直接写通配符定义全路径,然后悲剧了,它不支持这种写法
flutter: assets: - lib/widgets/**/*.dart
GG,只能想其他办法了,想了很多方法都不行,只能从外部入手,用 idea 插件的形式,实现自动化扫描生成路径
安装:Plugins 中搜索Flutter Code Helper
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 平台,可以方便的体验各种组件的效果,结合文章中的代码预览方案,就可以更加快速的上手各种组件用法了~
审核编辑:刘清
-
Rayc
+关注
关注
0文章
2浏览量
6015 -
flutter
+关注
关注
0文章
13浏览量
441
原文标题:Flutter如何将代码显示到界面上
文章出处:【微信号:OSC开源社区,微信公众号:OSC开源社区】欢迎添加关注!文章转载请注明出处。
发布评论请先 登录
相关推荐
评论