wip(ssr): basic components
This commit is contained in:
parent
27e2e482e9
commit
ee5ed73361
@ -79,7 +79,7 @@ return function render() {
|
|||||||
const _component_Foo = _resolveComponent(\\"Foo\\")
|
const _component_Foo = _resolveComponent(\\"Foo\\")
|
||||||
const _component_bar_baz = _resolveComponent(\\"bar-baz\\")
|
const _component_bar_baz = _resolveComponent(\\"bar-baz\\")
|
||||||
const _component_barbaz = _resolveComponent(\\"barbaz\\")
|
const _component_barbaz = _resolveComponent(\\"barbaz\\")
|
||||||
const _directive_my_dir = _resolveDirective(\\"my_dir\\")
|
const _directive_my_dir = _resolveDirective(\\"my_dir\\")
|
||||||
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
@ -353,17 +353,21 @@ function genModulePreamble(
|
|||||||
function genAssets(
|
function genAssets(
|
||||||
assets: string[],
|
assets: string[],
|
||||||
type: 'component' | 'directive',
|
type: 'component' | 'directive',
|
||||||
context: CodegenContext
|
{ helper, push, newline }: CodegenContext
|
||||||
) {
|
) {
|
||||||
const resolver = context.helper(
|
const resolver = helper(
|
||||||
type === 'component' ? RESOLVE_COMPONENT : RESOLVE_DIRECTIVE
|
type === 'component' ? RESOLVE_COMPONENT : RESOLVE_DIRECTIVE
|
||||||
)
|
)
|
||||||
for (let i = 0; i < assets.length; i++) {
|
for (let i = 0; i < assets.length; i++) {
|
||||||
const id = assets[i]
|
const id = assets[i]
|
||||||
context.push(
|
push(
|
||||||
`const ${toValidAssetId(id, type)} = ${resolver}(${JSON.stringify(id)})`
|
`const ${toValidAssetId(id, type)} = ${resolver}(${JSON.stringify(id)})`
|
||||||
)
|
)
|
||||||
context.newline()
|
if (i < assets.length - 1) {
|
||||||
|
newline()
|
||||||
|
} else {
|
||||||
|
push(`\n`)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ export { transformOn } from './transforms/vOn'
|
|||||||
export { transformBind } from './transforms/vBind'
|
export { transformBind } from './transforms/vBind'
|
||||||
|
|
||||||
// exported for compiler-ssr
|
// exported for compiler-ssr
|
||||||
export { MERGE_PROPS } from './runtimeHelpers'
|
export * from './runtimeHelpers'
|
||||||
export { processIf } from './transforms/vIf'
|
export { processIf } from './transforms/vIf'
|
||||||
export { processFor, createForLoopParams } from './transforms/vFor'
|
export { processFor, createForLoopParams } from './transforms/vFor'
|
||||||
export {
|
export {
|
||||||
@ -42,7 +42,7 @@ export {
|
|||||||
processExpression
|
processExpression
|
||||||
} from './transforms/transformExpression'
|
} from './transforms/transformExpression'
|
||||||
export { trackVForSlotScopes, trackSlotScopes } from './transforms/vSlot'
|
export { trackVForSlotScopes, trackSlotScopes } from './transforms/vSlot'
|
||||||
export { buildProps } from './transforms/transformElement'
|
export { resolveComponentType, buildProps } from './transforms/transformElement'
|
||||||
export { processSlotOutlet } from './transforms/transformSlotOutlet'
|
export { processSlotOutlet } from './transforms/transformSlotOutlet'
|
||||||
|
|
||||||
// utility, but need to rewrite typing to avoid dts relying on @vue/shared
|
// utility, but need to rewrite typing to avoid dts relying on @vue/shared
|
||||||
|
@ -465,7 +465,8 @@ function parseTag(
|
|||||||
} else if (
|
} else if (
|
||||||
isCoreComponent(tag) ||
|
isCoreComponent(tag) ||
|
||||||
(options.isBuiltInComponent && options.isBuiltInComponent(tag)) ||
|
(options.isBuiltInComponent && options.isBuiltInComponent(tag)) ||
|
||||||
/^[A-Z]/.test(tag)
|
/^[A-Z]/.test(tag) ||
|
||||||
|
tag === 'component'
|
||||||
) {
|
) {
|
||||||
tagType = ElementTypes.COMPONENT
|
tagType = ElementTypes.COMPONENT
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,8 @@ import {
|
|||||||
createSimpleExpression,
|
createSimpleExpression,
|
||||||
createObjectExpression,
|
createObjectExpression,
|
||||||
Property,
|
Property,
|
||||||
createSequenceExpression
|
createSequenceExpression,
|
||||||
|
ComponentNode
|
||||||
} from '../ast'
|
} from '../ast'
|
||||||
import { PatchFlags, PatchFlagNames, isSymbol } from '@vue/shared'
|
import { PatchFlags, PatchFlagNames, isSymbol } from '@vue/shared'
|
||||||
import { createCompilerError, ErrorCodes } from '../errors'
|
import { createCompilerError, ErrorCodes } from '../errors'
|
||||||
@ -35,7 +36,8 @@ import {
|
|||||||
getInnerRange,
|
getInnerRange,
|
||||||
toValidAssetId,
|
toValidAssetId,
|
||||||
findProp,
|
findProp,
|
||||||
isCoreComponent
|
isCoreComponent,
|
||||||
|
isBindKey
|
||||||
} from '../utils'
|
} from '../utils'
|
||||||
import { buildSlots } from './vSlot'
|
import { buildSlots } from './vSlot'
|
||||||
import { isStaticNode } from './hoistStatic'
|
import { isStaticNode } from './hoistStatic'
|
||||||
@ -58,69 +60,30 @@ export const transformElement: NodeTransform = (node, context) => {
|
|||||||
// perform the work on exit, after all child expressions have been
|
// perform the work on exit, after all child expressions have been
|
||||||
// processed and merged.
|
// processed and merged.
|
||||||
return function postTransformElement() {
|
return function postTransformElement() {
|
||||||
const { tag, tagType, props } = node
|
const { tag, props } = node
|
||||||
const builtInComponentSymbol =
|
const isComponent = node.tagType === ElementTypes.COMPONENT
|
||||||
isCoreComponent(tag) || context.isBuiltInComponent(tag)
|
|
||||||
const isComponent = tagType === ElementTypes.COMPONENT
|
// <svg> and <foreignObject> must be forced into blocks so that block
|
||||||
|
// updates inside get proper isSVG flag at runtime. (#639, #643)
|
||||||
|
// This is technically web-specific, but splitting the logic out of core
|
||||||
|
// leads to too much unnecessary complexity.
|
||||||
|
const shouldUseBlock =
|
||||||
|
!isComponent && (tag === 'svg' || tag === 'foreignObject')
|
||||||
|
|
||||||
|
const nodeType = isComponent
|
||||||
|
? resolveComponentType(node as ComponentNode, context)
|
||||||
|
: `"${tag}"`
|
||||||
|
|
||||||
|
const args: CallExpression['arguments'] = [nodeType]
|
||||||
|
|
||||||
let hasProps = props.length > 0
|
let hasProps = props.length > 0
|
||||||
let patchFlag: number = 0
|
let patchFlag: number = 0
|
||||||
let runtimeDirectives: DirectiveNode[] | undefined
|
let runtimeDirectives: DirectiveNode[] | undefined
|
||||||
let dynamicPropNames: string[] | undefined
|
let dynamicPropNames: string[] | undefined
|
||||||
let dynamicComponent: string | CallExpression | undefined
|
|
||||||
let shouldUseBlock = false
|
|
||||||
|
|
||||||
// handle dynamic component
|
|
||||||
const isProp = tag === 'component' && findProp(node, 'is')
|
|
||||||
if (isProp) {
|
|
||||||
// static <component is="foo" />
|
|
||||||
if (isProp.type === NodeTypes.ATTRIBUTE) {
|
|
||||||
const tag = isProp.value && isProp.value.content
|
|
||||||
if (tag) {
|
|
||||||
context.helper(RESOLVE_COMPONENT)
|
|
||||||
context.components.add(tag)
|
|
||||||
dynamicComponent = toValidAssetId(tag, `component`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// dynamic <component :is="asdf" />
|
|
||||||
else if (isProp.exp) {
|
|
||||||
dynamicComponent = createCallExpression(
|
|
||||||
context.helper(RESOLVE_DYNAMIC_COMPONENT),
|
|
||||||
// _ctx.$ exposes the owner instance of current render function
|
|
||||||
[isProp.exp, context.prefixIdentifiers ? `_ctx.$` : `$`]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let nodeType
|
|
||||||
if (dynamicComponent) {
|
|
||||||
nodeType = dynamicComponent
|
|
||||||
} else if (builtInComponentSymbol) {
|
|
||||||
nodeType = context.helper(builtInComponentSymbol)
|
|
||||||
} else if (isComponent) {
|
|
||||||
// user component w/ resolve
|
|
||||||
context.helper(RESOLVE_COMPONENT)
|
|
||||||
context.components.add(tag)
|
|
||||||
nodeType = toValidAssetId(tag, `component`)
|
|
||||||
} else {
|
|
||||||
// plain element
|
|
||||||
nodeType = `"${tag}"`
|
|
||||||
// <svg> and <foreignObject> must be forced into blocks so that block
|
|
||||||
// updates inside get proper isSVG flag at runtime. (#639, #643)
|
|
||||||
// This is technically web-specific, but splitting the logic out of core
|
|
||||||
// leads to too much unnecessary complexity.
|
|
||||||
shouldUseBlock = tag === 'svg' || tag === 'foreignObject'
|
|
||||||
}
|
|
||||||
|
|
||||||
const args: CallExpression['arguments'] = [nodeType]
|
|
||||||
// props
|
// props
|
||||||
if (hasProps) {
|
if (hasProps) {
|
||||||
const propsBuildResult = buildProps(
|
const propsBuildResult = buildProps(node, context)
|
||||||
node,
|
|
||||||
context,
|
|
||||||
// skip reserved "is" prop <component is>
|
|
||||||
isProp ? node.props.filter(p => p !== isProp) : node.props
|
|
||||||
)
|
|
||||||
patchFlag = propsBuildResult.patchFlag
|
patchFlag = propsBuildResult.patchFlag
|
||||||
dynamicPropNames = propsBuildResult.dynamicPropNames
|
dynamicPropNames = propsBuildResult.dynamicPropNames
|
||||||
runtimeDirectives = propsBuildResult.directives
|
runtimeDirectives = propsBuildResult.directives
|
||||||
@ -130,6 +93,7 @@ export const transformElement: NodeTransform = (node, context) => {
|
|||||||
args.push(propsBuildResult.props)
|
args.push(propsBuildResult.props)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// children
|
// children
|
||||||
const hasChildren = node.children.length > 0
|
const hasChildren = node.children.length > 0
|
||||||
if (hasChildren) {
|
if (hasChildren) {
|
||||||
@ -140,11 +104,7 @@ export const transformElement: NodeTransform = (node, context) => {
|
|||||||
// Portal is not a real component has dedicated handling in the renderer
|
// Portal is not a real component has dedicated handling in the renderer
|
||||||
// KeepAlive should not track its own deps so that it can be used inside
|
// KeepAlive should not track its own deps so that it can be used inside
|
||||||
// Transition
|
// Transition
|
||||||
if (
|
if (isComponent && nodeType !== PORTAL && nodeType !== KEEP_ALIVE) {
|
||||||
isComponent &&
|
|
||||||
builtInComponentSymbol !== PORTAL &&
|
|
||||||
builtInComponentSymbol !== KEEP_ALIVE
|
|
||||||
) {
|
|
||||||
const { slots, hasDynamicSlots } = buildSlots(node, context)
|
const { slots, hasDynamicSlots } = buildSlots(node, context)
|
||||||
args.push(slots)
|
args.push(slots)
|
||||||
if (hasDynamicSlots) {
|
if (hasDynamicSlots) {
|
||||||
@ -171,6 +131,7 @@ export const transformElement: NodeTransform = (node, context) => {
|
|||||||
args.push(node.children)
|
args.push(node.children)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// patchFlag & dynamicPropNames
|
// patchFlag & dynamicPropNames
|
||||||
if (patchFlag !== 0) {
|
if (patchFlag !== 0) {
|
||||||
if (!hasChildren) {
|
if (!hasChildren) {
|
||||||
@ -219,13 +180,45 @@ export const transformElement: NodeTransform = (node, context) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function stringifyDynamicPropNames(props: string[]): string {
|
export function resolveComponentType(
|
||||||
let propsNamesString = `[`
|
node: ComponentNode,
|
||||||
for (let i = 0, l = props.length; i < l; i++) {
|
context: TransformContext
|
||||||
propsNamesString += JSON.stringify(props[i])
|
) {
|
||||||
if (i < l - 1) propsNamesString += ', '
|
const { tag } = node
|
||||||
|
|
||||||
|
// 1. dynamic component
|
||||||
|
const isProp = node.tag === 'component' && findProp(node, 'is')
|
||||||
|
if (isProp) {
|
||||||
|
// static <component is="foo" />
|
||||||
|
if (isProp.type === NodeTypes.ATTRIBUTE) {
|
||||||
|
const isType = isProp.value && isProp.value.content
|
||||||
|
if (isType) {
|
||||||
|
context.helper(RESOLVE_COMPONENT)
|
||||||
|
context.components.add(isType)
|
||||||
|
return toValidAssetId(isType, `component`)
|
||||||
}
|
}
|
||||||
return propsNamesString + `]`
|
}
|
||||||
|
// dynamic <component :is="asdf" />
|
||||||
|
else if (isProp.exp) {
|
||||||
|
return createCallExpression(
|
||||||
|
context.helper(RESOLVE_DYNAMIC_COMPONENT),
|
||||||
|
// _ctx.$ exposes the owner instance of current render function
|
||||||
|
[isProp.exp, context.prefixIdentifiers ? `_ctx.$` : `$`]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. built-in components (Portal, Transition, KeepAlive, Suspense...)
|
||||||
|
const builtIn = isCoreComponent(tag) || context.isBuiltInComponent(tag)
|
||||||
|
if (builtIn) {
|
||||||
|
context.helper(builtIn)
|
||||||
|
return builtIn
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. user component (resolve)
|
||||||
|
context.helper(RESOLVE_COMPONENT)
|
||||||
|
context.components.add(tag)
|
||||||
|
return toValidAssetId(tag, `component`)
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PropsExpression = ObjectExpression | CallExpression | ExpressionNode
|
export type PropsExpression = ObjectExpression | CallExpression | ExpressionNode
|
||||||
@ -241,7 +234,7 @@ export function buildProps(
|
|||||||
patchFlag: number
|
patchFlag: number
|
||||||
dynamicPropNames: string[]
|
dynamicPropNames: string[]
|
||||||
} {
|
} {
|
||||||
const elementLoc = node.loc
|
const { tag, loc: elementLoc } = node
|
||||||
const isComponent = node.tagType === ElementTypes.COMPONENT
|
const isComponent = node.tagType === ElementTypes.COMPONENT
|
||||||
let properties: ObjectExpression['properties'] = []
|
let properties: ObjectExpression['properties'] = []
|
||||||
const mergeArgs: PropsExpression[] = []
|
const mergeArgs: PropsExpression[] = []
|
||||||
@ -288,6 +281,10 @@ export function buildProps(
|
|||||||
if (name === 'ref') {
|
if (name === 'ref') {
|
||||||
hasRef = true
|
hasRef = true
|
||||||
}
|
}
|
||||||
|
// skip :is on <component>
|
||||||
|
if (name === 'is' && tag === 'component') {
|
||||||
|
continue
|
||||||
|
}
|
||||||
properties.push(
|
properties.push(
|
||||||
createObjectProperty(
|
createObjectProperty(
|
||||||
createSimpleExpression(
|
createSimpleExpression(
|
||||||
@ -305,6 +302,8 @@ export function buildProps(
|
|||||||
} else {
|
} else {
|
||||||
// directives
|
// directives
|
||||||
const { name, arg, exp, loc } = prop
|
const { name, arg, exp, loc } = prop
|
||||||
|
const isBind = name === 'bind'
|
||||||
|
const isOn = name === 'on'
|
||||||
|
|
||||||
// skip v-slot - it is handled by its dedicated transform.
|
// skip v-slot - it is handled by its dedicated transform.
|
||||||
if (name === 'slot') {
|
if (name === 'slot') {
|
||||||
@ -315,17 +314,16 @@ export function buildProps(
|
|||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// skip v-once - it is handled by its dedicated transform.
|
// skip v-once - it is handled by its dedicated transform.
|
||||||
if (name === 'once') {
|
if (name === 'once') {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
// skip :is on <component>
|
||||||
const isBind = name === 'bind'
|
if (isBind && tag === 'component' && isBindKey(arg, 'is')) {
|
||||||
const isOn = name === 'on'
|
continue
|
||||||
|
}
|
||||||
// skip v-on in SSR compilation
|
// skip v-on in SSR compilation
|
||||||
if (ssr && isOn) {
|
if (isOn && ssr) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -518,3 +516,12 @@ function buildDirectiveArgs(
|
|||||||
}
|
}
|
||||||
return createArrayExpression(dirArgs, dir.loc)
|
return createArrayExpression(dirArgs, dir.loc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function stringifyDynamicPropNames(props: string[]): string {
|
||||||
|
let propsNamesString = `[`
|
||||||
|
for (let i = 0, l = props.length; i < l; i++) {
|
||||||
|
propsNamesString += JSON.stringify(props[i])
|
||||||
|
if (i < l - 1) propsNamesString += ', '
|
||||||
|
}
|
||||||
|
return propsNamesString + `]`
|
||||||
|
}
|
||||||
|
@ -44,10 +44,7 @@ export const transformModel: DirectiveTransform = (dir, node, context) => {
|
|||||||
const eventName = arg
|
const eventName = arg
|
||||||
? arg.type === NodeTypes.SIMPLE_EXPRESSION && arg.isStatic
|
? arg.type === NodeTypes.SIMPLE_EXPRESSION && arg.isStatic
|
||||||
? `onUpdate:${arg.content}`
|
? `onUpdate:${arg.content}`
|
||||||
: createCompoundExpression([
|
: createCompoundExpression(['"onUpdate:" + ', arg])
|
||||||
'"onUpdate:" + ',
|
|
||||||
...(arg.type === NodeTypes.SIMPLE_EXPRESSION ? [arg] : arg.children)
|
|
||||||
])
|
|
||||||
: `onUpdate:modelValue`
|
: `onUpdate:modelValue`
|
||||||
|
|
||||||
const props = [
|
const props = [
|
||||||
|
@ -192,19 +192,21 @@ export function findProp(
|
|||||||
if (p.name === name && p.value) {
|
if (p.name === name && p.value) {
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
} else if (
|
} else if (p.name === 'bind' && p.exp && isBindKey(p.arg, name)) {
|
||||||
p.name === 'bind' &&
|
|
||||||
p.arg &&
|
|
||||||
p.arg.type === NodeTypes.SIMPLE_EXPRESSION &&
|
|
||||||
p.arg.isStatic &&
|
|
||||||
p.arg.content === name &&
|
|
||||||
p.exp
|
|
||||||
) {
|
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isBindKey(arg: DirectiveNode['arg'], name: string): boolean {
|
||||||
|
return !!(
|
||||||
|
arg &&
|
||||||
|
arg.type === NodeTypes.SIMPLE_EXPRESSION &&
|
||||||
|
arg.isStatic &&
|
||||||
|
arg.content === name
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export function hasDynamicKeyVBind(node: ElementNode): boolean {
|
export function hasDynamicKeyVBind(node: ElementNode): boolean {
|
||||||
return node.props.some(
|
return node.props.some(
|
||||||
p =>
|
p =>
|
||||||
|
48
packages/compiler-ssr/__tests__/ssrComponent.spec.ts
Normal file
48
packages/compiler-ssr/__tests__/ssrComponent.spec.ts
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import { compile } from '../src'
|
||||||
|
|
||||||
|
describe('ssr: components', () => {
|
||||||
|
test('basic', () => {
|
||||||
|
expect(compile(`<foo id="a" :prop="b" />`).code).toMatchInlineSnapshot(`
|
||||||
|
"const { resolveComponent } = require(\\"vue\\")
|
||||||
|
const { _renderComponent } = require(\\"@vue/server-renderer\\")
|
||||||
|
|
||||||
|
return function ssrRender(_ctx, _push, _parent) {
|
||||||
|
const _component_foo = resolveComponent(\\"foo\\")
|
||||||
|
|
||||||
|
_renderComponent(_component_foo, {
|
||||||
|
id: \\"a\\",
|
||||||
|
prop: _ctx.b
|
||||||
|
}, null, _parent)
|
||||||
|
}"
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('dynamic component', () => {
|
||||||
|
expect(compile(`<component is="foo" prop="b" />`).code)
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"const { resolveComponent } = require(\\"vue\\")
|
||||||
|
const { _renderComponent } = require(\\"@vue/server-renderer\\")
|
||||||
|
|
||||||
|
return function ssrRender(_ctx, _push, _parent) {
|
||||||
|
const _component_foo = resolveComponent(\\"foo\\")
|
||||||
|
|
||||||
|
_renderComponent(_component_foo, { prop: \\"b\\" }, null, _parent)
|
||||||
|
}"
|
||||||
|
`)
|
||||||
|
|
||||||
|
expect(compile(`<compoonent :is="foo" prop="b" />`).code)
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"const { resolveComponent } = require(\\"vue\\")
|
||||||
|
const { _renderComponent } = require(\\"@vue/server-renderer\\")
|
||||||
|
|
||||||
|
return function ssrRender(_ctx, _push, _parent) {
|
||||||
|
const _component_compoonent = resolveComponent(\\"compoonent\\")
|
||||||
|
|
||||||
|
_renderComponent(_component_compoonent, {
|
||||||
|
is: _ctx.foo,
|
||||||
|
prop: \\"b\\"
|
||||||
|
}, null, _parent)
|
||||||
|
}"
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
})
|
@ -18,6 +18,7 @@ import { SSR_INTERPOLATE, ssrHelpers } from './runtimeHelpers'
|
|||||||
import { ssrProcessIf } from './transforms/ssrVIf'
|
import { ssrProcessIf } from './transforms/ssrVIf'
|
||||||
import { ssrProcessFor } from './transforms/ssrVFor'
|
import { ssrProcessFor } from './transforms/ssrVFor'
|
||||||
import { ssrProcessSlotOutlet } from './transforms/ssrTransformSlotOutlet'
|
import { ssrProcessSlotOutlet } from './transforms/ssrTransformSlotOutlet'
|
||||||
|
import { ssrProcessComponent } from './transforms/ssrTransformComponent'
|
||||||
|
|
||||||
// Because SSR codegen output is completely different from client-side output
|
// Because SSR codegen output is completely different from client-side output
|
||||||
// (e.g. multiple elements can be concatenated into a single template literal
|
// (e.g. multiple elements can be concatenated into a single template literal
|
||||||
@ -118,7 +119,7 @@ export function processChildren(
|
|||||||
context.pushStringPart(`</${child.tag}>`)
|
context.pushStringPart(`</${child.tag}>`)
|
||||||
}
|
}
|
||||||
} else if (child.tagType === ElementTypes.COMPONENT) {
|
} else if (child.tagType === ElementTypes.COMPONENT) {
|
||||||
// TODO
|
ssrProcessComponent(child, context)
|
||||||
} else if (child.tagType === ElementTypes.SLOT) {
|
} else if (child.tagType === ElementTypes.SLOT) {
|
||||||
ssrProcessSlotOutlet(child, context)
|
ssrProcessSlotOutlet(child, context)
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,64 @@
|
|||||||
import { NodeTransform, NodeTypes, ElementTypes } from '@vue/compiler-dom'
|
import {
|
||||||
|
NodeTransform,
|
||||||
|
NodeTypes,
|
||||||
|
ElementTypes,
|
||||||
|
createCallExpression,
|
||||||
|
resolveComponentType,
|
||||||
|
buildProps,
|
||||||
|
ComponentNode,
|
||||||
|
PORTAL,
|
||||||
|
SUSPENSE
|
||||||
|
} from '@vue/compiler-dom'
|
||||||
|
import { SSR_RENDER_COMPONENT } from '../runtimeHelpers'
|
||||||
|
import { SSRTransformContext } from '../ssrCodegenTransform'
|
||||||
|
import { isSymbol } from '@vue/shared'
|
||||||
|
|
||||||
export const ssrTransformComponent: NodeTransform = (node, context) => {
|
export const ssrTransformComponent: NodeTransform = (node, context) => {
|
||||||
if (
|
if (
|
||||||
node.type === NodeTypes.ELEMENT &&
|
node.type !== NodeTypes.ELEMENT ||
|
||||||
node.tagType === ElementTypes.COMPONENT
|
node.tagType !== ElementTypes.COMPONENT
|
||||||
) {
|
) {
|
||||||
return function ssrPostTransformComponent() {
|
return
|
||||||
// generate a _push(_renderComponent) call
|
|
||||||
// dynamic component as well
|
|
||||||
// !check if we need to bail out for slots
|
|
||||||
// TODO also handle scopeID here
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return function ssrPostTransformComponent() {
|
||||||
|
const component = resolveComponentType(node, context)
|
||||||
|
|
||||||
|
if (isSymbol(component)) {
|
||||||
|
// built-in compoonent
|
||||||
|
if (component === PORTAL) {
|
||||||
|
// TODO
|
||||||
|
} else if (component === SUSPENSE) {
|
||||||
|
// TODO fallthrough
|
||||||
|
// TODO option to use fallback content and resolve on client
|
||||||
|
} else {
|
||||||
|
// TODO fallthrough for KeepAlive & Transition
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// note we are not passing ssr: true here because for components, v-on
|
||||||
|
// handlers should still be passed
|
||||||
|
const { props } = buildProps(node, context)
|
||||||
|
|
||||||
|
// TODO slots
|
||||||
|
// TODO option for slots bail out
|
||||||
|
// TODO scopeId
|
||||||
|
|
||||||
|
node.ssrCodegenNode = createCallExpression(
|
||||||
|
context.helper(SSR_RENDER_COMPONENT),
|
||||||
|
[
|
||||||
|
component,
|
||||||
|
props || `null`,
|
||||||
|
`null`, // TODO slots
|
||||||
|
`_parent`
|
||||||
|
]
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function ssrProcessComponent(
|
||||||
|
node: ComponentNode,
|
||||||
|
context: SSRTransformContext
|
||||||
|
) {
|
||||||
|
context.pushStatement(node.ssrCodegenNode!)
|
||||||
|
}
|
||||||
|
@ -21,7 +21,8 @@ import {
|
|||||||
createAssignmentExpression,
|
createAssignmentExpression,
|
||||||
TextNode,
|
TextNode,
|
||||||
hasDynamicKeyVBind,
|
hasDynamicKeyVBind,
|
||||||
MERGE_PROPS
|
MERGE_PROPS,
|
||||||
|
isBindKey
|
||||||
} from '@vue/compiler-dom'
|
} from '@vue/compiler-dom'
|
||||||
import { escapeHtml, isBooleanAttr, isSSRSafeAttrName } from '@vue/shared'
|
import { escapeHtml, isBooleanAttr, isSSRSafeAttrName } from '@vue/shared'
|
||||||
import { createSSRCompilerError, SSRErrorCodes } from '../errors'
|
import { createSSRCompilerError, SSRErrorCodes } from '../errors'
|
||||||
@ -261,10 +262,7 @@ function isTextareaWithValue(
|
|||||||
return !!(
|
return !!(
|
||||||
node.tag === 'textarea' &&
|
node.tag === 'textarea' &&
|
||||||
prop.name === 'bind' &&
|
prop.name === 'bind' &&
|
||||||
prop.arg &&
|
isBindKey(prop.arg, 'value')
|
||||||
prop.arg.type === NodeTypes.SIMPLE_EXPRESSION &&
|
|
||||||
prop.arg.isStatic &&
|
|
||||||
prop.arg.content === 'value'
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,6 +19,13 @@ import { warn } from './warning'
|
|||||||
// resolveComponent, resolveDirective) during render
|
// resolveComponent, resolveDirective) during render
|
||||||
export let currentRenderingInstance: ComponentInternalInstance | null = null
|
export let currentRenderingInstance: ComponentInternalInstance | null = null
|
||||||
|
|
||||||
|
// exposed for server-renderer only
|
||||||
|
export function setCurrentRenderingInstance(
|
||||||
|
instance: ComponentInternalInstance | null
|
||||||
|
) {
|
||||||
|
currentRenderingInstance = instance
|
||||||
|
}
|
||||||
|
|
||||||
// dev only flag to track whether $attrs was used during render.
|
// dev only flag to track whether $attrs was used during render.
|
||||||
// If $attrs was used during render then the warning for failed attrs
|
// If $attrs was used during render then the warning for failed attrs
|
||||||
// fallthrough can be suppressed.
|
// fallthrough can be suppressed.
|
||||||
|
@ -101,7 +101,10 @@ export { registerRuntimeCompiler } from './component'
|
|||||||
|
|
||||||
// SSR -------------------------------------------------------------------------
|
// SSR -------------------------------------------------------------------------
|
||||||
import { createComponentInstance, setupComponent } from './component'
|
import { createComponentInstance, setupComponent } from './component'
|
||||||
import { renderComponentRoot } from './componentRenderUtils'
|
import {
|
||||||
|
renderComponentRoot,
|
||||||
|
setCurrentRenderingInstance
|
||||||
|
} from './componentRenderUtils'
|
||||||
import { isVNode, normalizeVNode } from './vnode'
|
import { isVNode, normalizeVNode } from './vnode'
|
||||||
|
|
||||||
// SSR utils are only exposed in cjs builds.
|
// SSR utils are only exposed in cjs builds.
|
||||||
@ -109,6 +112,7 @@ const _ssrUtils = {
|
|||||||
createComponentInstance,
|
createComponentInstance,
|
||||||
setupComponent,
|
setupComponent,
|
||||||
renderComponentRoot,
|
renderComponentRoot,
|
||||||
|
setCurrentRenderingInstance,
|
||||||
isVNode,
|
isVNode,
|
||||||
normalizeVNode
|
normalizeVNode
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,7 @@ import { SSRSlots } from './helpers/renderSlot'
|
|||||||
const {
|
const {
|
||||||
isVNode,
|
isVNode,
|
||||||
createComponentInstance,
|
createComponentInstance,
|
||||||
|
setCurrentRenderingInstance,
|
||||||
setupComponent,
|
setupComponent,
|
||||||
renderComponentRoot,
|
renderComponentRoot,
|
||||||
normalizeVNode
|
normalizeVNode
|
||||||
@ -135,7 +136,10 @@ function renderComponentSubTree(
|
|||||||
} else {
|
} else {
|
||||||
if (comp.ssrRender) {
|
if (comp.ssrRender) {
|
||||||
// optimized
|
// optimized
|
||||||
|
// set current rendering instance for asset resoolution
|
||||||
|
setCurrentRenderingInstance(instance)
|
||||||
comp.ssrRender(instance.proxy, push, instance)
|
comp.ssrRender(instance.proxy, push, instance)
|
||||||
|
setCurrentRenderingInstance(null)
|
||||||
} else if (comp.render) {
|
} else if (comp.render) {
|
||||||
renderVNode(push, renderComponentRoot(instance), instance)
|
renderVNode(push, renderComponentRoot(instance), instance)
|
||||||
} else {
|
} else {
|
||||||
|
Loading…
Reference in New Issue
Block a user