Code coverage report for lib/App.js

Statements: 74.12% (63 / 85)      Branches: 70% (14 / 20)      Functions: 56.25% (9 / 16)      Lines: 74.12% (63 / 85)      Ignored: none     

All files » lib/ » App.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399          1 1   1 1 1 1 1 1                 1   9 8   7             7                                       1                                                                                                           1                                                                                                               1         4   4   4 1         3   3                       3 3                 3         1   1                   2         2     2         1   1             1 1                           1 1 1                               1   5   5         1   1     4                         1         4   4   4   4   4   4   4 4     4   4                         1                                                           1         6   6   6         4               2     6       1  
/**
 * @file App
 * @author Leon(leon@outlook.com)
 */
 
var Promise = require('es6-promise').Promise;
var u = require('underscore');
 
var invariant = require('./util/invariant');
var locator = require('./locator');
var events = require('./events');
var Router = require('./Router');
var env = require('./env');
var url = require('./url');
 
/**
 * App
 *
 * @constructor
 * @param {!Object} options 参数
 * @param {Array.Object} options.routes 路由配置
 */
function App(options) {
 
    invariant(options, 'App need options');
    invariant(options.routes, 'App need routes');
 
    u.extend(this, options);
 
    /**
     * 路由器
     *
     * @member {module:Router}
     */
    this.router = new Router(this.routes);
 
}
 
/**
 * 启动App
 *
 * 此方法只能在client端使用,并且不会调用Page的`getInitialState()`方法加载资源
 * 因此,只适合同构状态下,有同步的数据支持时使用
 *
 *
 * @public
 *
 * @param {*} initialState 初始状态
 *
 * @fires module:events~app-bootstrap
 * @fires module:events~app-ready
 *
 * @return {Promise}
 */
App.prototype.bootstrap = function (initialState) {
 
    invariant(env.isClient, 'app-should bootstrap on client only');
 
    /**
     * @event module:events~app-bootstrap
     */
    events.emit('app-bootstrap');
 
    // 启动locator侦听浏览器的前进/后退事件
    locator.start().on('redirect', u.bind(this.onLocatorRedirect, this));
 
    var me = this;
 
    var request = url.parse(location.href);
    var route = this.route(request);
 
    return route
 
        ? Promise.reject({
            status: 404
        })
 
        : me
 
            // 加载页面模块
            .loadPage(route.page)
 
            // 通过初始数据直接启动页面
            .then(function (Page) {
 
                // 由于这里是客户端的逻辑,那么就记录一下当前页面
                var page = me.page = new Page(initialState);
 
                page.render(document.getElementById(me.main));
 
                /**
                 * @event module:events~app-ready
                 */
                events.emit('app-ready');
 
            });
 
};
 
/**
 * 当client端页面地址发生变化时的处理函数
 *
 * @private
 *
 * @param {!string} path  当前页面的path
 * @param {Object}  query query参数
 * @return {Promise}
 */
App.prototype.onLocatorRedirect = function (path, query) {
 
    var request = {
        path: path,
        query: query
    };
 
    var me = this;
 
    // 执行请求处理
    return me
        .execute(request)
        .then(function (result) {
 
            // 我们不需要因为要考虑页面间替换时,而去复用同一个Page实例
            // 只要直接生成新的页面实例,渲染即可
            // react会帮我们做对同一种ReactComponent渲染优化
 
            // 如果当前有一个正在展现的页面,
            // 那么把它销毁掉
            if (me.page) {
                me.page.dispose();
            }
 
            // 由于这里是客户端的逻辑,那么就记录一下当前页面
            var page = me.page = result.page;
 
            page.render(document.getElementById(me.main));
 
            /**
             * @event module:events~app-page-switch-succeed
             */
            events.emit('app-page-switch-succeed');
 
        });
 
};
 
 
/**
 * 处理一个请求
 *
 * @param {!Object}  request      请求
 * @param {?*}       initialState 初始数据状态
 * @return {Promise}
 *
 * @fires module:events~app-request
 * @fires module:events~app-get-initial-state
 * @fires module:events~app-get-initial-state-succeed
 * @fires module:events~app-get-initial-state-failed
 * @fires module:events~app-response-in-json
 * @fires module:events~app-response-in-html
 * @fires module:events~app-page-loaded
 * @fires module:events~app-page-bootstrap
 * @fires module:events~app-page-bootstrap-succeed
 */
App.prototype.execute = function (request) {
 
    /**
     * @event module:events~app-request
     */
    events.emit('app-request');
 
    var route = this.route(request);
 
    if (!route) {
        return Promise.reject({
            status: 404
        });
    }
 
    var page;
 
    return this
 
        // 加载页面模块
        .loadPage(route.page)
 
        // 加载初始化数据
        .then(function (Page) {
 
            // 我们不需要因为要考虑页面间替换时,而去复用同一个Page实例
            // 只要直接生成新的页面实例,渲染即可
            // react会帮我们做对同一种ReactComponent渲染优化
 
            page = new Page();
            return page.getInitialState(request);
 
        })
 
        // 渲染视图
        .then(function (state) {
 
            // 这里看一下是不是server端在处理ajax请求
            // 这种情况应该返回当前的数据即可
            if (env.isServer && request.xhr) {
 
                /**
                 * @event module:events~app-response-in-json
                 */
                events.emit('app-response-in-json');
 
                return {
                    state: state,
                    route: route
                };
 
            }
 
            /**
             * @event module:events~app-response-in-html
             */
            events.emit('app-response-in-html');
 
            /**
             * @event module:events~app-page-bootstrap
             */
            events.emit('app-page-bootstrap');
 
            // 初始化页面,触发页面的第一次数据剪裁
            page.init(state);
 
            /**
             * @event module:events~app-page-bootstrap-succeed
             */
            events.emit('app-page-bootstrap-succeed');
 
            return {
                page: page,
                route: route
            };
 
        })
        ['catch'](function (error) {
            events.emit('app-execute-error', error);
            throw error;
        });
 
};
 
/**
 * 根目录路径
 *
 * @public
 *
 * @param {!string} basePath 根目录路径
 *
 * @return {module:App}
 */
App.prototype.setBasePath = function (basePath) {
    this.basePath = basePath;
    return this;
};
 
/**
 * 加载Page类
 *
 * @protected
 *
 * @param {!string} page 页面模块路径
 *
 * @return {Promise}
 *
 * @fires module:events~app-page-loaded
 * @fires module:events~app-load-page-on-server
 * @fires module:events~app-load-page-on-client
 */
App.prototype.loadPage = function (page) {
 
    var pool = this.pool;
 
    if (pool && pool[page]) {
 
        /**
         * @event module:events~app-page-loaded
         */
        events.emit('app-page-loaded');
 
        return Promise.resolve(pool[page]);
    }
 
    return env.isServer ? this.resolveServerModule(page) : this.resolveClientModule(page);
 
};
 
/**
 * 服务器端加载Page模块
 *
 * @private
 *
 * @param {string} moduleId Page模块id
 *
 * @return {Promise}
 */
App.prototype.resolveServerModule = function (moduleId) {
 
    /**
     * @event module:events~app-load-page-on-server
     */
    events.emit('app-load-page-on-server', moduleId);
 
    var basePath = this.basePath;
 
    invariant(basePath, 'ei need a basePath to resolve your page');
 
    var path = basePath + '/' + moduleId;
 
    var Page = require(path);
 
    var pool = this.pool;
 
    Eif (!pool) {
        pool = this.pool = {};
    }
 
    pool[moduleId] = Page;
 
    return Promise.resolve(Page);
 
};
 
/**
 * 在客户端上加载Page模块
 *
 * @private
 *
 * @param {string} moduleId Page模块id
 *
 * @return {Promise}
 */
App.prototype.resolveClientModule = function (moduleId) {
 
    /**
     * @event module:events~app-load-page-on-client
     */
    events.emit('app-load-page-on-client');
 
    return new Promise(function (resolve, reject) {
 
        window.require([moduleId], function (Page) {
            resolve(Page);
        });
 
    });
 
};
 
/**
 * 路由
 *
 * @protected
 *
 * @param {!Object} request 请求
 *
 * @return {?Object}
 *
 * @fires module:events~app-route-succeed
 * @fires module:events~app-route-succeed
 * @fires module:events~app-route-failed
 */
App.prototype.route = function (request) {
 
    /**
     * @event module:events~app-route
     */
    events.emit('app-route');
 
    var config = this.router.route(request);
 
    if (config) {
 
        /**
         * @event module:events~app-route-succeed
         */
        events.emit('app-route-succeed');
 
    }
    else {
 
        /**
         * @event module:events~app-route-failed
         */
        events.emit('app-route-failed');
    }
 
    return config;
 
};
 
module.exports = App;