import Vue from '../../dist/vue.runtime.common.js'
import { compileWithWebpack } from './compile-with-webpack'
import { createRenderer } from '../../packages/vue-server-renderer'
import VueSSRClientPlugin from '../../packages/vue-server-renderer/client-plugin'
import { createRenderer as createBundleRenderer } from './ssr-bundle-render.spec.js'
const defaultTemplate = `
`
const interpolateTemplate = `{{ title }}{{{ snippet }}}`
function generateClientManifest (file, cb) {
compileWithWebpack(file, {
output: {
path: '/',
publicPath: '/',
filename: '[name].js'
},
optimization: {
runtimeChunk: {
name: 'manifest'
}
},
plugins: [
new VueSSRClientPlugin()
]
}, fs => {
cb(JSON.parse(fs.readFileSync('/vue-ssr-client-manifest.json', 'utf-8')))
})
}
function createRendererWithManifest (file, options, cb) {
if (typeof options === 'function') {
cb = options
options = null
}
generateClientManifest(file, clientManifest => {
createBundleRenderer(file, Object.assign({
asBundle: true,
template: defaultTemplate,
clientManifest
}, options), cb)
})
}
describe('SSR: template option', () => {
it('renderToString', done => {
const renderer = createRenderer({
template: defaultTemplate
})
const context = {
head: '',
styles: '',
state: { a: 1 }
}
renderer.renderToString(new Vue({
template: 'hi
'
}), context, (err, res) => {
expect(err).toBeNull()
expect(res).toContain(
`${context.head}${context.styles}` +
`hi
` +
`` +
``
)
done()
})
})
it('renderToString with interpolation', done => {
const renderer = createRenderer({
template: interpolateTemplate
})
const context = {
title: '',
snippet: 'foo
',
head: '',
styles: '',
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('renderToString with interpolation and context.rendered', done => {
const renderer = createRenderer({
template: interpolateTemplate
})
const context = {
title: '',
snippet: 'foo
',
head: '',
styles: '',
state: { a: 0 },
rendered: 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
})
const context = {
head: '',
styles: '',
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(
`${context.head}${context.styles}` +
`hi
` +
`` +
``
)
done()
})
})
it('renderToStream with interpolation', done => {
const renderer = createRenderer({
template: interpolateTemplate
})
const context = {
title: '',
snippet: 'foo
',
head: '',
styles: '',
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('renderToStream with interpolation and context.rendered', done => {
const renderer = createRenderer({
template: interpolateTemplate
})
const context = {
title: '',
snippet: 'foo
',
head: '',
styles: '',
state: { a: 0 },
rendered: 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,
template: defaultTemplate
}, renderer => {
const context = {
head: '',
styles: '',
state: { a: 1 },
url: '/test'
}
renderer.renderToString(context, (err, res) => {
expect(err).toBeNull()
expect(res).toContain(
`${context.head}${context.styles}` +
`/test
` +
`` +
``
)
expect(context.msg).toBe('hello')
done()
})
})
})
it('bundleRenderer + renderToStream', done => {
createBundleRenderer('app.js', {
asBundle: true,
template: defaultTemplate
}, renderer => {
const context = {
head: '',
styles: '',
state: { a: 1 },
url: '/test'
}
const stream = renderer.renderToStream(context)
let res = ''
stream.on('data', chunk => {
res += chunk.toString()
})
stream.on('end', () => {
expect(res).toContain(
`${context.head}${context.styles}` +
`/test
` +
`` +
``
)
expect(context.msg).toBe('hello')
done()
})
})
})
const expectedHTMLWithManifest = (options = {}) =>
`` +
// used chunks should have preload
`` +
`` +
`` +
`` +
// images and fonts are only preloaded when explicitly asked for
(options.preloadOtherAssets ? `` : ``) +
(options.preloadOtherAssets ? `` : ``) +
// unused chunks should have prefetch
(options.noPrefetch ? `` : ``) +
// css assets should be loaded
`` +
`` +
`async test.woff2 test.png
` +
// state should be inlined before scripts
`` +
// manifest chunk should be first
`` +
// async chunks should be before main chunk
`` +
`` +
``
createClientManifestAssertions(true)
createClientManifestAssertions(false)
function createClientManifestAssertions (runInNewContext) {
it('bundleRenderer + renderToString + clientManifest ()', done => {
createRendererWithManifest('split.js', { runInNewContext }, renderer => {
renderer.renderToString({ state: { a: 1 }}, (err, res) => {
expect(err).toBeNull()
expect(res).toContain(expectedHTMLWithManifest())
done()
})
})
})
it('bundleRenderer + renderToStream + clientManifest + shouldPreload', done => {
createRendererWithManifest('split.js', {
runInNewContext,
shouldPreload: (file, type) => {
if (type === 'image' || type === 'script' || type === 'font' || type === 'style') {
return true
}
}
}, renderer => {
const stream = renderer.renderToStream({ state: { a: 1 }})
let res = ''
stream.on('data', chunk => {
res += chunk.toString()
})
stream.on('end', () => {
expect(res).toContain(expectedHTMLWithManifest({
preloadOtherAssets: true
}))
done()
})
})
})
it('bundleRenderer + renderToStream + clientManifest + shouldPrefetch', done => {
createRendererWithManifest('split.js', {
runInNewContext,
shouldPrefetch: (file, type) => {
if (type === 'script') {
return false
}
}
}, renderer => {
const stream = renderer.renderToStream({ state: { a: 1 }})
let res = ''
stream.on('data', chunk => {
res += chunk.toString()
})
stream.on('end', () => {
expect(res).toContain(expectedHTMLWithManifest({
noPrefetch: true
}))
done()
})
})
})
it('bundleRenderer + renderToString + clientManifest + inject: false', done => {
createRendererWithManifest('split.js', {
runInNewContext,
template: `` +
`{{{ renderResourceHints() }}}{{{ renderStyles() }}}` +
`{{{ renderState({ windowKey: '__FOO__', contextKey: 'foo' }) }}}{{{ renderScripts() }}}` +
``,
inject: false
}, renderer => {
const context = { foo: { a: 1 }}
renderer.renderToString(context, (err, res) => {
expect(err).toBeNull()
expect(res).toContain(expectedHTMLWithManifest({
stateKey: '__FOO__'
}))
done()
})
})
})
it('bundleRenderer + renderToString + clientManifest + no template', done => {
createRendererWithManifest('split.js', {
runInNewContext,
template: null
}, renderer => {
const context = { foo: { a: 1 }}
renderer.renderToString(context, (err, res) => {
expect(err).toBeNull()
const customOutput =
`${
context.renderResourceHints() +
context.renderStyles()
}${
res +
context.renderState({
windowKey: '__FOO__',
contextKey: 'foo'
}) +
context.renderScripts()
}`
expect(customOutput).toContain(expectedHTMLWithManifest({
stateKey: '__FOO__'
}))
done()
})
})
})
it('whitespace insensitive interpolation', done => {
const interpolateTemplate = `{{title}}{{{snippet}}}`
const renderer = createRenderer({
template: interpolateTemplate
})
const context = {
title: '',
snippet: 'foo
',
head: '',
styles: '',
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('renderToString + nonce', done => {
const interpolateTemplate = `hello`
const renderer = createRenderer({
template: interpolateTemplate
})
const context = {
state: { a: 1 },
nonce: '4AEemGb0xJptoIGFP3Nd'
}
renderer.renderToString(new Vue({
template: 'hi
'
}), context, (err, res) => {
expect(err).toBeNull()
expect(res).toContain(
`` +
`hello` +
`` +
`hi
` +
`` +
``
)
done()
})
})
it('renderToString + custom serializer', done => {
const expected = `{"foo":123}`
const renderer = createRenderer({
template: defaultTemplate,
serializer: () => expected
})
const context = {
state: { a: 1 }
}
renderer.renderToString(new Vue({
template: 'hi
'
}), context, (err, res) => {
expect(err).toBeNull()
expect(res).toContain(
``
)
done()
})
})
}
})