mirror of
https://gitee.com/vuejs/vue.git
synced 2024-12-04 21:17:55 +08:00
Merge branch 'ssr-improvements' of git://github.com/blake-newman/vue into ssr
This commit is contained in:
commit
270b0ac021
@ -39,6 +39,18 @@ export function lifecycleMixin (Vue) {
|
||||
}
|
||||
}
|
||||
}
|
||||
this.renderStaticTrees()
|
||||
this._watcher = new Watcher(this, this._render, this._update)
|
||||
this._update(this._watcher.value)
|
||||
this._mounted = true
|
||||
// root instance, call ready on self
|
||||
if (this.$root === this) {
|
||||
callHook(this, 'ready')
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
Vue.prototype._renderStaticTrees = function () {
|
||||
// render static sub-trees for once on mount
|
||||
const staticRenderFns = this.$options.staticRenderFns
|
||||
if (staticRenderFns) {
|
||||
@ -47,13 +59,6 @@ export function lifecycleMixin (Vue) {
|
||||
this._staticTrees[i] = staticRenderFns[i].call(this._renderProxy)
|
||||
}
|
||||
}
|
||||
this._watcher = new Watcher(this, this._render, this._update)
|
||||
this._update(this._watcher.value)
|
||||
this._mounted = true
|
||||
// root instance, call ready on self
|
||||
if (this.$root === this) {
|
||||
callHook(this, 'ready')
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
@ -105,7 +110,7 @@ export function lifecycleMixin (Vue) {
|
||||
}
|
||||
|
||||
Vue.prototype.$forceUpdate = function () {
|
||||
this._watcher.update()
|
||||
this._update(this._render())
|
||||
}
|
||||
|
||||
Vue.prototype.$destroy = function () {
|
||||
|
@ -31,12 +31,15 @@ export function createComponent (Ctor, data, parent, children, context) {
|
||||
if (Ctor.resolved) {
|
||||
Ctor = Ctor.resolved
|
||||
} else {
|
||||
resolveAsyncComponent(Ctor, () => {
|
||||
const resolved = resolveAsyncComponent(Ctor, () => {
|
||||
// it's ok to queue this on every render because
|
||||
// $forceUpdate is buffered.
|
||||
parent.$forceUpdate()
|
||||
})
|
||||
return
|
||||
if (!resolved || !resolved.cid) {
|
||||
return
|
||||
}
|
||||
Ctor = resolved
|
||||
}
|
||||
}
|
||||
|
||||
@ -110,31 +113,37 @@ function destroy (vnode) {
|
||||
|
||||
function resolveAsyncComponent (factory, cb) {
|
||||
if (factory.resolved) {
|
||||
// cached
|
||||
cb(factory.resolved)
|
||||
return factory.resolved
|
||||
} else if (factory.requested) {
|
||||
// pool callbacks
|
||||
factory.pendingCallbacks.push(cb)
|
||||
} else {
|
||||
factory.requested = true
|
||||
const cbs = factory.pendingCallbacks = [cb]
|
||||
factory(function resolve (res) {
|
||||
factory.resolved = factory(function resolve (res) {
|
||||
if (isObject(res)) {
|
||||
res = Vue.extend(res)
|
||||
}
|
||||
// cache resolved
|
||||
factory.resolved = res
|
||||
|
||||
// invoke callbacks
|
||||
for (let i = 0, l = cbs.length; i < l; i++) {
|
||||
cbs[i](res)
|
||||
// Reset pending callbacks
|
||||
factory.pendingCallbacks = []
|
||||
}
|
||||
|
||||
return res
|
||||
}, function reject (reason) {
|
||||
process.env.NODE_ENV !== 'production' && warn(
|
||||
`Failed to resolve async component: ${factory}` +
|
||||
(reason ? `\nReason: ${reason}` : '')
|
||||
)
|
||||
})
|
||||
return factory.resolved
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function extractProps (data, Ctor) {
|
||||
|
@ -1,13 +1,29 @@
|
||||
import { createSyncRenderer } from './create-sync-renderer'
|
||||
import { createStreamingRenderer } from './create-streaming-renderer'
|
||||
import RenderStream from './render-stream'
|
||||
import { render } from './render'
|
||||
|
||||
export function createRenderer ({
|
||||
modules = [],
|
||||
directives = {},
|
||||
isUnaryTag = (() => false)
|
||||
} = {}) {
|
||||
function _render (component, write, done) {
|
||||
render(modules, directives, isUnaryTag)(component, write, done)
|
||||
}
|
||||
|
||||
return {
|
||||
renderToString: createSyncRenderer(modules, directives, isUnaryTag),
|
||||
renderToStream: createStreamingRenderer(modules, directives, isUnaryTag)
|
||||
renderToString (component) {
|
||||
let result = ''
|
||||
_render(component, (str, next) => {
|
||||
result += str
|
||||
next && next()
|
||||
})
|
||||
return result
|
||||
},
|
||||
renderToStream (component) {
|
||||
return new RenderStream((write, done) => {
|
||||
_render(component, write, done)
|
||||
})
|
||||
},
|
||||
render: _render
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,28 @@
|
||||
import RenderStream from './render-stream'
|
||||
import { renderStartingTag } from './render-starting-tag'
|
||||
|
||||
export function createStreamingRenderer (modules, directives, isUnaryTag) {
|
||||
function renderComponent (component, write, next, isRoot) {
|
||||
component.$mount()
|
||||
renderNode(component._vnode, write, next, isRoot)
|
||||
}
|
||||
|
||||
export function render (modules, directives, isUnaryTag) {
|
||||
function renderNode (node, write, next, isRoot) {
|
||||
if (node.componentOptions) {
|
||||
node.data.hook.init(node)
|
||||
renderComponent(node.child, write, next, isRoot)
|
||||
const { Ctor, propsData, listeners, parent, children } = node.componentOptions
|
||||
const options = {
|
||||
parent,
|
||||
propsData,
|
||||
_parentVnode: node,
|
||||
_parentListeners: listeners,
|
||||
_renderChildren: children
|
||||
}
|
||||
// check inline-template render functions
|
||||
const inlineTemplate = node.data.inlineTemplate
|
||||
if (inlineTemplate) {
|
||||
options.render = inlineTemplate.render
|
||||
options.staticRenderFns = inlineTemplate.staticRenderFns
|
||||
}
|
||||
const child = new Ctor(options)
|
||||
child._mount = () => {
|
||||
child._renderStaticTrees()
|
||||
renderNode(child._render(), write, next)
|
||||
}
|
||||
child.$mount(node.elm)
|
||||
} else {
|
||||
if (node.tag) {
|
||||
renderElement(node, write, next, isRoot)
|
||||
@ -53,9 +65,8 @@ export function createStreamingRenderer (modules, directives, isUnaryTag) {
|
||||
}
|
||||
}
|
||||
|
||||
return function renderToStream (component) {
|
||||
return new RenderStream((write, done) => {
|
||||
renderComponent(component, write, done, true)
|
||||
})
|
||||
return function render (component, write, done) {
|
||||
component._renderStaticTrees()
|
||||
renderNode(component._render(), write, done, true)
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"spec_dir": "test/ssr",
|
||||
"spec_files": [
|
||||
"ssr.sync.spec.js",
|
||||
"ssr.string.spec.js",
|
||||
"ssr.stream.spec.js"
|
||||
],
|
||||
"helpers": [
|
||||
|
@ -9,25 +9,43 @@ describe('SSR: renderToStream', () => {
|
||||
template: `
|
||||
<div>
|
||||
<p class="hi">yoyo</p>
|
||||
<div id="ho" :class="{ red: isRed }"></div>
|
||||
<div id="ho" :class="[testClass, { red: isRed }]"></div>
|
||||
<span>{{ test }}</span>
|
||||
<input :value="test">
|
||||
<test></test>
|
||||
<b-comp></b-comp>
|
||||
<c-comp></c-comp>
|
||||
</div>
|
||||
`,
|
||||
data: {
|
||||
test: 'hi',
|
||||
isRed: true
|
||||
isRed: true,
|
||||
testClass: 'a'
|
||||
},
|
||||
components: {
|
||||
test: {
|
||||
render: function () {
|
||||
return this.$createElement('div', { class: ['a'] }, 'hahahaha')
|
||||
bComp (resolve) {
|
||||
return resolve({
|
||||
render () {
|
||||
return this.$createElement('test-async-2')
|
||||
},
|
||||
components: {
|
||||
testAsync2 (resolve) {
|
||||
return resolve({
|
||||
created () { this.$parent.$parent.testClass = 'b' },
|
||||
render () {
|
||||
return this.$createElement('div', { class: [this.$parent.$parent.testClass] }, 'test')
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
cComp: {
|
||||
render () {
|
||||
return this.$createElement('div', { class: [this.$parent.testClass] }, 'test')
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
let res = ''
|
||||
stream.on('data', chunk => {
|
||||
res += chunk
|
||||
@ -35,11 +53,12 @@ describe('SSR: renderToStream', () => {
|
||||
stream.on('end', () => {
|
||||
expect(res).toContain(
|
||||
'<div server-rendered="true">' +
|
||||
'<p class="hi">yoyo</p>' +
|
||||
'<div id="ho" class="red"></div>' +
|
||||
'<span>hi</span>' +
|
||||
'<input value="hi">' +
|
||||
'<div class="a">hahahaha</div>' +
|
||||
'<p class="hi">yoyo</p>' +
|
||||
'<div id="ho" class="a red"></div>' +
|
||||
'<span>hi</span>' +
|
||||
'<input value="hi">' +
|
||||
'<div class="b">test</div>' +
|
||||
'<div class="b">test</div>' +
|
||||
'</div>'
|
||||
)
|
||||
done()
|
||||
|
@ -52,7 +52,9 @@ describe('SSR: renderToString', () => {
|
||||
fontSize: 14,
|
||||
color: 'red'
|
||||
}
|
||||
})).toContain('<div server-rendered="true" style="font-size:14px;color:red;background-color:black"></div>')
|
||||
})).toContain(
|
||||
'<div server-rendered="true" style="font-size:14px;color:red;background-color:black"></div>'
|
||||
)
|
||||
})
|
||||
|
||||
it('text interpolation', () => {
|
||||
@ -75,11 +77,7 @@ describe('SSR: renderToString', () => {
|
||||
child: {
|
||||
props: ['msg'],
|
||||
data () {
|
||||
return { name: 'foo' }
|
||||
},
|
||||
created () {
|
||||
// checking setting state in created hook works in ssr
|
||||
this.name = 'bar'
|
||||
return { name: 'bar' }
|
||||
},
|
||||
render () {
|
||||
const h = this.$createElement
|
||||
@ -90,6 +88,109 @@ describe('SSR: renderToString', () => {
|
||||
})).toContain('<div server-rendered="true" class="foo bar">hello bar</div>')
|
||||
})
|
||||
|
||||
it('has correct lifecycle during render', () => {
|
||||
let lifecycleCount = 1
|
||||
expect(renderVmWithOptions({
|
||||
template: '<div><span>{{ val }}</span><test></test></div>',
|
||||
data: {
|
||||
val: 'hi'
|
||||
},
|
||||
init () {
|
||||
expect(lifecycleCount++).toBe(1)
|
||||
},
|
||||
created () {
|
||||
this.val = 'hello'
|
||||
expect(this.val).toBe('hello')
|
||||
expect(lifecycleCount++).toBe(2)
|
||||
},
|
||||
components: {
|
||||
test: {
|
||||
init () {
|
||||
expect(lifecycleCount++).toBe(3)
|
||||
},
|
||||
created () {
|
||||
expect(lifecycleCount++).toBe(4)
|
||||
},
|
||||
render () {
|
||||
expect(lifecycleCount++).toBeGreaterThan(4)
|
||||
return this.$createElement('span', { class: ['b'] }, 'testAsync')
|
||||
}
|
||||
}
|
||||
}
|
||||
})).toContain(
|
||||
'<div server-rendered="true">' +
|
||||
'<span>hello</span>' +
|
||||
'<span class="b">testAsync</span>' +
|
||||
'</div>'
|
||||
)
|
||||
})
|
||||
|
||||
it('renders asynchronous component', () => {
|
||||
expect(renderVmWithOptions({
|
||||
template: `
|
||||
<div>
|
||||
<test-async></test-async>
|
||||
</div>
|
||||
`,
|
||||
components: {
|
||||
testAsync (resolve) {
|
||||
return resolve({
|
||||
render () {
|
||||
return this.$createElement('span', { class: ['b'] }, 'testAsync')
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})).toContain('<div server-rendered="true"><span class="b">testAsync</span></div>')
|
||||
})
|
||||
|
||||
it('renders asynchronous component (hoc)', () => {
|
||||
expect(renderVmWithOptions({
|
||||
template: '<test-async></test-async>',
|
||||
components: {
|
||||
testAsync (resolve) {
|
||||
return resolve({
|
||||
render () {
|
||||
return this.$createElement('span', { class: ['b'] }, 'testAsync')
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})).toContain('<span server-rendered="true" class="b">testAsync</span>')
|
||||
})
|
||||
|
||||
it('renders nested asynchronous component', () => {
|
||||
expect(renderVmWithOptions({
|
||||
template: `
|
||||
<div>
|
||||
<test-async></test-async>
|
||||
</div>
|
||||
`,
|
||||
components: {
|
||||
testAsync (resolve) {
|
||||
const options = compileToFunctions(`
|
||||
<span class="b">
|
||||
<test-sub-async></test-sub-async>
|
||||
</span>
|
||||
`, { preserveWhitespace: false })
|
||||
|
||||
options.components = {
|
||||
testSubAsync (resolve) {
|
||||
return resolve({
|
||||
render () {
|
||||
return this.$createElement('div', { class: ['c'] }, 'testSubAsync')
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
return resolve(options)
|
||||
}
|
||||
}
|
||||
})).toContain(
|
||||
'<div server-rendered="true"><span class="b"><div class="c">testSubAsync</div></span></div>'
|
||||
)
|
||||
})
|
||||
|
||||
it('everything together', () => {
|
||||
expect(renderVmWithOptions({
|
||||
template: `
|
||||
@ -98,8 +199,9 @@ describe('SSR: renderToString', () => {
|
||||
<div id="ho" :class="{ red: isRed }"></div>
|
||||
<span>{{ test }}</span>
|
||||
<input :value="test">
|
||||
<test></test>
|
||||
<img :src="imageUrl">
|
||||
<test></test>
|
||||
<test-async></test-async>
|
||||
</div>
|
||||
`,
|
||||
data: {
|
||||
@ -109,9 +211,16 @@ describe('SSR: renderToString', () => {
|
||||
},
|
||||
components: {
|
||||
test: {
|
||||
render: function () {
|
||||
return this.$createElement('div', { class: ['a'] }, 'hahahaha')
|
||||
render () {
|
||||
return this.$createElement('div', { class: ['a'] }, 'test')
|
||||
}
|
||||
},
|
||||
testAsync (resolve) {
|
||||
return resolve({
|
||||
render () {
|
||||
return this.$createElement('span', { class: ['b'] }, 'testAsync')
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})).toContain(
|
||||
@ -120,8 +229,9 @@ describe('SSR: renderToString', () => {
|
||||
'<div id="ho" class="red"></div>' +
|
||||
'<span>hi</span>' +
|
||||
'<input value="hi">' +
|
||||
'<div class="a">hahahaha</div>' +
|
||||
'<img src="https://vuejs.org/images/logo.png">' +
|
||||
'<div class="a">test</div>' +
|
||||
'<span class="b">testAsync</span>' +
|
||||
'</div>'
|
||||
)
|
||||
})
|
Loading…
Reference in New Issue
Block a user