45 Star 91 Fork 23

taiji1986 / freemina

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README
Apache-2.0

#FreeMina: An open mina compatible framework for running in browser or webview. ##一个兼容微信小程序Mina框架的开源框架 从小程序的设计来看,微信正走向封闭生态。我们开发的微信小程序很难在其他地方使用。

最近一段时间,我花了大量精力来查找相关资料。包括React、React Native。我本来不算一个JS程序员,但也为此学习或了解了Bable WebPatch,ES2015等等一系列我原本不熟悉的内容。

真正有巨大收获的是 @phodal 大神发的五篇文章, 它还做了一个for fun的框架winv。 仔细学习了这个框架,并提交了一个补丁,改善了一点点小功能。昨天晚上,我就在考虑到底是在这个框架上修改,还是自己开一个。

反复思量,觉得如果要改进,那么基本上要重写所有的代码,和重开一个无益。 一个项目初始的架构很重要,差的架构让人难以提起改进的兴趣。另外,大神有6个月没有更新项目了。

总之,再次感谢 @phodal 。

FreeMina项目地址 https://github.com/taiji1985/freemina

##设计目的和计划

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

当然这个目标从现在看有些“宏伟”了。

要做的工作:

  • 解析wxml dom,并生成相应的html。 这一点, @phodal 已经做了大量的贡献。但性能需要改进一下。 另外,我学习了facebook的diff算法,准备在今后的改进中加入。

  • wxml中{{}}格式数据的处理。我给winv这个项目添加了 {{obj.name}}这样的支持。 但还缺少if和for这两个非常重要的环节。

  • 事件系统。 目前已经实现了一些,但还远远没有完成。但大体的设计已经有了。

  • 打包等项目工具 。 微信小程序将所有的文件全部打包在一起。这个并非简单的用 webpack进行打包。还对程序作了一定的预处理。对于将xml生成为js的做法,我觉得还需要考虑,到底需不需要这么做。 json的处理相对简单,require进去就好。

    我实现打包工具的思路是

    1. 首先给Page打包,给添加上两个参数,把xml和文件名一起传给Page函数。
    2. 使用webpack等工具打包到一起。
  • App支持。 wx中有很多函数,没有App的帮助是无法实现的。 这一部分的做法

    1. 在web中能用web试下你的用web实现,不能实现的暂不实现。
    2. 在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.opt eventHandler被调用时,给出了一个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_'

然后查找this.opt就是你传进来的那个对象是否有onLoad的声明。 如果有,则调用,如果没有,则尝试在this中查找,如果还是没有,就真的没有了。

下面说比较重要的渲染问题 ##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 = '<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中的渲染函数


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

  1. 事件机制还不完善。
  2. 渲染的diff和apply的实现。
  3. setData这个核心的函数实现。
  4. 参照微信文档进行界面完全兼容
  5. 完善wx的API函数(可能会用Android实现) IOS就算了,听说基于WebView的通不过审核! 另外,我没钱买Mac。。。

##最后 感谢您的阅读。如果有可能请贡献些代码。。。

Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright {yyyy} {name of copyright owner} Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

简介

一个微信小程序框架的兼容框架,可以运行于浏 览器和webview中。后期会支持将自己的App打包成android等平台应用 展开 收起
微信
Apache-2.0
取消

发行版

暂无发行版

贡献者

全部

近期动态

加载更多
不能加载更多了
微信
1
https://gitee.com/yangtf/freemina.git
git@gitee.com:yangtf/freemina.git
yangtf
freemina
freemina
master

搜索帮助

14c37bed 8189591 565d56ea 8189591