Immutable state on SSR (toString) (#649)
* Init stores with state * Reset state.components on toString
This commit is contained in:
@@ -42,7 +42,7 @@ function Choo (opts) {
|
|||||||
this._hasWindow = typeof window !== 'undefined'
|
this._hasWindow = typeof window !== 'undefined'
|
||||||
this._cache = opts.cache
|
this._cache = opts.cache
|
||||||
this._loaded = false
|
this._loaded = false
|
||||||
this._stores = []
|
this._stores = [ondomtitlechange]
|
||||||
this._tree = null
|
this._tree = null
|
||||||
|
|
||||||
// state
|
// state
|
||||||
@@ -66,11 +66,13 @@ function Choo (opts) {
|
|||||||
|
|
||||||
// listen for title changes; available even when calling .toString()
|
// listen for title changes; available even when calling .toString()
|
||||||
if (this._hasWindow) this.state.title = document.title
|
if (this._hasWindow) this.state.title = document.title
|
||||||
this.emitter.prependListener(this._events.DOMTITLECHANGE, function (title) {
|
function ondomtitlechange (state) {
|
||||||
assert.equal(typeof title, 'string', 'events.DOMTitleChange: title should be type string')
|
self.emitter.prependListener(self._events.DOMTITLECHANGE, function (title) {
|
||||||
self.state.title = title
|
assert.equal(typeof title, 'string', 'events.DOMTitleChange: title should be type string')
|
||||||
if (self._hasWindow) document.title = title
|
state.title = title
|
||||||
})
|
if (self._hasWindow) document.title = title
|
||||||
|
})
|
||||||
|
}
|
||||||
timing()
|
timing()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,7 +103,7 @@ Choo.prototype.start = function () {
|
|||||||
var self = this
|
var self = this
|
||||||
if (this._historyEnabled) {
|
if (this._historyEnabled) {
|
||||||
this.emitter.prependListener(this._events.NAVIGATE, function () {
|
this.emitter.prependListener(this._events.NAVIGATE, function () {
|
||||||
self._matchRoute()
|
self._matchRoute(self.state)
|
||||||
if (self._loaded) {
|
if (self._loaded) {
|
||||||
self.emitter.emit(self._events.RENDER)
|
self.emitter.emit(self._events.RENDER)
|
||||||
setTimeout(scrollToAnchor.bind(null, window.location.hash), 0)
|
setTimeout(scrollToAnchor.bind(null, window.location.hash), 0)
|
||||||
@@ -142,7 +144,7 @@ Choo.prototype.start = function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this._setCache(this.state)
|
this._setCache(this.state)
|
||||||
this._matchRoute()
|
this._matchRoute(this.state)
|
||||||
this._stores.forEach(function (initStore) {
|
this._stores.forEach(function (initStore) {
|
||||||
initStore(self.state)
|
initStore(self.state)
|
||||||
})
|
})
|
||||||
@@ -212,26 +214,28 @@ Choo.prototype.mount = function mount (selector) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Choo.prototype.toString = function (location, state) {
|
Choo.prototype.toString = function (location, state) {
|
||||||
Object.assign(this.state, state || {})
|
state = state || {}
|
||||||
|
state.components = state.components || {}
|
||||||
|
state.events = Object.assign({}, state.events, this._events)
|
||||||
|
|
||||||
assert.notEqual(typeof window, 'object', 'choo.mount: window was found. .toString() must be called in Node, use .start() or .mount() if running in the browser')
|
assert.notEqual(typeof window, 'object', 'choo.mount: window was found. .toString() must be called in Node, use .start() or .mount() if running in the browser')
|
||||||
assert.equal(typeof location, 'string', 'choo.toString: location should be type string')
|
assert.equal(typeof location, 'string', 'choo.toString: location should be type string')
|
||||||
assert.equal(typeof this.state, 'object', 'choo.toString: state should be type object')
|
assert.equal(typeof state, 'object', 'choo.toString: state should be type object')
|
||||||
|
|
||||||
var self = this
|
this._setCache(state)
|
||||||
this._setCache(this.state)
|
this._matchRoute(state, location)
|
||||||
this._matchRoute(location)
|
this.emitter.removeAllListeners()
|
||||||
this._stores.forEach(function (initStore) {
|
this._stores.forEach(function (initStore) {
|
||||||
initStore(self.state)
|
initStore(state)
|
||||||
})
|
})
|
||||||
|
|
||||||
var html = this._prerender(this.state)
|
var html = this._prerender(state)
|
||||||
assert.ok(html, 'choo.toString: no valid value returned for the route ' + location)
|
assert.ok(html, 'choo.toString: no valid value returned for the route ' + location)
|
||||||
assert(!Array.isArray(html), 'choo.toString: return value was an array for the route ' + location)
|
assert(!Array.isArray(html), 'choo.toString: return value was an array for the route ' + location)
|
||||||
return typeof html.outerHTML === 'string' ? html.outerHTML : html.toString()
|
return typeof html.outerHTML === 'string' ? html.outerHTML : html.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
Choo.prototype._matchRoute = function (locationOverride) {
|
Choo.prototype._matchRoute = function (state, locationOverride) {
|
||||||
var location, queryString
|
var location, queryString
|
||||||
if (locationOverride) {
|
if (locationOverride) {
|
||||||
location = locationOverride.replace(/\?.+$/, '').replace(/\/$/, '')
|
location = locationOverride.replace(/\?.+$/, '').replace(/\/$/, '')
|
||||||
@@ -244,11 +248,10 @@ Choo.prototype._matchRoute = function (locationOverride) {
|
|||||||
}
|
}
|
||||||
var matched = this.router.match(location)
|
var matched = this.router.match(location)
|
||||||
this._handler = matched.cb
|
this._handler = matched.cb
|
||||||
this.state.href = location
|
state.href = location
|
||||||
this.state.query = nanoquery(queryString)
|
state.query = nanoquery(queryString)
|
||||||
this.state.route = matched.route
|
state.route = matched.route
|
||||||
this.state.params = matched.params
|
state.params = matched.params
|
||||||
return this.state
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Choo.prototype._prerender = function (state) {
|
Choo.prototype._prerender = function (state) {
|
||||||
|
|||||||
@@ -224,3 +224,37 @@ tape('state should include cache', function (t) {
|
|||||||
t.equal(arg, 'arg', 'constructor args were forwarded')
|
t.equal(arg, 'arg', 'constructor args were forwarded')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
tape('state should not mutate on toString', function (t) {
|
||||||
|
t.plan(6)
|
||||||
|
|
||||||
|
var app = choo()
|
||||||
|
app.use(store)
|
||||||
|
|
||||||
|
var routes = ['foo', 'bar']
|
||||||
|
var states = routes.map(function (route) {
|
||||||
|
var state = {}
|
||||||
|
app.route(`/${route}`, view)
|
||||||
|
app.toString(`/${route}`, state)
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
|
||||||
|
for (var i = 0, len = routes.length; i < len; i++) {
|
||||||
|
t.equal(states[i].test, routes[i], 'store was used')
|
||||||
|
t.equal(states[i].title, routes[i], 'title was added to state')
|
||||||
|
}
|
||||||
|
|
||||||
|
function store (state, emitter) {
|
||||||
|
state.test = null
|
||||||
|
emitter.on('test', function (str) {
|
||||||
|
t.equal(state.test, null, 'state has been reset')
|
||||||
|
state.test = str
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function view (state, emit) {
|
||||||
|
emit('test', state.route)
|
||||||
|
emit(state.events.DOMTITLECHANGE, state.route)
|
||||||
|
return html`<body>Hello ${state.route}</body>`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user