You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

347 lines
10 KiB

import LRU from 'lru-cache'
import { compileWithWebpack } from './compile-with-webpack'
import { createBundleRenderer } from '../../packages/vue-server-renderer'
import VueSSRServerPlugin from '../../packages/vue-server-renderer/server-plugin'
export function createRenderer (file, options, cb) {
if (typeof options === 'function') {
cb = options
options = undefined
}
const asBundle = !!(options && options.asBundle)
if (options) delete options.asBundle
compileWithWebpack(file, {
target: 'node',
devtool: asBundle ? '#source-map' : false,
output: {
path: '/',
filename: 'bundle.js',
libraryTarget: 'commonjs2'
},
externals: [require.resolve('../../dist/vue.runtime.common.js')],
plugins: asBundle
? [new VueSSRServerPlugin()]
: []
}, fs => {
const bundle = asBundle
? JSON.parse(fs.readFileSync('/vue-ssr-server-bundle.json', 'utf-8'))
: fs.readFileSync('/bundle.js', 'utf-8')
const renderer = createBundleRenderer(bundle, options)
cb(renderer)
})
}
describe('SSR: bundle renderer', () => {
createAssertions(true)
createAssertions(false)
})
function createAssertions (runInNewContext) {
it('renderToString', done => {
createRenderer('app.js', { runInNewContext }, renderer => {
const context = { url: '/test' }
renderer.renderToString(context, (err, res) => {
expect(err).toBeNull()
expect(res).toBe('<div data-server-rendered="true">/test</div>')
expect(context.msg).toBe('hello')
done()
})
})
})
it('renderToStream', done => {
createRenderer('app.js', { runInNewContext }, renderer => {
const context = { url: '/test' }
const stream = renderer.renderToStream(context)
let res = ''
stream.on('data', chunk => {
res += chunk.toString()
})
stream.on('end', () => {
expect(res).toBe('<div data-server-rendered="true">/test</div>')
expect(context.msg).toBe('hello')
done()
})
})
})
it('renderToString catch error', done => {
createRenderer('error.js', { runInNewContext }, renderer => {
renderer.renderToString(err => {
expect(err.message).toBe('foo')
done()
})
})
})
it('renderToString catch Promise rejection', done => {
createRenderer('promise-rejection.js', { runInNewContext }, renderer => {
renderer.renderToString(err => {
expect(err.message).toBe('foo')
done()
})
})
})
it('renderToStream catch error', done => {
createRenderer('error.js', { runInNewContext }, renderer => {
const stream = renderer.renderToStream()
stream.on('error', err => {
expect(err.message).toBe('foo')
done()
})
})
})
it('renderToStream catch Promise rejection', done => {
createRenderer('promise-rejection.js', { runInNewContext }, renderer => {
const stream = renderer.renderToStream()
stream.on('error', err => {
expect(err.message).toBe('foo')
done()
})
})
})
it('render with cache (get/set)', done => {
const cache = {}
const get = jasmine.createSpy('get')
const set = jasmine.createSpy('set')
const options = {
runInNewContext,
cache: {
// async
get: (key, cb) => {
setTimeout(() => {
get(key)
cb(cache[key])
}, 0)
},
set: (key, val) => {
set(key, val)
cache[key] = val
}
}
}
createRenderer('cache.js', options, renderer => {
const expected = '<div data-server-rendered="true">/test</div>'
const key = 'app::1'
renderer.renderToString((err, res) => {
expect(err).toBeNull()
expect(res).toBe(expected)
expect(get).toHaveBeenCalledWith(key)
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)
expect(get.calls.count()).toBe(2)
expect(set.calls.count()).toBe(1)
done()
})
})
})
})
it('render with cache (get/set/has)', done => {
const cache = {}
const has = jasmine.createSpy('has')
const get = jasmine.createSpy('get')
const set = jasmine.createSpy('set')
const options = {
runInNewContext,
cache: {
// async
has: (key, cb) => {
has(key)
cb(!!cache[key])
},
// sync
get: key => {
get(key)
return cache[key]
},
set: (key, val) => {
set(key, val)
cache[key] = val
}
}
}
createRenderer('cache.js', options, renderer => {
const expected = '<div data-server-rendered="true">/test</div>'
const key = 'app::1'
renderer.renderToString((err, res) => {
expect(err).toBeNull()
expect(res).toBe(expected)
expect(has).toHaveBeenCalledWith(key)
expect(get).not.toHaveBeenCalled()
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)
expect(has.calls.count()).toBe(2)
expect(get.calls.count()).toBe(1)
expect(set.calls.count()).toBe(1)
done()
})
})
})
})
it('render with cache (nested)', done => {
const cache = new LRU({ maxAge: Infinity })
spyOn(cache, 'get').and.callThrough()
spyOn(cache, 'set').and.callThrough()
const options = {
cache,
runInNewContext
}
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
expect(context2.registered).toEqual(['app', 'child', 'grandchild'])
done()
})
})
})
})
it('render with cache (opt-out)', done => {
const cache = {}
const get = jasmine.createSpy('get')
const set = jasmine.createSpy('set')
const options = {
runInNewContext,
cache: {
// async
get: (key, cb) => {
setTimeout(() => {
get(key)
cb(cache[key])
}, 0)
},
set: (key, val) => {
set(key, val)
cache[key] = val
}
}
}
createRenderer('cache-opt-out.js', options, renderer => {
const expected = '<div data-server-rendered="true">/test</div>'
renderer.renderToString((err, res) => {
expect(err).toBeNull()
expect(res).toBe(expected)
expect(get).not.toHaveBeenCalled()
expect(set).not.toHaveBeenCalled()
renderer.renderToString((err, res) => {
expect(err).toBeNull()
expect(res).toBe(expected)
expect(get).not.toHaveBeenCalled()
expect(set).not.toHaveBeenCalled()
done()
})
})
})
})
it('renderToString (bundle format with code split)', done => {
createRenderer('split.js', { runInNewContext, asBundle: true }, renderer => {
const context = { url: '/test' }
renderer.renderToString(context, (err, res) => {
expect(err).toBeNull()
expect(res).toBe('<div data-server-rendered="true">/test<div>async test.woff2 test.png</div></div>')
done()
})
})
})
it('renderToStream (bundle format with code split)', done => {
createRenderer('split.js', { runInNewContext, asBundle: true }, renderer => {
const context = { url: '/test' }
const stream = renderer.renderToStream(context)
let res = ''
stream.on('data', chunk => {
res += chunk.toString()
})
stream.on('end', () => {
expect(res).toBe('<div data-server-rendered="true">/test<div>async test.woff2 test.png</div></div>')
done()
})
})
})
it('renderToString catch error (bundle format with source map)', done => {
createRenderer('error.js', { runInNewContext, asBundle: true }, renderer => {
renderer.renderToString(err => {
expect(err.stack).toContain('test/ssr/fixtures/error.js:1:6')
expect(err.message).toBe('foo')
done()
})
})
})
it('renderToString catch error (bundle format with source map)', done => {
createRenderer('error.js', { runInNewContext, asBundle: true }, renderer => {
const stream = renderer.renderToStream()
stream.on('error', err => {
expect(err.stack).toContain('test/ssr/fixtures/error.js:1:6')
expect(err.message).toBe('foo')
done()
})
})
})
it('renderToString return Promise', done => {
createRenderer('app.js', { runInNewContext }, renderer => {
const context = { url: '/test' }
renderer.renderToString(context).then(res => {
expect(res).toBe('<div data-server-rendered="true">/test</div>')
expect(context.msg).toBe('hello')
done()
})
})
})
it('renderToString return Promise (error)', done => {
createRenderer('error.js', { runInNewContext }, renderer => {
renderer.renderToString().catch(err => {
expect(err.message).toBe('foo')
done()
})
})
})
it('renderToString return Promise (Promise rejection)', done => {
createRenderer('promise-rejection.js', { runInNewContext }, renderer => {
renderer.renderToString().catch(err => {
expect(err.message).toBe('foo')
done()
})
})
})
}