Skip to content

Commit

Permalink
fix(ssr): fix ssr scopeId on component root
Browse files Browse the repository at this point in the history
  • Loading branch information
yyx990803 committed Jun 27, 2020
1 parent 978d952 commit afe13e0
Show file tree
Hide file tree
Showing 7 changed files with 137 additions and 46 deletions.
11 changes: 7 additions & 4 deletions packages/compiler-core/src/codegen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,12 +204,15 @@ export function generate(
}

// enter render function
if (genScopeId && !ssr) {
push(`const render = ${PURE_ANNOTATION}_withId(`)
}
if (!ssr) {
if (genScopeId) {
push(`const render = ${PURE_ANNOTATION}_withId(`)
}
push(`function render(_ctx, _cache) {`)
} else {
if (genScopeId) {
push(`const ssrRender = ${PURE_ANNOTATION}_withId(`)
}
push(`function ssrRender(_ctx, _push, _parent, _attrs) {`)
}
indent()
Expand Down Expand Up @@ -272,7 +275,7 @@ export function generate(
deindent()
push(`}`)

if (genScopeId && !ssr) {
if (genScopeId) {
push(`)`)
}

Expand Down
57 changes: 33 additions & 24 deletions packages/compiler-ssr/__tests__/ssrScopeId.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,32 +6,37 @@ describe('ssr: scopeId', () => {
test('basic', () => {
expect(
compile(`<div><span>hello</span></div>`, {
scopeId
scopeId,
mode: 'module'
}).code
).toMatchInlineSnapshot(`
"const { ssrRenderAttrs: _ssrRenderAttrs } = require(\\"@vue/server-renderer\\")
"import { withScopeId as _withScopeId } from \\"vue\\"
import { ssrRenderAttrs as _ssrRenderAttrs } from \\"@vue/server-renderer\\"
const _withId = /*#__PURE__*/_withScopeId(\\"data-v-xxxxxxx\\")
return function ssrRender(_ctx, _push, _parent, _attrs) {
export const ssrRender = /*#__PURE__*/_withId(function ssrRender(_ctx, _push, _parent, _attrs) {
_push(\`<div\${_ssrRenderAttrs(_attrs)} data-v-xxxxxxx><span data-v-xxxxxxx>hello</span></div>\`)
}"
})"
`)
})

test('inside slots (only text)', () => {
// should have no branching inside slot
expect(
compile(`<foo>foo</foo>`, {
scopeId
scopeId,
mode: 'module'
}).code
).toMatchInlineSnapshot(`
"const { resolveComponent: _resolveComponent, withCtx: _withCtx, createTextVNode: _createTextVNode } = require(\\"vue\\")
const { ssrRenderComponent: _ssrRenderComponent } = require(\\"@vue/server-renderer\\")
"import { resolveComponent as _resolveComponent, withCtx as _withCtx, createTextVNode as _createTextVNode, withScopeId as _withScopeId } from \\"vue\\"
import { ssrRenderComponent as _ssrRenderComponent } from \\"@vue/server-renderer\\"
const _withId = /*#__PURE__*/_withScopeId(\\"data-v-xxxxxxx\\")
return function ssrRender(_ctx, _push, _parent, _attrs) {
export const ssrRender = /*#__PURE__*/_withId(function ssrRender(_ctx, _push, _parent, _attrs) {
const _component_foo = _resolveComponent(\\"foo\\")
_push(_ssrRenderComponent(_component_foo, _attrs, {
default: _withCtx((_, _push, _parent, _scopeId) => {
default: _withId((_, _push, _parent, _scopeId) => {
if (_push) {
_push(\`foo\`)
} else {
Expand All @@ -42,24 +47,26 @@ describe('ssr: scopeId', () => {
}),
_: 1
}, _parent))
}"
})"
`)
})

test('inside slots (with elements)', () => {
expect(
compile(`<foo><span>hello</span></foo>`, {
scopeId
scopeId,
mode: 'module'
}).code
).toMatchInlineSnapshot(`
"const { resolveComponent: _resolveComponent, withCtx: _withCtx, createVNode: _createVNode } = require(\\"vue\\")
const { ssrRenderComponent: _ssrRenderComponent } = require(\\"@vue/server-renderer\\")
"import { resolveComponent as _resolveComponent, withCtx as _withCtx, createVNode as _createVNode, withScopeId as _withScopeId } from \\"vue\\"
import { ssrRenderComponent as _ssrRenderComponent } from \\"@vue/server-renderer\\"
const _withId = /*#__PURE__*/_withScopeId(\\"data-v-xxxxxxx\\")
return function ssrRender(_ctx, _push, _parent, _attrs) {
export const ssrRender = /*#__PURE__*/_withId(function ssrRender(_ctx, _push, _parent, _attrs) {
const _component_foo = _resolveComponent(\\"foo\\")
_push(_ssrRenderComponent(_component_foo, _attrs, {
default: _withCtx((_, _push, _parent, _scopeId) => {
default: _withId((_, _push, _parent, _scopeId) => {
if (_push) {
_push(\`<span data-v-xxxxxxx\${_scopeId}>hello</span>\`)
} else {
Expand All @@ -70,29 +77,31 @@ describe('ssr: scopeId', () => {
}),
_: 1
}, _parent))
}"
})"
`)
})

test('nested slots', () => {
expect(
compile(`<foo><span>hello</span><bar><span/></bar></foo>`, {
scopeId
scopeId,
mode: 'module'
}).code
).toMatchInlineSnapshot(`
"const { resolveComponent: _resolveComponent, withCtx: _withCtx, createVNode: _createVNode } = require(\\"vue\\")
const { ssrRenderComponent: _ssrRenderComponent } = require(\\"@vue/server-renderer\\")
"import { resolveComponent as _resolveComponent, withCtx as _withCtx, createVNode as _createVNode, withScopeId as _withScopeId } from \\"vue\\"
import { ssrRenderComponent as _ssrRenderComponent } from \\"@vue/server-renderer\\"
const _withId = /*#__PURE__*/_withScopeId(\\"data-v-xxxxxxx\\")
return function ssrRender(_ctx, _push, _parent, _attrs) {
export const ssrRender = /*#__PURE__*/_withId(function ssrRender(_ctx, _push, _parent, _attrs) {
const _component_foo = _resolveComponent(\\"foo\\")
const _component_bar = _resolveComponent(\\"bar\\")
_push(_ssrRenderComponent(_component_foo, _attrs, {
default: _withCtx((_, _push, _parent, _scopeId) => {
default: _withId((_, _push, _parent, _scopeId) => {
if (_push) {
_push(\`<span data-v-xxxxxxx\${_scopeId}>hello</span>\`)
_push(_ssrRenderComponent(_component_bar, null, {
default: _withCtx((_, _push, _parent, _scopeId) => {
default: _withId((_, _push, _parent, _scopeId) => {
if (_push) {
_push(\`<span data-v-xxxxxxx\${_scopeId}></span>\`)
} else {
Expand All @@ -107,7 +116,7 @@ describe('ssr: scopeId', () => {
return [
_createVNode(\\"span\\", null, \\"hello\\"),
_createVNode(_component_bar, null, {
default: _withCtx(() => [
default: _withId(() => [
_createVNode(\\"span\\")
]),
_: 1
Expand All @@ -117,7 +126,7 @@ describe('ssr: scopeId', () => {
}),
_: 1
}, _parent))
}"
})"
`)
})
})
12 changes: 7 additions & 5 deletions packages/runtime-core/src/componentRenderUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,12 +150,14 @@ export function renderComponentRoot(

// inherit scopeId
const scopeId = vnode.scopeId
if (scopeId) {
root = cloneVNode(root, { [scopeId]: '' })
}
const treeOwnerId = parent && parent.type.__scopeId
if (treeOwnerId && treeOwnerId !== scopeId) {
root = cloneVNode(root, { [treeOwnerId + '-s']: '' })
const slotScopeId =
treeOwnerId && treeOwnerId !== scopeId ? treeOwnerId + '-s' : null
if (scopeId || slotScopeId) {
const extras: Data = {}
if (scopeId) extras[scopeId] = ''
if (slotScopeId) extras[slotScopeId] = ''
root = cloneVNode(root, extras)
}

// inherit directives
Expand Down
6 changes: 3 additions & 3 deletions packages/server-renderer/__tests__/renderToString.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
} from 'vue'
import { escapeHtml, mockWarn } from '@vue/shared'
import { renderToString } from '../src/renderToString'
import { ssrRenderSlot } from '../src/helpers/ssrRenderSlot'
import { ssrRenderSlot, SSRSlot } from '../src/helpers/ssrRenderSlot'
import { ssrRenderComponent } from '../src/helpers/ssrRenderComponent'

mockWarn()
Expand Down Expand Up @@ -222,9 +222,9 @@ describe('ssr: renderToString', () => {
{ msg: 'hello' },
{
// optimized slot using string push
default: ({ msg }: any, push: any, _p: any) => {
default: (({ msg }, push, _p) => {
push(`<span>${msg}</span>`)
},
}) as SSRSlot,
// important to avoid slots being normalized
_: 1 as any
},
Expand Down
62 changes: 62 additions & 0 deletions packages/server-renderer/__tests__/ssrScopeId.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { createApp, withScopeId } from 'vue'
import { renderToString } from '../src/renderToString'
import { ssrRenderComponent, ssrRenderAttrs, ssrRenderSlot } from '../src'

describe('ssr: scoped id on component root', () => {
test('basic', async () => {
const withParentId = withScopeId('parent')

const Child = {
ssrRender: (ctx: any, push: any, parent: any, attrs: any) => {
push(`<div${ssrRenderAttrs(attrs)}></div>`)
}
}

const Comp = {
ssrRender: withParentId((ctx: any, push: any, parent: any) => {
push(ssrRenderComponent(Child), null, null, parent)
})
}

const result = await renderToString(createApp(Comp))
expect(result).toBe(`<div parent></div>`)
})

test('inside slot', async () => {
const withParentId = withScopeId('parent')

const Child = {
ssrRender: (_: any, push: any, _parent: any, attrs: any) => {
push(`<div${ssrRenderAttrs(attrs)} child></div>`)
}
}

const Wrapper = {
__scopeId: 'wrapper',
ssrRender: (ctx: any, push: any, parent: any) => {
ssrRenderSlot(ctx.$slots, 'default', {}, null, push, parent)
}
}

const Comp = {
ssrRender: withParentId((_: any, push: any, parent: any) => {
push(
ssrRenderComponent(
Wrapper,
null,
{
default: withParentId((_: any, push: any, parent: any) => {
push(ssrRenderComponent(Child, null, null, parent))
}),
_: 1
} as any,
parent
)
)
})
}

const result = await renderToString(createApp(Comp))
expect(result).toBe(`<!--[--><div parent wrapper-s child></div><!--]-->`)
})
})
17 changes: 10 additions & 7 deletions packages/server-renderer/src/helpers/ssrRenderSlot.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ComponentInternalInstance, Slot, Slots } from 'vue'
import { ComponentInternalInstance, Slots } from 'vue'
import { Props, PushFn, renderVNodeChildren } from '../render'

export type SSRSlots = Record<string, SSRSlot>
Expand All @@ -21,13 +21,16 @@ export function ssrRenderSlot(
push(`<!--[-->`)
const slotFn = slots[slotName]
if (slotFn) {
if (slotFn.length > 1) {
// only ssr-optimized slot fns accept more than 1 arguments
const scopeId = parentComponent && parentComponent.type.__scopeId
slotFn(slotProps, push, parentComponent, scopeId ? ` ${scopeId}-s` : ``)
} else {
const scopeId = parentComponent && parentComponent.type.__scopeId
const ret = slotFn(
slotProps,
push,
parentComponent,
scopeId ? ` ${scopeId}-s` : ``
)
if (Array.isArray(ret)) {
// normal slot
renderVNodeChildren(push, (slotFn as Slot)(slotProps), parentComponent)
renderVNodeChildren(push, ret, parentComponent)
}
} else if (fallbackRenderFn) {
fallbackRenderFn()
Expand Down
18 changes: 15 additions & 3 deletions packages/server-renderer/src/render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,11 +109,23 @@ function renderComponentSubTree(

if (comp.ssrRender) {
// optimized
// resolve fallthrough attrs
let attrs =
instance.type.inheritAttrs !== false ? instance.attrs : undefined

// inherited scopeId
const scopeId = instance.vnode.scopeId
const treeOwnerId = instance.parent && instance.parent.type.__scopeId
const slotScopeId =
treeOwnerId && treeOwnerId !== scopeId ? treeOwnerId + '-s' : null
if (scopeId || slotScopeId) {
attrs = { ...attrs }
if (scopeId) attrs[scopeId] = ''
if (slotScopeId) attrs[slotScopeId] = ''
}

// set current rendering instance for asset resolution
setCurrentRenderingInstance(instance)
// fallthrough attrs
const attrs =
instance.type.inheritAttrs !== false ? instance.attrs : undefined
comp.ssrRender(instance.proxy, push, instance, attrs)
setCurrentRenderingInstance(null)
} else if (instance.render) {
Expand Down

0 comments on commit afe13e0

Please sign in to comment.