Skip to content

Commit c902e1f

Browse files
committed
ssr component-level caching + context injection
1 parent 1d8f3a2 commit c902e1f

File tree

9 files changed

+103
-56
lines changed

9 files changed

+103
-56
lines changed

build/build.js

+1-1
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', 'entities'],
75+
external: ['stream', 'entities', 'lru-cache'],
7676
out: 'packages/vue-server-renderer/create-renderer.js'
7777
}
7878
]

build/webpack.ssr.dev.config.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ module.exports = {
1313
alias: alias
1414
},
1515
externals: {
16-
'entities': true
16+
'entities': true,
17+
'lru-cache': true
1718
},
1819
module: {
1920
loaders: [

flow/modules.js

+6
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,9 @@ declare module 'source-map' {
1010
toString(): string;
1111
}
1212
}
13+
14+
declare module 'lru-cache' {
15+
declare var exports: {
16+
(): any
17+
}
18+
}

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
"karma-sourcemap-loader": "^0.3.0",
7474
"karma-webpack": "^1.7.0",
7575
"lodash": "^4.13.1",
76+
"lru-cache": "^4.0.1",
7677
"nightwatch": "^0.9.0",
7778
"phantomjs-prebuilt": "^2.1.1",
7879
"rollup": "^0.26.3",

packages/vue-server-renderer/index.js

+25-14
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,39 @@
1-
var createRenderer = require('./create-renderer')
2-
var Module = require('./module')
1+
'use strict'
32

4-
function runAsNewModule (code) {
5-
var path = '__app__'
6-
var m = new Module(path, null, true /* isBundle */)
3+
const createRenderer = require('./create-renderer')
4+
const Module = require('./module')
5+
const stream = require('stream')
6+
7+
function runAsNewModule (code, context) {
8+
const path = '__app__'
9+
const m = new Module(path, null, context)
710
m.load(path)
811
m._compile(code, path)
9-
return Object.prototype.hasOwnProperty.call(m.exports, 'default')
12+
const res = Object.prototype.hasOwnProperty.call(m.exports, 'default')
1013
? m.exports.default
1114
: m.exports
15+
if (typeof res.then !== 'function') {
16+
throw new Error('SSR bundle should export a Promise.')
17+
}
18+
return res
1219
}
1320

1421
exports.createRenderer = createRenderer
1522

16-
exports.createBundleRenderer = function (code, options) {
17-
var renderer = createRenderer(options)
23+
exports.createBundleRenderer = function (code, rendererOptions) {
24+
const renderer = createRenderer(rendererOptions)
1825
return {
19-
renderToString: function (cb) {
20-
var app = runAsNewModule(code)
21-
renderer.renderToString(app, cb)
26+
renderToString: (context, cb) => {
27+
runAsNewModule(code, context).then(app => {
28+
renderer.renderToString(app, cb)
29+
})
2230
},
23-
renderToStream: function () {
24-
var app = runAsNewModule(code)
25-
return renderer.renderToStream(app)
31+
renderToStream: (context) => {
32+
const res = new stream.PassThrough()
33+
runAsNewModule(code, context).then(app => {
34+
renderer.renderToStream(app).pipe(res)
35+
})
36+
return res
2637
}
2738
}
2839
}

packages/vue-server-renderer/module.js

+27-30
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,22 @@
11
// thanks to airbnb/hypernova
2+
'use strict'
23

3-
var NativeModule = require('module')
4-
var path = require('path')
5-
var assert = require('assert')
6-
var vm = require('vm')
4+
const NativeModule = require('module')
5+
const path = require('path')
6+
const assert = require('assert')
7+
const vm = require('vm')
78

8-
var NativeModules = process.binding('natives')
9+
const NativeModules = process.binding('natives')
910

10-
var moduleExtensions = Object.assign({}, NativeModule._extensions)
11+
const moduleExtensions = Object.assign({}, NativeModule._extensions)
1112

1213
function isNativeModule (id) {
1314
return Object.prototype.hasOwnProperty.call(NativeModules, id)
1415
}
1516

1617
// Creates a sandbox so we don't share globals across different runs.
17-
function createContext () {
18-
var sandbox = {
18+
function createContext (context) {
19+
const sandbox = {
1920
Buffer,
2021
clearImmediate,
2122
clearInterval,
@@ -24,22 +25,22 @@ function createContext () {
2425
setInterval,
2526
setTimeout,
2627
console,
27-
process
28+
process,
29+
__VUE_SSR_CONTEXT__: context || {}
2830
}
2931
sandbox.global = sandbox
3032
return sandbox
3133
}
3234

33-
function Module (id, parent, isBundle) {
34-
var cache = parent ? parent.cache : null
35+
function Module (id, parent, context) {
36+
const cache = parent ? parent.cache : null
3537
this.id = id
3638
this.exports = {}
3739
this.cache = cache || {}
3840
this.parent = parent
3941
this.filename = null
4042
this.loaded = false
41-
this.context = parent ? parent.context : createContext()
42-
this.isBundle = isBundle
43+
this.context = parent ? parent.context : createContext(context)
4344
}
4445

4546
Module.prototype.load = function (filename) {
@@ -49,8 +50,8 @@ Module.prototype.load = function (filename) {
4950
}
5051

5152
Module.prototype.run = function (filename) {
52-
var ext = path.extname(filename)
53-
var extension = moduleExtensions[ext] ? ext : '.js'
53+
const ext = path.extname(filename)
54+
const extension = moduleExtensions[ext] ? ext : '.js'
5455
moduleExtensions[extension](this, filename)
5556
this.loaded = true
5657
}
@@ -61,55 +62,51 @@ Module.prototype.require = function (filePath) {
6162
}
6263

6364
Module.prototype._compile = function (content, filename) {
64-
var self = this
65-
66-
function r (filePath) {
67-
return self.require(filePath)
68-
}
65+
const r = filePath => this.require(filePath)
6966
r.resolve = request => NativeModule._resolveFilename(request, this)
7067
r.main = process.mainModule
7168
r.extensions = moduleExtensions
7269
r.cache = this.cache
7370

74-
var dirname = path.dirname(filename)
71+
const dirname = path.dirname(filename)
7572

7673
// create wrapper function
77-
var wrapper = NativeModule.wrap(content)
74+
const wrapper = NativeModule.wrap(content)
7875

79-
var options = {
76+
const options = {
8077
filename,
8178
displayErrors: true
8279
}
8380

84-
var compiledWrapper = vm.runInNewContext(wrapper, this.context, options)
81+
const compiledWrapper = vm.runInNewContext(wrapper, this.context, options)
8582
return compiledWrapper.call(this.exports, this.exports, r, this, filename, dirname)
8683
}
8784

8885
Module.load = function (id, filename) {
89-
var m = new Module(id)
86+
const m = new Module(id)
9087
filename = filename || id
9188
m.load(filename)
9289
m.run(filename)
9390
return m
9491
}
9592

9693
Module.loadFile = function (file, parent) {
97-
var filename = NativeModule._resolveFilename(file, parent)
94+
const filename = NativeModule._resolveFilename(file, parent)
9895

9996
if (parent) {
100-
var cachedModule = parent.cache[filename]
97+
const cachedModule = parent.cache[filename]
10198
if (cachedModule) return cachedModule.exports
10299
}
103100

104-
if (parent.isBundle || isNativeModule(filename)) {
101+
if (isNativeModule(filename)) {
105102
return require(filename)
106103
}
107104

108-
var m = new Module(filename, parent)
105+
const m = new Module(filename, parent)
109106

110107
m.cache[filename] = m
111108

112-
var hadException = true
109+
let hadException = true
113110

114111
try {
115112
m.load(filename)

src/entries/web-server-renderer.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export default function publicCreateRenderer (options?: Object = {}): {
1515
return createRenderer({
1616
isUnaryTag,
1717
modules,
18-
directives
18+
directives,
19+
cache: options.cache || {}
1920
})
2021
}

src/server/create-renderer.js

+5-3
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,13 @@ export const MAX_STACK_DEPTH = 1000
99
export function createRenderer ({
1010
modules = [],
1111
directives = {},
12-
isUnaryTag = (() => false)
12+
isUnaryTag = (() => false),
13+
cache = {}
1314
}: {
1415
modules: Array<Function>,
1516
directives: Object,
16-
isUnaryTag: Function
17+
isUnaryTag: Function,
18+
cache: Object
1719
} = {}): {
1820
renderToString: Function,
1921
renderToStream: Function
@@ -25,7 +27,7 @@ export function createRenderer ({
2527
'by turning data observation off.'
2628
)
2729
}
28-
const render = createRenderFunction(modules, directives, isUnaryTag)
30+
const render = createRenderFunction(modules, directives, isUnaryTag, cache)
2931

3032
return {
3133
renderToString (

src/server/render.js

+34-6
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,32 @@
22

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

78
const encodeHTMLCached = cached(encodeHTML)
9+
const defaultOptions = {
10+
max: 5000
11+
}
812

913
export function createRenderFunction (
1014
modules: Array<Function>,
1115
directives: Object,
12-
isUnaryTag: Function
16+
isUnaryTag: Function,
17+
cacheOptions: Object
1318
) {
19+
const cache = LRU(Object.assign({}, defaultOptions, cacheOptions))
20+
1421
function renderNode (
1522
node: VNode,
1623
write: Function,
1724
next: Function,
1825
isRoot: boolean
1926
) {
2027
if (node.componentOptions) {
21-
const child = createComponentInstanceForVnode(node)._render()
28+
const child =
29+
getCachedComponent(node) ||
30+
createComponentInstanceForVnode(node)._render()
2231
child.parent = node
2332
renderNode(child, write, next, isRoot)
2433
} else {
@@ -30,6 +39,21 @@ export function createRenderFunction (
3039
}
3140
}
3241

42+
function getCachedComponent (node) {
43+
const Ctor = node.componentOptions.Ctor
44+
const getKey = Ctor.options.server && Ctor.options.server.getCacheKey
45+
if (getKey) {
46+
const key = Ctor.cid + '::' + getKey(node.componentOptions.propsData)
47+
if (cache.has(key)) {
48+
return cache.get(key)
49+
} else {
50+
const res = createComponentInstanceForVnode(node)._render()
51+
cache.set(key, res)
52+
return res
53+
}
54+
}
55+
}
56+
3357
function renderElement (
3458
el: VNode,
3559
write: Function,
@@ -70,6 +94,9 @@ export function createRenderFunction (
7094
}
7195

7296
function renderStartingTag (node: VNode) {
97+
if (node._rendered) {
98+
return node._rendered
99+
}
73100
let markup = `<${node.tag}`
74101
if (node.data) {
75102
// check directives
@@ -97,13 +124,14 @@ export function createRenderFunction (
97124
if (node.host && (scopeId = node.host.$options._scopeId)) {
98125
markup += ` ${scopeId}`
99126
}
100-
while (node) {
101-
if ((scopeId = node.context.$options._scopeId)) {
127+
let _node = node
128+
while (_node) {
129+
if ((scopeId = _node.context.$options._scopeId)) {
102130
markup += ` ${scopeId}`
103131
}
104-
node = node.parent
132+
_node = _node.parent
105133
}
106-
return markup + '>'
134+
return (node._rendered = markup + '>')
107135
}
108136

109137
return function render (

0 commit comments

Comments
 (0)