feat(compiler-ssr): compile portal (#775)
This commit is contained in:
parent
312513d255
commit
d8ed0e7fbf
@ -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)
|
||||
}"
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -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.`
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
29
packages/server-renderer/__tests__/ssrRenderPortal.spec.ts
Normal file
29
packages/server-renderer/__tests__/ssrRenderPortal.spec.ts
Normal file
@ -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
packages/server-renderer/src/helpers/ssrRenderPortal.ts
Normal file
20
packages/server-renderer/src/helpers/ssrRenderPortal.ts
Normal file
@ -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()
|
||||
}
|
@ -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 {
|
||||
|
@ -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
|
||||
|
||||
@ -62,7 +65,7 @@ export type SSRContext = {
|
||||
>
|
||||
}
|
||||
|
||||
function createBuffer() {
|
||||
export function createBuffer() {
|
||||
let appendable = false
|
||||
let hasAsync = false
|
||||
const buffer: SSRBuffer = []
|
||||
|
Loading…
Reference in New Issue
Block a user