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() }) }) } })