From 1fe877f71f9f0f9185f1e8588dcb0ac3872ee6bf Mon Sep 17 00:00:00 2001 From: Guillaume Chau Date: Sat, 3 Nov 2018 09:01:19 +0900 Subject: [PATCH 01/11] feat: ssrPrefetch option --- src/server/render.js | 43 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/src/server/render.js b/src/server/render.js index 592a6175c5f..f3d1f4465f9 100644 --- a/src/server/render.js +++ b/src/server/render.js @@ -18,6 +18,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`) } } @@ -48,6 +49,21 @@ const normalizeRender = vm => { } } +function waitForSsrPrefetch (vm, resolve, reject) { + if (isDef(vm.$options.ssrPrefetch)) { + try { + const result = vm.$options.ssrPrefetch.call(vm, vm) + if (result && typeof result.then === 'function') { + result.then(resolve).catch(reject) + return + } + } catch (e) { + reject(e) + } + } + resolve() +} + function renderNode (node, isRoot, context) { if (node.isString) { renderStringNode(node, context) @@ -165,13 +181,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) { @@ -391,6 +414,10 @@ export function createRenderFunction ( }) installSSRHelpers(component) normalizeRender(component) - renderNode(component._render(), true, context) + + const resolve = () => { + renderNode(component._render(), true, context) + } + waitForSsrPrefetch(component, resolve, done) } } From 7c985e2cbb8e4e4fa26a65de9426fb27a4e94d42 Mon Sep 17 00:00:00 2001 From: Guillaume Chau Date: Sat, 3 Nov 2018 09:01:31 +0900 Subject: [PATCH 02/11] test: ssrPrefetch --- test/ssr/ssr-string.spec.js | 114 ++++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) diff --git a/test/ssr/ssr-string.spec.js b/test/ssr/ssr-string.spec.js index abf91526dd1..45d51b08240 100644 --- a/test/ssr/ssr-string.spec.js +++ b/test/ssr/ssr-string.spec.js @@ -1277,6 +1277,120 @@ describe('SSR: renderToString', () => { done() }) }) + + it('should support ssrPrefetch option', done => { + renderVmWithOptions({ + template: ` +
{{ count }}
+ `, + data: { + count: 0 + }, + ssrPrefetch () { + return new Promise((resolve) => { + setTimeout(() => { + this.count = 42 + resolve() + }, 1) + }) + } + }, result => { + expect(result).toContain('
42
') + done() + }) + }) + + it('should support ssrPrefetch option (nested)', done => { + renderVmWithOptions({ + template: ` +
+ {{ count }} + +
+ `, + data: { + count: 0 + }, + ssrPrefetch () { + return new Promise((resolve) => { + setTimeout(() => { + this.count = 42 + resolve() + }, 1) + }) + }, + components: { + nestedPrefetch: { + template: ` +
{{ message }}
+ `, + data () { + return { + message: '' + } + }, + ssrPrefetch () { + return new Promise((resolve) => { + setTimeout(() => { + this.message = 'vue.js' + resolve() + }, 1) + }) + } + } + } + }, result => { + expect(result).toContain('
42
vue.js
') + done() + }) + }) + + it('should support ssrPrefetch option (nested async)', done => { + renderVmWithOptions({ + template: ` +
+ {{ count }} + +
+ `, + data: { + count: 0 + }, + ssrPrefetch () { + return new Promise((resolve) => { + setTimeout(() => { + this.count = 42 + resolve() + }, 1) + }) + }, + components: { + nestedPrefetch (resolve) { + resolve({ + template: ` +
{{ message }}
+ `, + data () { + return { + message: '' + } + }, + ssrPrefetch () { + return new Promise((resolve) => { + setTimeout(() => { + this.message = 'vue.js' + resolve() + }, 1) + }) + } + }) + } + } + }, result => { + expect(result).toContain('
42
vue.js
') + done() + }) + }) }) function renderVmWithOptions (options, cb) { From ceee9b25a6d9ff6a6575319e98e819145b98da38 Mon Sep 17 00:00:00 2001 From: Guillaume Chau Date: Sat, 3 Nov 2018 10:41:52 +0900 Subject: [PATCH 03/11] fix: flow types --- flow/options.js | 1 + 1 file changed, 1 insertion(+) diff --git a/flow/options.js b/flow/options.js index 2e138a62ab9..ceaf8571e62 100644 --- a/flow/options.js +++ b/flow/options.js @@ -44,6 +44,7 @@ declare type ComponentOptions = { beforeDestroy?: Function; destroyed?: Function; errorCaptured?: () => boolean | void; + ssrPrefetch?: Function; // assets directives?: { [key: string]: Object }; From 27b4799d67c2b9d349bfd9a74706ffe539e357c0 Mon Sep 17 00:00:00 2001 From: Guillaume Chau Date: Sat, 3 Nov 2018 10:47:18 +0900 Subject: [PATCH 04/11] types: typescript --- types/options.d.ts | 1 + types/test/options-test.ts | 3 +++ 2 files changed, 4 insertions(+) diff --git a/types/options.d.ts b/types/options.d.ts index bd96b6476d3..d56f05a990f 100644 --- a/types/options.d.ts +++ b/types/options.d.ts @@ -84,6 +84,7 @@ export interface ComponentOptions< activated?(): void; deactivated?(): void; errorCaptured?(err: Error, vm: Vue, info: string): boolean | void; + ssrPrefetch?(this: V): Promise; directives?: { [key: string]: DirectiveFunction | DirectiveOptions }; components?: { [key: string]: Component | AsyncComponent }; diff --git a/types/test/options-test.ts b/types/test/options-test.ts index 1c6ecff6daa..899d82d76f0 100644 --- a/types/test/options-test.ts +++ b/types/test/options-test.ts @@ -222,6 +222,9 @@ Vue.component('component', { info.toUpperCase() return true }, + ssrPrefetch () { + return Promise.resolve() + }, directives: { a: { From edc325733cd90ede318dd760e8502d16b830d57a Mon Sep 17 00:00:00 2001 From: Guillaume Chau Date: Sat, 3 Nov 2018 11:13:50 +0900 Subject: [PATCH 05/11] feat: merge ssrPrefetch --- src/server/render.js | 15 ++++++++++----- src/shared/constants.js | 3 ++- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/server/render.js b/src/server/render.js index f3d1f4465f9..c507b769f44 100644 --- a/src/server/render.js +++ b/src/server/render.js @@ -50,13 +50,18 @@ const normalizeRender = vm => { } function waitForSsrPrefetch (vm, resolve, reject) { - if (isDef(vm.$options.ssrPrefetch)) { + const handlers = vm.$options.ssrPrefetch + if (isDef(handlers)) { try { - const result = vm.$options.ssrPrefetch.call(vm, vm) - if (result && typeof result.then === 'function') { - result.then(resolve).catch(reject) - return + 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) } diff --git a/src/shared/constants.js b/src/shared/constants.js index 84d019fb4ca..018d657b662 100644 --- a/src/shared/constants.js +++ b/src/shared/constants.js @@ -17,5 +17,6 @@ export const LIFECYCLE_HOOKS = [ 'destroyed', 'activated', 'deactivated', - 'errorCaptured' + 'errorCaptured', + 'ssrPrefetch' ] From 5686aa46cb27a03c54e32a191b61afc87a15f29d Mon Sep 17 00:00:00 2001 From: Guillaume Chau Date: Sat, 3 Nov 2018 11:20:48 +0900 Subject: [PATCH 06/11] test: more tests --- test/ssr/ssr-string.spec.js | 58 +++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/test/ssr/ssr-string.spec.js b/test/ssr/ssr-string.spec.js index 45d51b08240..89d9398c3e2 100644 --- a/test/ssr/ssr-string.spec.js +++ b/test/ssr/ssr-string.spec.js @@ -1391,6 +1391,64 @@ describe('SSR: renderToString', () => { 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: ` +
+ {{ count }} +
{{ message }}
+
+ `, + data: { + count: 0 + }, + ssrPrefetch () { + return new Promise((resolve) => { + setTimeout(() => { + this.count = 42 + resolve() + }, 1) + }) + } + }, result => { + expect(result).toContain('
42
vue.js
') + done() + }) + }) + + it(`should skip ssrPrefetch option that doesn't return a promise`, done => { + renderVmWithOptions({ + template: ` +
{{ count }}
+ `, + data: { + count: 0 + }, + ssrPrefetch () { + setTimeout(() => { + this.count = 42 + }, 1) + } + }, result => { + expect(result).toContain('
0
') + done() + }) + }) }) function renderVmWithOptions (options, cb) { From fa286b051f5412f5c121d87a09d8613216a328e1 Mon Sep 17 00:00:00 2001 From: Guillaume Chau Date: Wed, 7 Nov 2018 01:48:43 +0100 Subject: [PATCH 07/11] feat(ssr): context.onRenderComplete --- src/server/create-renderer.js | 13 +++++++++++++ src/server/render-stream.js | 1 + 2 files changed, 14 insertions(+) diff --git a/src/server/create-renderer.js b/src/server/create-renderer.js index c045a426fe6..610ebdb7879 100644 --- a/src/server/create-renderer.js +++ b/src/server/create-renderer.js @@ -79,6 +79,9 @@ export function createRenderer ({ }, cb) try { render(component, write, context, err => { + if (context && context.onRenderComplete) { + context.onRenderComplete(context) + } if (template) { result = templateRenderer.renderSync(result, context) } @@ -106,6 +109,11 @@ export function createRenderer ({ render(component, write, context, done) }) if (!template) { + if (context && context.onRenderComplete) { + renderStream.once('beforeEnd', () => { + context.onRenderComplete(context) + }) + } return renderStream } else { const templateStream = templateRenderer.createStream(context) @@ -113,6 +121,11 @@ export function createRenderer ({ templateStream.emit('error', err) }) renderStream.pipe(templateStream) + if (context && context.onRenderComplete) { + renderStream.once('beforeEnd', () => { + context.onRenderComplete(context) + }) + } return templateStream } } diff --git a/src/server/render-stream.js b/src/server/render-stream.js index d76012afb5b..bc358e20e95 100644 --- a/src/server/render-stream.js +++ b/src/server/render-stream.js @@ -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) From 68ded0531c2cacf7fefa25960a68f962f5343814 Mon Sep 17 00:00:00 2001 From: Guillaume Chau Date: Wed, 7 Nov 2018 01:49:22 +0100 Subject: [PATCH 08/11] test(ssr): context.onRenderComplete --- test/ssr/ssr-stream.spec.js | 22 ++++++++++ test/ssr/ssr-string.spec.js | 16 ++++++++ test/ssr/ssr-template.spec.js | 75 +++++++++++++++++++++++++++++++++++ 3 files changed, 113 insertions(+) diff --git a/test/ssr/ssr-stream.spec.js b/test/ssr/ssr-stream.spec.js index 5973b5ed0d8..1805f1b2fb6 100644 --- a/test/ssr/ssr-stream.spec.js +++ b/test/ssr/ssr-stream.spec.js @@ -102,4 +102,26 @@ describe('SSR: renderToStream', () => { stream1.read(1) stream2.read(1) }) + + it('should call context.onRenderComplete', done => { + let a = 0 + const stream = renderToStream(new Vue({ + template: ` +
Hello
+ ` + }), { + onRenderComplete: () => { + a = 42 + } + }) + let res = '' + stream.on('data', chunk => { + res += chunk + }) + stream.on('end', () => { + expect(res).toContain('
Hello
') + expect(a).toBe(42) + done() + }) + }) }) diff --git a/test/ssr/ssr-string.spec.js b/test/ssr/ssr-string.spec.js index 89d9398c3e2..0d0ae0847dd 100644 --- a/test/ssr/ssr-string.spec.js +++ b/test/ssr/ssr-string.spec.js @@ -1449,6 +1449,22 @@ describe('SSR: renderToString', () => { done() }) }) + + it('should call context.onRenderComplete', done => { + let a = 0 + renderToString(new Vue({ + template: '
Hello
' + }), { + onRenderComplete: () => { + a = 42 + } + }, (err, res) => { + expect(err).toBeNull() + expect(res).toContain('
Hello
') + expect(a).toBe(42) + done() + }) + }) }) function renderVmWithOptions (options, cb) { diff --git a/test/ssr/ssr-template.spec.js b/test/ssr/ssr-template.spec.js index 2491b591d87..08ddd1a2bb4 100644 --- a/test/ssr/ssr-template.spec.js +++ b/test/ssr/ssr-template.spec.js @@ -99,6 +99,41 @@ describe('SSR: template option', () => { }) }) + it('renderToString with interpolation and context.onRenderComplete', done => { + const renderer = createRenderer({ + template: interpolateTemplate + }) + + const context = { + title: '', + snippet: '
foo
', + head: '', + styles: '', + state: { a: 0 }, + onRenderComplete: context => { + context.state.a = 1 + } + } + + renderer.renderToString(new Vue({ + template: '
hi
' + }), context, (err, res) => { + expect(err).toBeNull() + expect(res).toContain( + `` + + // double mustache should be escaped + `<script>hacks</script>` + + `${context.head}${context.styles}` + + `
hi
` + + `` + + // triple should be raw + `
foo
` + + `` + ) + done() + }) + }) + it('renderToStream', done => { const renderer = createRenderer({ template: defaultTemplate @@ -166,6 +201,46 @@ describe('SSR: template option', () => { }) }) + it('renderToStream with interpolation and context.onRenderComplete', done => { + const renderer = createRenderer({ + template: interpolateTemplate + }) + + const context = { + title: '', + snippet: '
foo
', + head: '', + styles: '', + state: { a: 0 }, + onRenderComplete: context => { + context.state.a = 1 + } + } + + const stream = renderer.renderToStream(new Vue({ + template: '
hi
' + }), context) + + let res = '' + stream.on('data', chunk => { + res += chunk + }) + stream.on('end', () => { + expect(res).toContain( + `` + + // double mustache should be escaped + `<script>hacks</script>` + + `${context.head}${context.styles}` + + `
hi
` + + `` + + // triple should be raw + `
foo
` + + `` + ) + done() + }) + }) + it('bundleRenderer + renderToString', done => { createBundleRenderer('app.js', { asBundle: true, From 99765c77b2371143feb9e1b220eda0a912d8733e Mon Sep 17 00:00:00 2001 From: Guillaume Chau Date: Tue, 6 Nov 2018 22:53:53 -0800 Subject: [PATCH 09/11] fix: ssrPrefetch not an array --- src/server/render.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/server/render.js b/src/server/render.js index c507b769f44..4f4aef195cd 100644 --- a/src/server/render.js +++ b/src/server/render.js @@ -50,8 +50,9 @@ const normalizeRender = vm => { } function waitForSsrPrefetch (vm, resolve, reject) { - const handlers = vm.$options.ssrPrefetch + 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++) { From 366a8b4ecbd3151c77387aed83ab9d7a130ab361 Mon Sep 17 00:00:00 2001 From: Guillaume Chau Date: Tue, 6 Nov 2018 22:57:02 -0800 Subject: [PATCH 10/11] chore: rename onRenderComplete to rendered --- src/server/create-renderer.js | 12 ++++++------ test/ssr/ssr-stream.spec.js | 4 ++-- test/ssr/ssr-string.spec.js | 4 ++-- test/ssr/ssr-template.spec.js | 8 ++++---- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/server/create-renderer.js b/src/server/create-renderer.js index 610ebdb7879..3cadad6d137 100644 --- a/src/server/create-renderer.js +++ b/src/server/create-renderer.js @@ -79,8 +79,8 @@ export function createRenderer ({ }, cb) try { render(component, write, context, err => { - if (context && context.onRenderComplete) { - context.onRenderComplete(context) + if (context && context.rendered) { + context.rendered(context) } if (template) { result = templateRenderer.renderSync(result, context) @@ -109,9 +109,9 @@ export function createRenderer ({ render(component, write, context, done) }) if (!template) { - if (context && context.onRenderComplete) { + if (context && context.rendered) { renderStream.once('beforeEnd', () => { - context.onRenderComplete(context) + context.rendered(context) }) } return renderStream @@ -121,9 +121,9 @@ export function createRenderer ({ templateStream.emit('error', err) }) renderStream.pipe(templateStream) - if (context && context.onRenderComplete) { + if (context && context.rendered) { renderStream.once('beforeEnd', () => { - context.onRenderComplete(context) + context.rendered(context) }) } return templateStream diff --git a/test/ssr/ssr-stream.spec.js b/test/ssr/ssr-stream.spec.js index 1805f1b2fb6..04e79658984 100644 --- a/test/ssr/ssr-stream.spec.js +++ b/test/ssr/ssr-stream.spec.js @@ -103,14 +103,14 @@ describe('SSR: renderToStream', () => { stream2.read(1) }) - it('should call context.onRenderComplete', done => { + it('should call context.rendered', done => { let a = 0 const stream = renderToStream(new Vue({ template: `
Hello
` }), { - onRenderComplete: () => { + rendered: () => { a = 42 } }) diff --git a/test/ssr/ssr-string.spec.js b/test/ssr/ssr-string.spec.js index 0d0ae0847dd..d51dfedf23f 100644 --- a/test/ssr/ssr-string.spec.js +++ b/test/ssr/ssr-string.spec.js @@ -1450,12 +1450,12 @@ describe('SSR: renderToString', () => { }) }) - it('should call context.onRenderComplete', done => { + it('should call context.rendered', done => { let a = 0 renderToString(new Vue({ template: '
Hello
' }), { - onRenderComplete: () => { + rendered: () => { a = 42 } }, (err, res) => { diff --git a/test/ssr/ssr-template.spec.js b/test/ssr/ssr-template.spec.js index 08ddd1a2bb4..1747d52929d 100644 --- a/test/ssr/ssr-template.spec.js +++ b/test/ssr/ssr-template.spec.js @@ -99,7 +99,7 @@ describe('SSR: template option', () => { }) }) - it('renderToString with interpolation and context.onRenderComplete', done => { + it('renderToString with interpolation and context.rendered', done => { const renderer = createRenderer({ template: interpolateTemplate }) @@ -110,7 +110,7 @@ describe('SSR: template option', () => { head: '', styles: '', state: { a: 0 }, - onRenderComplete: context => { + rendered: context => { context.state.a = 1 } } @@ -201,7 +201,7 @@ describe('SSR: template option', () => { }) }) - it('renderToStream with interpolation and context.onRenderComplete', done => { + it('renderToStream with interpolation and context.rendered', done => { const renderer = createRenderer({ template: interpolateTemplate }) @@ -212,7 +212,7 @@ describe('SSR: template option', () => { head: '', styles: '', state: { a: 0 }, - onRenderComplete: context => { + rendered: context => { context.state.a = 1 } } From 4234ab2f4661780ebf37caaccddfc65ac305b3db Mon Sep 17 00:00:00 2001 From: Guillaume Chau Date: Thu, 8 Nov 2018 10:49:59 -0800 Subject: [PATCH 11/11] test(flow): fix error --- src/server/create-renderer.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/server/create-renderer.js b/src/server/create-renderer.js index 3cadad6d137..0d335462147 100644 --- a/src/server/create-renderer.js +++ b/src/server/create-renderer.js @@ -110,8 +110,9 @@ export function createRenderer ({ }) if (!template) { if (context && context.rendered) { + const rendered = context.rendered renderStream.once('beforeEnd', () => { - context.rendered(context) + rendered(context) }) } return renderStream @@ -122,8 +123,9 @@ export function createRenderer ({ }) renderStream.pipe(templateStream) if (context && context.rendered) { + const rendered = context.rendered renderStream.once('beforeEnd', () => { - context.rendered(context) + rendered(context) }) } return templateStream