Skip to content

Commit

Permalink
feat(compiler-ssr): compile portal (#775)
Browse files Browse the repository at this point in the history
  • Loading branch information
dsseng authored Feb 26, 2020
1 parent 312513d commit d8ed0e7
Show file tree
Hide file tree
Showing 8 changed files with 109 additions and 10 deletions.
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
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

0 comments on commit d8ed0e7

Please sign in to comment.