前言
如何優雅的將項目中的代碼,亦或是你的 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瀏覽量
6014 -
flutter
+關注
關注
0文章
13瀏覽量
437
原文標題:Flutter如何將代碼顯示到界面上
文章出處:【微信號:OSC開源社區,微信公眾號:OSC開源社區】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論