sprite框架介绍

sprite框架是我自己编写的第一个框架,一个移动端单页web应用轻量级框架。

目前是面向我们公司的后端研发人员的,编写初衷想替代配置繁多,并且强烈依赖node进行调试打包和发布的webpack移动应用框架。

sprite框架的特点在于完全使用es5语法,不依赖于任何服务器,可以放在任意服务器上进行开发和调试,配置很少只需修改一个文件,并且不用打包,开发完成可以直接发布。

技术栈:
require全家桶+vue+vue-router+mint-ui(饿了么移动端组件)+axios+jquery+自己实现的vue路由递归解析器

require全家桶 负责全应用模块化,上面提到的所有外部组件包括jquery和lodash等,以及开发人员写的业务vue对象,都统一由require管控加载。

vue和vue-router 分别负责业务逻辑和页面路由,实现应用整体单页化。

axios 用于前后端交互,api调用。

jquery 因为dom选择器很强大,所以加载到框架中作为辅助开发的组件,当然也可用来代替axios。

我自己写的vue路由解析器,用来递归解析全应用页面的注册文件,最终生成整个应用的vue路由对象。

demo应用下载地址:

点击下载demoApp

解压后,在应用根目录下使用shell执行命令:node server.js即可运行(需要安装node.js)

应用目录结构:

主要代码:

config/pageRegister.js :

开发者需要将应用的所有页面模块在该文件中注册

/**
 * 该文件用于注册本应用的所有页面,包括嵌套的页面
 * PAGE_ARR中的每个对象对应一个应用的页面
 * 页面对象的属性说明:
 * {
        id:             [必填] 页面的唯一标识,要求在本文件内不能重复,
        vueJsPath:      [必填] 用于渲染该页面的vue对象所在的js文件的目录地址,要求以module/开头,
        vueRoute:       [必填] 该页面对应的vue路由路径,
        vueRouteName:   [必填] 该页面对应的vue路由名称,在vue路由跳转时使用,
        childrenIds:    [可选] 如果该页面有子页面,则childrenIds为子页面的id数组,
        isIndex:        [可选] 该页面是否为首页,是首页则配置为true,否则可以不配置
 * }
 */

window.PAGE_ARR = [{
        id: "zero",
        vueJsPath: "modules/page0/page0",
        vueRoute: "/zero",
        vueRouteName: "zero",
        isIndex: true
    },
    {
        id: "one",
        vueJsPath: "modules/page1/page1",
        vueRoute: "/one",
        vueRouteName: "one",
        childrenIds: ['page1_1', 'page1_2']
    },
    {
        id: "page1_1",
        vueJsPath: "modules/page1/page1_1/page11",
        vueRoute: "/page1_1",
        vueRouteName: "page1_1",
        childrenIds: ['page1_1_1']
    },
    {
        id: "page1_2",
        vueJsPath: "modules/page1/page1_2/page12",
        vueRoute: "/page1_2",
        vueRouteName: "page1_2"
    },
    {
        id: "page1_1_1",
        vueJsPath: "modules/page1/page1_1/page1_1_1/page111",
        vueRoute: "/page1_1_1",
        vueRouteName: "page1_1_1"
    },
    {
        id: "two",
        vueJsPath: "modules/page2/page2",
        vueRoute: "/two",
        vueRouteName: "two"
    }
];

config/pubConfig.js:

框架的主要配置,所有外部的组件库以及本应用的页面模块,都在此配入require的paths中,以此配置来使用requireJs达到应用完全模块化的目的。

其中,外部的组件库由于其地址是固定不变的,可以直接配置,而由于应用中的页面模块js文件地址是由开发者创建的,因此需要使用上面介绍的pageRegister.js,通过pageRegister.js注册的页面模块,可以获取到requireJs所需要的js文件地址。

(function(require) {
    var requireConfig = {
        baseUrl: '/',
        paths: {
            vue: 'lib/vue',
            routeParse: 'config/routeParse',
            vueRouter: 'lib/vue-router',
            text: 'lib/require-text',
            css: 'lib/require-css',
            util: 'public/util/util',
            MINT: 'lib/mint/index',
            jquery: 'lib/jquery',
            axios: 'lib/axios'
        },
        shim: {
            MINT: {
                deps: ['vue', 'css!lib/mint/style.css']
            }
        }
    };
    var requir_default_arr = ['vue', 'vueRouter', 'MINT', 'util', 'routeParse', 'jquery', 'axios'];
    var require_page_arr = [];
    for (var index in window.PAGE_ARR) {
        requireConfig.paths[PAGE_ARR[index].id] = PAGE_ARR[index].vueJsPath;
        require_page_arr.push(PAGE_ARR[index].id);
    }
    var all_modules_arr = requir_default_arr.concat(require_page_arr);

    window.REQUIRE_MODULES = {
        all: all_modules_arr,
        default: requir_default_arr
    };

    require.config(requireConfig);
})(require)

config/routeParse.js:

用于将pageRegister.js的内容解析为vue路由数组对象,由于支持页面中嵌套子页面,因此是一个递归解析过程

define(function() {
    return {
        getVueRoute: function(_arguments) {
            var self = this;
            var routes = [];
            //获取arguments参数数组,从第一个非组件的位置开始获取,之后的所有参数都认为是vue页面
            var pages = [];
            for (var i = REQUIRE_MODULES.default.length; i < _arguments.length; ++i) {
                var pageInit = _arguments[i];
                pages.push(pageInit());
            }
            //子节点列表
            var childrenIdList = [];
            for (var i = 0; i < PAGE_ARR.length; ++i) {
                PAGE_ARR[i].index = i;
                PAGE_ARR[i].component = pages[i];
                if (PAGE_ARR[i].childrenIds && PAGE_ARR[i].childrenIds instanceof Array) {
                    childrenIdList = childrenIdList.concat(PAGE_ARR[i].childrenIds);
                }
            }
            //构建子节点映射,通id可以获取到这个子节点,并且在原数组中标记子节点的isChild为true
            var childrenObjs = {};
            for (var i = 0; i < childrenIdList.length; ++i) {
                for (var j = 0; j < PAGE_ARR.length; ++j) {
                    if (PAGE_ARR[j].id === childrenIdList[i]) {
                        PAGE_ARR[j].isChild = true;
                        var key = PAGE_ARR[j].id;
                        childrenObjs[key] = PAGE_ARR[j];
                    }
                }
            }

            //标志是否存在首页
            var indexExists = false;
            //把PAGE_ARR解析为vue的路由数组
            for (var i = 0; i < PAGE_ARR.length; ++i) {
                var page = PAGE_ARR[i];
                if (!page.isChild) {
                    //页面对应vue组件
                    var pageComponent = page.component;
                    //路由跳转时用到的名称
                    var pageName = page.vueRouteName;
                    //路由路径
                    var pagePath = page.vueRoute;

                    //判断是否是首页
                    if (!indexExists && page.isIndex) {
                        routes.push({
                            path: '/',
                            component: pageComponent
                        });
                        //每个应用只有一个首页
                        indexExists = true;
                    }
                    var routeObj = {};
                    routeObj.path = pagePath;
                    routeObj.component = pageComponent;
                    routeObj.name = pageName;
                    //递归添加子路由
                    self.addChildrenRoute(page, routeObj, childrenObjs);
                    //将当前页面增加到vue的路由对象中
                    routes.push(routeObj);
                }
            }

            return routes;
        },
        /**
         * 递归解析页面子路由
         * page:当前页面
         * routeObj:当前页面对应的路由对象
         * childrenObjsMap:子页面对象映射表
         */
        addChildrenRoute: function(page, routeObj, childrenObjsMap) {
            var self = this;
            //如果当前页面存在子页面,则将子页面加入当前页面的子路由
            if (page.childrenIds && page.childrenIds instanceof Array) {
                routeObj.children = [];
                page.childrenIds.map(function(childId, index) {
                    //,使用子页面的id通过子页面对象映射表获取子页面对象
                    var child = childrenObjsMap[childId];
                    var childrouteObj = {};
                    childrouteObj.component = child.component;
                    childrouteObj.name = child.vueRouteName;
                    childrouteObj.path = child.vueRoute.substr(child.vueRoute.indexOf('/') + 1);
                    //递归添加子页面的嵌套子页面
                    self.addChildrenRoute(child, childrouteObj, childrenObjsMap);
                    routeObj.children.push(childrouteObj);
                });
            }
            //递归出口
            else {
                return;
            }
        }
    }
})

index.html:

单页应用的主体html,引入框架所需的requiraJs以及其他js、css文件

<html>

<head>
    <!-- 用来禁止浏览器缓存,使得每次请求index.html都从服务器下载最新版本 -->
    <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
    <meta http-equiv="Pragma" content="no-cache" />
    <meta http-equiv="Expires" content="0" />
    <!-- 标识html使用UTF-8编码 -->
    <meta charset="utf-8">
    <!-- 让viewport的宽度等于物理设备上的真实分辨率,不允许用户缩放 -->
    <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <link rel="stylesheet" href="public/css/style.css">
    <!-- 引入require -->
    <script src="lib/require.js"></script>
    <!-- 引入本应用的业务页面注册文件 -->
    <script src="config/pageRegister.js"></script>
    <!-- 引入框架的配置文件 -->
    <script src="config/pubConfig.js"></script>
    <!-- 应用主入口 -->
    <script src="main.js"></script>
</head>

<body>
    <div id="app">
        <!-- 主路由出口 -->
        <!-- 路由匹配到的组件将渲染在这里 -->
        <div class="main__content">
            <router-view></router-view>
        </div>
    </div>
</body>

</html>

main.js:

应用的主入口,加载所有的外部组件库,并根据开发者配置的pageRegister.js动态生成vue路由,解析路由时,使用框架提供的vue路由递归解析器,因此支持在页面中嵌套子页面。获得vue所需的route数组后,将vue对象挂载到index.html中的id为’app’的div上,将该div作为应用页面的主要容器,应用的所有页面模块都将在这个div的<router-view></router-view>中渲染和切换

require(REQUIRE_MODULES.all, function(Vue, VueRouter, mintUI, util, routeParse, $, axios) {
    //将axios输出到全局作用域
    window.axios = axios;
    //使用vue路由组件
    Vue.use(VueRouter);
    //使用饿了么移动端组件mint-ui
    Vue.use(mintUI);

    //根据pageRegister.js中的配置生成vue路由数组
    var routes = routeParse.getVueRoute(arguments);
    //生成VueRouter对象
    var router = new VueRouter({
        routes: routes
    });

    //挂载主vue对象
    app = new Vue({
        el: '#app',
        router
    });

});

api.js:

用于注册应用中使用到的api地址,使用require(‘api’)即可获取

define(function() {
    APP_CONIFG_HOST = 'http://xxx.com/api/';
    return {
        apiDemo1: APP_CONIFG_HOST + '/apiDemo1.do',
        apiDemo2: APP_CONIFG_HOST + '/apiDemo2.do'
    }
})

public/util/util.js:

应用中的公共代码可以写在util.js中,使用require(‘util’)即可获取

define(function() {
    var util = {
        test: function() {
            console.log('this is a function in util.js');
        }
    }

    return util;
})

modules/page1/page1.js:

页面模块js文件,文件名可由开发者自己定义,本文件为开发者编写的业务代码,生成一个用来渲染页面的vue对象

define(function(require, exports, module) {
    var util = require('util');
    var util = require('api');
    //加载vue对象对应的模板文件
    var tpl = require('text!modules/page1/page1.html');

    return function() {
        //使用vue的语法定义页面的vue对象,业务逻辑都在该对象上实现
        var page = {
            template: tpl,
            data: function() {
                return {}
            },
            created: function() {

            },
            methods: {
                test: function() {
                    console.log(test)
                }
            }
        }
        return page;
    }

})

modules/page1/page1.html:

该文件为vue对象使用的模板,采用vue的语法编写,在该文件中可以使用mint-ui中的所有组件,当然也可以使用自己定义的vue组件

<div style="margin-top: 32px;">
    <mt-header fixed title="页面1"></mt-header>
    <router-link style="font-size:16px;margin-left:8px;" to="/"> <回到首页 </router-link>
    <!-- <mt-button size="normal" type="primary" @click="backIndex">回到首页</mt-button> -->
    <br>
    <div style="text-align: center;"><img src="public/images/test.jpg"></div>
    <div style="text-align: center;color: #9e9e9e;"><span>以上是public/images/目录下的图片示例</span></div>
    <div style="margin-top:16px;">
        <router-view></router-view>
    </div>
    <mt-tabbar v-model="selected" fixed>
        <mt-tab-item id="1" @click.native="showPage11">
            <img slot="icon" src="./././public/images/test.jpg"> 子菜单1
        </mt-tab-item>
        <mt-tab-item id="2" @click.native="showPage12">
            <img slot="icon" src="./././public/images/test.jpg"> 子菜单2
        </mt-tab-item>
    </mt-tabbar>
</div>

发表评论

电子邮件地址不会被公开。 必填项已用*标注