Skip to content

Commit ba3bec8

Browse files
committed
SSR: allow user to provide own (possibly async) cache implementation
1 parent 6cf1929 commit ba3bec8

File tree

9 files changed

+177
-46
lines changed

9 files changed

+177
-46
lines changed

build/build.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ var builds = [
7272
{
7373
entry: 'src/entries/web-server-renderer.js',
7474
format: 'cjs',
75-
external: ['stream', 'module', 'vm', 'entities', 'lru-cache'],
75+
external: ['stream', 'module', 'vm', 'entities'],
7676
out: 'packages/vue-server-renderer/index.js'
7777
}
7878
]

build/webpack.ssr.dev.config.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@ module.exports = {
1313
alias: alias
1414
},
1515
externals: {
16-
'entities': true,
17-
'lru-cache': true
16+
'entities': true
1817
},
1918
module: {
2019
noParse: /run-in-vm/,

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,6 @@
7474
"karma-sourcemap-loader": "^0.3.0",
7575
"karma-webpack": "^1.7.0",
7676
"lodash": "^4.13.1",
77-
"lru-cache": "^4.0.1",
7877
"nightwatch": "^0.9.0",
7978
"phantomjs-prebuilt": "^2.1.1",
8079
"rollup": "^0.33.0",

packages/vue-server-renderer/package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,7 @@
1818
"url": "https://github.com/vuejs/vue/issues"
1919
},
2020
"dependencies": {
21-
"entities": "^1.1.1",
22-
"lru-cache": "^4.0.1"
21+
"entities": "^1.1.1"
2322
},
2423
"homepage": "https://github.com/vuejs/vue#readme"
2524
}

src/entries/web-server-renderer.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export function createRenderer (options?: Object = {}): {
1717
isUnaryTag,
1818
modules,
1919
directives,
20-
cache: options.cache || {}
20+
cache: options.cache
2121
})
2222
}
2323

src/server/create-renderer.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@ export function createRenderer ({
99
modules = [],
1010
directives = {},
1111
isUnaryTag = (() => false),
12-
cache = {}
12+
cache
1313
}: {
1414
modules: Array<Function>,
1515
directives: Object,
1616
isUnaryTag: Function,
17-
cache: Object
17+
cache: ?Object
1818
} = {}): {
1919
renderToString: Function,
2020
renderToStream: Function

src/server/render.js

Lines changed: 67 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,33 @@
22

33
import { cached } from 'shared/util'
44
import { encodeHTML } from 'entities'
5-
import LRU from 'lru-cache'
65
import { createComponentInstanceForVnode } from 'core/vdom/create-component'
76

87
const encodeHTMLCached = cached(encodeHTML)
9-
const defaultOptions = {
10-
max: 5000
8+
9+
const normalizeAsync = (cache, method) => {
10+
const fn = cache[method]
11+
if (!fn) {
12+
return
13+
} else if (fn.length > 1) {
14+
return (key, cb) => fn.call(cache, key, cb)
15+
} else {
16+
return (key, cb) => cb(fn.call(cache, key))
17+
}
1118
}
1219

1320
export function createRenderFunction (
1421
modules: Array<Function>,
1522
directives: Object,
1623
isUnaryTag: Function,
17-
cacheOptions: Object
24+
cache: any
1825
) {
19-
const cache = LRU(Object.assign({}, defaultOptions, cacheOptions))
26+
if (cache && (!cache.get || !cache.set)) {
27+
throw new Error('renderer cache must implement at least get & set.')
28+
}
29+
30+
const get = cache && normalizeAsync(cache, 'get')
31+
const has = cache && normalizeAsync(cache, 'has')
2032

2133
function renderNode (
2234
node: VNode,
@@ -28,35 +40,34 @@ export function createRenderFunction (
2840
// check cache hit
2941
const Ctor = node.componentOptions.Ctor
3042
const getKey = Ctor.options.server && Ctor.options.server.getCacheKey
31-
if (getKey) {
43+
if (getKey && cache) {
3244
const key = Ctor.cid + '::' + getKey(node.componentOptions.propsData)
33-
if (cache.has(key)) {
34-
return write(cache.get(key), next)
45+
if (has) {
46+
has(key, hit => {
47+
if (hit) {
48+
get(key, res => write(res, next))
49+
} else {
50+
renderComponentWithCache(node, write, next, isRoot, cache, key)
51+
}
52+
})
3553
} else {
36-
write.caching = true
37-
const buffer = write.cacheBuffer
38-
const bufferIndex = buffer.push('') - 1
39-
const _next = next
40-
next = () => {
41-
const result = buffer[bufferIndex]
42-
cache.set(key, result)
43-
if (bufferIndex === 0) {
44-
// this is a top-level cached component,
45-
// exit caching mode.
46-
write.caching = false
54+
get(key, res => {
55+
if (res) {
56+
write(res, next)
4757
} else {
48-
// parent component is also being cached,
49-
// merge self into parent's result
50-
buffer[bufferIndex - 1] += result
58+
renderComponentWithCache(node, write, next, isRoot, cache, key)
5159
}
52-
buffer.length = bufferIndex
53-
_next()
54-
}
60+
})
5561
}
62+
} else {
63+
if (getKey) {
64+
console.error(
65+
'Component implemented server.getCacheKey, ' +
66+
'but no cache was provided to the renderer.'
67+
)
68+
}
69+
renderComponent(node, write, next, isRoot)
5670
}
57-
const child = createComponentInstanceForVnode(node)._render()
58-
child.parent = node
59-
renderNode(child, write, next, isRoot)
6071
} else {
6172
if (node.tag) {
6273
renderElement(node, write, next, isRoot)
@@ -66,12 +77,34 @@ export function createRenderFunction (
6677
}
6778
}
6879

69-
function renderElement (
70-
el: VNode,
71-
write: Function,
72-
next: Function,
73-
isRoot: boolean
74-
) {
80+
function renderComponent (node, write, next, isRoot) {
81+
const child = createComponentInstanceForVnode(node)._render()
82+
child.parent = node
83+
renderNode(child, write, next, isRoot)
84+
}
85+
86+
function renderComponentWithCache (node, write, next, isRoot, cache, key) {
87+
write.caching = true
88+
const buffer = write.cacheBuffer
89+
const bufferIndex = buffer.push('') - 1
90+
renderComponent(node, write, () => {
91+
const result = buffer[bufferIndex]
92+
cache.set(key, result)
93+
if (bufferIndex === 0) {
94+
// this is a top-level cached component,
95+
// exit caching mode.
96+
write.caching = false
97+
} else {
98+
// parent component is also being cached,
99+
// merge self into parent's result
100+
buffer[bufferIndex - 1] += result
101+
}
102+
buffer.length = bufferIndex
103+
next()
104+
}, isRoot)
105+
}
106+
107+
function renderElement (el, write, next, isRoot) {
75108
if (isRoot) {
76109
if (!el.data) el.data = {}
77110
if (!el.data.attrs) el.data.attrs = {}

test/ssr/fixtures/cache.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import Vue from '../../../dist/vue.common.js'
2+
3+
const app = {
4+
props: ['id'],
5+
server: {
6+
getCacheKey: props => props.id
7+
},
8+
render (h) {
9+
return h('div', '/test')
10+
}
11+
}
12+
13+
export default () => {
14+
return Promise.resolve(new Vue({
15+
render: h => h(app, { props: { id: 1 }})
16+
}))
17+
}

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

Lines changed: 87 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import MemoeryFS from 'memory-fs'
44
import { createBundleRenderer } from '../../packages/vue-server-renderer'
55

66
const rendererCache = {}
7-
function createRenderer (file, cb) {
8-
if (rendererCache[file]) {
7+
function createRenderer (file, cb, options) {
8+
if (!options && rendererCache[file]) {
99
return cb(rendererCache[file])
1010
}
1111
const compiler = webpack({
@@ -26,7 +26,7 @@ function createRenderer (file, cb) {
2626
expect(err).toBeFalsy()
2727
expect(stats.errors).toBeFalsy()
2828
const code = fs.readFileSync('/bundle.js', 'utf-8')
29-
const renderer = rendererCache[file] = createBundleRenderer(code)
29+
const renderer = rendererCache[file] = createBundleRenderer(code, options)
3030
cb(renderer)
3131
})
3232
}
@@ -78,4 +78,88 @@ describe('SSR: bundle renderer', () => {
7878
})
7979
})
8080
})
81+
82+
it('render with cache (get/set)', done => {
83+
const cache = {}
84+
const get = jasmine.createSpy('get')
85+
const set = jasmine.createSpy('set')
86+
const options = {
87+
cache: {
88+
// async
89+
get: (key, cb) => {
90+
setTimeout(() => {
91+
get(key)
92+
cb(cache[key])
93+
}, 0)
94+
},
95+
set: (key, val) => {
96+
set(key, val)
97+
cache[key] = val
98+
}
99+
}
100+
}
101+
createRenderer('cache.js', renderer => {
102+
const expected = '<div server-rendered="true">&sol;test</div>'
103+
const key = '1::1'
104+
renderer.renderToString((err, res) => {
105+
expect(err).toBeNull()
106+
expect(res).toBe(expected)
107+
expect(get).toHaveBeenCalledWith(key)
108+
expect(set).toHaveBeenCalledWith(key, expected)
109+
expect(cache[key]).toBe(expected)
110+
renderer.renderToString((err, res) => {
111+
expect(err).toBeNull()
112+
expect(res).toBe(expected)
113+
expect(get.calls.count()).toBe(2)
114+
expect(set.calls.count()).toBe(1)
115+
done()
116+
})
117+
})
118+
}, options)
119+
})
120+
121+
it('render with cache (get/set/has)', done => {
122+
const cache = {}
123+
const has = jasmine.createSpy('has')
124+
const get = jasmine.createSpy('get')
125+
const set = jasmine.createSpy('set')
126+
const options = {
127+
cache: {
128+
// async
129+
has: (key, cb) => {
130+
has(key)
131+
cb(!!cache[key])
132+
},
133+
// sync
134+
get: key => {
135+
get(key)
136+
return cache[key]
137+
},
138+
set: (key, val) => {
139+
set(key, val)
140+
cache[key] = val
141+
}
142+
}
143+
}
144+
createRenderer('cache.js', renderer => {
145+
const expected = '<div server-rendered="true">&sol;test</div>'
146+
const key = '1::1'
147+
renderer.renderToString((err, res) => {
148+
expect(err).toBeNull()
149+
expect(res).toBe(expected)
150+
expect(has).toHaveBeenCalledWith(key)
151+
expect(get).not.toHaveBeenCalled()
152+
expect(set).toHaveBeenCalledWith(key, expected)
153+
expect(cache[key]).toBe(expected)
154+
renderer.renderToString((err, res) => {
155+
expect(err).toBeNull()
156+
expect(res).toBe(expected)
157+
expect(has.calls.count()).toBe(2)
158+
expect(get.calls.count()).toBe(1)
159+
expect(set.calls.count()).toBe(1)
160+
done()
161+
})
162+
})
163+
}, options)
164+
})
81165
})

0 commit comments

Comments
 (0)