Skip to content

Commit

Permalink
feat(ssr): ssrPrefetch option + context.rendered hook (#9017)
Browse files Browse the repository at this point in the history
  • Loading branch information
Akryum authored and yyx990803 committed Dec 20, 2018
1 parent f036cce commit d7a533d
Show file tree
Hide file tree
Showing 10 changed files with 349 additions and 9 deletions.
1 change: 1 addition & 0 deletions flow/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ declare type ComponentOptions = {
beforeDestroy?: Function;
destroyed?: Function;
errorCaptured?: () => boolean | void;
ssrPrefetch?: Function;

// assets
directives?: { [key: string]: Object };
Expand Down
15 changes: 15 additions & 0 deletions src/server/create-renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ export function createRenderer ({
}, cb)
try {
render(component, write, context, err => {
if (context && context.rendered) {
context.rendered(context)
}
if (template) {
result = templateRenderer.renderSync(result, context)
}
Expand Down Expand Up @@ -106,13 +109,25 @@ export function createRenderer ({
render(component, write, context, done)
})
if (!template) {
if (context && context.rendered) {
const rendered = context.rendered
renderStream.once('beforeEnd', () => {
rendered(context)
})
}
return renderStream
} else {
const templateStream = templateRenderer.createStream(context)
renderStream.on('error', err => {
templateStream.emit('error', err)
})
renderStream.pipe(templateStream)
if (context && context.rendered) {
const rendered = context.rendered
renderStream.once('beforeEnd', () => {
rendered(context)
})
}
return templateStream
}
}
Expand Down
1 change: 1 addition & 0 deletions src/server/render-stream.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export default class RenderStream extends stream.Readable {
})

this.end = () => {
this.emit('beforeEnd')
// the rendering is finished; we should push out the last of the buffer.
this.done = true
this.push(this.buffer)
Expand Down
49 changes: 41 additions & 8 deletions src/server/render.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ let warned = Object.create(null)
const warnOnce = msg => {
if (!warned[msg]) {
warned[msg] = true
// eslint-disable-next-line no-console
console.warn(`\n\u001b[31m${msg}\u001b[39m\n`)
}
}
Expand Down Expand Up @@ -49,6 +50,27 @@ const normalizeRender = vm => {
}
}

function waitForSsrPrefetch (vm, resolve, reject) {
let handlers = vm.$options.ssrPrefetch
if (isDef(handlers)) {
if (!Array.isArray(handlers)) handlers = [handlers]
try {
const promises = []
for (let i = 0, j = handlers.length; i < j; i++) {
const result = handlers[i].call(vm, vm)
if (result && typeof result.then === 'function') {
promises.push(result)
}
}
Promise.all(promises).then(resolve).catch(reject)
return
} catch (e) {
reject(e)
}
}
resolve()
}

function renderNode (node, isRoot, context) {
if (node.isString) {
renderStringNode(node, context)
Expand Down Expand Up @@ -166,13 +188,20 @@ function renderComponentInner (node, isRoot, context) {
context.activeInstance
)
normalizeRender(child)
const childNode = child._render()
childNode.parent = node
context.renderStates.push({
type: 'Component',
prevActive
})
renderNode(childNode, isRoot, context)

const resolve = () => {
const childNode = child._render()
childNode.parent = node
context.renderStates.push({
type: 'Component',
prevActive
})
renderNode(childNode, isRoot, context)
}

const reject = context.done

waitForSsrPrefetch(child, resolve, reject)
}

function renderAsyncComponent (node, isRoot, context) {
Expand Down Expand Up @@ -394,6 +423,10 @@ export function createRenderFunction (
})
installSSRHelpers(component)
normalizeRender(component)
renderNode(component._render(), true, context)

const resolve = () => {
renderNode(component._render(), true, context)
}
waitForSsrPrefetch(component, resolve, done)
}
}
3 changes: 2 additions & 1 deletion src/shared/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ export const LIFECYCLE_HOOKS = [
'destroyed',
'activated',
'deactivated',
'errorCaptured'
'errorCaptured',
'ssrPrefetch'
]
22 changes: 22 additions & 0 deletions test/ssr/ssr-stream.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,4 +102,26 @@ describe('SSR: renderToStream', () => {
stream1.read(1)
stream2.read(1)
})

it('should call context.rendered', done => {
let a = 0
const stream = renderToStream(new Vue({
template: `
<div>Hello</div>
`
}), {
rendered: () => {
a = 42
}
})
let res = ''
stream.on('data', chunk => {
res += chunk
})
stream.on('end', () => {
expect(res).toContain('<div data-server-rendered="true">Hello</div>')
expect(a).toBe(42)
done()
})
})
})
188 changes: 188 additions & 0 deletions test/ssr/ssr-string.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -1311,6 +1311,194 @@ describe('SSR: renderToString', () => {
done()
})
})

it('should support ssrPrefetch option', done => {
renderVmWithOptions({
template: `
<div>{{ count }}</div>
`,
data: {
count: 0
},
ssrPrefetch () {
return new Promise((resolve) => {
setTimeout(() => {
this.count = 42
resolve()
}, 1)
})
}
}, result => {
expect(result).toContain('<div data-server-rendered="true">42</div>')
done()
})
})

it('should support ssrPrefetch option (nested)', done => {
renderVmWithOptions({
template: `
<div>
<span>{{ count }}</span>
<nested-prefetch></nested-prefetch>
</div>
`,
data: {
count: 0
},
ssrPrefetch () {
return new Promise((resolve) => {
setTimeout(() => {
this.count = 42
resolve()
}, 1)
})
},
components: {
nestedPrefetch: {
template: `
<div>{{ message }}</div>
`,
data () {
return {
message: ''
}
},
ssrPrefetch () {
return new Promise((resolve) => {
setTimeout(() => {
this.message = 'vue.js'
resolve()
}, 1)
})
}
}
}
}, result => {
expect(result).toContain('<div data-server-rendered="true"><span>42</span> <div>vue.js</div></div>')
done()
})
})

it('should support ssrPrefetch option (nested async)', done => {
renderVmWithOptions({
template: `
<div>
<span>{{ count }}</span>
<nested-prefetch></nested-prefetch>
</div>
`,
data: {
count: 0
},
ssrPrefetch () {
return new Promise((resolve) => {
setTimeout(() => {
this.count = 42
resolve()
}, 1)
})
},
components: {
nestedPrefetch (resolve) {
resolve({
template: `
<div>{{ message }}</div>
`,
data () {
return {
message: ''
}
},
ssrPrefetch () {
return new Promise((resolve) => {
setTimeout(() => {
this.message = 'vue.js'
resolve()
}, 1)
})
}
})
}
}
}, result => {
expect(result).toContain('<div data-server-rendered="true"><span>42</span> <div>vue.js</div></div>')
done()
})
})

it('should merge ssrPrefetch option', done => {
const mixin = {
data: {
message: ''
},
ssrPrefetch () {
return new Promise((resolve) => {
setTimeout(() => {
this.message = 'vue.js'
resolve()
}, 1)
})
}
}
renderVmWithOptions({
mixins: [mixin],
template: `
<div>
<span>{{ count }}</span>
<div>{{ message }}</div>
</div>
`,
data: {
count: 0
},
ssrPrefetch () {
return new Promise((resolve) => {
setTimeout(() => {
this.count = 42
resolve()
}, 1)
})
}
}, result => {
expect(result).toContain('<div data-server-rendered="true"><span>42</span> <div>vue.js</div></div>')
done()
})
})

it(`should skip ssrPrefetch option that doesn't return a promise`, done => {
renderVmWithOptions({
template: `
<div>{{ count }}</div>
`,
data: {
count: 0
},
ssrPrefetch () {
setTimeout(() => {
this.count = 42
}, 1)
}
}, result => {
expect(result).toContain('<div data-server-rendered="true">0</div>')
done()
})
})

it('should call context.rendered', done => {
let a = 0
renderToString(new Vue({
template: '<div>Hello</div>'
}), {
rendered: () => {
a = 42
}
}, (err, res) => {
expect(err).toBeNull()
expect(res).toContain('<div data-server-rendered="true">Hello</div>')
expect(a).toBe(42)
done()
})
})
})

function renderVmWithOptions (options, cb) {
Expand Down
Loading

0 comments on commit d7a533d

Please sign in to comment.