微前端架构如何改变企业的开发模式与效率提升
880
2022-10-08
FreeMina- 兼容微信小程序Mina框架
FreeMina: An open mina compatible framework for running in browser or
webview.
一个兼容微信小程序Mina框架的开源框架
从小程序的设计来看,微信正走向封闭生态。我们开发的微信小程序很难在其他地方使用。
最近一段时间,我花了大量精力来查找相关资料。包括React、React Native。我本来不算一个js程序员,但也为此学习或了解了BableWebPatch,ES2015等等一系列我原本不熟悉的内容。
真正有巨大收获的是 @phodal 大神发的五篇文章,它还做了一个for fun的框架winv。仔细学习了这个框架,并提交了一个补丁,改善了一点点小功能。昨天晚上,我就在考虑到底是在这个框架上修改,还是自己开一个。
反复思量,觉得如果要改进,那么基本上要重写所有的代码,和重开一个无益。一个项目初始的架构很重要,差的架构让人难以提起改进的兴趣。另外,大神有6个月没有更新项目了。
总之,再次感谢 @phodal 。
设计目的和计划
完全兼容微信小程序的所有API。让微信小程序能移植到自己的APP上。
当然这个目标从现在看有些“宏伟”了。
要做的工作:
解析wxml dom,并生成相应的html。这一点, @phodal 已经做了大量的贡献。但性能需要改进一下。另外,我学习了facebook的diff算法,准备在今后的改进中加入。wxml中{{}}格式数据的处理。我给winv这个项目添加了 {{obj.name}}这样的支持。但还缺少if和for这两个非常重要的环节。事件系统。 目前已经实现了一些,但还远远没有完成。但大体的设计已经有了。打包等项目工具 。 微信小程序将所有的文件全部打包在一起。这个并非简单的用webpack进行打包。还对程序作了一定的预处理。对于将xml生成为js的做法,我觉得还需要考虑,到底需不需要这么做。json的处理相对简单,require进去就好。
我实现打包工具的思路是
首先给Page打包,给添加上两个参数,把xml和文件名一起传给Page函数。使用webpack等工具打包到一起。App支持。 wx中有很多函数,没有App的帮助是无法实现的。 这一部分的做法在web中能用web试下你的用web实现,不能实现的暂不实现。在App中,给出原生支持。。不过我目前只会android。苹果的没钱买那么贵的设备。毕竟玩票性质。。。
实现方案
项目工具
整个项目使用nodejs管理。使用gulp完成编译和监视文件自动编译的功能。使用bable进行ES6的转义。 直接拿了别人项目的配置。。。(羞。。。)详见package.json
项目入口
项目的入口是src/freemina.js
/** * Created by Tongfeng Yang on 2017/1/25. */import Page from './Page'import App from './App'import FException from './FException'const freemina={ addPage(opt,name,wxml){ console.log("add Page :"+name); if(!window.App ){ throw new FException("App() function should be called before calling Page"); } let p = new Page(opt,name); p.setWXml(wxml); window.App.addPage(name,p); }, setApp(opt){ console.log("set App called"); window.App = new App(opt); }, start(){ window.Page = this.addPage; window.App = this.setApp;// let e = new CustomEvent('onLaunch',{});// window.App.eventHandler(e) }, finishLoad(){ var e={type:"onLaunch",detail:{}}; window.App.eventHandler(e); }}export default freemina;
包含了setApp和addPage方法,这个方法在start方法中被暴露到window中,所以,就可以使用App({})和Page({})的方法来使用它们。setApp直接创建App类。 addPage方法首先创建Page对象,随后调用App的addPage方法将其加入管理之中。并使用setWxml将wxml设置进去。
App类
先看代码
/** * Created by Tongfeng Yang on 2017/1/25. */export default class App{ constructor(opt){ this.opt = opt; this.pageMap = []; this.addPage=this.addPage.bind(this); this.eventHandler=this.eventHandler.bind(this); this.render=this.render.bind(this); this.regEvent.bind(this)(); } regEvent(){ document.addEventListener("onShow",(e)=>{ //onLoad function of page is called succ }); } addPage(name,p){ if(this.pageMap.length==0){ this.curPage = p; } p.setName(name); this.pageMap[name] = p; } eventHandler(e){ //CustomEvent if(this.opt[e.type]){ this.opt[e.type](e.detail); } if(e.type == "onLaunch"){ let ename = this.curPage.name+"_onLoad"; e= new CustomEvent(ename,{}); document.dispatchEvent(e); } } render(){ if(this.curPage){ curPage.render(); } }}
使用ES6实现的,老实说,如果不是能用class,我是不愿意入js这个坑的。但一堆堆的bind还是亮瞎了我眼。。。
App对象维护一个Page对象列表。和一个curPage指向当前对象。目前,默认认为第一个注册的Page是入口。(因为还没实现App的配置,所以暂时忍一下吧!!!)
下面是事件处理的核心eventHandler。我们在写App时这么写:App({onLaunch:function(){…}})这个传进app的是一个对象或者说是hashmap。在app的构造函数中,传递给了this.opteventHandler被调用时,给出了一个e,这个e可以是CustomEvent,也可以是
{type:'onLaunch',detail:{}}
这样的对象。如果收到上述的这个onLaunch消息,这个函数就会判断opt中(就是你传进的对象)是否有这个方法。如果有,则调用它。
调用万onLaunch开始调用Page的onLoad了。怎么调用呢?在这里发出一个消息。如果页面的名字是index,那么就发出index_onLoad消息。index这个页面会监听这个消息,进而收到这个onLoad事件。
下面来看Page的实现
Page的实现
先贴代码。
/** * Created by Tongfeng Yang on 2017/1/25. */import WXmlParser from "./WXmlParser"const event_list = [ 'onLoad','onDestory','render'];export default class Page{ constructor(opt){ this.opt = opt; this.eventHandler=this.eventHandler.bind(this); this.registerEventHandler=this.registerEventHandler.bind(this); this.removeEventListener=this.removeEventListener.bind(this); this.setName=this.setName.bind(this); this.render=this.render.bind(this); this.setWXml=this.setWXml.bind(this); this.fireMyEvent = this.fireMyEvent.bind(this); this.getData =this.getData.bind(this); } setName(name){ if(name == this.name)return; this.removeEventListener(); this.name = name; this.registerEventHandler(); } removeEventListener(){ for(var e in event_list ){ let ename = event_list[e]; console.log("removeEventListener:"+this.name+'_'+ename); document.removeEventListener(this.name+'_'+ename); } } registerEventHandler(){ for(var e in event_list ){ let ename = event_list[e]; console.log("addEventListener:"+this.name+'_'+ename); document.addEventListener(this.name+'_'+ename,this.eventHandler); } } getData(){ return this.opt.data; } eventHandler(e){ //CustomEvent console.log("page this = "+this); console.log(this); let type = e.type.slice(this.name.length+1); // eg : index_onLoad , remove 'index_' console.log("recv event "+e.type); if(this.opt[type]){ this.opt[type]({}); }else if(this[type]){ this[type].bind(this)(); }else{ console.log("Page: unknown event "+ type); } if(type == 'onLoad'){ //if onload finish ,start to render this.render(); //this.fireMyEvent.bind(this)('render'); } } setWXml(wxml){ this.wxml = wxml; } render(){ console.log("render called"); let template = this.wxml; let parser =new WXmlParser(this.getData()); let domJson = parser.stringToDomJSON(template)[0]; let dom = parser.jsonToDom(domJson); document.getElementById('app').appendChild(dom); this.fireEvent('onShow');//for App object } fireMyEvent(type){ type = this.name+"_"+type; console.log("fireEvent "+type); document.dispatchEvent(new CustomEvent(type,{})); } fireEvent(type){ console.log("fireEvent "+type); document.dispatchEvent(new CustomEvent(type,{})); }}
构造函数中又是一堆晃瞎我眼的bind。另外你换进来的那个对象仍然被存到了opt中。setName 函数是被App调用的。 设置了这个页面的名字。在名字设定后,就会注册一堆事件监听者。注册的列表在event_list这个变量里。以后这个列表可以逐渐完善。上面说到的那个index_onLoad事件就是通过页面名字和事件名拼接出来的。
事件监听函数是eventHandle。把onLoad这个字眼从index_onLoad中切除来。
let type = e.type.slice(this.name.length+1); // eg : index_onLoad , remove 'index_'
下面说比较重要的渲染问题
WXmlParser渲染wxml文件
这部分参考了winv,里面也有少量我贡献的嗲吗,我只是对其做了重构,以方便调用。看代码
/** * Created by Tongfeng Yang on 2017/1/25. * Some code copied from https://github.com/phodal/winv ,which is under MIT . */class Utils{ removeTemplateTag(str){ return str.substr(2, str.length - 4); } isTemplateTag(string){ return /{{[a-zA-Z1-9\\.]+}}/.test(string); }}export default class WXmlParser{ constructor(data){ this.data= data; this.stringToDomJSON=this.stringToDomJSON.bind(this); this.nodeToJSON=this.nodeToJSON.bind(this); this.jsonToDom=this.jsonToDom.bind(this); this.domParser = this.domParser.bind(this); this.getData = this.getData.bind(this); this.utils = new Utils(); } stringToDomJSON(string){ string = '
Page.js中的渲染函数
render(){ console.log("render called"); let template = this.wxml; let parser =new WXmlParser(this.getData()); let domJson = parser.stringToDomJSON(template)[0]; let dom = parser.jsonToDom(domJson); document.getElementById('app').appendChild(dom); this.fireEvent('onShow');//for App object }
基本原理首先通过DOMParser将wxml解析一下(domParser)。编程一个dom对象。将其变为domJSON.然后再讲domJSON转换会dom对象。这一步中包含{{}}标签的处理。用的正则表达式匹配。
最后,这个问题还是很多的。 比如那个appendChild。。在后面的开发中会替换成diff和apply。渲染完了,发送事件。
TODO
事件机制还不完善。渲染的diff和apply的实现。setData这个核心的函数实现。参照微信文档进行界面完全兼容完善wx的API函数(可能会用Android实现) IOS就算了,听说基于WebView的通不过审核! 另外,我没钱买Mac。。。
最后
感谢您的阅读。如果有可能请贡献些代码。。。
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。
发表评论
暂时没有评论,来抢沙发吧~