blazor wasm开发chrome插件
用blazor(Wasm)開(kāi)發(fā)了一個(gè)chrome插件感覺(jué)效率挺高的,分享給大家
先簡(jiǎn)單介紹下WebAssembly的原理:
“WebAssembly是一種用于基于堆棧的虛擬機(jī)的二進(jìn)制指令格式”
image如上圖,瀏覽器在執(zhí)行js時(shí)是會(huì)經(jīng)歷 Parser轉(zhuǎn)成語(yǔ)法樹(shù)->Compiler轉(zhuǎn)成字節(jié)碼->JIT即時(shí)字節(jié)碼解釋執(zhí)行
因?yàn)閃ebAssembly 模塊已經(jīng)被編譯成一種 JavaScript 字節(jié)碼形式,現(xiàn)代支持 WebAssembly 的 JavaScript 引擎可以在其 JIT 組件中可以直接解釋執(zhí)行!
mono團(tuán)隊(duì)把開(kāi)源跨平臺(tái).NET運(yùn)行時(shí)Mono(也是unity3d的運(yùn)行時(shí))編譯成了WebAssembly ,那么開(kāi)發(fā)的.net程序就可以通過(guò)這個(gè)運(yùn)行時(shí)在瀏覽器中加載net程序執(zhí)行。
近日vs2022發(fā)布了,blazor的功能得到進(jìn)一步提升,
支持AOT將.NET代碼直接編譯為WebAssembly字節(jié)碼
支持NativeFileReference添加c語(yǔ)言和rust等原生依賴(手動(dòng)狗頭)
進(jìn)入正題
開(kāi)發(fā)瀏覽器插件,常見(jiàn)的就是按照插件的這幾塊api來(lái)進(jìn)行擴(kuò)展
右鍵菜單擴(kuò)展
Backgroud(可以理解為每個(gè)插件都有一個(gè)后臺(tái)一直運(yùn)行的模塊)
popup(瀏覽器右上角點(diǎn)擊插件彈出的窗口模塊)
contentScript(嵌入到你想要嵌入的網(wǎng)站內(nèi)執(zhí)行)
devtools(開(kāi)發(fā)面板擴(kuò)展模塊)
首先基于這個(gè)大佬的模板搭建工程
https://github.com/mingyaulee/Blazor.BrowserExtension
基于模板的話會(huì)幫你引入哪些包
image我也躺了很多坑,看看我給大佬提的issue,和大佬一起成長(zhǎng)
這里我總結(jié)一套非常高效的方案給大家:
Backgroud用csharp寫(xiě)
popup,option等的html不要用balzor寫(xiě),balzor加載html沒(méi)有任何優(yōu)勢(shì)
contentScript用js寫(xiě),內(nèi)嵌到網(wǎng)站的,如果是balzor的話會(huì)初始化的時(shí)候卡1~2s左右,這個(gè)會(huì)嚴(yán)重影響體驗(yàn)
js和csharp交互
這里把BackGround(csharp開(kāi)發(fā))作為插件后端 html和js作為插件的前端的方式
右鍵菜單擴(kuò)展
在BackGround里面寫(xiě),包括響應(yīng)事件
//選中跳轉(zhuǎn)菜單 await?WebExtensions.ContextMenus.Create(new?WebExtensions.Net.Menus.CreateProperties {Title?=?"測(cè)試菜單",Contexts?=?new?List<ContextType>{ContextType.Selection},//data是選中的內(nèi)容包裝對(duì)象Onclick?=?async?(data,?tab)?=>?{?await?test(data).ConfigureAwait(false);?} },?EmptyAction);//非選中跳轉(zhuǎn)菜單await?WebExtensions.ContextMenus.Create(new?WebExtensions.Net.Menus.CreateProperties {Title?=?"跳轉(zhuǎn)百度",Onclick?=?async?(d,?tab)?=>?{?await?OpenUrl("https://www.baidu.com").ConfigureAwait(false);?} },?EmptyAction);contentScript/popup等
用js寫(xiě),有2種方式來(lái)和Backgroud通訊
1. 事件一來(lái)一回的方式
contentScript中發(fā)送消息給BackGround
chrome.runtime.sendMessage("消息體",?function?()?{?});chrome.runtime.onMessage.addListener(function?(request,?sender,?sendResponse)?{//處理backgroup發(fā)來(lái)的消息});BackGround注冊(cè)事件用來(lái)接收js發(fā)過(guò)來(lái)的消息
//注冊(cè)事件接收js過(guò)來(lái)的消息 await?WebExtensions.Runtime.OnMessage.AddListener(OnReceivedCommand);//處理事件 private?bool?OnReceivedCommand(object?obj,?MessageSender?sender,?Action?action){Console.WriteLine("OnCommand:"?+?key?+?$",from?TabId:{sender.Tab.Id}");//處理完成后發(fā)送事件給js那邊await?WebExtensions.Tabs.SendMessage(sender.Tab.Id.Value,?"處理完成了",?new?SendMessageOptions()); }2. 長(zhǎng)連接方式
js端
var?port?=?chrome.extension.connect({name:?"test" });port.onMessage.addListener(function?(msg)?{console.log(msg); });$('#test').click(e?=>?{port.postMessage('發(fā)消息'); });csharp端
await?WebExtensions.Runtime.OnConnect.AddListener(port?=> {Console.WriteLine(port.Name?+?"---》connection");port.OnMessage.AddListener(new?DelegateMethod(async?(msg)?=>{//處理消息}));});目前這種方式有一個(gè)需要優(yōu)化,就是無(wú)法在csharp端主動(dòng)推送消息給js端 給大佬提了issue了,相信很快可以fix https://github.com/mingyaulee/WebExtensions.Net/issues/14
配置/存儲(chǔ)相關(guān)
有兩種方法:
1. chrome.storage.local
這里我封裝了一個(gè)類專門(mén)操作
public?class?ChromLocalStorage {private?readonly?IWebExtensionsApi?_webExtensionsApi;private?readonly?IJSRuntime?_jsRuntime;public?ChromLocalStorage(IWebExtensionsApi?webExtensionsApi,?IJSRuntime?JsRuntime){_webExtensionsApi?=?webExtensionsApi;_jsRuntime?=?JsRuntime;}///?<summary>///?調(diào)用chrom.storage.local?set?把?key?和?value設(shè)置進(jìn)去///?key返回///?</summary>///?<param?name="value"></param>///?<param?name="existKey"></param>///?<returns></returns>public?async?Task<string>?localSet(string?value,string?existKey??=?null){var?key?=?existKey????"key_"?+?DateTime.Now.ToString("yyyyMMddHHmmss");byte[]?bytes?=?Encoding.UTF8.GetBytes(value);var?encode?=?Convert.ToBase64String(bytes);var?jss?=?"var?"?+?key?+?"?=?{'"?+?key?+?"':'"?+?encode?+?"'}";await?_jsRuntime.InvokeVoidAsync("eval",?jss);object?data2?=?await?_jsRuntime.InvokeAsync<object>("eval",?key);await?_jsRuntime.InvokeVoidAsync("chrome.storage.local.set",?data2);Console.WriteLine($"call?chrome.storage.local.set,key:{key},value:{value},base64Value:{encode}");return?key;}public?async?Task<string>?localSet<T>(T?value){if?(value?is?string?s){return?await?localSet(s,null);}//轉(zhuǎn)成jsonstringvar?serialize?=?JsonSerializer.Serialize(value);return?await?localSet(serialize,null);}public?async?Task<T>?localGet<T>(string?key){var?data?=?await?localGet(key);T?deserialize?=?JsonSerializer.Deserialize<T>(data);return?deserialize;}public?async?Task<string>?localGet(string?key,bool?remove=true){try{var?local?=?await?_webExtensionsApi.Storage.GetLocal();var?getData?=?await?local.Get(new?StorageAreaGetKeys(key));var?data?=?getData.ToString();if?(string.IsNullOrEmpty(data)){return?string.Empty;}var?value?=?data.Split(new?string[]?{?":\""?},?StringSplitOptions.None)[1].Split(new?string[]?{?"\""?},?StringSplitOptions.None)[0];var?str?=?Convert.FromBase64String(value);var?bastStr?=?Encoding.UTF8.GetString(str);//Console.WriteLine($"call?chrome.storage.local.get,key:{key},value:{bastStr},base64Value:{value}");if?(remove)?await?local.Remove(new?StorageAreaRemoveKeys(key));return?bastStr;}catch?(Exception?e){return?"";}}public?async?Task?localRemove(string?key){var?local?=?await?_webExtensionsApi.Storage.GetLocal();await?local.Remove(new?StorageAreaRemoveKeys(key));} }2. 6.0推出的新技術(shù):采用EFCore + Sqlite
需要用到native的庫(kù) https://github.com/SteveSandersonMS/BlazeOrbital/blob/main/BlazeOrbital/ManufacturingHub/Data/e_sqlite3.o
下載下來(lái)后放入工程中,然后引入
image這里還有一個(gè)關(guān)鍵
https://github.com/SteveSandersonMS/BlazeOrbital/blob/main/BlazeOrbital/ManufacturingHub/wwwroot/dbstorage.js
下載這個(gè)js后放入工程中,這個(gè)js是將sqlite和本地的indexdb進(jìn)行同步的
//EF的DbContext public?class?ClientSideDbContext?:?DbContext {//定義你要存儲(chǔ)的表模型public?DbSet<Part>?Parts?{?get;?set;?}?=?default!;public?ClientSideDbContext(DbContextOptions<ClientSideDbContext>?options):?base(options){}protected?override?void?OnModelCreating(ModelBuilder?modelBuilder){base.OnModelCreating(modelBuilder);//設(shè)置你的表的索引等modelBuilder.Entity<Part>().HasIndex(x?=>?x.Id);modelBuilder.Entity<Part>().HasIndex(x?=>?x.Name);modelBuilder.Entity<Part>().Property(x?=>?x.Name).UseCollation("nocase");} }//sqlite的初始化以及獲取DBContext的方法封裝 public?class?DataSynchronizer {public?const?string?SqliteDbFilename?=?"app.db";private?readonly?Task?firstTimeSetupTask;private?readonly?IDbContextFactory<ClientSideDbContext>?dbContextFactory;public?DataSynchronizer(IJSRuntime?js,?IDbContextFactory<ClientSideDbContext>?dbContextFactory){this.dbContextFactory?=?dbContextFactory;firstTimeSetupTask?=?FirstTimeSetupAsync(js);}public?async?Task<ClientSideDbContext>?GetPreparedDbContextAsync(){await?firstTimeSetupTask;return?await?dbContextFactory.CreateDbContextAsync();}private?async?Task?FirstTimeSetupAsync(IJSRuntime?js){//只加載一次?讓sqlite和indexdb同步var?module?=?await?js.InvokeAsync<IJSObjectReference>("import",?"./js/dbstorage.js");if?(RuntimeInformation.IsOSPlatform(OSPlatform.Create("browser"))){await?module.InvokeVoidAsync("synchronizeFileWithIndexedDb",?SqliteDbFilename);}using?var?db?=?await?dbContextFactory.CreateDbContextAsync();await?db.Database.EnsureCreatedAsync();}}image在Program.cs進(jìn)行注冊(cè)
那么你就可以在Backgroud里面注入并在初始化方法中拿到db上下文
[Inject]?public?DataSynchronizer?DataSynchronizer?{?get;?set;?}//db上下文 private?ClientSideDbContext?db;protected?override?async?Task?OnInitializedAsync() {await?base.OnInitializedAsync();db?=?await?DataSynchronizer.GetPreparedDbContextAsync(); }推薦用新的方式,EF寫(xiě)起來(lái)更爽更高效,拿到db上下文 就可以很簡(jiǎn)單的操作插件里面所有用到存儲(chǔ)配置等!
這種方式比較適合了解.net生態(tài)的人,結(jié)合.net的一些庫(kù)還可以實(shí)現(xiàn)很多好玩的功能
excel導(dǎo)出
二維碼生成
ajax攔截,轉(zhuǎn)發(fā)等
我是正東,開(kāi)發(fā)chrome插件其實(shí)很簡(jiǎn)單,這種方式對(duì)于我來(lái)說(shuō)比較高效 哈哈
歡迎白嫖 順手點(diǎn)個(gè)贊吧!
總結(jié)
以上是生活随笔為你收集整理的blazor wasm开发chrome插件的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: .NET 6新特性试用 | 隐式usin
- 下一篇: 如何高效的将 DataReader 转成