<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          FreeMina兼容微信小程序Mina框架

          聯(lián)合創(chuàng)作 · 2023-09-24 20:17

          FreeMina: An open mina compatible framework for running in browser or webview.

          一個兼容微信小程序Mina框架的開源框架

          從小程序的設(shè)計來看,微信正走向封閉生態(tài)。我們開發(fā)的微信小程序很難在其他地方使用。

          最近一段時間,我花了大量精力來查找相關(guān)資料。包括React、React Native。我本來不算一個JS程序員,但也為此學習或了解了Bable
          WebPatch,ES2015等等一系列我原本不熟悉的內(nèi)容。

          真正有巨大收獲的是 @phodal 大神發(fā)的五篇文章,
          它還做了一個for fun的框架winv。 仔細學習了這個框架,并提交了一個補丁,改善了一點點小功能。昨天晚上,我就在考慮到底是在這個框架上修改,還是自己開一個。

          反復(fù)思量,覺得如果要改進,那么基本上要重寫所有的代碼,和重開一個無益。
          一個項目初始的架構(gòu)很重要,差的架構(gòu)讓人難以提起改進的興趣。另外,大神有6個月沒有更新項目了。

          總之,再次感謝 @phodal 。

          設(shè)計目的和計劃

          完全兼容微信小程序的所有API。讓微信小程序能移植到自己的APP上。

          當然這個目標從現(xiàn)在看有些“宏偉”了。

          要做的工作:

          • 解析wxml dom,并生成相應(yīng)的html。
            這一點, @phodal 已經(jīng)做了大量的貢獻。但性能需要改進一下。
            另外,我學習了facebook的diff算法,準備在今后的改進中加入。

          • wxml中{{}}格式數(shù)據(jù)的處理。我給winv這個項目添加了 {{obj.name}}這樣的支持。
            但還缺少if和for這兩個非常重要的環(huán)節(jié)。

          • 事件系統(tǒng)。 目前已經(jīng)實現(xiàn)了一些,但還遠遠沒有完成。但大體的設(shè)計已經(jīng)有了。

          • 打包等項目工具 。 微信小程序?qū)⑺械奈募看虬谝黄稹_@個并非簡單的用
            webpack進行打包。還對程序作了一定的預(yù)處理。對于將xml生成為js的做法,我覺得還需要考慮,到底需不需要這么做。
            json的處理相對簡單,require進去就好。

          我實現(xiàn)打包工具的思路是

          1. 首先給Page打包,給添加上兩個參數(shù),把xml和文件名一起傳給Page函數(shù)。

          2. 使用webpack等工具打包到一起。

          • App支持。 wx中有很多函數(shù),沒有App的幫助是無法實現(xiàn)的。 這一部分的做法

            1. 在web中能用web試下你的用web實現(xiàn),不能實現(xiàn)的暫不實現(xiàn)。

            2. 在App中,給出原生支持。。不過我目前只會android。蘋果的沒錢買那么貴的設(shè)備。畢竟玩票性質(zhì)。。。

          實現(xiàn)方案

          項目工具

          整個項目使用nodejs管理。使用gulp完成編譯和監(jiān)視文件自動編譯的功能。
          使用bable進行ES6的轉(zhuǎn)義。 直接拿了別人項目的配置。。。(羞。。。)
          詳見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直接創(chuàng)建App類。 addPage方法首先創(chuàng)建Page對象,隨后調(diào)用App的addPage方法
          將其加入管理之中。并使用setWxml將wxml設(shè)置進去。

          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實現(xiàn)的,老實說,如果不是能用class,我是不愿意入js這個坑的。
          但一堆堆的bind還是亮瞎了我眼。。。

          App對象維護一個Page對象列表。和一個curPage指向當前對象。
          目前,默認認為第一個注冊的Page是入口。(因為還沒實現(xiàn)App的配置,所以暫時忍一下吧!!!)

          下面是事件處理的核心eventHandler。
          我們在寫App時這么寫:
          App({
            onLaunch:function(){...}
          })
          這個傳進app的是一個對象或者說是hashmap。在app的構(gòu)造函數(shù)中,傳遞給了this.opt
          eventHandler被調(diào)用時,給出了一個e,這個e可以是CustomEvent,也可以是

          {type:'onLaunch',detail:{}}

          這樣的對象。如果收到上述的這個onLaunch消息,這個函數(shù)就會判斷opt中(就是你傳進的對象)
          是否有這個方法。如果有,則調(diào)用它。

          調(diào)用萬onLaunch開始調(diào)用Page的onLoad了。怎么調(diào)用呢?
          在這里發(fā)出一個消息。如果頁面的名字是index,那么就發(fā)出
          index_onLoad消息。index這個頁面會監(jiān)聽這個消息,進而收到這個onLoad事件。

          下面來看Page的實現(xiàn)

          Page的實現(xiàn)

          先貼代碼。

          /**
           * 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,{}));
              }}

          構(gòu)造函數(shù)中又是一堆晃瞎我眼的bind。另外你換進來的那個對象仍然被存到了opt中。
          setName 函數(shù)是被App調(diào)用的。 設(shè)置了這個頁面的名字。在名字設(shè)定后,就會注冊一堆事件監(jiān)聽者。
          注冊的列表在event_list這個變量里。以后這個列表可以逐漸完善。
          上面說到的那個index_onLoad事件就是通過頁面名字和事件名拼接出來的。

          事件監(jiān)聽函數(shù)是eventHandle。把onLoad這個字眼從index_onLoad中切除來。

                  let type = e.type.slice(this.name.length+1); // eg : index_onLoad , remove 'index_'

          然后查找this.opt就是你傳進來的那個對象是否有onLoad的聲明。
          如果有,則調(diào)用,如果沒有,則嘗試在this中查找,如果還是沒有,就真的沒有了。

          下面說比較重要的渲染問題

          WXmlParser渲染wxml文件

          這部分參考了winv,里面也有少量我貢獻的嗲嗎,我只是對其做了重構(gòu),以方便調(diào)用。看代碼

          /**
           * 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 = '<div class="page"><div class="page__hd">' + string + '</div></div>';
                  var json = this.nodeToJSON(this.domParser(string));
                  if (json.nodeType === 9) {
                      json = json.childNodes;
                  }
                  return json;
              }
          
              getData(key) {
                  if(!key)return null;
                  var ka = key.split(".");
                  var ret = this.data[ka[0]];
                  for(var i = 1;i<ka.length;i++){
                      if(!ret)return null; //can't find !
                      ret= ret[ka[i]];
                  }
                  return ret;
              }
          
              nodeToJSON(node){
                  // Code base on https://gist.github.com/sstur/7379870
                  node = node || this;
                  var obj = {
                      nodeType: node.nodeType
                  };
                  if (node.tagName) {
                      obj.tagName = 'winv-' + node.tagName.toLowerCase();
                  } else if (node.nodeName) {
                      obj.nodeName = node.nodeName;
                  }
                  if (node.nodeValue) {
                      obj.nodeValue = node.nodeValue;
                      if(this.utils.isTemplateTag(node.nodeValue)){
                          obj.nodeValue = this.getData(this.utils.removeTemplateTag(node.nodeValue));
                      }
                  }
                  var attrs = node.attributes;
                  if (attrs) {
                      var length = attrs.length;
                      var arr = obj.attributes = new Array(length);
                      for (var i = 0; i < length; i++) {
                          var attr = attrs[i];
                          arr[i] = [attr.nodeName, attr.nodeValue];
                      }
                  }
                  var childNodes = node.childNodes;
                  if (childNodes) {
                      length = childNodes.length;
                      arr = obj.childNodes = new Array(length);
                      for (i = 0; i < length; i++) {
                          arr[i] = this.nodeToJSON(childNodes[i]);
                      }
                  }
                  return obj;
              }
          
              jsonToDom(obj)
              {
                  // Code base on https://gist.github.com/sstur/7379870
                  if (typeof obj == 'string') {
                      obj = JSON.parse(obj);
                  }
                  var node, nodeType = obj.nodeType;
                  switch (nodeType) {
                      case 1: //ELEMENT_NODE
                          node = document.createElement(obj.tagName);
                          var attributes = obj.attributes || [];
                          for (var i = 0, len = attributes.length; i < len; i++) {
                              var attr = attributes[i];
                              node.setAttribute(attr[0], attr[1]);
                          }
                          break;
                      case 3: //TEXT_NODE
                          node = document.createTextNode(obj.nodeValue);
                          break;
                      case 8: //COMMENT_NODE
                          node = document.createComment(obj.nodeValue);
                          break;
                      case 9: //DOCUMENT_NODE
                          node = document.implementation.createDocument('http://www.w3.org/1999/xhtml', 'html', null);
                          break;
                      case 10: //DOCUMENT_TYPE_NODE
                          node = document.implementation.createDocumentType(obj.nodeName);
                          break;
                      case 11: //DOCUMENT_FRAGMENT_NODE
                          node = document.createDocumentFragment();
                          break;
                      default:
                          return node;
                  }
                  if (nodeType == 1 || nodeType == 11) {
                      var childNodes = obj.childNodes || [];
                      for (i = 0, len = childNodes.length; i <  len; i++) {
                          node.appendChild(this.jsonToDom(childNodes[i]));
                      }
                  }
                  return node;
              }
          
              domParser(string){
                  var parser = new DOMParser();
                  return parser.parseFromString(string, 'text/xml');
              }}

          Page.js中的渲染函數(shù)

          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對象。將其
          變?yōu)閐omJSON.然后再講domJSON轉(zhuǎn)換會dom對象。這一步中包含{{}}標簽的處理。
          用的正則表達式匹配。

          最后,這個問題還是很多的。 比如那個appendChild。。在后面的開發(fā)中會替換成
          diff和apply。
          渲染完了,發(fā)送事件。

          TODO

          1. 事件機制還不完善。

          2. 渲染的diff和apply的實現(xiàn)。

          3. setData這個核心的函數(shù)實現(xiàn)。

          4. 參照微信文檔進行界面完全兼容

          5. 完善wx的API函數(shù)(可能會用Android實現(xiàn)) IOS就算了,聽說基于WebView的通不過審核! 另外,我沒錢買Mac。。。

          最后

          感謝您的閱讀。如果有可能請貢獻些代碼。。。

          瀏覽 21
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          編輯 分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          編輯 分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  aa中文字幕在一起 | 亚洲无码草逼逼 | 爱插综合网 | 欧美三级片播放 | 亚洲欧美精品suv |