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 {
|
export const enum SSRErrorCodes {
|
||||||
X_SSR_CUSTOM_DIRECTIVE_NO_TRANSFORM = DOMErrorCodes.__EXTEND_POINT__,
|
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 } = {
|
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_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_LOOSE_CONTAIN = Symbol(`ssrLooseContain`)
|
||||||
export const SSR_RENDER_DYNAMIC_MODEL = Symbol(`ssrRenderDynamicModel`)
|
export const SSR_RENDER_DYNAMIC_MODEL = Symbol(`ssrRenderDynamicModel`)
|
||||||
export const SSR_GET_DYNAMIC_MODEL_PROPS = Symbol(`ssrGetDynamicModelProps`)
|
export const SSR_GET_DYNAMIC_MODEL_PROPS = Symbol(`ssrGetDynamicModelProps`)
|
||||||
|
export const SSR_RENDER_PORTAL = Symbol(`ssrRenderPortal`)
|
||||||
|
|
||||||
export const ssrHelpers = {
|
export const ssrHelpers = {
|
||||||
[SSR_INTERPOLATE]: `ssrInterpolate`,
|
[SSR_INTERPOLATE]: `ssrInterpolate`,
|
||||||
@ -27,7 +28,8 @@ export const ssrHelpers = {
|
|||||||
[SSR_LOOSE_EQUAL]: `ssrLooseEqual`,
|
[SSR_LOOSE_EQUAL]: `ssrLooseEqual`,
|
||||||
[SSR_LOOSE_CONTAIN]: `ssrLooseContain`,
|
[SSR_LOOSE_CONTAIN]: `ssrLooseContain`,
|
||||||
[SSR_RENDER_DYNAMIC_MODEL]: `ssrRenderDynamicModel`,
|
[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
|
// Note: these are helpers imported from @vue/server-renderer
|
||||||
|
@ -31,9 +31,11 @@ import {
|
|||||||
createTransformContext,
|
createTransformContext,
|
||||||
traverseNode,
|
traverseNode,
|
||||||
ExpressionNode,
|
ExpressionNode,
|
||||||
TemplateNode
|
TemplateNode,
|
||||||
|
findProp,
|
||||||
|
JSChildNode
|
||||||
} from '@vue/compiler-dom'
|
} from '@vue/compiler-dom'
|
||||||
import { SSR_RENDER_COMPONENT } from '../runtimeHelpers'
|
import { SSR_RENDER_COMPONENT, SSR_RENDER_PORTAL } from '../runtimeHelpers'
|
||||||
import {
|
import {
|
||||||
SSRTransformContext,
|
SSRTransformContext,
|
||||||
processChildren,
|
processChildren,
|
||||||
@ -134,7 +136,34 @@ export function ssrProcessComponent(
|
|||||||
const component = componentTypeMap.get(node)!
|
const component = componentTypeMap.get(node)!
|
||||||
|
|
||||||
if (component === PORTAL) {
|
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
|
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'
|
} from './helpers/ssrRenderAttrs'
|
||||||
export { ssrInterpolate } from './helpers/ssrInterpolate'
|
export { ssrInterpolate } from './helpers/ssrInterpolate'
|
||||||
export { ssrRenderList } from './helpers/ssrRenderList'
|
export { ssrRenderList } from './helpers/ssrRenderList'
|
||||||
|
export { ssrRenderPortal } from './helpers/ssrRenderPortal'
|
||||||
|
|
||||||
// v-model helpers
|
// v-model helpers
|
||||||
export {
|
export {
|
||||||
|
@ -45,9 +45,12 @@ const {
|
|||||||
// - A resolved buffer (recursive arrays of strings that can be unrolled
|
// - A resolved buffer (recursive arrays of strings that can be unrolled
|
||||||
// synchronously)
|
// synchronously)
|
||||||
// - An async buffer (a Promise that resolves to a resolved buffer)
|
// - An async buffer (a Promise that resolves to a resolved buffer)
|
||||||
type SSRBuffer = SSRBufferItem[]
|
export type SSRBuffer = SSRBufferItem[]
|
||||||
type SSRBufferItem = string | ResolvedSSRBuffer | Promise<ResolvedSSRBuffer>
|
export type SSRBufferItem =
|
||||||
type ResolvedSSRBuffer = (string | ResolvedSSRBuffer)[]
|
| string
|
||||||
|
| ResolvedSSRBuffer
|
||||||
|
| Promise<ResolvedSSRBuffer>
|
||||||
|
export type ResolvedSSRBuffer = (string | ResolvedSSRBuffer)[]
|
||||||
|
|
||||||
export type PushFn = (item: SSRBufferItem) => void
|
export type PushFn = (item: SSRBufferItem) => void
|
||||||
|
|
||||||
@ -62,7 +65,7 @@ export type SSRContext = {
|
|||||||
>
|
>
|
||||||
}
|
}
|
||||||
|
|
||||||
function createBuffer() {
|
export function createBuffer() {
|
||||||
let appendable = false
|
let appendable = false
|
||||||
let hasAsync = false
|
let hasAsync = false
|
||||||
const buffer: SSRBuffer = []
|
const buffer: SSRBuffer = []
|
||||||
|
Loading…
x
Reference in New Issue
Block a user