简年2:微信小程序的一个web实现(浏览器Chrome运行)

自从接触微信小程序以来,就希望能把小程序放到web中运行,但自己对VirtualNode,React等都不熟悉,只能慢慢学习。

这几天常使用React-Native做一个小的app,练手。但遇到了很多困难。 比如android很多组件不全的问题,全部需要git,npm去找,然后自己写本地模块。。。 开发效率可以说是低爆了。

读取的资料:

笔记1 让你的「微信小程序」运行在 Chrome 浏览器上

其中介绍了它对微信源代码的分析。
给出了预览
GitHub: https://github.com/phodal/weapp-webdemo
预览: http://weapp.phodal.com/

微信小程序的三大组件的处理方法

js wxml 和 wcss

js被封装为一个包

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

wxml被转义为function,即一个js文件

这个应该类似是react的jsx(拼写忘了!!!)的。
使用 "微信web开发者工具\package.nw\app\dist\weapp\onlinevendor"目录中的wcc可以完成这一任务
例子:

/*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被转义为css

使用上述目录下的wcsc文件可以将其转义为一个css文件。
$gwx函数的主要做用就是构造界面
在下面的代码中被调用

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

微信类库在哪里

在WXWebview.js中,不过这个文件是打包过的文件。

拆分开是这样的
define.js,这里就是定义AMD模块化的地方
exparser.js,用于转换WXML标签到HTML标签
exparser-behvaior.js,定义不同标签的一些行为
mobile.js,应该是一个事件库,好像我并不关心。
page.js,核心代码,即Page、App的定义所在。
report.js, 你所说的一切都能够用作为你的呈堂证供 。
virtual_dom.js,一个virtual dom实现结合wcc使用,里面应该还有component.css,也可能是叫weui
wa-wx.js,定义微信各种API以及WebView和Native的地方,和下面的WX有冲突。
wx.js,同上,但是略有不同。
wxJSBridge.js,Weixin JS Bridge
(引用 http://www.tuicool.com/articles/juquYfy

渲染流程

先发送一个CustomEvent(参考 https://developer.mozilla.org/zh-CN/docs/Web/API/CustomEvent/CustomEvent)。
这个event被下面的函数接受并处理

document.addEventListener("generateFuncReady", function (e) {
var generateFunc = e.detail.generateFunc; //就是我们$gwx函数生成的那个函数
wx.onAppDataChange && generateFunc && wx.onAppDataChange(function (e) { //如果不为空,则添加事件响应函数如下
var i = generateFunc((0, d.getData)()); //调用这个函数生成界面(这个d从哪里来的?应该是个页面,因为Page中有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", {}));
}
})
})

这个函数,先是获取了e.detail。就是CustomEvent的第二个参数,我们给的一个对象。
然后获取了它的generateFunc这个函数就是我们前面定义的$gwx('index.wxml')的返回值(应该还是一个函数),也就是我们的界面渲染函数。

如果wx.onAppDataChange不为空,generateFunc也不为空,那么就添加触发函数给onAppDataChange。 在这个函数中,先调用generateFunc获取了界面,i应该是一颗dom树。
给i定义tag为body。然后如果是第一次渲染,就。。。。
后面还有一些diff和apply,大体知道意思。。。但整体没看太明白。

最后,git上给的demo没有跑起来,跑出来是空白一片。

读其他文件发现,$gwx('test.wxml')必须给出正确的wxml文件名和路径才能生成一个文件
然后调用这个函数就会生成一个json。

这种设计应该是为了能在一个js文件中包含所有的wxml而设计的。

一个微信小程序Mina的兼容框架

还是上一个文章同样的作者,在研究了微信的框架后,决定自己实现一个小程序的兼容框架。
这个框架在github https://github.com/phodal/winv

拜读了这篇文章。阅读了其中的代码
目前这个框架只是一个简单的demo而已。

这是winv.js的代码。 。。 额,之所以它起这个名字因为是MINA倒过来。。。恶趣味。
我加了一些注释

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文件解析为一个json
    var dom = this.jsonToDom(domJson);  //将json转义为自己的dom树
/**
           微信为什么要这么干呢? 也许有几个原因。
          一个是经过一次转化,可以有效的过滤掉那些它不喜欢的tag。只留下它允许的tag
         另一个是不是要处理一下{{}}这种数据标签?
**/
    document.getElementById('app').appendChild(dom);
    for (var event in window.eventPool) {
      window.eventPool[event]();
    }
  },
  Page (options) { //这是实现微信的那个Page函数,是一个页面管理。 option就是咱们创建的时候见的那个对象
    for (var option in options) { //遍历这个对象所有的属性和方法
      if ('on' === option.slice(0, 2)) { //如果是on开头的,那就是一个事件。
        window.eventPool.push(options[option]); //都塞到eventPool里干嘛?有待商榷啊。。。。
      }
      if ('data' === option) { //如果是data,将值放到globalData里。 这样写似乎只支持一个页面。。。
        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) { //获取数据
    return window.globalData[key];
  },
  utils: { //几个工具方法
    removeTemplateTag(str){
      return str.substr(2, str.length - 4);
    },
    isTemplateTag(string){  
      return /{{[a-zA-Z1-9]+}}/.test(string);
    }
  },
  stringToDomJSON(string){ //添加包装后,将字符串转成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将字符串转成dom
    var parser = new DOMParser();
    return parser.parseFromString(string, 'text/xml');
  },
  nodeToJSON(node){ //一个递归算法,将原始的dom树编程一个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) {//如果有值,检查是不是模板,是的话,就获取数据进行替换。
    //哎!这里还不支持if语句和for,任重道远啊。
      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) //JSON转回dom。额。。。react的diff在哪里? 这个算法比上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)

关于这个winx框架

总体来看,这个框架就是一个玩具框架,它向我们展示了微信小程序的处理流程,但距离真正产品还差很远。

其一: 只支持一个页面。
其二: 页面的所有事件处理都没有整。
其三: 在运行期进行解析,程序的源代码都包含在内。(.wxml文件),不利于源码的保护。

我昨天在这个项目贡献了一些代码,添加了{{obj.name}}这种语法的支持。

我在考虑要不要在这个框架上做下去。。是否自己搭建一个框架。把react包装一下来实现?
还是只用它的webpack,自己写diff和apply?

待读未读文章列表

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,723评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,485评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,998评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,323评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,355评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,079评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,389评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,019评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,519评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,971评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,100评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,738评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,293评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,289评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,517评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,547评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,834评论 2 345

推荐阅读更多精彩内容