fix(ssr): handle fallthrough attrs in ssr compile output

This commit is contained in:
Evan You 2020-06-26 14:23:50 -04:00
parent 30584bcc61
commit d5dbd27193
21 changed files with 776 additions and 488 deletions

View File

@ -210,7 +210,7 @@ export function generate(
if (!ssr) { if (!ssr) {
push(`function render(_ctx, _cache) {`) push(`function render(_ctx, _cache) {`)
} else { } else {
push(`function ssrRender(_ctx, _push, _parent) {`) push(`function ssrRender(_ctx, _push, _parent, _attrs) {`)
} }
indent() indent()

View File

@ -3,16 +3,16 @@ import { compile } from '../src'
describe('ssr: components', () => { describe('ssr: components', () => {
test('basic', () => { test('basic', () => {
expect(compile(`<foo id="a" :prop="b" />`).code).toMatchInlineSnapshot(` expect(compile(`<foo id="a" :prop="b" />`).code).toMatchInlineSnapshot(`
"const { resolveComponent: _resolveComponent } = require(\\"vue\\") "const { resolveComponent: _resolveComponent, mergeProps: _mergeProps } = require(\\"vue\\")
const { ssrRenderComponent: _ssrRenderComponent } = require(\\"@vue/server-renderer\\") const { ssrRenderComponent: _ssrRenderComponent } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent) { return function ssrRender(_ctx, _push, _parent, _attrs) {
const _component_foo = _resolveComponent(\\"foo\\") const _component_foo = _resolveComponent(\\"foo\\")
_push(_ssrRenderComponent(_component_foo, { _push(_ssrRenderComponent(_component_foo, _mergeProps({
id: \\"a\\", id: \\"a\\",
prop: _ctx.b prop: _ctx.b
}, null, _parent)) }, _attrs), null, _parent))
}" }"
`) `)
}) })
@ -20,21 +20,21 @@ describe('ssr: components', () => {
test('dynamic component', () => { test('dynamic component', () => {
expect(compile(`<component is="foo" prop="b" />`).code) expect(compile(`<component is="foo" prop="b" />`).code)
.toMatchInlineSnapshot(` .toMatchInlineSnapshot(`
"const { resolveDynamicComponent: _resolveDynamicComponent } = require(\\"vue\\") "const { resolveDynamicComponent: _resolveDynamicComponent, mergeProps: _mergeProps } = require(\\"vue\\")
const { ssrRenderComponent: _ssrRenderComponent } = require(\\"@vue/server-renderer\\") const { ssrRenderComponent: _ssrRenderComponent } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent) { return function ssrRender(_ctx, _push, _parent, _attrs) {
_push(_ssrRenderComponent(_resolveDynamicComponent(\\"foo\\"), { prop: \\"b\\" }, null, _parent)) _push(_ssrRenderComponent(_resolveDynamicComponent(\\"foo\\"), _mergeProps({ prop: \\"b\\" }, _attrs), null, _parent))
}" }"
`) `)
expect(compile(`<component :is="foo" prop="b" />`).code) expect(compile(`<component :is="foo" prop="b" />`).code)
.toMatchInlineSnapshot(` .toMatchInlineSnapshot(`
"const { resolveDynamicComponent: _resolveDynamicComponent } = require(\\"vue\\") "const { resolveDynamicComponent: _resolveDynamicComponent, mergeProps: _mergeProps } = require(\\"vue\\")
const { ssrRenderComponent: _ssrRenderComponent } = require(\\"@vue/server-renderer\\") const { ssrRenderComponent: _ssrRenderComponent } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent) { return function ssrRender(_ctx, _push, _parent, _attrs) {
_push(_ssrRenderComponent(_resolveDynamicComponent(_ctx.foo), { prop: \\"b\\" }, null, _parent)) _push(_ssrRenderComponent(_resolveDynamicComponent(_ctx.foo), _mergeProps({ prop: \\"b\\" }, _attrs), null, _parent))
}" }"
`) `)
}) })
@ -45,10 +45,10 @@ describe('ssr: components', () => {
"const { resolveComponent: _resolveComponent, withCtx: _withCtx, createVNode: _createVNode, createTextVNode: _createTextVNode } = require(\\"vue\\") "const { resolveComponent: _resolveComponent, withCtx: _withCtx, createVNode: _createVNode, createTextVNode: _createTextVNode } = require(\\"vue\\")
const { ssrRenderComponent: _ssrRenderComponent } = require(\\"@vue/server-renderer\\") const { ssrRenderComponent: _ssrRenderComponent } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent) { return function ssrRender(_ctx, _push, _parent, _attrs) {
const _component_foo = _resolveComponent(\\"foo\\") const _component_foo = _resolveComponent(\\"foo\\")
_push(_ssrRenderComponent(_component_foo, null, { _push(_ssrRenderComponent(_component_foo, _attrs, {
default: _withCtx((_, _push, _parent, _scopeId) => { default: _withCtx((_, _push, _parent, _scopeId) => {
if (_push) { if (_push) {
_push(\`hello<div\${_scopeId}></div>\`) _push(\`hello<div\${_scopeId}></div>\`)
@ -71,10 +71,10 @@ describe('ssr: components', () => {
"const { resolveComponent: _resolveComponent, withCtx: _withCtx, toDisplayString: _toDisplayString, createTextVNode: _createTextVNode } = require(\\"vue\\") "const { resolveComponent: _resolveComponent, withCtx: _withCtx, toDisplayString: _toDisplayString, createTextVNode: _createTextVNode } = require(\\"vue\\")
const { ssrRenderComponent: _ssrRenderComponent, ssrInterpolate: _ssrInterpolate } = require(\\"@vue/server-renderer\\") const { ssrRenderComponent: _ssrRenderComponent, ssrInterpolate: _ssrInterpolate } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent) { return function ssrRender(_ctx, _push, _parent, _attrs) {
const _component_foo = _resolveComponent(\\"foo\\") const _component_foo = _resolveComponent(\\"foo\\")
_push(_ssrRenderComponent(_component_foo, null, { _push(_ssrRenderComponent(_component_foo, _attrs, {
default: _withCtx(({ msg }, _push, _parent, _scopeId) => { default: _withCtx(({ msg }, _push, _parent, _scopeId) => {
if (_push) { if (_push) {
_push(\`\${_ssrInterpolate(msg + _ctx.outer)}\`) _push(\`\${_ssrInterpolate(msg + _ctx.outer)}\`)
@ -100,10 +100,10 @@ describe('ssr: components', () => {
"const { resolveComponent: _resolveComponent, withCtx: _withCtx, createTextVNode: _createTextVNode } = require(\\"vue\\") "const { resolveComponent: _resolveComponent, withCtx: _withCtx, createTextVNode: _createTextVNode } = require(\\"vue\\")
const { ssrRenderComponent: _ssrRenderComponent } = require(\\"@vue/server-renderer\\") const { ssrRenderComponent: _ssrRenderComponent } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent) { return function ssrRender(_ctx, _push, _parent, _attrs) {
const _component_foo = _resolveComponent(\\"foo\\") const _component_foo = _resolveComponent(\\"foo\\")
_push(_ssrRenderComponent(_component_foo, null, { _push(_ssrRenderComponent(_component_foo, _attrs, {
default: _withCtx((_, _push, _parent, _scopeId) => { default: _withCtx((_, _push, _parent, _scopeId) => {
if (_push) { if (_push) {
_push(\`foo\`) _push(\`foo\`)
@ -137,10 +137,10 @@ describe('ssr: components', () => {
"const { resolveComponent: _resolveComponent, withCtx: _withCtx, createTextVNode: _createTextVNode, createSlots: _createSlots } = require(\\"vue\\") "const { resolveComponent: _resolveComponent, withCtx: _withCtx, createTextVNode: _createTextVNode, createSlots: _createSlots } = require(\\"vue\\")
const { ssrRenderComponent: _ssrRenderComponent } = require(\\"@vue/server-renderer\\") const { ssrRenderComponent: _ssrRenderComponent } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent) { return function ssrRender(_ctx, _push, _parent, _attrs) {
const _component_foo = _resolveComponent(\\"foo\\") const _component_foo = _resolveComponent(\\"foo\\")
_push(_ssrRenderComponent(_component_foo, null, _createSlots({ _: 1 }, [ _push(_ssrRenderComponent(_component_foo, _attrs, _createSlots({ _: 1 }, [
(_ctx.ok) (_ctx.ok)
? { ? {
name: \\"named\\", name: \\"named\\",
@ -169,10 +169,10 @@ describe('ssr: components', () => {
"const { resolveComponent: _resolveComponent, withCtx: _withCtx, toDisplayString: _toDisplayString, createTextVNode: _createTextVNode, renderList: _renderList, createSlots: _createSlots } = require(\\"vue\\") "const { resolveComponent: _resolveComponent, withCtx: _withCtx, toDisplayString: _toDisplayString, createTextVNode: _createTextVNode, renderList: _renderList, createSlots: _createSlots } = require(\\"vue\\")
const { ssrRenderComponent: _ssrRenderComponent, ssrInterpolate: _ssrInterpolate } = require(\\"@vue/server-renderer\\") const { ssrRenderComponent: _ssrRenderComponent, ssrInterpolate: _ssrInterpolate } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent) { return function ssrRender(_ctx, _push, _parent, _attrs) {
const _component_foo = _resolveComponent(\\"foo\\") const _component_foo = _resolveComponent(\\"foo\\")
_push(_ssrRenderComponent(_component_foo, null, _createSlots({ _: 1 }, [ _push(_ssrRenderComponent(_component_foo, _attrs, _createSlots({ _: 1 }, [
_renderList(_ctx.names, (key) => { _renderList(_ctx.names, (key) => {
return { return {
name: key, name: key,
@ -210,10 +210,10 @@ describe('ssr: components', () => {
"const { resolveComponent: _resolveComponent, withCtx: _withCtx, renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock, createVNode: _createVNode, createCommentVNode: _createCommentVNode } = require(\\"vue\\") "const { resolveComponent: _resolveComponent, withCtx: _withCtx, renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createBlock: _createBlock, createVNode: _createVNode, createCommentVNode: _createCommentVNode } = require(\\"vue\\")
const { ssrRenderComponent: _ssrRenderComponent, ssrRenderList: _ssrRenderList } = require(\\"@vue/server-renderer\\") const { ssrRenderComponent: _ssrRenderComponent, ssrRenderList: _ssrRenderList } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent) { return function ssrRender(_ctx, _push, _parent, _attrs) {
const _component_foo = _resolveComponent(\\"foo\\") const _component_foo = _resolveComponent(\\"foo\\")
_push(_ssrRenderComponent(_component_foo, null, { _push(_ssrRenderComponent(_component_foo, _attrs, {
foo: _withCtx(({ list }, _push, _parent, _scopeId) => { foo: _withCtx(({ list }, _push, _parent, _scopeId) => {
if (_push) { if (_push) {
if (_ctx.ok) { if (_ctx.ok) {
@ -270,7 +270,7 @@ describe('ssr: components', () => {
expect(compile(`<transition><div/></transition>`).code) expect(compile(`<transition><div/></transition>`).code)
.toMatchInlineSnapshot(` .toMatchInlineSnapshot(`
" "
return function ssrRender(_ctx, _push, _parent) { return function ssrRender(_ctx, _push, _parent, _attrs) {
_push(\`<div></div>\`) _push(\`<div></div>\`)
}" }"
`) `)
@ -278,7 +278,7 @@ describe('ssr: components', () => {
expect(compile(`<transition-group><div/></transition-group>`).code) expect(compile(`<transition-group><div/></transition-group>`).code)
.toMatchInlineSnapshot(` .toMatchInlineSnapshot(`
" "
return function ssrRender(_ctx, _push, _parent) { return function ssrRender(_ctx, _push, _parent, _attrs) {
_push(\`<!--[--><div></div><!--]-->\`) _push(\`<!--[--><div></div><!--]-->\`)
}" }"
`) `)
@ -288,7 +288,7 @@ describe('ssr: components', () => {
"const { resolveComponent: _resolveComponent } = require(\\"vue\\") "const { resolveComponent: _resolveComponent } = require(\\"vue\\")
const { ssrRenderComponent: _ssrRenderComponent } = require(\\"@vue/server-renderer\\") const { ssrRenderComponent: _ssrRenderComponent } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent) { return function ssrRender(_ctx, _push, _parent, _attrs) {
const _component_foo = _resolveComponent(\\"foo\\") const _component_foo = _resolveComponent(\\"foo\\")
_push(_ssrRenderComponent(_component_foo, null, null, _parent)) _push(_ssrRenderComponent(_component_foo, null, null, _parent))

View File

@ -23,23 +23,28 @@ describe('ssr: element', () => {
describe('children override', () => { describe('children override', () => {
test('v-html', () => { test('v-html', () => {
expect(getCompiledString(`<div v-html="foo"/>`)).toMatchInlineSnapshot( expect(getCompiledString(`<div v-html="foo"/>`)).toMatchInlineSnapshot(`
`"\`<div>\${_ctx.foo}</div>\`"` "\`<div>\${
) _ctx.foo
}</div>\`"
`)
}) })
test('v-text', () => { test('v-text', () => {
expect(getCompiledString(`<div v-text="foo"/>`)).toMatchInlineSnapshot( expect(getCompiledString(`<div v-text="foo"/>`)).toMatchInlineSnapshot(`
`"\`<div>\${_ssrInterpolate(_ctx.foo)}</div>\`"` "\`<div>\${
) _ssrInterpolate(_ctx.foo)
}</div>\`"
`)
}) })
test('<textarea> with dynamic value', () => { test('<textarea> with dynamic value', () => {
expect( expect(getCompiledString(`<textarea :value="foo"/>`))
getCompiledString(`<textarea :value="foo"/>`) .toMatchInlineSnapshot(`
).toMatchInlineSnapshot( "\`<textarea>\${
`"\`<textarea>\${_ssrInterpolate(_ctx.foo)}</textarea>\`"` _ssrInterpolate(_ctx.foo)
) }</textarea>\`"
`)
}) })
test('<textarea> with static value', () => { test('<textarea> with static value', () => {
@ -51,13 +56,14 @@ describe('ssr: element', () => {
test('<textarea> with dynamic v-bind', () => { test('<textarea> with dynamic v-bind', () => {
expect(compile(`<textarea v-bind="obj">fallback</textarea>`).code) expect(compile(`<textarea v-bind="obj">fallback</textarea>`).code)
.toMatchInlineSnapshot(` .toMatchInlineSnapshot(`
"const { ssrRenderAttrs: _ssrRenderAttrs, ssrInterpolate: _ssrInterpolate } = require(\\"@vue/server-renderer\\") "const { mergeProps: _mergeProps } = require(\\"vue\\")
const { ssrRenderAttrs: _ssrRenderAttrs, ssrInterpolate: _ssrInterpolate } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent) { return function ssrRender(_ctx, _push, _parent, _attrs) {
let _temp0 let _temp0
_push(\`<textarea\${ _push(\`<textarea\${
_ssrRenderAttrs(_temp0 = _ctx.obj, \\"textarea\\") _ssrRenderAttrs(_temp0 = _mergeProps(_ctx.obj, _attrs), \\"textarea\\")
}>\${ }>\${
_ssrInterpolate((\\"value\\" in _temp0) ? _temp0.value : \\"fallback\\") _ssrInterpolate((\\"value\\" in _temp0) ? _temp0.value : \\"fallback\\")
}</textarea>\`) }</textarea>\`)
@ -71,10 +77,11 @@ describe('ssr: element', () => {
isCustomElement: () => true isCustomElement: () => true
}).code }).code
).toMatchInlineSnapshot(` ).toMatchInlineSnapshot(`
"const { ssrRenderAttrs: _ssrRenderAttrs } = require(\\"@vue/server-renderer\\") "const { mergeProps: _mergeProps } = require(\\"vue\\")
const { ssrRenderAttrs: _ssrRenderAttrs } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent) { return function ssrRender(_ctx, _push, _parent, _attrs) {
_push(\`<my-foo\${_ssrRenderAttrs(_ctx.obj, \\"my-foo\\")}></my-foo>\`) _push(\`<my-foo\${_ssrRenderAttrs(_mergeProps(_ctx.obj, _attrs), \\"my-foo\\")}></my-foo>\`)
}" }"
`) `)
}) })
@ -88,107 +95,126 @@ describe('ssr: element', () => {
}) })
test('v-bind:class', () => { test('v-bind:class', () => {
expect( expect(getCompiledString(`<div id="foo" :class="bar"></div>`))
getCompiledString(`<div id="foo" :class="bar"></div>`) .toMatchInlineSnapshot(`
).toMatchInlineSnapshot( "\`<div id=\\"foo\\" class=\\"\${
`"\`<div id=\\"foo\\" class=\\"\${_ssrRenderClass(_ctx.bar)}\\"></div>\`"` _ssrRenderClass(_ctx.bar)
) }\\"></div>\`"
`)
}) })
test('static class + v-bind:class', () => { test('static class + v-bind:class', () => {
expect( expect(getCompiledString(`<div class="foo" :class="bar"></div>`))
getCompiledString(`<div class="foo" :class="bar"></div>`) .toMatchInlineSnapshot(`
).toMatchInlineSnapshot( "\`<div class=\\"\${
`"\`<div class=\\"\${_ssrRenderClass([_ctx.bar, \\"foo\\"])}\\"></div>\`"` _ssrRenderClass([_ctx.bar, \\"foo\\"])
) }\\"></div>\`"
`)
}) })
test('v-bind:style', () => { test('v-bind:style', () => {
expect( expect(getCompiledString(`<div id="foo" :style="bar"></div>`))
getCompiledString(`<div id="foo" :style="bar"></div>`) .toMatchInlineSnapshot(`
).toMatchInlineSnapshot( "\`<div id=\\"foo\\" style=\\"\${
`"\`<div id=\\"foo\\" style=\\"\${_ssrRenderStyle(_ctx.bar)}\\"></div>\`"` _ssrRenderStyle(_ctx.bar)
) }\\"></div>\`"
`)
}) })
test('static style + v-bind:style', () => { test('static style + v-bind:style', () => {
expect( expect(getCompiledString(`<div style="color:red;" :style="bar"></div>`))
getCompiledString(`<div style="color:red;" :style="bar"></div>`) .toMatchInlineSnapshot(`
).toMatchInlineSnapshot( "\`<div style=\\"\${
`"\`<div style=\\"\${_ssrRenderStyle([{\\"color\\":\\"red\\"}, _ctx.bar])}\\"></div>\`"` _ssrRenderStyle([{\\"color\\":\\"red\\"}, _ctx.bar])
) }\\"></div>\`"
`)
}) })
test('v-bind:key (boolean)', () => { test('v-bind:key (boolean)', () => {
expect( expect(getCompiledString(`<input type="checkbox" :checked="checked">`))
getCompiledString(`<input type="checkbox" :checked="checked">`) .toMatchInlineSnapshot(`
).toMatchInlineSnapshot( "\`<input type=\\"checkbox\\"\${
`"\`<input type=\\"checkbox\\"\${(_ctx.checked) ? \\" checked\\" : \\"\\"}>\`"` (_ctx.checked) ? \\" checked\\" : \\"\\"
) }>\`"
`)
}) })
test('v-bind:key (non-boolean)', () => { test('v-bind:key (non-boolean)', () => {
expect( expect(getCompiledString(`<div :id="id" class="bar"></div>`))
getCompiledString(`<div :id="id" class="bar"></div>`) .toMatchInlineSnapshot(`
).toMatchInlineSnapshot( "\`<div\${
`"\`<div\${_ssrRenderAttr(\\"id\\", _ctx.id)} class=\\"bar\\"></div>\`"` _ssrRenderAttr(\\"id\\", _ctx.id)
) } class=\\"bar\\"></div>\`"
`)
}) })
test('v-bind:[key]', () => { test('v-bind:[key]', () => {
expect( expect(getCompiledString(`<div v-bind:[key]="value"></div>`))
getCompiledString(`<div v-bind:[key]="value"></div>`) .toMatchInlineSnapshot(`
).toMatchInlineSnapshot( "\`<div\${
`"\`<div\${_ssrRenderAttrs({ [_ctx.key]: _ctx.value })}></div>\`"` _ssrRenderAttrs({ [_ctx.key]: _ctx.value })
) }></div>\`"
`)
expect(getCompiledString(`<div class="foo" v-bind:[key]="value"></div>`)) expect(getCompiledString(`<div class="foo" v-bind:[key]="value"></div>`))
.toMatchInlineSnapshot(` .toMatchInlineSnapshot(`
"\`<div\${_ssrRenderAttrs({ "\`<div\${
class: \\"foo\\", _ssrRenderAttrs({
[_ctx.key]: _ctx.value class: \\"foo\\",
})}></div>\`" [_ctx.key]: _ctx.value
})
}></div>\`"
`) `)
expect(getCompiledString(`<div :id="id" v-bind:[key]="value"></div>`)) expect(getCompiledString(`<div :id="id" v-bind:[key]="value"></div>`))
.toMatchInlineSnapshot(` .toMatchInlineSnapshot(`
"\`<div\${_ssrRenderAttrs({ "\`<div\${
id: _ctx.id, _ssrRenderAttrs({
[_ctx.key]: _ctx.value id: _ctx.id,
})}></div>\`" [_ctx.key]: _ctx.value
})
}></div>\`"
`) `)
}) })
test('v-bind="obj"', () => { test('v-bind="obj"', () => {
expect( expect(getCompiledString(`<div v-bind="obj"></div>`))
getCompiledString(`<div v-bind="obj"></div>`) .toMatchInlineSnapshot(`
).toMatchInlineSnapshot(`"\`<div\${_ssrRenderAttrs(_ctx.obj)}></div>\`"`) "\`<div\${
_ssrRenderAttrs(_ctx.obj)
}></div>\`"
`)
expect( expect(getCompiledString(`<div class="foo" v-bind="obj"></div>`))
getCompiledString(`<div class="foo" v-bind="obj"></div>`) .toMatchInlineSnapshot(`
).toMatchInlineSnapshot( "\`<div\${
`"\`<div\${_ssrRenderAttrs(_mergeProps({ class: \\"foo\\" }, _ctx.obj))}></div>\`"` _ssrRenderAttrs(_mergeProps({ class: \\"foo\\" }, _ctx.obj))
) }></div>\`"
`)
expect( expect(getCompiledString(`<div :id="id" v-bind="obj"></div>`))
getCompiledString(`<div :id="id" v-bind="obj"></div>`) .toMatchInlineSnapshot(`
).toMatchInlineSnapshot( "\`<div\${
`"\`<div\${_ssrRenderAttrs(_mergeProps({ id: _ctx.id }, _ctx.obj))}></div>\`"` _ssrRenderAttrs(_mergeProps({ id: _ctx.id }, _ctx.obj))
) }></div>\`"
`)
// dynamic key + v-bind="object" // dynamic key + v-bind="object"
expect( expect(getCompiledString(`<div :[key]="id" v-bind="obj"></div>`))
getCompiledString(`<div :[key]="id" v-bind="obj"></div>`) .toMatchInlineSnapshot(`
).toMatchInlineSnapshot( "\`<div\${
`"\`<div\${_ssrRenderAttrs(_mergeProps({ [_ctx.key]: _ctx.id }, _ctx.obj))}></div>\`"` _ssrRenderAttrs(_mergeProps({ [_ctx.key]: _ctx.id }, _ctx.obj))
) }></div>\`"
`)
// should merge class and :class // should merge class and :class
expect(getCompiledString(`<div class="a" :class="b" v-bind="obj"></div>`)) expect(getCompiledString(`<div class="a" :class="b" v-bind="obj"></div>`))
.toMatchInlineSnapshot(` .toMatchInlineSnapshot(`
"\`<div\${_ssrRenderAttrs(_mergeProps({ "\`<div\${
class: [\\"a\\", _ctx.b] _ssrRenderAttrs(_mergeProps({
}, _ctx.obj))}></div>\`" class: [\\"a\\", _ctx.b]
}, _ctx.obj))
}></div>\`"
`) `)
// should merge style and :style // should merge style and :style
@ -197,9 +223,11 @@ describe('ssr: element', () => {
`<div style="color:red;" :style="b" v-bind="obj"></div>` `<div style="color:red;" :style="b" v-bind="obj"></div>`
) )
).toMatchInlineSnapshot(` ).toMatchInlineSnapshot(`
"\`<div\${_ssrRenderAttrs(_mergeProps({ "\`<div\${
style: [{\\"color\\":\\"red\\"}, _ctx.b] _ssrRenderAttrs(_mergeProps({
}, _ctx.obj))}></div>\`" style: [{\\"color\\":\\"red\\"}, _ctx.b]
}, _ctx.obj))
}></div>\`"
`) `)
}) })
@ -210,9 +238,12 @@ describe('ssr: element', () => {
expect( expect(
getCompiledString(`<div id="foo" v-on="bar"/>`) getCompiledString(`<div id="foo" v-on="bar"/>`)
).toMatchInlineSnapshot(`"\`<div id=\\"foo\\"></div>\`"`) ).toMatchInlineSnapshot(`"\`<div id=\\"foo\\"></div>\`"`)
expect( expect(getCompiledString(`<div v-bind="foo" v-on="bar"/>`))
getCompiledString(`<div v-bind="foo" v-on="bar"/>`) .toMatchInlineSnapshot(`
).toMatchInlineSnapshot(`"\`<div\${_ssrRenderAttrs(_ctx.foo)}></div>\`"`) "\`<div\${
_ssrRenderAttrs(_ctx.foo)
}></div>\`"
`)
}) })
}) })
}) })

View File

@ -6,7 +6,7 @@ describe('ssr compile: teleport', () => {
.toMatchInlineSnapshot(` .toMatchInlineSnapshot(`
"const { ssrRenderTeleport: _ssrRenderTeleport } = require(\\"@vue/server-renderer\\") "const { ssrRenderTeleport: _ssrRenderTeleport } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent) { return function ssrRender(_ctx, _push, _parent, _attrs) {
_ssrRenderTeleport(_push, (_push) => { _ssrRenderTeleport(_push, (_push) => {
_push(\`<div></div>\`) _push(\`<div></div>\`)
}, _ctx.target, false, _parent) }, _ctx.target, false, _parent)
@ -18,26 +18,26 @@ describe('ssr compile: teleport', () => {
expect( expect(
compile(`<teleport :target="target" disabled><div/></teleport>`).code compile(`<teleport :target="target" disabled><div/></teleport>`).code
).toMatchInlineSnapshot(` ).toMatchInlineSnapshot(`
"const { ssrRenderTeleport: _ssrRenderTeleport } = require(\\"@vue/server-renderer\\") "const { ssrRenderTeleport: _ssrRenderTeleport } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent) { return function ssrRender(_ctx, _push, _parent, _attrs) {
_ssrRenderTeleport(_push, (_push) => { _ssrRenderTeleport(_push, (_push) => {
_push(\`<div></div>\`) _push(\`<div></div>\`)
}, _ctx.target, true, _parent) }, _ctx.target, true, _parent)
}" }"
`) `)
expect( expect(
compile(`<teleport :target="target" :disabled="foo"><div/></teleport>`) compile(`<teleport :target="target" :disabled="foo"><div/></teleport>`)
.code .code
).toMatchInlineSnapshot(` ).toMatchInlineSnapshot(`
"const { ssrRenderTeleport: _ssrRenderTeleport } = require(\\"@vue/server-renderer\\") "const { ssrRenderTeleport: _ssrRenderTeleport } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent) { return function ssrRender(_ctx, _push, _parent, _attrs) {
_ssrRenderTeleport(_push, (_push) => { _ssrRenderTeleport(_push, (_push) => {
_push(\`<div></div>\`) _push(\`<div></div>\`)
}, _ctx.target, _ctx.foo, _parent) }, _ctx.target, _ctx.foo, _parent)
}" }"
`) `)
}) })
}) })

View File

@ -9,9 +9,10 @@ describe('ssr: scopeId', () => {
scopeId scopeId
}).code }).code
).toMatchInlineSnapshot(` ).toMatchInlineSnapshot(`
" "const { ssrRenderAttrs: _ssrRenderAttrs } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent) {
_push(\`<div data-v-xxxxxxx><span data-v-xxxxxxx>hello</span></div>\`) return function ssrRender(_ctx, _push, _parent, _attrs) {
_push(\`<div\${_ssrRenderAttrs(_attrs)} data-v-xxxxxxx><span data-v-xxxxxxx>hello</span></div>\`)
}" }"
`) `)
}) })
@ -26,10 +27,10 @@ describe('ssr: scopeId', () => {
"const { resolveComponent: _resolveComponent, withCtx: _withCtx, createTextVNode: _createTextVNode } = require(\\"vue\\") "const { resolveComponent: _resolveComponent, withCtx: _withCtx, createTextVNode: _createTextVNode } = require(\\"vue\\")
const { ssrRenderComponent: _ssrRenderComponent } = require(\\"@vue/server-renderer\\") const { ssrRenderComponent: _ssrRenderComponent } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent) { return function ssrRender(_ctx, _push, _parent, _attrs) {
const _component_foo = _resolveComponent(\\"foo\\") const _component_foo = _resolveComponent(\\"foo\\")
_push(_ssrRenderComponent(_component_foo, null, { _push(_ssrRenderComponent(_component_foo, _attrs, {
default: _withCtx((_, _push, _parent, _scopeId) => { default: _withCtx((_, _push, _parent, _scopeId) => {
if (_push) { if (_push) {
_push(\`foo\`) _push(\`foo\`)
@ -54,10 +55,10 @@ describe('ssr: scopeId', () => {
"const { resolveComponent: _resolveComponent, withCtx: _withCtx, createVNode: _createVNode } = require(\\"vue\\") "const { resolveComponent: _resolveComponent, withCtx: _withCtx, createVNode: _createVNode } = require(\\"vue\\")
const { ssrRenderComponent: _ssrRenderComponent } = require(\\"@vue/server-renderer\\") const { ssrRenderComponent: _ssrRenderComponent } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent) { return function ssrRender(_ctx, _push, _parent, _attrs) {
const _component_foo = _resolveComponent(\\"foo\\") const _component_foo = _resolveComponent(\\"foo\\")
_push(_ssrRenderComponent(_component_foo, null, { _push(_ssrRenderComponent(_component_foo, _attrs, {
default: _withCtx((_, _push, _parent, _scopeId) => { default: _withCtx((_, _push, _parent, _scopeId) => {
if (_push) { if (_push) {
_push(\`<span data-v-xxxxxxx\${_scopeId}>hello</span>\`) _push(\`<span data-v-xxxxxxx\${_scopeId}>hello</span>\`)
@ -82,11 +83,11 @@ describe('ssr: scopeId', () => {
"const { resolveComponent: _resolveComponent, withCtx: _withCtx, createVNode: _createVNode } = require(\\"vue\\") "const { resolveComponent: _resolveComponent, withCtx: _withCtx, createVNode: _createVNode } = require(\\"vue\\")
const { ssrRenderComponent: _ssrRenderComponent } = require(\\"@vue/server-renderer\\") const { ssrRenderComponent: _ssrRenderComponent } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent) { return function ssrRender(_ctx, _push, _parent, _attrs) {
const _component_foo = _resolveComponent(\\"foo\\") const _component_foo = _resolveComponent(\\"foo\\")
const _component_bar = _resolveComponent(\\"bar\\") const _component_bar = _resolveComponent(\\"bar\\")
_push(_ssrRenderComponent(_component_foo, null, { _push(_ssrRenderComponent(_component_foo, _attrs, {
default: _withCtx((_, _push, _parent, _scopeId) => { default: _withCtx((_, _push, _parent, _scopeId) => {
if (_push) { if (_push) {
_push(\`<span data-v-xxxxxxx\${_scopeId}>hello</span>\`) _push(\`<span data-v-xxxxxxx\${_scopeId}>hello</span>\`)

View File

@ -5,7 +5,7 @@ describe('ssr: <slot>', () => {
expect(compile(`<slot/>`).code).toMatchInlineSnapshot(` expect(compile(`<slot/>`).code).toMatchInlineSnapshot(`
"const { ssrRenderSlot: _ssrRenderSlot } = require(\\"@vue/server-renderer\\") "const { ssrRenderSlot: _ssrRenderSlot } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent) { return function ssrRender(_ctx, _push, _parent, _attrs) {
_ssrRenderSlot(_ctx.$slots, \\"default\\", {}, null, _push, _parent) _ssrRenderSlot(_ctx.$slots, \\"default\\", {}, null, _push, _parent)
}" }"
`) `)
@ -15,7 +15,7 @@ describe('ssr: <slot>', () => {
expect(compile(`<slot name="foo" />`).code).toMatchInlineSnapshot(` expect(compile(`<slot name="foo" />`).code).toMatchInlineSnapshot(`
"const { ssrRenderSlot: _ssrRenderSlot } = require(\\"@vue/server-renderer\\") "const { ssrRenderSlot: _ssrRenderSlot } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent) { return function ssrRender(_ctx, _push, _parent, _attrs) {
_ssrRenderSlot(_ctx.$slots, \\"foo\\", {}, null, _push, _parent) _ssrRenderSlot(_ctx.$slots, \\"foo\\", {}, null, _push, _parent)
}" }"
`) `)
@ -25,7 +25,7 @@ describe('ssr: <slot>', () => {
expect(compile(`<slot :name="bar.baz" />`).code).toMatchInlineSnapshot(` expect(compile(`<slot :name="bar.baz" />`).code).toMatchInlineSnapshot(`
"const { ssrRenderSlot: _ssrRenderSlot } = require(\\"@vue/server-renderer\\") "const { ssrRenderSlot: _ssrRenderSlot } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent) { return function ssrRender(_ctx, _push, _parent, _attrs) {
_ssrRenderSlot(_ctx.$slots, _ctx.bar.baz, {}, null, _push, _parent) _ssrRenderSlot(_ctx.$slots, _ctx.bar.baz, {}, null, _push, _parent)
}" }"
`) `)
@ -36,7 +36,7 @@ describe('ssr: <slot>', () => {
.toMatchInlineSnapshot(` .toMatchInlineSnapshot(`
"const { ssrRenderSlot: _ssrRenderSlot } = require(\\"@vue/server-renderer\\") "const { ssrRenderSlot: _ssrRenderSlot } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent) { return function ssrRender(_ctx, _push, _parent, _attrs) {
_ssrRenderSlot(_ctx.$slots, \\"foo\\", { _ssrRenderSlot(_ctx.$slots, \\"foo\\", {
p: 1, p: 1,
bar: \\"2\\" bar: \\"2\\"
@ -50,7 +50,7 @@ describe('ssr: <slot>', () => {
.toMatchInlineSnapshot(` .toMatchInlineSnapshot(`
"const { ssrRenderSlot: _ssrRenderSlot, ssrInterpolate: _ssrInterpolate } = require(\\"@vue/server-renderer\\") "const { ssrRenderSlot: _ssrRenderSlot, ssrInterpolate: _ssrInterpolate } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent) { return function ssrRender(_ctx, _push, _parent, _attrs) {
_ssrRenderSlot(_ctx.$slots, \\"default\\", {}, () => { _ssrRenderSlot(_ctx.$slots, \\"default\\", {}, () => {
_push(\`some \${_ssrInterpolate(_ctx.fallback)} content\`) _push(\`some \${_ssrInterpolate(_ctx.fallback)} content\`)
}, _push, _parent) }, _push, _parent)

View File

@ -6,7 +6,7 @@ describe('ssr compile: suspense', () => {
"const { resolveComponent: _resolveComponent, withCtx: _withCtx } = require(\\"vue\\") "const { resolveComponent: _resolveComponent, withCtx: _withCtx } = require(\\"vue\\")
const { ssrRenderComponent: _ssrRenderComponent, ssrRenderSuspense: _ssrRenderSuspense } = require(\\"@vue/server-renderer\\") const { ssrRenderComponent: _ssrRenderComponent, ssrRenderSuspense: _ssrRenderSuspense } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent) { return function ssrRender(_ctx, _push, _parent, _attrs) {
const _component_foo = _resolveComponent(\\"foo\\") const _component_foo = _resolveComponent(\\"foo\\")
_ssrRenderSuspense(_push, { _ssrRenderSuspense(_push, {
@ -33,7 +33,7 @@ describe('ssr compile: suspense', () => {
"const { resolveComponent: _resolveComponent, withCtx: _withCtx } = require(\\"vue\\") "const { resolveComponent: _resolveComponent, withCtx: _withCtx } = require(\\"vue\\")
const { ssrRenderComponent: _ssrRenderComponent, ssrRenderSuspense: _ssrRenderSuspense } = require(\\"@vue/server-renderer\\") const { ssrRenderComponent: _ssrRenderComponent, ssrRenderSuspense: _ssrRenderSuspense } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent) { return function ssrRender(_ctx, _push, _parent, _attrs) {
const _component_foo = _resolveComponent(\\"foo\\") const _component_foo = _resolveComponent(\\"foo\\")
_ssrRenderSuspense(_push, { _ssrRenderSuspense(_push, {

View File

@ -45,7 +45,7 @@ describe('ssr: text', () => {
expect(compile(`foo {{ bar }} baz`).code).toMatchInlineSnapshot(` expect(compile(`foo {{ bar }} baz`).code).toMatchInlineSnapshot(`
"const { ssrInterpolate: _ssrInterpolate } = require(\\"@vue/server-renderer\\") "const { ssrInterpolate: _ssrInterpolate } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent) { return function ssrRender(_ctx, _push, _parent, _attrs) {
_push(\`foo \${_ssrInterpolate(_ctx.bar)} baz\`) _push(\`foo \${_ssrInterpolate(_ctx.bar)} baz\`)
}" }"
`) `)
@ -56,10 +56,12 @@ describe('ssr: text', () => {
compile(`<div><span>{{ foo }} bar</span><span>baz {{ qux }}</span></div>`) compile(`<div><span>{{ foo }} bar</span><span>baz {{ qux }}</span></div>`)
.code .code
).toMatchInlineSnapshot(` ).toMatchInlineSnapshot(`
"const { ssrInterpolate: _ssrInterpolate } = require(\\"@vue/server-renderer\\") "const { ssrRenderAttrs: _ssrRenderAttrs, ssrInterpolate: _ssrInterpolate } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent) { return function ssrRender(_ctx, _push, _parent, _attrs) {
_push(\`<div><span>\${ _push(\`<div\${
_ssrRenderAttrs(_attrs)
}><span>\${
_ssrInterpolate(_ctx.foo) _ssrInterpolate(_ctx.foo)
} bar</span><span>baz \${ } bar</span><span>baz \${
_ssrInterpolate(_ctx.qux) _ssrInterpolate(_ctx.qux)

View File

@ -5,7 +5,7 @@ describe('ssr: v-for', () => {
expect(compile(`<div v-for="i in list" />`).code).toMatchInlineSnapshot(` expect(compile(`<div v-for="i in list" />`).code).toMatchInlineSnapshot(`
"const { ssrRenderList: _ssrRenderList } = require(\\"@vue/server-renderer\\") "const { ssrRenderList: _ssrRenderList } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent) { return function ssrRender(_ctx, _push, _parent, _attrs) {
_push(\`<!--[-->\`) _push(\`<!--[-->\`)
_ssrRenderList(_ctx.list, (i) => { _ssrRenderList(_ctx.list, (i) => {
_push(\`<div></div>\`) _push(\`<div></div>\`)
@ -20,7 +20,7 @@ describe('ssr: v-for', () => {
.toMatchInlineSnapshot(` .toMatchInlineSnapshot(`
"const { ssrRenderList: _ssrRenderList } = require(\\"@vue/server-renderer\\") "const { ssrRenderList: _ssrRenderList } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent) { return function ssrRender(_ctx, _push, _parent, _attrs) {
_push(\`<!--[-->\`) _push(\`<!--[-->\`)
_ssrRenderList(_ctx.list, (i) => { _ssrRenderList(_ctx.list, (i) => {
_push(\`<div>foo<span>bar</span></div>\`) _push(\`<div>foo<span>bar</span></div>\`)
@ -40,7 +40,7 @@ describe('ssr: v-for', () => {
).toMatchInlineSnapshot(` ).toMatchInlineSnapshot(`
"const { ssrInterpolate: _ssrInterpolate, ssrRenderList: _ssrRenderList } = require(\\"@vue/server-renderer\\") "const { ssrInterpolate: _ssrInterpolate, ssrRenderList: _ssrRenderList } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent) { return function ssrRender(_ctx, _push, _parent, _attrs) {
_push(\`<!--[-->\`) _push(\`<!--[-->\`)
_ssrRenderList(_ctx.list, (row, i) => { _ssrRenderList(_ctx.list, (row, i) => {
_push(\`<div><!--[-->\`) _push(\`<div><!--[-->\`)
@ -63,7 +63,7 @@ describe('ssr: v-for', () => {
.toMatchInlineSnapshot(` .toMatchInlineSnapshot(`
"const { ssrInterpolate: _ssrInterpolate, ssrRenderList: _ssrRenderList } = require(\\"@vue/server-renderer\\") "const { ssrInterpolate: _ssrInterpolate, ssrRenderList: _ssrRenderList } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent) { return function ssrRender(_ctx, _push, _parent, _attrs) {
_push(\`<!--[-->\`) _push(\`<!--[-->\`)
_ssrRenderList(_ctx.list, (i) => { _ssrRenderList(_ctx.list, (i) => {
_push(\`<!--[-->\${_ssrInterpolate(i)}<!--]-->\`) _push(\`<!--[-->\${_ssrInterpolate(i)}<!--]-->\`)
@ -80,7 +80,7 @@ describe('ssr: v-for', () => {
).toMatchInlineSnapshot(` ).toMatchInlineSnapshot(`
"const { ssrInterpolate: _ssrInterpolate, ssrRenderList: _ssrRenderList } = require(\\"@vue/server-renderer\\") "const { ssrInterpolate: _ssrInterpolate, ssrRenderList: _ssrRenderList } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent) { return function ssrRender(_ctx, _push, _parent, _attrs) {
_push(\`<!--[-->\`) _push(\`<!--[-->\`)
_ssrRenderList(_ctx.list, (i) => { _ssrRenderList(_ctx.list, (i) => {
_push(\`<span>\${_ssrInterpolate(i)}</span>\`) _push(\`<span>\${_ssrInterpolate(i)}</span>\`)
@ -98,7 +98,7 @@ describe('ssr: v-for', () => {
).toMatchInlineSnapshot(` ).toMatchInlineSnapshot(`
"const { ssrInterpolate: _ssrInterpolate, ssrRenderList: _ssrRenderList } = require(\\"@vue/server-renderer\\") "const { ssrInterpolate: _ssrInterpolate, ssrRenderList: _ssrRenderList } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent) { return function ssrRender(_ctx, _push, _parent, _attrs) {
_push(\`<!--[-->\`) _push(\`<!--[-->\`)
_ssrRenderList(_ctx.list, (i) => { _ssrRenderList(_ctx.list, (i) => {
_push(\`<!--[--><span>\${ _push(\`<!--[--><span>\${
@ -122,7 +122,7 @@ describe('ssr: v-for', () => {
expect(code).toMatchInlineSnapshot(` expect(code).toMatchInlineSnapshot(`
"const { ssrInterpolate: _ssrInterpolate, ssrRenderList: _ssrRenderList } = require(\\"@vue/server-renderer\\") "const { ssrInterpolate: _ssrInterpolate, ssrRenderList: _ssrRenderList } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent) { return function ssrRender(_ctx, _push, _parent, _attrs) {
_push(\`<!--[-->\`) _push(\`<!--[-->\`)
_ssrRenderList(_ctx.list, ({ foo }, index) => { _ssrRenderList(_ctx.list, ({ foo }, index) => {
_push(\`<div>\${_ssrInterpolate(foo + _ctx.bar + index)}</div>\`) _push(\`<div>\${_ssrInterpolate(foo + _ctx.bar + index)}</div>\`)

View File

@ -3,10 +3,11 @@ import { compile } from '../src'
describe('ssr: v-if', () => { describe('ssr: v-if', () => {
test('basic', () => { test('basic', () => {
expect(compile(`<div v-if="foo"></div>`).code).toMatchInlineSnapshot(` expect(compile(`<div v-if="foo"></div>`).code).toMatchInlineSnapshot(`
" "const { ssrRenderAttrs: _ssrRenderAttrs } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent) {
return function ssrRender(_ctx, _push, _parent, _attrs) {
if (_ctx.foo) { if (_ctx.foo) {
_push(\`<div></div>\`) _push(\`<div\${_ssrRenderAttrs(_attrs)}></div>\`)
} else { } else {
_push(\`<!---->\`) _push(\`<!---->\`)
} }
@ -17,10 +18,11 @@ describe('ssr: v-if', () => {
test('with nested content', () => { test('with nested content', () => {
expect(compile(`<div v-if="foo">hello<span>ok</span></div>`).code) expect(compile(`<div v-if="foo">hello<span>ok</span></div>`).code)
.toMatchInlineSnapshot(` .toMatchInlineSnapshot(`
" "const { ssrRenderAttrs: _ssrRenderAttrs } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent) {
return function ssrRender(_ctx, _push, _parent, _attrs) {
if (_ctx.foo) { if (_ctx.foo) {
_push(\`<div>hello<span>ok</span></div>\`) _push(\`<div\${_ssrRenderAttrs(_attrs)}>hello<span>ok</span></div>\`)
} else { } else {
_push(\`<!---->\`) _push(\`<!---->\`)
} }
@ -31,12 +33,13 @@ describe('ssr: v-if', () => {
test('v-if + v-else', () => { test('v-if + v-else', () => {
expect(compile(`<div v-if="foo"/><span v-else/>`).code) expect(compile(`<div v-if="foo"/><span v-else/>`).code)
.toMatchInlineSnapshot(` .toMatchInlineSnapshot(`
" "const { ssrRenderAttrs: _ssrRenderAttrs } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent) {
return function ssrRender(_ctx, _push, _parent, _attrs) {
if (_ctx.foo) { if (_ctx.foo) {
_push(\`<div></div>\`) _push(\`<div\${_ssrRenderAttrs(_attrs)}></div>\`)
} else { } else {
_push(\`<span></span>\`) _push(\`<span\${_ssrRenderAttrs(_attrs)}></span>\`)
} }
}" }"
`) `)
@ -45,12 +48,13 @@ describe('ssr: v-if', () => {
test('v-if + v-else-if', () => { test('v-if + v-else-if', () => {
expect(compile(`<div v-if="foo"/><span v-else-if="bar"/>`).code) expect(compile(`<div v-if="foo"/><span v-else-if="bar"/>`).code)
.toMatchInlineSnapshot(` .toMatchInlineSnapshot(`
" "const { ssrRenderAttrs: _ssrRenderAttrs } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent) {
return function ssrRender(_ctx, _push, _parent, _attrs) {
if (_ctx.foo) { if (_ctx.foo) {
_push(\`<div></div>\`) _push(\`<div\${_ssrRenderAttrs(_attrs)}></div>\`)
} else if (_ctx.bar) { } else if (_ctx.bar) {
_push(\`<span></span>\`) _push(\`<span\${_ssrRenderAttrs(_attrs)}></span>\`)
} else { } else {
_push(\`<!---->\`) _push(\`<!---->\`)
} }
@ -61,14 +65,15 @@ describe('ssr: v-if', () => {
test('v-if + v-else-if + v-else', () => { test('v-if + v-else-if + v-else', () => {
expect(compile(`<div v-if="foo"/><span v-else-if="bar"/><p v-else/>`).code) expect(compile(`<div v-if="foo"/><span v-else-if="bar"/><p v-else/>`).code)
.toMatchInlineSnapshot(` .toMatchInlineSnapshot(`
" "const { ssrRenderAttrs: _ssrRenderAttrs } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent) {
return function ssrRender(_ctx, _push, _parent, _attrs) {
if (_ctx.foo) { if (_ctx.foo) {
_push(\`<div></div>\`) _push(\`<div\${_ssrRenderAttrs(_attrs)}></div>\`)
} else if (_ctx.bar) { } else if (_ctx.bar) {
_push(\`<span></span>\`) _push(\`<span\${_ssrRenderAttrs(_attrs)}></span>\`)
} else { } else {
_push(\`<p></p>\`) _push(\`<p\${_ssrRenderAttrs(_attrs)}></p>\`)
} }
}" }"
`) `)
@ -78,7 +83,7 @@ describe('ssr: v-if', () => {
expect(compile(`<template v-if="foo">hello</template>`).code) expect(compile(`<template v-if="foo">hello</template>`).code)
.toMatchInlineSnapshot(` .toMatchInlineSnapshot(`
" "
return function ssrRender(_ctx, _push, _parent) { return function ssrRender(_ctx, _push, _parent, _attrs) {
if (_ctx.foo) { if (_ctx.foo) {
_push(\`<!--[-->hello<!--]-->\`) _push(\`<!--[-->hello<!--]-->\`)
} else { } else {
@ -92,10 +97,11 @@ describe('ssr: v-if', () => {
// single element should not wrap with fragment // single element should not wrap with fragment
expect(compile(`<template v-if="foo"><div>hi</div></template>`).code) expect(compile(`<template v-if="foo"><div>hi</div></template>`).code)
.toMatchInlineSnapshot(` .toMatchInlineSnapshot(`
" "const { ssrRenderAttrs: _ssrRenderAttrs } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent) {
return function ssrRender(_ctx, _push, _parent, _attrs) {
if (_ctx.foo) { if (_ctx.foo) {
_push(\`<div>hi</div>\`) _push(\`<div\${_ssrRenderAttrs(_attrs)}>hi</div>\`)
} else { } else {
_push(\`<!---->\`) _push(\`<!---->\`)
} }
@ -108,7 +114,7 @@ describe('ssr: v-if', () => {
compile(`<template v-if="foo"><div>hi</div><div>ho</div></template>`).code compile(`<template v-if="foo"><div>hi</div><div>ho</div></template>`).code
).toMatchInlineSnapshot(` ).toMatchInlineSnapshot(`
" "
return function ssrRender(_ctx, _push, _parent) { return function ssrRender(_ctx, _push, _parent, _attrs) {
if (_ctx.foo) { if (_ctx.foo) {
_push(\`<!--[--><div>hi</div><div>ho</div><!--]-->\`) _push(\`<!--[--><div>hi</div><div>ho</div><!--]-->\`)
} else { } else {
@ -124,7 +130,7 @@ describe('ssr: v-if', () => {
).toMatchInlineSnapshot(` ).toMatchInlineSnapshot(`
"const { ssrRenderList: _ssrRenderList } = require(\\"@vue/server-renderer\\") "const { ssrRenderList: _ssrRenderList } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent) { return function ssrRender(_ctx, _push, _parent, _attrs) {
if (_ctx.foo) { if (_ctx.foo) {
_push(\`<!--[-->\`) _push(\`<!--[-->\`)
_ssrRenderList(_ctx.list, (i) => { _ssrRenderList(_ctx.list, (i) => {
@ -144,12 +150,13 @@ describe('ssr: v-if', () => {
`<template v-if="foo"><div>hi</div><div>ho</div></template><div v-else/>` `<template v-if="foo"><div>hi</div><div>ho</div></template><div v-else/>`
).code ).code
).toMatchInlineSnapshot(` ).toMatchInlineSnapshot(`
" "const { ssrRenderAttrs: _ssrRenderAttrs } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent) {
return function ssrRender(_ctx, _push, _parent, _attrs) {
if (_ctx.foo) { if (_ctx.foo) {
_push(\`<!--[--><div>hi</div><div>ho</div><!--]-->\`) _push(\`<!--[--><div>hi</div><div>ho</div><!--]-->\`)
} else { } else {
_push(\`<div></div>\`) _push(\`<div\${_ssrRenderAttrs(_attrs)}></div>\`)
} }
}" }"
`) `)

View File

@ -1,136 +1,186 @@
import { compile } from '../src' import { compile } from '../src'
function compileWithWrapper(src: string) {
return compile(`<div>${src}</div>`)
}
describe('ssr: v-model', () => { describe('ssr: v-model', () => {
test('<input> (text types)', () => { test('<input> (text types)', () => {
expect(compile(`<input v-model="bar">`).code).toMatchInlineSnapshot(` expect(compileWithWrapper(`<input v-model="bar">`).code)
"const { ssrRenderAttr: _ssrRenderAttr } = require(\\"@vue/server-renderer\\") .toMatchInlineSnapshot(`
"const { ssrRenderAttr: _ssrRenderAttr, ssrRenderAttrs: _ssrRenderAttrs } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent) { return function ssrRender(_ctx, _push, _parent, _attrs) {
_push(\`<input\${_ssrRenderAttr(\\"value\\", _ctx.bar)}>\`) _push(\`<div\${
_ssrRenderAttrs(_attrs)
}><input\${
_ssrRenderAttr(\\"value\\", _ctx.bar)
}></div>\`)
}" }"
`) `)
expect(compile(`<input type="email" v-model="bar">`).code) expect(compileWithWrapper(`<input type="email" v-model="bar">`).code)
.toMatchInlineSnapshot(` .toMatchInlineSnapshot(`
"const { ssrRenderAttr: _ssrRenderAttr } = require(\\"@vue/server-renderer\\") "const { ssrRenderAttr: _ssrRenderAttr, ssrRenderAttrs: _ssrRenderAttrs } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent) { return function ssrRender(_ctx, _push, _parent, _attrs) {
_push(\`<input type=\\"email\\"\${_ssrRenderAttr(\\"value\\", _ctx.bar)}>\`) _push(\`<div\${
_ssrRenderAttrs(_attrs)
}><input type=\\"email\\"\${
_ssrRenderAttr(\\"value\\", _ctx.bar)
}></div>\`)
}" }"
`) `)
}) })
test('<input type="radio">', () => { test('<input type="radio">', () => {
expect(compile(`<input type="radio" value="foo" v-model="bar">`).code) expect(
.toMatchInlineSnapshot(` compileWithWrapper(`<input type="radio" value="foo" v-model="bar">`).code
"const { ssrLooseEqual: _ssrLooseEqual } = require(\\"@vue/server-renderer\\") ).toMatchInlineSnapshot(`
"const { ssrLooseEqual: _ssrLooseEqual, ssrRenderAttrs: _ssrRenderAttrs } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent) { return function ssrRender(_ctx, _push, _parent, _attrs) {
_push(\`<input type=\\"radio\\" value=\\"foo\\"\${(_ssrLooseEqual(_ctx.bar, \\"foo\\")) ? \\" checked\\" : \\"\\"}>\`) _push(\`<div\${
_ssrRenderAttrs(_attrs)
}><input type=\\"radio\\" value=\\"foo\\"\${
(_ssrLooseEqual(_ctx.bar, \\"foo\\")) ? \\" checked\\" : \\"\\"
}></div>\`)
}" }"
`) `)
}) })
test('<input type="checkbox"', () => { test('<input type="checkbox"', () => {
expect(compile(`<input type="checkbox" v-model="bar">`).code) expect(compileWithWrapper(`<input type="checkbox" v-model="bar">`).code)
.toMatchInlineSnapshot(` .toMatchInlineSnapshot(`
"const { ssrLooseContain: _ssrLooseContain } = require(\\"@vue/server-renderer\\") "const { ssrLooseContain: _ssrLooseContain, ssrRenderAttrs: _ssrRenderAttrs } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent) { return function ssrRender(_ctx, _push, _parent, _attrs) {
_push(\`<input type=\\"checkbox\\"\${((Array.isArray(_ctx.bar)) _push(\`<div\${
? _ssrLooseContain(_ctx.bar, null) _ssrRenderAttrs(_attrs)
: _ctx.bar) ? \\" checked\\" : \\"\\"}>\`) }><input type=\\"checkbox\\"\${
((Array.isArray(_ctx.bar))
? _ssrLooseContain(_ctx.bar, null)
: _ctx.bar) ? \\" checked\\" : \\"\\"
}></div>\`)
}" }"
`) `)
expect(compile(`<input type="checkbox" value="foo" v-model="bar">`).code) expect(
.toMatchInlineSnapshot(` compileWithWrapper(`<input type="checkbox" value="foo" v-model="bar">`)
"const { ssrLooseContain: _ssrLooseContain } = require(\\"@vue/server-renderer\\") .code
).toMatchInlineSnapshot(`
"const { ssrLooseContain: _ssrLooseContain, ssrRenderAttrs: _ssrRenderAttrs } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent) { return function ssrRender(_ctx, _push, _parent, _attrs) {
_push(\`<input type=\\"checkbox\\" value=\\"foo\\"\${((Array.isArray(_ctx.bar)) _push(\`<div\${
? _ssrLooseContain(_ctx.bar, \\"foo\\") _ssrRenderAttrs(_attrs)
: _ctx.bar) ? \\" checked\\" : \\"\\"}>\`) }><input type=\\"checkbox\\" value=\\"foo\\"\${
((Array.isArray(_ctx.bar))
? _ssrLooseContain(_ctx.bar, \\"foo\\")
: _ctx.bar) ? \\" checked\\" : \\"\\"
}></div>\`)
}" }"
`) `)
}) })
test('<textarea>', () => { test('<textarea>', () => {
expect(compile(`<textarea v-model="foo">bar</textarea>`).code) expect(compileWithWrapper(`<textarea v-model="foo">bar</textarea>`).code)
.toMatchInlineSnapshot(` .toMatchInlineSnapshot(`
"const { ssrInterpolate: _ssrInterpolate } = require(\\"@vue/server-renderer\\") "const { ssrRenderAttrs: _ssrRenderAttrs, ssrInterpolate: _ssrInterpolate } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent) { return function ssrRender(_ctx, _push, _parent, _attrs) {
_push(\`<textarea>\${_ssrInterpolate(_ctx.foo)}</textarea>\`) _push(\`<div\${
_ssrRenderAttrs(_attrs)
}><textarea>\${
_ssrInterpolate(_ctx.foo)
}</textarea></div>\`)
}" }"
`) `)
}) })
test('<input :type="x">', () => { test('<input :type="x">', () => {
expect(compile(`<input :type="x" v-model="foo">`).code) expect(compileWithWrapper(`<input :type="x" v-model="foo">`).code)
.toMatchInlineSnapshot(` .toMatchInlineSnapshot(`
"const { ssrRenderAttr: _ssrRenderAttr, ssrRenderDynamicModel: _ssrRenderDynamicModel } = require(\\"@vue/server-renderer\\") "const { ssrRenderAttr: _ssrRenderAttr, ssrRenderDynamicModel: _ssrRenderDynamicModel, ssrRenderAttrs: _ssrRenderAttrs } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent) { return function ssrRender(_ctx, _push, _parent, _attrs) {
_push(\`<input\${ _push(\`<div\${
_ssrRenderAttrs(_attrs)
}><input\${
_ssrRenderAttr(\\"type\\", _ctx.x) _ssrRenderAttr(\\"type\\", _ctx.x)
}\${ }\${
_ssrRenderDynamicModel(_ctx.x, _ctx.foo, null) _ssrRenderDynamicModel(_ctx.x, _ctx.foo, null)
}>\`) }></div>\`)
}" }"
`) `)
expect(compile(`<input :type="x" v-model="foo" value="bar">`).code) expect(
.toMatchInlineSnapshot(` compileWithWrapper(`<input :type="x" v-model="foo" value="bar">`).code
"const { ssrRenderAttr: _ssrRenderAttr, ssrRenderDynamicModel: _ssrRenderDynamicModel } = require(\\"@vue/server-renderer\\") ).toMatchInlineSnapshot(`
"const { ssrRenderAttr: _ssrRenderAttr, ssrRenderDynamicModel: _ssrRenderDynamicModel, ssrRenderAttrs: _ssrRenderAttrs } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent) { return function ssrRender(_ctx, _push, _parent, _attrs) {
_push(\`<input\${ _push(\`<div\${
_ssrRenderAttrs(_attrs)
}><input\${
_ssrRenderAttr(\\"type\\", _ctx.x) _ssrRenderAttr(\\"type\\", _ctx.x)
}\${ }\${
_ssrRenderDynamicModel(_ctx.x, _ctx.foo, \\"bar\\") _ssrRenderDynamicModel(_ctx.x, _ctx.foo, \\"bar\\")
} value=\\"bar\\">\`) } value=\\"bar\\"></div>\`)
}" }"
`) `)
expect(compile(`<input :type="x" v-model="foo" :value="bar">`).code) expect(
.toMatchInlineSnapshot(` compileWithWrapper(`<input :type="x" v-model="foo" :value="bar">`).code
"const { ssrRenderAttr: _ssrRenderAttr, ssrRenderDynamicModel: _ssrRenderDynamicModel } = require(\\"@vue/server-renderer\\") ).toMatchInlineSnapshot(`
"const { ssrRenderAttr: _ssrRenderAttr, ssrRenderDynamicModel: _ssrRenderDynamicModel, ssrRenderAttrs: _ssrRenderAttrs } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent) { return function ssrRender(_ctx, _push, _parent, _attrs) {
_push(\`<input\${ _push(\`<div\${
_ssrRenderAttrs(_attrs)
}><input\${
_ssrRenderAttr(\\"type\\", _ctx.x) _ssrRenderAttr(\\"type\\", _ctx.x)
}\${ }\${
_ssrRenderDynamicModel(_ctx.x, _ctx.foo, _ctx.bar) _ssrRenderDynamicModel(_ctx.x, _ctx.foo, _ctx.bar)
}\${ }\${
_ssrRenderAttr(\\"value\\", _ctx.bar) _ssrRenderAttr(\\"value\\", _ctx.bar)
}>\`) }></div>\`)
}" }"
`) `)
}) })
test('<input v-bind="obj">', () => { test('<input v-bind="obj">', () => {
expect(compile(`<input v-bind="obj" v-model="foo">`).code) expect(compileWithWrapper(`<input v-bind="obj" v-model="foo">`).code)
.toMatchInlineSnapshot(` .toMatchInlineSnapshot(`
"const { mergeProps: _mergeProps } = require(\\"vue\\") "const { mergeProps: _mergeProps } = require(\\"vue\\")
const { ssrRenderAttrs: _ssrRenderAttrs, ssrGetDynamicModelProps: _ssrGetDynamicModelProps } = require(\\"@vue/server-renderer\\") const { ssrRenderAttrs: _ssrRenderAttrs, ssrGetDynamicModelProps: _ssrGetDynamicModelProps } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent) { return function ssrRender(_ctx, _push, _parent, _attrs) {
let _temp0 let _temp0
_push(\`<input\${_ssrRenderAttrs((_temp0 = _ctx.obj, _mergeProps(_temp0, _ssrGetDynamicModelProps(_temp0, _ctx.foo))))}>\`) _push(\`<div\${
_ssrRenderAttrs(_attrs)
}><input\${
_ssrRenderAttrs((_temp0 = _ctx.obj, _mergeProps(_temp0, _ssrGetDynamicModelProps(_temp0, _ctx.foo))))
}></div>\`)
}" }"
`) `)
expect(compile(`<input id="x" v-bind="obj" v-model="foo" class="y">`).code) expect(
.toMatchInlineSnapshot(` compileWithWrapper(`<input id="x" v-bind="obj" v-model="foo" class="y">`)
.code
).toMatchInlineSnapshot(`
"const { mergeProps: _mergeProps } = require(\\"vue\\") "const { mergeProps: _mergeProps } = require(\\"vue\\")
const { ssrRenderAttrs: _ssrRenderAttrs, ssrGetDynamicModelProps: _ssrGetDynamicModelProps } = require(\\"@vue/server-renderer\\") const { ssrRenderAttrs: _ssrRenderAttrs, ssrGetDynamicModelProps: _ssrGetDynamicModelProps } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent) { return function ssrRender(_ctx, _push, _parent, _attrs) {
let _temp0 let _temp0
_push(\`<input\${_ssrRenderAttrs((_temp0 = _mergeProps({ id: \\"x\\" }, _ctx.obj, { class: \\"y\\" }), _mergeProps(_temp0, _ssrGetDynamicModelProps(_temp0, _ctx.foo))))}>\`) _push(\`<div\${
_ssrRenderAttrs(_attrs)
}><input\${
_ssrRenderAttrs((_temp0 = _mergeProps({ id: \\"x\\" }, _ctx.obj, { class: \\"y\\" }), _mergeProps(_temp0, _ssrGetDynamicModelProps(_temp0, _ctx.foo))))
}></div>\`)
}" }"
`) `)
}) })

View File

@ -1,78 +1,118 @@
import { compile } from '../src' import { compile } from '../src'
describe('ssr: v-show', () => { function compileWithWrapper(src: string) {
test('basic', () => { return compile(`<div>${src}</div>`)
expect(compile(`<div v-show="foo"/>`).code).toMatchInlineSnapshot(` }
"const { ssrRenderStyle: _ssrRenderStyle } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent) { describe('ssr: v-show', () => {
_push(\`<div style=\\"\${_ssrRenderStyle((_ctx.foo) ? null : { display: \\"none\\" })}\\"></div>\`) test('basic as root', () => {
expect(compile(`<div v-show="foo"/>`).code).toMatchInlineSnapshot(`
"const { mergeProps: _mergeProps } = require(\\"vue\\")
const { ssrRenderAttrs: _ssrRenderAttrs } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent, _attrs) {
_push(\`<div\${_ssrRenderAttrs(_mergeProps({
style: (_ctx.foo) ? null : { display: \\"none\\" }
}, _attrs))}></div>\`)
}"
`)
})
test('basic', () => {
expect(compileWithWrapper(`<div v-show="foo"/>`).code)
.toMatchInlineSnapshot(`
"const { ssrRenderStyle: _ssrRenderStyle, ssrRenderAttrs: _ssrRenderAttrs } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent, _attrs) {
_push(\`<div\${
_ssrRenderAttrs(_attrs)
}><div style=\\"\${
_ssrRenderStyle((_ctx.foo) ? null : { display: \\"none\\" })
}\\"></div></div>\`)
}" }"
`) `)
}) })
test('with static style', () => { test('with static style', () => {
expect(compile(`<div style="color:red" v-show="foo"/>`).code) expect(compileWithWrapper(`<div style="color:red" v-show="foo"/>`).code)
.toMatchInlineSnapshot(` .toMatchInlineSnapshot(`
"const { ssrRenderStyle: _ssrRenderStyle } = require(\\"@vue/server-renderer\\") "const { ssrRenderStyle: _ssrRenderStyle, ssrRenderAttrs: _ssrRenderAttrs } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent) { return function ssrRender(_ctx, _push, _parent, _attrs) {
_push(\`<div style=\\"\${_ssrRenderStyle([ _push(\`<div\${
{\\"color\\":\\"red\\"}, _ssrRenderAttrs(_attrs)
(_ctx.foo) ? null : { display: \\"none\\" } }><div style=\\"\${
])}\\"></div>\`) _ssrRenderStyle([
{\\"color\\":\\"red\\"},
(_ctx.foo) ? null : { display: \\"none\\" }
])
}\\"></div></div>\`)
}" }"
`) `)
}) })
test('with dynamic style', () => { test('with dynamic style', () => {
expect(compile(`<div :style="{ color: 'red' }" v-show="foo"/>`).code) expect(
.toMatchInlineSnapshot(` compileWithWrapper(`<div :style="{ color: 'red' }" v-show="foo"/>`).code
"const { ssrRenderStyle: _ssrRenderStyle } = require(\\"@vue/server-renderer\\") ).toMatchInlineSnapshot(`
"const { ssrRenderStyle: _ssrRenderStyle, ssrRenderAttrs: _ssrRenderAttrs } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent) { return function ssrRender(_ctx, _push, _parent, _attrs) {
_push(\`<div style=\\"\${_ssrRenderStyle([ _push(\`<div\${
{ color: 'red' }, _ssrRenderAttrs(_attrs)
(_ctx.foo) ? null : { display: \\"none\\" } }><div style=\\"\${
])}\\"></div>\`) _ssrRenderStyle([
{ color: 'red' },
(_ctx.foo) ? null : { display: \\"none\\" }
])
}\\"></div></div>\`)
}" }"
`) `)
}) })
test('with static + dynamic style', () => { test('with static + dynamic style', () => {
expect( expect(
compile(`<div style="color:red" :style="{ fontSize: 14 }" v-show="foo"/>`) compileWithWrapper(
.code `<div style="color:red" :style="{ fontSize: 14 }" v-show="foo"/>`
).code
).toMatchInlineSnapshot(` ).toMatchInlineSnapshot(`
"const { ssrRenderStyle: _ssrRenderStyle } = require(\\"@vue/server-renderer\\") "const { ssrRenderStyle: _ssrRenderStyle, ssrRenderAttrs: _ssrRenderAttrs } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent) { return function ssrRender(_ctx, _push, _parent, _attrs) {
_push(\`<div style=\\"\${_ssrRenderStyle([ _push(\`<div\${
{\\"color\\":\\"red\\"}, _ssrRenderAttrs(_attrs)
{ fontSize: 14 }, }><div style=\\"\${
(_ctx.foo) ? null : { display: \\"none\\" } _ssrRenderStyle([
])}\\"></div>\`) {\\"color\\":\\"red\\"},
{ fontSize: 14 },
(_ctx.foo) ? null : { display: \\"none\\" }
])
}\\"></div></div>\`)
}" }"
`) `)
}) })
test('with v-bind', () => { test('with v-bind', () => {
expect( expect(
compile( compileWithWrapper(
`<div v-bind="baz" style="color:red" :style="{ fontSize: 14 }" v-show="foo"/>` `<div v-bind="baz" style="color:red" :style="{ fontSize: 14 }" v-show="foo"/>`
).code ).code
).toMatchInlineSnapshot(` ).toMatchInlineSnapshot(`
"const { mergeProps: _mergeProps } = require(\\"vue\\") "const { mergeProps: _mergeProps } = require(\\"vue\\")
const { ssrRenderAttrs: _ssrRenderAttrs } = require(\\"@vue/server-renderer\\") const { ssrRenderAttrs: _ssrRenderAttrs } = require(\\"@vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent) { return function ssrRender(_ctx, _push, _parent, _attrs) {
_push(\`<div\${_ssrRenderAttrs(_mergeProps(_ctx.baz, { _push(\`<div\${
style: [ _ssrRenderAttrs(_attrs)
{\\"color\\":\\"red\\"}, }><div\${
{ fontSize: 14 }, _ssrRenderAttrs(_mergeProps(_ctx.baz, {
(_ctx.foo) ? null : { display: \\"none\\" } style: [
] {\\"color\\":\\"red\\"},
}))}></div>\`) { fontSize: 14 },
(_ctx.foo) ? null : { display: \\"none\\" }
]
}))
}></div></div>\`)
}" }"
`) `)
}) })

View File

@ -1,5 +1,17 @@
import { compile } from '../src' import { compile } from '../src'
export function getCompiledString(src: string): string { export function getCompiledString(src: string): string {
return compile(src).code.match(/_push\(([^]*)\)/)![1] // Wrap src template in a root div so that it doesn't get injected
// fallthrough attr. This results in less noise in generated snapshots
// but also means this util can only be used for non-root cases.
const { code } = compile(`<div>${src}</div>`)
const match = code.match(
/_push\(\`<div\${\s*_ssrRenderAttrs\(_attrs\)\s*}>([^]*)<\/div>\`\)/
)
if (!match) {
throw new Error(`Unexpected compile result:\n${code}`)
}
return `\`${match[1]}\``
} }

View File

@ -23,6 +23,7 @@ import { ssrTransformIf } from './transforms/ssrVIf'
import { ssrTransformFor } from './transforms/ssrVFor' import { ssrTransformFor } from './transforms/ssrVFor'
import { ssrTransformModel } from './transforms/ssrVModel' import { ssrTransformModel } from './transforms/ssrVModel'
import { ssrTransformShow } from './transforms/ssrVShow' import { ssrTransformShow } from './transforms/ssrVShow'
import { ssrInjectFallthroughAttrs } from './transforms/ssrInjectFallthroughAttrs'
export function compile( export function compile(
template: string, template: string,
@ -55,6 +56,7 @@ export function compile(
trackVForSlotScopes, trackVForSlotScopes,
transformExpression, transformExpression,
ssrTransformSlotOutlet, ssrTransformSlotOutlet,
ssrInjectFallthroughAttrs,
ssrTransformElement, ssrTransformElement,
ssrTransformComponent, ssrTransformComponent,
trackSlotScopes, trackSlotScopes,

View File

@ -0,0 +1,52 @@
import {
NodeTransform,
NodeTypes,
ElementTypes,
locStub,
createSimpleExpression,
RootNode,
TemplateChildNode,
ParentNode,
findDir
} from '@vue/compiler-dom'
const hasSingleChild = (node: ParentNode): boolean =>
node.children.filter(n => n.type !== NodeTypes.COMMENT).length === 1
export const ssrInjectFallthroughAttrs: NodeTransform = (node, context) => {
// _attrs is provided as a function argument.
// mark it as a known identifier so that it doesn't get prefixed by
// transformExpression.
if (node.type === NodeTypes.ROOT) {
context.identifiers._attrs = 1
}
const parent = context.parent
if (!parent || parent.type !== NodeTypes.ROOT) {
return
}
if (node.type === NodeTypes.IF_BRANCH && hasSingleChild(node)) {
injectFallthroughAttrs(node.children[0])
} else if (hasSingleChild(parent)) {
injectFallthroughAttrs(node)
}
}
function injectFallthroughAttrs(node: RootNode | TemplateChildNode) {
if (
node.type === NodeTypes.ELEMENT &&
(node.tagType === ElementTypes.ELEMENT ||
node.tagType === ElementTypes.COMPONENT) &&
!findDir(node, 'for')
) {
node.props.push({
type: NodeTypes.DIRECTIVE,
name: 'bind',
arg: undefined,
exp: createSimpleExpression(`_attrs`, false),
modifiers: [],
loc: locStub
})
}
}

View File

@ -23,7 +23,8 @@ import {
hasDynamicKeyVBind, hasDynamicKeyVBind,
MERGE_PROPS, MERGE_PROPS,
isBindKey, isBindKey,
createSequenceExpression createSequenceExpression,
InterpolationNode
} from '@vue/compiler-dom' } from '@vue/compiler-dom'
import { import {
escapeHtml, escapeHtml,
@ -53,30 +54,40 @@ const rawChildrenMap = new WeakMap<
export const ssrTransformElement: NodeTransform = (node, context) => { export const ssrTransformElement: NodeTransform = (node, context) => {
if ( if (
node.type === NodeTypes.ELEMENT && node.type !== NodeTypes.ELEMENT ||
node.tagType === ElementTypes.ELEMENT node.tagType !== ElementTypes.ELEMENT
) { ) {
return function ssrPostTransformElement() { return
// element }
// generate the template literal representing the open tag.
const openTag: TemplateLiteral['elements'] = [`<${node.tag}`]
// some tags need to be pasesd to runtime for special checks
const needTagForRuntime =
node.tag === 'textarea' || node.tag.indexOf('-') > 0
// v-bind="obj" or v-bind:[key] can potentially overwrite other static return function ssrPostTransformElement() {
// attrs and can affect final rendering result, so when they are present // element
// we need to bail out to full `renderAttrs` // generate the template literal representing the open tag.
const hasDynamicVBind = hasDynamicKeyVBind(node) const openTag: TemplateLiteral['elements'] = [`<${node.tag}`]
if (hasDynamicVBind) { // some tags need to be pasesd to runtime for special checks
const { props } = buildProps(node, context, node.props, true /* ssr */) const needTagForRuntime =
if (props) { node.tag === 'textarea' || node.tag.indexOf('-') > 0
const propsExp = createCallExpression(
context.helper(SSR_RENDER_ATTRS),
[props]
)
if (node.tag === 'textarea') { // v-bind="obj" or v-bind:[key] can potentially overwrite other static
// attrs and can affect final rendering result, so when they are present
// we need to bail out to full `renderAttrs`
const hasDynamicVBind = hasDynamicKeyVBind(node)
if (hasDynamicVBind) {
const { props } = buildProps(node, context, node.props, true /* ssr */)
if (props) {
const propsExp = createCallExpression(
context.helper(SSR_RENDER_ATTRS),
[props]
)
if (node.tag === 'textarea') {
const existingText = node.children[0] as
| TextNode
| InterpolationNode
| undefined
// If interpolation, this is dynamic <textarea> content, potentially
// injected by v-model and takes higher priority than v-bind value
if (!existingText || existingText.type !== NodeTypes.INTERPOLATION) {
// <textarea> with dynamic v-bind. We don't know if the final props // <textarea> with dynamic v-bind. We don't know if the final props
// will contain .value, so we will have to do something special: // will contain .value, so we will have to do something special:
// assign the merged props to a temp variable, and check whether // assign the merged props to a temp variable, and check whether
@ -88,7 +99,6 @@ export const ssrTransformElement: NodeTransform = (node, context) => {
props props
) )
] ]
const existingText = node.children[0] as TextNode | undefined
rawChildrenMap.set( rawChildrenMap.set(
node, node,
createCallExpression(context.helper(SSR_INTERPOLATE), [ createCallExpression(context.helper(SSR_INTERPOLATE), [
@ -103,189 +113,189 @@ export const ssrTransformElement: NodeTransform = (node, context) => {
) )
]) ])
) )
} else if (node.tag === 'input') { }
// <input v-bind="obj" v-model> } else if (node.tag === 'input') {
// we need to determine the props to render for the dynamic v-model // <input v-bind="obj" v-model>
// and merge it with the v-bind expression. // we need to determine the props to render for the dynamic v-model
const vModel = findVModel(node) // and merge it with the v-bind expression.
if (vModel) { const vModel = findVModel(node)
// 1. save the props (san v-model) in a temp variable if (vModel) {
const tempId = `_temp${context.temps++}` // 1. save the props (san v-model) in a temp variable
const tempExp = createSimpleExpression(tempId, false) const tempId = `_temp${context.temps++}`
propsExp.arguments = [ const tempExp = createSimpleExpression(tempId, false)
createSequenceExpression([ propsExp.arguments = [
createAssignmentExpression(tempExp, props), createSequenceExpression([
createCallExpression(context.helper(MERGE_PROPS), [ createAssignmentExpression(tempExp, props),
tempExp, createCallExpression(context.helper(MERGE_PROPS), [
createCallExpression( tempExp,
context.helper(SSR_GET_DYNAMIC_MODEL_PROPS), createCallExpression(
[ context.helper(SSR_GET_DYNAMIC_MODEL_PROPS),
tempExp, // existing props [
vModel.exp! // model tempExp, // existing props
] vModel.exp! // model
) ]
]) )
]) ])
] ])
} ]
} }
if (needTagForRuntime) {
propsExp.arguments.push(`"${node.tag}"`)
}
openTag.push(propsExp)
} }
if (needTagForRuntime) {
propsExp.arguments.push(`"${node.tag}"`)
}
openTag.push(propsExp)
} }
}
// book keeping static/dynamic class merging. // book keeping static/dynamic class merging.
let dynamicClassBinding: CallExpression | undefined = undefined let dynamicClassBinding: CallExpression | undefined = undefined
let staticClassBinding: string | undefined = undefined let staticClassBinding: string | undefined = undefined
// all style bindings are converted to dynamic by transformStyle. // all style bindings are converted to dynamic by transformStyle.
// but we need to make sure to merge them. // but we need to make sure to merge them.
let dynamicStyleBinding: CallExpression | undefined = undefined let dynamicStyleBinding: CallExpression | undefined = undefined
for (let i = 0; i < node.props.length; i++) { for (let i = 0; i < node.props.length; i++) {
const prop = node.props[i] const prop = node.props[i]
// special cases with children override // special cases with children override
if (prop.type === NodeTypes.DIRECTIVE) { if (prop.type === NodeTypes.DIRECTIVE) {
if (prop.name === 'html' && prop.exp) { if (prop.name === 'html' && prop.exp) {
rawChildrenMap.set(node, prop.exp) rawChildrenMap.set(node, prop.exp)
} else if (prop.name === 'text' && prop.exp) { } else if (prop.name === 'text' && prop.exp) {
node.children = [createInterpolation(prop.exp, prop.loc)]
} else if (prop.name === 'slot') {
context.onError(
createCompilerError(ErrorCodes.X_V_SLOT_MISPLACED, prop.loc)
)
} else if (isTextareaWithValue(node, prop) && prop.exp) {
if (!hasDynamicVBind) {
node.children = [createInterpolation(prop.exp, prop.loc)] node.children = [createInterpolation(prop.exp, prop.loc)]
} else if (prop.name === 'slot') { }
} else {
// Directive transforms.
const directiveTransform = context.directiveTransforms[prop.name]
if (!directiveTransform) {
// no corresponding ssr directive transform found.
context.onError( context.onError(
createCompilerError(ErrorCodes.X_V_SLOT_MISPLACED, prop.loc) createSSRCompilerError(
SSRErrorCodes.X_SSR_CUSTOM_DIRECTIVE_NO_TRANSFORM,
prop.loc
)
) )
} else if (isTextareaWithValue(node, prop) && prop.exp) { } else if (!hasDynamicVBind) {
if (!hasDynamicVBind) { const { props, ssrTagParts } = directiveTransform(
node.children = [createInterpolation(prop.exp, prop.loc)] prop,
node,
context
)
if (ssrTagParts) {
openTag.push(...ssrTagParts)
} }
} else { for (let j = 0; j < props.length; j++) {
// Directive transforms. const { key, value } = props[j]
const directiveTransform = context.directiveTransforms[prop.name] if (key.type === NodeTypes.SIMPLE_EXPRESSION && key.isStatic) {
if (!directiveTransform) { let attrName = key.content
// no corresponding ssr directive transform found. // static key attr
context.onError( if (attrName === 'class') {
createSSRCompilerError( openTag.push(
SSRErrorCodes.X_SSR_CUSTOM_DIRECTIVE_NO_TRANSFORM, ` class="`,
prop.loc (dynamicClassBinding = createCallExpression(
) context.helper(SSR_RENDER_CLASS),
) [value]
} else if (!hasDynamicVBind) { )),
const { props, ssrTagParts } = directiveTransform( `"`
prop, )
node, } else if (attrName === 'style') {
context if (dynamicStyleBinding) {
) // already has style binding, merge into it.
if (ssrTagParts) { mergeCall(dynamicStyleBinding, value)
openTag.push(...ssrTagParts) } else {
}
for (let j = 0; j < props.length; j++) {
const { key, value } = props[j]
if (key.type === NodeTypes.SIMPLE_EXPRESSION && key.isStatic) {
let attrName = key.content
// static key attr
if (attrName === 'class') {
openTag.push( openTag.push(
` class="`, ` style="`,
(dynamicClassBinding = createCallExpression( (dynamicStyleBinding = createCallExpression(
context.helper(SSR_RENDER_CLASS), context.helper(SSR_RENDER_STYLE),
[value] [value]
)), )),
`"` `"`
) )
} else if (attrName === 'style') {
if (dynamicStyleBinding) {
// already has style binding, merge into it.
mergeCall(dynamicStyleBinding, value)
} else {
openTag.push(
` style="`,
(dynamicStyleBinding = createCallExpression(
context.helper(SSR_RENDER_STYLE),
[value]
)),
`"`
)
}
} else {
attrName =
node.tag.indexOf('-') > 0
? attrName // preserve raw name on custom elements
: propsToAttrMap[attrName] || attrName.toLowerCase()
if (isBooleanAttr(attrName)) {
openTag.push(
createConditionalExpression(
value,
createSimpleExpression(' ' + attrName, true),
createSimpleExpression('', true),
false /* no newline */
)
)
} else if (isSSRSafeAttrName(attrName)) {
openTag.push(
createCallExpression(context.helper(SSR_RENDER_ATTR), [
key,
value
])
)
} else {
context.onError(
createSSRCompilerError(
SSRErrorCodes.X_SSR_UNSAFE_ATTR_NAME,
key.loc
)
)
}
} }
} else { } else {
// dynamic key attr attrName =
// this branch is only encountered for custom directive node.tag.indexOf('-') > 0
// transforms that returns properties with dynamic keys ? attrName // preserve raw name on custom elements
const args: CallExpression['arguments'] = [key, value] : propsToAttrMap[attrName] || attrName.toLowerCase()
if (needTagForRuntime) { if (isBooleanAttr(attrName)) {
args.push(`"${node.tag}"`) openTag.push(
} createConditionalExpression(
openTag.push( value,
createCallExpression( createSimpleExpression(' ' + attrName, true),
context.helper(SSR_RENDER_DYNAMIC_ATTR), createSimpleExpression('', true),
args false /* no newline */
)
) )
) } else if (isSSRSafeAttrName(attrName)) {
openTag.push(
createCallExpression(context.helper(SSR_RENDER_ATTR), [
key,
value
])
)
} else {
context.onError(
createSSRCompilerError(
SSRErrorCodes.X_SSR_UNSAFE_ATTR_NAME,
key.loc
)
)
}
} }
} else {
// dynamic key attr
// this branch is only encountered for custom directive
// transforms that returns properties with dynamic keys
const args: CallExpression['arguments'] = [key, value]
if (needTagForRuntime) {
args.push(`"${node.tag}"`)
}
openTag.push(
createCallExpression(
context.helper(SSR_RENDER_DYNAMIC_ATTR),
args
)
)
} }
} }
} }
} else { }
// special case: value on <textarea> } else {
if (node.tag === 'textarea' && prop.name === 'value' && prop.value) { // special case: value on <textarea>
rawChildrenMap.set(node, escapeHtml(prop.value.content)) if (node.tag === 'textarea' && prop.name === 'value' && prop.value) {
} else if (!hasDynamicVBind) { rawChildrenMap.set(node, escapeHtml(prop.value.content))
// static prop } else if (!hasDynamicVBind) {
if (prop.name === 'class' && prop.value) { // static prop
staticClassBinding = JSON.stringify(prop.value.content) if (prop.name === 'class' && prop.value) {
} staticClassBinding = JSON.stringify(prop.value.content)
openTag.push(
` ${prop.name}` +
(prop.value ? `="${escapeHtml(prop.value.content)}"` : ``)
)
} }
openTag.push(
` ${prop.name}` +
(prop.value ? `="${escapeHtml(prop.value.content)}"` : ``)
)
} }
} }
// handle co-existence of dynamic + static class bindings
if (dynamicClassBinding && staticClassBinding) {
mergeCall(dynamicClassBinding, staticClassBinding)
removeStaticBinding(openTag, 'class')
}
if (context.scopeId) {
openTag.push(` ${context.scopeId}`)
}
node.ssrCodegenNode = createTemplateLiteral(openTag)
} }
// handle co-existence of dynamic + static class bindings
if (dynamicClassBinding && staticClassBinding) {
mergeCall(dynamicClassBinding, staticClassBinding)
removeStaticBinding(openTag, 'class')
}
if (context.scopeId) {
openTag.push(` ${context.scopeId}`)
}
node.ssrCodegenNode = createTemplateLiteral(openTag)
} }
} }

View File

@ -119,7 +119,8 @@ export interface ComponentOptionsBase<
ssrRender?: ( ssrRender?: (
ctx: any, ctx: any,
push: (item: any) => void, push: (item: any) => void,
parentInstance: ComponentInternalInstance parentInstance: ComponentInternalInstance,
attrs?: Data
) => void ) => void
/** /**

View File

@ -554,8 +554,7 @@ export function normalizeChildren(vnode: VNode, children: unknown) {
const handlersRE = /^on|^vnode/ const handlersRE = /^on|^vnode/
export function mergeProps(...args: (Data & VNodeProps)[]) { export function mergeProps(...args: (Data & VNodeProps)[]) {
const ret: Data = {} const ret = extend({}, args[0])
extend(ret, args[0])
for (let i = 1; i < args.length; i++) { for (let i = 1; i < args.length; i++) {
const toMerge = args[i] const toMerge = args[i]
for (const key in toMerge) { for (const key in toMerge) {

View File

@ -65,7 +65,7 @@ describe('ssr: renderToString', () => {
expect( expect(
await renderToString( await renderToString(
createApp( createApp(
defineComponent((props: {}) => { defineComponent(() => {
const msg = ref('hello') const msg = ref('hello')
return () => h('div', msg.value) return () => h('div', msg.value)
}) })
@ -89,31 +89,6 @@ describe('ssr: renderToString', () => {
).toBe(`<div>hello</div>`) ).toBe(`<div>hello</div>`)
}) })
describe('template components', () => {
test('render', async () => {
expect(
await renderToString(
createApp({
data() {
return { msg: 'hello' }
},
template: `<div>{{ msg }}</div>`
})
)
).toBe(`<div>hello</div>`)
})
test('handle compiler errors', async () => {
await renderToString(createApp({ template: `<` }))
expect(
'Template compilation error: Unexpected EOF in tag.\n' +
'1 | <\n' +
' | ^'
).toHaveBeenWarned()
})
})
test('nested vnode components', async () => { test('nested vnode components', async () => {
const Child = { const Child = {
props: ['msg'], props: ['msg'],
@ -247,7 +222,7 @@ describe('ssr: renderToString', () => {
{ msg: 'hello' }, { msg: 'hello' },
{ {
// optimized slot using string push // optimized slot using string push
default: ({ msg }: any, push: any, p: any) => { default: ({ msg }: any, push: any, _p: any) => {
push(`<span>${msg}</span>`) push(`<span>${msg}</span>`)
}, },
// important to avoid slots being normalized // important to avoid slots being normalized
@ -583,4 +558,29 @@ describe('ssr: renderToString', () => {
) )
}) })
}) })
describe('integration w/ compiled template', () => {
test('render', async () => {
expect(
await renderToString(
createApp({
data() {
return { msg: 'hello' }
},
template: `<div>{{ msg }}</div>`
})
)
).toBe(`<div>hello</div>`)
})
test('handle compiler errors', async () => {
await renderToString(createApp({ template: `<` }))
expect(
'Template compilation error: Unexpected EOF in tag.\n' +
'1 | <\n' +
' | ^'
).toHaveBeenWarned()
})
})
}) })

View File

@ -0,0 +1,78 @@
import { createApp } from 'vue'
import { renderToString } from '../src/renderToString'
describe('ssr: attr fallthrough', () => {
test('basic', async () => {
const Child = {
template: `<div class="foo" />`
}
const Parent = {
components: { Child },
template: `<child class="bar"/>`
}
const app = createApp(Parent)
expect(await renderToString(app)).toBe(`<div class="foo bar"></div>`)
})
test('with v-if', async () => {
const Child = {
props: ['ok'],
template: `<div v-if="ok" class="foo" /><span v-else />`
}
const Parent = {
props: ['ok'],
components: { Child },
template: `<child :ok="ok" class="bar"/>`
}
expect(await renderToString(createApp(Parent, { ok: true }))).toBe(
`<div class="foo bar"></div>`
)
expect(await renderToString(createApp(Parent, { ok: false }))).toBe(
`<span class="bar"></span>`
)
})
test('with v-model', async () => {
const Child = {
props: ['text'],
template: `<input v-model="text">`
}
const Parent = {
components: { Child },
template: `<child text="hello" class="bar"/>`
}
expect(await renderToString(createApp(Parent))).toBe(
`<input class="bar" value="hello">`
)
})
test('with v-bind', async () => {
const Child = {
props: ['obj'],
template: `<div v-bind="obj" />`
}
const Parent = {
components: { Child },
template: `<child :obj="{ class: 'foo' }" class="bar"/>`
}
expect(await renderToString(createApp(Parent))).toBe(
`<div class="foo bar"></div>`
)
})
test('nested fallthrough', async () => {
const Child = {
props: ['id'],
template: `<div :id="id"></div>`
}
const Parent = {
components: { Child },
template: `<child id="foo" class="bar"/>`
}
// pass to parent, fallthrough to child and merge
const app = createApp(Parent, { class: 'baz' })
expect(await renderToString(app)).toBe(
`<div id="foo" class="bar baz"></div>`
)
})
})

View File

@ -111,7 +111,10 @@ function renderComponentSubTree(
// optimized // optimized
// set current rendering instance for asset resolution // set current rendering instance for asset resolution
setCurrentRenderingInstance(instance) setCurrentRenderingInstance(instance)
comp.ssrRender(instance.proxy, push, instance) // fallthrough attrs
const attrs =
instance.type.inheritAttrs !== false ? instance.attrs : undefined
comp.ssrRender(instance.proxy, push, instance, attrs)
setCurrentRenderingInstance(null) setCurrentRenderingInstance(null)
} else if (instance.render) { } else if (instance.render) {
renderVNode(push, renderComponentRoot(instance), instance) renderVNode(push, renderComponentRoot(instance), instance)