diff --git a/example/index.js b/example/index.js index 117d05e3..4e5a17aa 100644 --- a/example/index.js +++ b/example/index.js @@ -1,3 +1,4 @@ +var split = require('split-require') var css = require('sheetify') var choo = require('../') @@ -15,4 +16,6 @@ app.route('#active', require('./views/main')) app.route('#completed', require('./views/main')) app.route('*', require('./views/main')) +app.experimentalAsyncRoute('/async', () => split('./views/async')) + module.exports = app.mount('body') diff --git a/example/package.json b/example/package.json index fdd34598..3e82b553 100644 --- a/example/package.json +++ b/example/package.json @@ -15,7 +15,8 @@ "todomvc-common": "^1.0.3" }, "devDependencies": { - "bankai": "^9.0.0-rc6", + "bankai": "9.10.4", + "split-require": "^3.1.0", "standard": "^9.0.1" } } diff --git a/example/views/async.js b/example/views/async.js new file mode 100644 index 00000000..d31b5455 --- /dev/null +++ b/example/views/async.js @@ -0,0 +1,11 @@ +var html = require('bel') // cannot require choo/html because it's a nested repo + +module.exports = mainView + +function mainView (state, emit) { + return html` + + Async route + + ` +} diff --git a/index.js b/index.js index 79a7c525..1838a45d 100644 --- a/index.js +++ b/index.js @@ -42,6 +42,8 @@ function Choo (opts) { this._hasWindow = typeof window !== 'undefined' this._createLocation = nanolocation this._cache = opts.cache + this._asyncProxy = null // proxy for async routes + this._asyncRoutes = {} this._loaded = false this._stores = [] this._tree = null @@ -76,10 +78,66 @@ function Choo (opts) { Choo.prototype.route = function (route, handler) { assert.equal(typeof route, 'string', 'choo.route: route should be type string') - assert.equal(typeof handler, 'function', 'choo.handler: route should be type function') + assert.equal(typeof handler, 'function', 'choo.route: handler should be type function') this.router.on(route, handler) } +// Register a route to be loaded asynchronously. +Choo.prototype.experimentalAsyncRoute = function (route, loader) { + assert.equal(typeof route, 'string', 'choo.asyncRoute: asyncRoute should be type string') + assert.equal(typeof loader, 'function', 'choo.asyncRoute: loader should be type function') + + var IDLE = 0 + var LOADING = 1 + var LOADED = 2 + + var loadingState = IDLE + var renderRoute = null + var view = null + var self = this + + this.route(route, function (state, emit) { + if (!self._asyncProxy) self._initAsyncProxy() + // Begin loading the bundle on the first call + if (loadingState === IDLE) { + emit('choo:async-route-start', state.route) + renderRoute = state.route + loadingState = LOADING + + var p = loader(onload) + assert(p && p.then, 'choo.asyncRoute: async route should return a Promise') + p.then(onload.bind(null, null), onload) + return self._asyncProxy + } else if (loadingState === LOADING) { + return self._asyncProxy + } else { + // loadingState === LOADED + return view(state, emit) + } + + function onload (err, _view) { + if (err) { + emit('error', err) + loadingState = IDLE + return + } + + emit('choo:async-route-end', renderRoute, _view) + loadingState = LOADED + view = _view + + // Only rerender if we are still on the same route + if (state.route === renderRoute) emit('render') + } + }) +} + +Choo.prototype._initAsyncProxy = function () { + var tagName = this._tree ? this._tree.nodeName : 'body' + this._asyncProxy = document.createElement(tagName) + this._asyncProxy.isSameNode = function () { return true } +} + Choo.prototype.use = function (cb) { assert.equal(typeof cb, 'function', 'choo.use: cb should be type function') var self = this