From 3375687df5e7751d3df69190c99f4ce13b8f1993 Mon Sep 17 00:00:00 2001 From: Evan You Date: Mon, 24 Feb 2020 23:05:35 -0500 Subject: [PATCH 1/2] fix(runtime-core): fix slot fallback + slots typing fix #772 --- packages/runtime-core/src/componentSlots.ts | 2 +- packages/runtime-core/src/helpers/renderSlot.ts | 6 +++--- packages/runtime-dom/__tests__/modules/class.spec.ts | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/runtime-core/src/componentSlots.ts b/packages/runtime-core/src/componentSlots.ts index 79c8f9d4317..2958903e152 100644 --- a/packages/runtime-core/src/componentSlots.ts +++ b/packages/runtime-core/src/componentSlots.ts @@ -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 diff --git a/packages/runtime-core/src/helpers/renderSlot.ts b/packages/runtime-core/src/helpers/renderSlot.ts index eb16bc02200..186cdfed1eb 100644 --- a/packages/runtime-core/src/helpers/renderSlot.ts +++ b/packages/runtime-core/src/helpers/renderSlot.ts @@ -1,5 +1,5 @@ import { Data } from '../component' -import { Slot } from '../componentSlots' +import { Slots } from '../componentSlots' import { VNodeArrayChildren, openBlock, @@ -11,7 +11,7 @@ import { PatchFlags } from '@vue/shared' import { warn } from '../warning' export function renderSlot( - slots: Record, + slots: Slots, name: string, props: Data = {}, // this is not a user-facing function, so the fallback is always generated by @@ -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 ` + diff --git a/packages/runtime-dom/__tests__/modules/class.spec.ts b/packages/runtime-dom/__tests__/modules/class.spec.ts index c12780859c0..2874cbea561 100644 --- a/packages/runtime-dom/__tests__/modules/class.spec.ts +++ b/packages/runtime-dom/__tests__/modules/class.spec.ts @@ -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] } }) @@ -122,7 +122,7 @@ describe('class', () => { { class: 'staticClass' }, - [this.$slots.default()] + [this.$slots.default!()] ) } }) From bac29573df7ad8e580eab44ef2206ecaafb6dc94 Mon Sep 17 00:00:00 2001 From: Dmitry Sharshakov Date: Tue, 25 Feb 2020 15:25:12 +0300 Subject: [PATCH 2/2] feat(compiler-ssr): compile portal --- .../__tests__/ssrComponent.spec.ts | 13 +++++++ packages/compiler-ssr/src/errors.ts | 6 ++-- packages/compiler-ssr/src/runtimeHelpers.ts | 4 ++- .../src/transforms/ssrTransformComponent.ts | 35 +++++++++++++++++-- .../__tests__/ssrRenderPortal.spec.ts | 29 +++++++++++++++ .../src/helpers/ssrRenderPortal.ts | 20 +++++++++++ packages/server-renderer/src/index.ts | 1 + .../server-renderer/src/renderToString.ts | 11 +++--- 8 files changed, 109 insertions(+), 10 deletions(-) create mode 100644 packages/server-renderer/__tests__/ssrRenderPortal.spec.ts create mode 100644 packages/server-renderer/src/helpers/ssrRenderPortal.ts diff --git a/packages/compiler-ssr/__tests__/ssrComponent.spec.ts b/packages/compiler-ssr/__tests__/ssrComponent.spec.ts index 0cca3cefee2..8efe67a5c9a 100644 --- a/packages/compiler-ssr/__tests__/ssrComponent.spec.ts +++ b/packages/compiler-ssr/__tests__/ssrComponent.spec.ts @@ -309,5 +309,18 @@ describe('ssr: components', () => { }" `) }) + + test('portal rendering', () => { + expect(compile(`
`).code) + .toMatchInlineSnapshot(` + "const { ssrRenderPortal: _ssrRenderPortal } = require(\\"@vue/server-renderer\\") + + return function ssrRender(_ctx, _push, _parent) { + _ssrRenderPortal((_push) => { + _push(\`
\`) + }, _ctx.target, _parent) + }" + `) + }) }) }) diff --git a/packages/compiler-ssr/src/errors.ts b/packages/compiler-ssr/src/errors.ts index 30431fb0818..fdc99160b01 100644 --- a/packages/compiler-ssr/src/errors.ts +++ b/packages/compiler-ssr/src/errors.ts @@ -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.` } diff --git a/packages/compiler-ssr/src/runtimeHelpers.ts b/packages/compiler-ssr/src/runtimeHelpers.ts index 2f394514de6..01875cf05ca 100644 --- a/packages/compiler-ssr/src/runtimeHelpers.ts +++ b/packages/compiler-ssr/src/runtimeHelpers.ts @@ -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`, @@ -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 diff --git a/packages/compiler-ssr/src/transforms/ssrTransformComponent.ts b/packages/compiler-ssr/src/transforms/ssrTransformComponent.ts index a9c38c70cfa..87c6988bfb8 100644 --- a/packages/compiler-ssr/src/transforms/ssrTransformComponent.ts +++ b/packages/compiler-ssr/src/transforms/ssrTransformComponent.ts @@ -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, @@ -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 } diff --git a/packages/server-renderer/__tests__/ssrRenderPortal.spec.ts b/packages/server-renderer/__tests__/ssrRenderPortal.spec.ts new file mode 100644 index 00000000000..70031dd4ee0 --- /dev/null +++ b/packages/server-renderer/__tests__/ssrRenderPortal.spec.ts @@ -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(`
content
`) + }, + '#target', + _parent + ) + } + }), + ctx + ) + expect(ctx.portals!['#target']).toBe(`
content
`) + }) +}) diff --git a/packages/server-renderer/src/helpers/ssrRenderPortal.ts b/packages/server-renderer/src/helpers/ssrRenderPortal.ts new file mode 100644 index 00000000000..12c2282334a --- /dev/null +++ b/packages/server-renderer/src/helpers/ssrRenderPortal.ts @@ -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() +} diff --git a/packages/server-renderer/src/index.ts b/packages/server-renderer/src/index.ts index 59b2b45f8fb..315b3ae18c7 100644 --- a/packages/server-renderer/src/index.ts +++ b/packages/server-renderer/src/index.ts @@ -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 { diff --git a/packages/server-renderer/src/renderToString.ts b/packages/server-renderer/src/renderToString.ts index 0aac04cd14a..14c041c6561 100644 --- a/packages/server-renderer/src/renderToString.ts +++ b/packages/server-renderer/src/renderToString.ts @@ -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 -type ResolvedSSRBuffer = (string | ResolvedSSRBuffer)[] +export type SSRBuffer = SSRBufferItem[] +export type SSRBufferItem = + | string + | ResolvedSSRBuffer + | Promise +export type ResolvedSSRBuffer = (string | ResolvedSSRBuffer)[] export type PushFn = (item: SSRBufferItem) => void @@ -62,7 +65,7 @@ export type SSRContext = { > } -function createBuffer() { +export function createBuffer() { let appendable = false let hasAsync = false const buffer: SSRBuffer = []