Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(compiler-ssr): compile portal #775

Merged
merged 2 commits into from
Feb 26, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions packages/compiler-ssr/__tests__/ssrComponent.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -309,5 +309,18 @@ describe('ssr: components', () => {
}"
`)
})

test('portal rendering', () => {
expect(compile(`<portal :target="target"><div/></portal>`).code)
.toMatchInlineSnapshot(`
"const { ssrRenderPortal: _ssrRenderPortal } = require(\\"@vue/server-renderer\\")

return function ssrRender(_ctx, _push, _parent) {
_ssrRenderPortal((_push) => {
_push(\`<div></div>\`)
}, _ctx.target, _parent)
}"
`)
})
})
})
6 changes: 4 additions & 2 deletions packages/compiler-ssr/src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@ export function createSSRCompilerError(

export const enum SSRErrorCodes {
X_SSR_CUSTOM_DIRECTIVE_NO_TRANSFORM = DOMErrorCodes.__EXTEND_POINT__,
X_SSR_UNSAFE_ATTR_NAME
X_SSR_UNSAFE_ATTR_NAME,
X_SSR_NO_PORTAL_TARGET
}

export const SSRErrorMessages: { [code: number]: string } = {
[SSRErrorCodes.X_SSR_CUSTOM_DIRECTIVE_NO_TRANSFORM]: `Custom directive is missing corresponding SSR transform and will be ignored.`,
[SSRErrorCodes.X_SSR_UNSAFE_ATTR_NAME]: `Unsafe attribute name for SSR.`
[SSRErrorCodes.X_SSR_UNSAFE_ATTR_NAME]: `Unsafe attribute name for SSR.`,
[SSRErrorCodes.X_SSR_NO_PORTAL_TARGET]: `No target prop on portal element.`
}
4 changes: 3 additions & 1 deletion packages/compiler-ssr/src/runtimeHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export const SSR_LOOSE_EQUAL = Symbol(`ssrLooseEqual`)
export const SSR_LOOSE_CONTAIN = Symbol(`ssrLooseContain`)
export const SSR_RENDER_DYNAMIC_MODEL = Symbol(`ssrRenderDynamicModel`)
export const SSR_GET_DYNAMIC_MODEL_PROPS = Symbol(`ssrGetDynamicModelProps`)
export const SSR_RENDER_PORTAL = Symbol(`ssrRenderPortal`)

export const ssrHelpers = {
[SSR_INTERPOLATE]: `ssrInterpolate`,
Expand All @@ -27,7 +28,8 @@ export const ssrHelpers = {
[SSR_LOOSE_EQUAL]: `ssrLooseEqual`,
[SSR_LOOSE_CONTAIN]: `ssrLooseContain`,
[SSR_RENDER_DYNAMIC_MODEL]: `ssrRenderDynamicModel`,
[SSR_GET_DYNAMIC_MODEL_PROPS]: `ssrGetDynamicModelProps`
[SSR_GET_DYNAMIC_MODEL_PROPS]: `ssrGetDynamicModelProps`,
[SSR_RENDER_PORTAL]: `ssrRenderPortal`
}

// Note: these are helpers imported from @vue/server-renderer
Expand Down
35 changes: 32 additions & 3 deletions packages/compiler-ssr/src/transforms/ssrTransformComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,11 @@ import {
createTransformContext,
traverseNode,
ExpressionNode,
TemplateNode
TemplateNode,
findProp,
JSChildNode
} from '@vue/compiler-dom'
import { SSR_RENDER_COMPONENT } from '../runtimeHelpers'
import { SSR_RENDER_COMPONENT, SSR_RENDER_PORTAL } from '../runtimeHelpers'
import {
SSRTransformContext,
processChildren,
Expand Down Expand Up @@ -134,7 +136,34 @@ export function ssrProcessComponent(
const component = componentTypeMap.get(node)!

if (component === PORTAL) {
// TODO
const targetProp = findProp(node, 'target')
if (!targetProp) return

let target: JSChildNode
if (targetProp.type === NodeTypes.ATTRIBUTE && targetProp.value) {
target = createSimpleExpression(targetProp.value.content, true)
} else if (targetProp.type === NodeTypes.DIRECTIVE && targetProp.exp) {
target = targetProp.exp
} else {
return
}

const contentRenderFn = createFunctionExpression(
[`_push`],
undefined, // Body is added later
true, // newline
false, // isSlot
node.loc
)
contentRenderFn.body = processChildrenAsStatement(node.children, context)
context.pushStatement(
createCallExpression(context.helper(SSR_RENDER_PORTAL), [
contentRenderFn,
target,
`_parent`
])
)

return
}

Expand Down
2 changes: 1 addition & 1 deletion packages/runtime-core/src/componentSlots.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { isKeepAlive } from './components/KeepAlive'
export type Slot = (...args: any[]) => VNode[]

export type InternalSlots = {
[name: string]: Slot
[name: string]: Slot | undefined
}

export type Slots = Readonly<InternalSlots>
Expand Down
6 changes: 3 additions & 3 deletions packages/runtime-core/src/helpers/renderSlot.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Data } from '../component'
import { Slot } from '../componentSlots'
import { Slots } from '../componentSlots'
import {
VNodeArrayChildren,
openBlock,
Expand All @@ -11,7 +11,7 @@ import { PatchFlags } from '@vue/shared'
import { warn } from '../warning'

export function renderSlot(
slots: Record<string, Slot>,
slots: Slots,
name: string,
props: Data = {},
// this is not a user-facing function, so the fallback is always generated by
Expand All @@ -20,7 +20,7 @@ export function renderSlot(
): VNode {
let slot = slots[name]

if (__DEV__ && slot.length > 1) {
if (__DEV__ && slot && slot.length > 1) {
warn(
`SSR-optimized slot function detected in a non-SSR-optimized render ` +
`function. You need to mark this component with $dynamic-slots in the ` +
Expand Down
6 changes: 3 additions & 3 deletions packages/runtime-dom/__tests__/modules/class.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,14 +103,14 @@ describe('class', () => {
const component1 = defineComponent({
props: {},
render() {
return this.$slots.default()[0]
return this.$slots.default!()[0]
}
})

const component2 = defineComponent({
props: {},
render() {
return this.$slots.default()[0]
return this.$slots.default!()[0]
}
})

Expand All @@ -122,7 +122,7 @@ describe('class', () => {
{
class: 'staticClass'
},
[this.$slots.default()]
[this.$slots.default!()]
)
}
})
Expand Down
29 changes: 29 additions & 0 deletions packages/server-renderer/__tests__/ssrRenderPortal.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { createApp } from 'vue'
import { renderToString, SSRContext } from '../src/renderToString'
import { ssrRenderPortal } from '../src/helpers/ssrRenderPortal'

describe('ssrRenderPortal', () => {
test('portal rendering', async () => {
const ctx = {
portals: {}
} as SSRContext
await renderToString(
createApp({
data() {
return { msg: 'hello' }
},
ssrRender(_ctx, _push, _parent) {
ssrRenderPortal(
_push => {
_push(`<div>content</div>`)
},
'#target',
_parent
)
}
}),
ctx
)
expect(ctx.portals!['#target']).toBe(`<div>content</div>`)
})
})
20 changes: 20 additions & 0 deletions packages/server-renderer/src/helpers/ssrRenderPortal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { ComponentInternalInstance, ssrContextKey } from 'vue'
import { SSRContext, createBuffer, PushFn } from '../renderToString'

export function ssrRenderPortal(
contentRenderFn: (push: PushFn) => void,
target: string,
parentComponent: ComponentInternalInstance
) {
const { getBuffer, push } = createBuffer()

contentRenderFn(push)

const context = parentComponent.appContext.provides[
ssrContextKey as any
] as SSRContext
const portalBuffers =
context.__portalBuffers || (context.__portalBuffers = {})

portalBuffers[target] = getBuffer()
}
1 change: 1 addition & 0 deletions packages/server-renderer/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export {
} from './helpers/ssrRenderAttrs'
export { ssrInterpolate } from './helpers/ssrInterpolate'
export { ssrRenderList } from './helpers/ssrRenderList'
export { ssrRenderPortal } from './helpers/ssrRenderPortal'

// v-model helpers
export {
Expand Down
11 changes: 7 additions & 4 deletions packages/server-renderer/src/renderToString.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,12 @@ const {
// - A resolved buffer (recursive arrays of strings that can be unrolled
// synchronously)
// - An async buffer (a Promise that resolves to a resolved buffer)
type SSRBuffer = SSRBufferItem[]
type SSRBufferItem = string | ResolvedSSRBuffer | Promise<ResolvedSSRBuffer>
type ResolvedSSRBuffer = (string | ResolvedSSRBuffer)[]
export type SSRBuffer = SSRBufferItem[]
export type SSRBufferItem =
| string
| ResolvedSSRBuffer
| Promise<ResolvedSSRBuffer>
export type ResolvedSSRBuffer = (string | ResolvedSSRBuffer)[]

export type PushFn = (item: SSRBufferItem) => void

Expand All @@ -62,7 +65,7 @@ export type SSRContext = {
>
}

function createBuffer() {
export function createBuffer() {
let appendable = false
let hasAsync = false
const buffer: SSRBuffer = []
Expand Down