簡(jiǎn)年2:微信小程序的一個(gè)web實(shí)現(xiàn)(瀏覽器Chrome運(yùn)行)

自從接觸微信小程序以來,就希望能把小程序放到web中運(yùn)行,但自己對(duì)VirtualNode,React等都不熟悉,只能慢慢學(xué)習(xí)。

這幾天常使用React-Native做一個(gè)小的app,練手。但遇到了很多困難。 比如android很多組件不全的問題,全部需要git,npm去找,然后自己寫本地模塊。。。 開發(fā)效率可以說是低爆了。

讀取的資料:

筆記1 讓你的「微信小程序」運(yùn)行在 Chrome 瀏覽器上

其中介紹了它對(duì)微信源代碼的分析。
給出了預(yù)覽
GitHub: https://github.com/phodal/weapp-webdemo
預(yù)覽: http://weapp.phodal.com/

微信小程序的三大組件的處理方法

js wxml 和 wcss

js被封裝為一個(gè)包

define("app.js", function(require, module){var window={Math:Math} // 兼容babel/
,location,document,navigator,self,localStorage,history,Caches; App({ onLaunch: function () {
//這里是原本xx.js的內(nèi)容
  }
    })
}); require("app.js"); 

wxml被轉(zhuǎn)義為function,即一個(gè)js文件

這個(gè)應(yīng)該類似是react的jsx(拼寫忘了?。。。┑?。
使用 "微信web開發(fā)者工具\(yùn)package.nw\app\dist\weapp\onlinevendor"目錄中的wcc可以完成這一任務(wù)
例子:

/*v0.7cc_20160919*/
var $gwxc
var $gaic={}
$gwx=function(path,global){
function _(a,b){b&&a.children.push(b);}
function _n(tag){$gwxc++;if($gwxc>=16000){throw 'enough, dom limit exceeded, you don\'t do stupid things, do you?'};return {tag:tag.substr(0,3)=='wx-'?tag:'wx-'+tag,attr:{},children:[]}}
function _s(scope,env,key){return typeof(scope[key])!='undefined'?scope[key]:env[key]}
function _wl(tname){console.warn('template `' + tname + '` is being call recursively, will be stop.')}
function _ai(i,p,e,me){var x=_grp(p,e,me);if(x)i.push(x);else{console.warn('path `'+p+'` not found from `'+me+'`')}}
function _grp(p,e,me){if(p[0]!='/'){var mepart=me.split('/');mepart.pop();var ppart=p.split('/');for(var i=0;i<ppart.length;i++){if( ppart[i]=='..')mepart.pop();else if(!ppart[i])continue;else mepart.push(ppart[i]);}p=mepart.join('/');}if(me[0]=='.'&&p[0]=='/')p='.'+p;if(e[p])return p;if(e[p+'.wxml'])return p+'.wxml';}
//以下省略好多字。

wcss被轉(zhuǎn)義為css

使用上述目錄下的wcsc文件可以將其轉(zhuǎn)義為一個(gè)css文件。
$gwx函數(shù)的主要做用就是構(gòu)造界面。
在下面的代碼中被調(diào)用

javascript
document.dispatchEvent(new CustomEvent("generateFuncReady", {
detail: {
generateFunc: $gwx('index.wxml')
}
}))

微信類庫在哪里

在WXWebview.js中,不過這個(gè)文件是打包過的文件。

拆分開是這樣的
define.js,這里就是定義AMD模塊化的地方
exparser.js,用于轉(zhuǎn)換WXML標(biāo)簽到HTML標(biāo)簽
exparser-behvaior.js,定義不同標(biāo)簽的一些行為
mobile.js,應(yīng)該是一個(gè)事件庫,好像我并不關(guān)心。
page.js,核心代碼,即Page、App的定義所在。
report.js, 你所說的一切都能夠用作為你的呈堂證供 。
virtual_dom.js,一個(gè)virtual dom實(shí)現(xiàn)結(jié)合wcc使用,里面應(yīng)該還有component.css,也可能是叫weui
wa-wx.js,定義微信各種API以及WebView和Native的地方,和下面的WX有沖突。
wx.js,同上,但是略有不同。
wxJSBridge.js,Weixin JS Bridge
(引用 http://www.tuicool.com/articles/juquYfy

渲染流程

先發(fā)送一個(gè)CustomEvent(參考 https://developer.mozilla.org/zh-CN/docs/Web/API/CustomEvent/CustomEvent)。
這個(gè)event被下面的函數(shù)接受并處理

document.addEventListener("generateFuncReady", function (e) {
var generateFunc = e.detail.generateFunc; //就是我們$gwx函數(shù)生成的那個(gè)函數(shù)
wx.onAppDataChange && generateFunc && wx.onAppDataChange(function (e) { //如果不為空,則添加事件響應(yīng)函數(shù)如下
var i = generateFunc((0, d.getData)()); //調(diào)用這個(gè)函數(shù)生成界面(這個(gè)d從哪里來的?應(yīng)該是個(gè)頁面,因?yàn)镻age中有data變量)
if (i.tag = "body", e.options && e.options.firstRender){ //如果是第一次渲染
e.ext && ("undefined" != typeof e.ext.webviewId && (window.__webviewId__ = e.ext.webviewId), "undefined" != typeof e.ext.downloadDomain && (window.__downloadDomain__ = e.ext.downloadDomain)), v = f(i, !0), b = v.render(), b.replaceDocumentElement(document.body), setTimeout(function () {
wx.publishPageEvent(p, {}), r("firstRenderTime", n, Date.now()), wx.initReady && wx.initReady()
}, 0);
} else {
var o = f(i, !1), a = v.diff(o);
a.apply(b), v = o, document.dispatchEvent(new CustomEvent("pageReRender", {}));
}
})
})

這個(gè)函數(shù),先是獲取了e.detail。就是CustomEvent的第二個(gè)參數(shù),我們給的一個(gè)對(duì)象。
然后獲取了它的generateFunc這個(gè)函數(shù)就是我們前面定義的$gwx('index.wxml')的返回值(應(yīng)該還是一個(gè)函數(shù)),也就是我們的界面渲染函數(shù)。

如果wx.onAppDataChange不為空,generateFunc也不為空,那么就添加觸發(fā)函數(shù)給onAppDataChange。 在這個(gè)函數(shù)中,先調(diào)用generateFunc獲取了界面,i應(yīng)該是一顆dom樹。
給i定義tag為body。然后如果是第一次渲染,就。。。。
后面還有一些diff和apply,大體知道意思。。。但整體沒看太明白。

最后,git上給的demo沒有跑起來,跑出來是空白一片。

讀其他文件發(fā)現(xiàn),$gwx('test.wxml')必須給出正確的wxml文件名和路徑才能生成一個(gè)文件
然后調(diào)用這個(gè)函數(shù)就會(huì)生成一個(gè)json。

這種設(shè)計(jì)應(yīng)該是為了能在一個(gè)js文件中包含所有的wxml而設(shè)計(jì)的。

一個(gè)微信小程序Mina的兼容框架

還是上一個(gè)文章同樣的作者,在研究了微信的框架后,決定自己實(shí)現(xiàn)一個(gè)小程序的兼容框架。
這個(gè)框架在github https://github.com/phodal/winv

拜讀了這篇文章。閱讀了其中的代碼
目前這個(gè)框架只是一個(gè)簡(jiǎn)單的demo而已。

這是winv.js的代碼。 。。 額,之所以它起這個(gè)名字因?yàn)槭荕INA倒過來。。。惡趣味。
我加了一些注釋

window.eventPool = [];
window.globalData = {};
const winv = {
  parser() {

  },
  components: [{

  }],
  setTemplate(template){  //wxml的代碼傳到這里
    this.template = template;
  },
  appRun() {
    var template = this.template;
    var domJson = this.stringToDomJSON(template)[0];  //將wxml文件解析為一個(gè)json
    var dom = this.jsonToDom(domJson);  //將json轉(zhuǎn)義為自己的dom樹
/**
           微信為什么要這么干呢? 也許有幾個(gè)原因。
          一個(gè)是經(jīng)過一次轉(zhuǎn)化,可以有效的過濾掉那些它不喜歡的tag。只留下它允許的tag
         另一個(gè)是不是要處理一下{{}}這種數(shù)據(jù)標(biāo)簽?
**/
    document.getElementById('app').appendChild(dom);
    for (var event in window.eventPool) {
      window.eventPool[event]();
    }
  },
  Page (options) { //這是實(shí)現(xiàn)微信的那個(gè)Page函數(shù),是一個(gè)頁面管理。 option就是咱們創(chuàng)建的時(shí)候見的那個(gè)對(duì)象
    for (var option in options) { //遍歷這個(gè)對(duì)象所有的屬性和方法
      if ('on' === option.slice(0, 2)) { //如果是on開頭的,那就是一個(gè)事件。
        window.eventPool.push(options[option]); //都塞到eventPool里干嘛?有待商榷啊。。。。
      }
      if ('data' === option) { //如果是data,將值放到globalData里。 這樣寫似乎只支持一個(gè)頁面。。。
        window.globalData = options[option];
      }
    }
  },
  App (options) {
    for (var option in options) {
      if ('on' === option.slice(0, 2)) { //如果是事件, 同樣處理
        window.eventPool.push(options[option]);
      }
    }
  },
  getData: function updateData(key) { //獲取數(shù)據(jù)
    return window.globalData[key];
  },
  utils: { //幾個(gè)工具方法
    removeTemplateTag(str){
      return str.substr(2, str.length - 4);
    },
    isTemplateTag(string){  
      return /{{[a-zA-Z1-9]+}}/.test(string);
    }
  },
  stringToDomJSON(string){ //添加包裝后,將字符串轉(zhuǎn)成json
    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;
  },
  domParser(string){ //使用js自帶的DomParser將字符串轉(zhuǎn)成dom
    var parser = new DOMParser();
    return parser.parseFromString(string, 'text/xml');
  },
  nodeToJSON(node){ //一個(gè)遞歸算法,將原始的dom樹編程一個(gè)json
    // Code base on https://gist.github.com/sstur/7379870
    node = node || this;
    var obj = {
      nodeType: node.nodeType
    };
    if (node.tagName) { //如果tag名字存在,添加winv-前綴
      obj.tagName = 'winv-' + node.tagName.toLowerCase();
    } else if (node.nodeName) {
      obj.nodeName = node.nodeName;
    }
    if (node.nodeValue) {//如果有值,檢查是不是模板,是的話,就獲取數(shù)據(jù)進(jìn)行替換。
    //哎!這里還不支持if語句和for,任重道遠(yuǎn)啊。
      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]); //遞歸調(diào)用,處理子
      }
    }
    return obj;
  },
  jsonToDom(obj) //JSON轉(zhuǎn)回dom。額。。。react的diff在哪里? 這個(gè)算法比上react要差了啊。
  {
    // 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;
  }
};

export default winv;

使用方法

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Winv Demo</title>
    <script src="./dist/winv.js"></script>
    <link rel="stylesheet" href="./styles/weui.css">
</head>
<body>
<div id="app"></div>
</body>
<script>
var App = winv.App;
var Page = winv.Page;
App({
    onLaunch: function() {
        console.log('On Launch');
    }
});
Page({
    data: {
        motto: 'hello, world'
    },
    onLoad: function() {
        console.log('On Load');
    }
});
winv.setTemplate('<view class="container"><text class="user-motto">{{motto}}</text></view>')
winv.appRun();
</script>
</html>

還是很像wx的。 App、Page、wxml(template)

關(guān)于這個(gè)winx框架

總體來看,這個(gè)框架就是一個(gè)玩具框架,它向我們展示了微信小程序的處理流程,但距離真正產(chǎn)品還差很遠(yuǎn)。

其一: 只支持一個(gè)頁面。
其二: 頁面的所有事件處理都沒有整。
其三: 在運(yùn)行期進(jìn)行解析,程序的源代碼都包含在內(nèi)。(.wxml文件),不利于源碼的保護(hù)。

我昨天在這個(gè)項(xiàng)目貢獻(xiàn)了一些代碼,添加了{(lán){obj.name}}這種語法的支持。

我在考慮要不要在這個(gè)框架上做下去。。是否自己搭建一個(gè)框架。把react包裝一下來實(shí)現(xiàn)?
還是只用它的webpack,自己寫diff和apply?

待讀未讀文章列表

http://www.tuicool.com/articles/aQNbUzI

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容