Evan You 8 years ago
parent
commit
4e40666d53
  1. 1
      package.json
  2. 4
      src/server/create-renderer.js
  3. 15
      src/server/render-context.js
  4. 33
      src/server/render.js
  5. 1
      src/server/write.js
  6. 57
      test/ssr/fixtures/nested-cache.js
  7. 48
      test/ssr/ssr-bundle-render.spec.js
  8. 2
      yarn.lock

1
package.json

@ -93,6 +93,7 @@
"karma-sourcemap-loader": "^0.3.0",
"karma-webpack": "^2.0.1",
"lodash": "^4.17.1",
"lru-cache": "^4.0.2",
"nightwatch": "^0.9.9",
"nightwatch-helpers": "^1.2.0",
"phantomjs-prebuilt": "^2.1.1",

4
src/server/create-renderer.js

@ -63,7 +63,7 @@ export function createRenderer ({
return false
}, done)
try {
render(component, write, () => {
render(component, write, context, () => {
if (template) {
result = templateRenderer.renderSync(result, context)
}
@ -79,7 +79,7 @@ export function createRenderer ({
context?: ?Object
): stream$Readable {
const renderStream = new RenderStream((write, done) => {
render(component, write, done)
render(component, write, context, done)
})
if (!template) {
if (context && clientManifest) {

15
src/server/render-context.js

@ -15,10 +15,12 @@ type RenderState = {
type: 'ComponentWithCache';
buffer: Array<string>;
bufferIndex: number;
componentBuffer: Array<Set<Class<Component>>>;
key: string;
};
export class RenderContext {
userContext: ?Object;
activeInstance: Component;
renderStates: Array<RenderState>;
write: (text: string, next: Function) => void;
@ -35,6 +37,7 @@ export class RenderContext {
has: ?(key: string, cb: Function) => void;
constructor (options: Object) {
this.userContext = options.userContext
this.activeInstance = options.activeInstance
this.renderStates = []
@ -80,8 +83,11 @@ export class RenderContext {
break
case 'ComponentWithCache':
this.renderStates.pop()
const { buffer, bufferIndex, key } = lastState
const result = buffer[bufferIndex]
const { buffer, bufferIndex, componentBuffer, key } = lastState
const result = {
html: buffer[bufferIndex],
components: componentBuffer[bufferIndex]
}
this.cache.set(key, result)
if (bufferIndex === 0) {
// this is a top-level cached component,
@ -90,9 +96,12 @@ export class RenderContext {
} else {
// parent component is also being cached,
// merge self into parent's result
buffer[bufferIndex - 1] += result
buffer[bufferIndex - 1] += result.html
const prev = componentBuffer[bufferIndex - 1]
result.components.forEach(c => prev.add(c))
}
buffer.length = bufferIndex
componentBuffer.length = bufferIndex
this.next()
break
}

33
src/server/render.js

@ -37,15 +37,20 @@ const normalizeRender = vm => {
}
function renderNode (node, isRoot, context) {
const { write, next } = context
const { write, next, userContext } = context
if (isDef(node.componentOptions)) {
// check cache hit
const Ctor = node.componentOptions.Ctor
const getKey = Ctor.options.serverCacheKey
const name = Ctor.options.name
// exposed by vue-loader, need to call this if cache hit because
// component lifecycle hooks will not be called.
const injectStyles = Ctor.options._injectStyles
const registerComponent = Ctor.options._ssrRegister
if (write.caching && isDef(registerComponent)) {
write.componentBuffer[write.componentBuffer.length - 1].add(registerComponent)
}
const cache = context.cache
if (isDef(getKey) && isDef(cache) && isDef(name)) {
const key = name + '::' + getKey(node.componentOptions.propsData)
@ -54,8 +59,9 @@ function renderNode (node, isRoot, context) {
(has: any)(key, hit => {
if (hit === true && isDef(get)) {
(get: any)(key, res => {
injectStyles && injectStyles.call({})
write(res, next)
registerComponent && registerComponent(userContext)
res.components.forEach(register => register(userContext))
write(res.html, next)
})
} else {
renderComponentWithCache(node, isRoot, key, context)
@ -64,8 +70,9 @@ function renderNode (node, isRoot, context) {
} else if (isDef(get)) {
(get: any)(key, res => {
if (isDef(res)) {
injectStyles && injectStyles.call({})
write(res, next)
registerComponent && registerComponent(userContext)
res.components.forEach(register => register(userContext))
write(res.html, next)
} else {
renderComponentWithCache(node, isRoot, key, context)
}
@ -101,7 +108,10 @@ function renderNode (node, isRoot, context) {
function renderComponent (node, isRoot, context) {
const prevActive = context.activeInstance
const child = context.activeInstance = createComponentInstanceForVnode(node, context.activeInstance)
const child = context.activeInstance = createComponentInstanceForVnode(
node,
context.activeInstance
)
normalizeRender(child)
const childNode = child._render()
childNode.parent = node
@ -117,9 +127,14 @@ function renderComponentWithCache (node, isRoot, key, context) {
write.caching = true
const buffer = write.cacheBuffer
const bufferIndex = buffer.push('') - 1
const componentBuffer = write.componentBuffer
componentBuffer.push(new Set())
context.renderStates.push({
type: 'ComponentWithCache',
buffer, bufferIndex, key
key,
buffer,
bufferIndex,
componentBuffer
})
renderComponent(node, isRoot, context)
}
@ -234,11 +249,13 @@ export function createRenderFunction (
return function render (
component: Component,
write: (text: string, next: Function) => void,
userContext: ?Object,
done: Function
) {
warned = Object.create(null)
const context = new RenderContext({
activeInstance: component,
userContext,
write, done, renderNode,
isUnaryTag, modules, directives,
cache

1
src/server/write.js

@ -28,5 +28,6 @@ export function createWriteFunction (
}
cachedWrite.caching = false
cachedWrite.cacheBuffer = []
cachedWrite.componentBuffer = []
return cachedWrite
}

57
test/ssr/fixtures/nested-cache.js

@ -0,0 +1,57 @@
import Vue from '../../../dist/vue.runtime.common.js'
function register (id, context) {
context = context || __VUE_SSR_CONTEXT__ // eslint-disable-line
context.registered.push(id)
}
const grandchild = {
name: 'grandchild',
props: ['id'],
_ssrRegister: context => {
register('grandchild', context)
},
beforeCreate () {
register('grandchild')
},
serverCacheKey: props => props.id,
render (h) {
return h('div', '/test')
}
}
const child = {
name: 'child',
props: ['id'],
_ssrRegister: context => {
register('child', context)
},
beforeCreate () {
register('child')
},
serverCacheKey: props => props.id,
render (h) {
return h(grandchild, { props: { id: this.id }})
}
}
const app = {
name: 'app',
props: ['id'],
_ssrRegister: context => {
register('app', context)
},
beforeCreate () {
register('app')
},
serverCacheKey: props => props.id,
render (h) {
return h(child, { props: { id: this.id }})
}
}
export default () => {
return Promise.resolve(new Vue({
render: h => h(app, { props: { id: 1 }})
}))
}

48
test/ssr/ssr-bundle-render.spec.js

@ -1,3 +1,4 @@
import LRU from 'lru-cache'
import { VueSSRServerPlugin } from 'vue-ssr-webpack-plugin'
import { compileWithWebpack } from './compile-with-webpack'
import { createBundleRenderer } from '../../packages/vue-server-renderer'
@ -105,8 +106,10 @@ describe('SSR: bundle renderer', () => {
expect(err).toBeNull()
expect(res).toBe(expected)
expect(get).toHaveBeenCalledWith(key)
expect(set).toHaveBeenCalledWith(key, expected)
expect(cache[key]).toBe(expected)
const setArgs = set.calls.argsFor(0)
expect(setArgs[0]).toBe(key)
expect(setArgs[1].html).toBe(expected)
expect(cache[key].html).toBe(expected)
renderer.renderToString((err, res) => {
expect(err).toBeNull()
expect(res).toBe(expected)
@ -149,8 +152,10 @@ describe('SSR: bundle renderer', () => {
expect(res).toBe(expected)
expect(has).toHaveBeenCalledWith(key)
expect(get).not.toHaveBeenCalled()
expect(set).toHaveBeenCalledWith(key, expected)
expect(cache[key]).toBe(expected)
const setArgs = set.calls.argsFor(0)
expect(setArgs[0]).toBe(key)
expect(setArgs[1].html).toBe(expected)
expect(cache[key].html).toBe(expected)
renderer.renderToString((err, res) => {
expect(err).toBeNull()
expect(res).toBe(expected)
@ -163,6 +168,41 @@ describe('SSR: bundle renderer', () => {
})
})
it('render with cache (nested)', done => {
const cache = LRU({ maxAge: Infinity })
spyOn(cache, 'get').and.callThrough()
spyOn(cache, 'set').and.callThrough()
const options = { cache }
createRenderer('nested-cache.js', options, renderer => {
const expected = '<div data-server-rendered="true">/test</div>'
const key = 'app::1'
const context1 = { registered: [] }
const context2 = { registered: [] }
renderer.renderToString(context1, (err, res) => {
expect(err).toBeNull()
expect(res).toBe(expected)
expect(cache.set.calls.count()).toBe(3) // 3 nested components cached
const cached = cache.get(key)
expect(cached.html).toBe(expected)
expect(cache.get.calls.count()).toBe(1)
// assert component usage registration for nested children
expect(context1.registered).toEqual(['app', 'child', 'grandchild'])
renderer.renderToString(context2, (err, res) => {
expect(err).toBeNull()
expect(res).toBe(expected)
expect(cache.set.calls.count()).toBe(3) // no new cache sets
expect(cache.get.calls.count()).toBe(2) // 1 get for root
console.log(context1)
console.log(context2)
done()
})
})
})
})
it('renderToString (bundle format with code split)', done => {
createRenderer('split.js', { asBundle: true }, renderer => {
const context = { url: '/test' }

2
yarn.lock

@ -3297,7 +3297,7 @@ lru-cache@2.2.x:
version "2.2.4"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.2.4.tgz#6c658619becf14031d0d0b594b16042ce4dc063d"
lru-cache@^4.0.1:
lru-cache@^4.0.1, lru-cache@^4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.0.2.tgz#1d17679c069cda5d040991a09dbc2c0db377e55e"
dependencies:

Loading…
Cancel
Save