const _component_Foo = _resolveComponent(\\"Foo\\")
const _component_bar_baz = _resolveComponent(\\"bar-baz\\")
const _component_barbaz = _resolveComponent(\\"barbaz\\")
- const _directive_my_dir = _resolveDirective(\\"my_dir\\")
-
+const _directive_my_dir = _resolveDirective(\\"my_dir\\")
+
return null
}
}"
export const render = withId(function render() {
const _ctx = this
const _component_Child = resolveComponent(\\"Child\\")
-
+
return (openBlock(), createBlock(_component_Child, null, {
default: withId(() => [
createVNode(\\"div\\")
export const render = withId(function render() {
const _ctx = this
const _component_Child = resolveComponent(\\"Child\\")
-
+
return (openBlock(), createBlock(_component_Child, null, createSlots({ _compiled: true }, [
(_ctx.ok)
? {
export const render = withId(function render() {
const _ctx = this
const _component_Child = resolveComponent(\\"Child\\")
-
+
return (openBlock(), createBlock(_component_Child, null, {
foo: withId(({ msg }) => [
createTextVNode(toDisplayString(msg), 1 /* TEXT */)
const { createVNode: _createVNode, withDirectives: _withDirectives, resolveDirective: _resolveDirective, createBlock: _createBlock, openBlock: _openBlock } = _Vue
const _directive_foo = _resolveDirective(\\"foo\\")
-
+
return (_openBlock(), _createBlock(\\"div\\", null, [
_withDirectives(_createVNode(\\"div\\", _hoisted_1, null, 32 /* NEED_PATCH */), [
[_directive_foo]
const { resolveComponent: _resolveComponent, createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
const _component_Comp = _resolveComponent(\\"Comp\\")
-
+
return (_openBlock(), _createBlock(\\"div\\", null, [
_createVNode(\\"div\\", _hoisted_1, [
_createVNode(_component_Comp)
const { toDisplayString: _toDisplayString, resolveComponent: _resolveComponent, createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
const _component_Comp = _resolveComponent(\\"Comp\\")
-
+
return (_openBlock(), _createBlock(_component_Comp, null, {
default: ({ foo }) => [_toDisplayString(_ctx.foo)],
_compiled: true
const { resolveComponent: _resolveComponent, createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
const _component_Comp = _resolveComponent(\\"Comp\\")
-
+
return (_openBlock(), _createBlock(\\"div\\", null, [
_createVNode(_component_Comp)
]))
const { renderList: _renderList, openBlock: _openBlock, createBlock: _createBlock, Fragment: _Fragment, createVNode: _createVNode, withDirectives: _withDirectives, resolveDirective: _resolveDirective } = _Vue
const _directive_foo = _resolveDirective(\\"foo\\")
-
+
return (_openBlock(false), _createBlock(_Fragment, null, _renderList(list, (i) => {
return (_openBlock(), _withDirectives(_createBlock(\\"div\\", null, null, 32 /* NEED_PATCH */), [
[_directive_foo]
const _cache = $cache
const _component_Comp = _resolveComponent(\\"Comp\\")
-
+
return (_openBlock(), _createBlock(\\"div\\", null, [
_cache[1] || (
_setBlockTracking(-1),
return function render() {
const _ctx = this
const _component_Comp = resolveComponent(\\"Comp\\")
-
+
return (openBlock(), createBlock(_component_Comp, null, {
[_ctx.one]: ({ foo }) => [toDisplayString(foo), toDisplayString(_ctx.bar)],
[_ctx.two]: ({ bar }) => [toDisplayString(_ctx.foo), toDisplayString(bar)],
return function render() {
const _ctx = this
const _component_Comp = resolveComponent(\\"Comp\\")
-
+
return (openBlock(), createBlock(_component_Comp, null, {
default: () => [
createVNode(\\"div\\")
return function render() {
const _ctx = this
const _component_Comp = resolveComponent(\\"Comp\\")
-
+
return (openBlock(), createBlock(_component_Comp, null, createSlots({ _compiled: true }, [
renderList(_ctx.list, (name) => {
return {
return function render() {
const _ctx = this
const _component_Comp = resolveComponent(\\"Comp\\")
-
+
return (openBlock(), createBlock(_component_Comp, null, createSlots({ _compiled: true }, [
(_ctx.ok)
? {
const { resolveComponent: _resolveComponent, createSlots: _createSlots, createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
const _component_Comp = _resolveComponent(\\"Comp\\")
-
+
return (_openBlock(), _createBlock(_component_Comp, null, _createSlots({ _compiled: true }, [
ok
? {
const { resolveComponent: _resolveComponent, createSlots: _createSlots, createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
const _component_Comp = _resolveComponent(\\"Comp\\")
-
+
return (_openBlock(), _createBlock(_component_Comp, null, _createSlots({ _compiled: true }, [
ok
? {
return function render() {
const _ctx = this
const _component_Comp = resolveComponent(\\"Comp\\")
-
+
return (openBlock(), createBlock(_component_Comp, null, {
one: ({ foo }) => [toDisplayString(foo), toDisplayString(_ctx.bar)],
two: ({ bar }) => [toDisplayString(_ctx.foo), toDisplayString(bar)],
const { createVNode: _createVNode, resolveComponent: _resolveComponent, createBlock: _createBlock, openBlock: _openBlock } = _Vue
const _component_Comp = _resolveComponent(\\"Comp\\")
-
+
return (_openBlock(), _createBlock(_component_Comp, null, {
one: () => [\\"foo\\"],
default: () => [
const _ctx = this
const _component_Inner = resolveComponent(\\"Inner\\")
const _component_Comp = resolveComponent(\\"Comp\\")
-
+
return (openBlock(), createBlock(_component_Comp, null, {
default: ({ foo }) => [
createVNode(_component_Inner, null, {
return function render() {
const _ctx = this
const _component_Comp = resolveComponent(\\"Comp\\")
-
+
return (openBlock(), createBlock(_component_Comp, null, {
default: ({ foo }) => [toDisplayString(foo), toDisplayString(_ctx.bar)],
_compiled: true
function genAssets(
assets: string[],
type: 'component' | 'directive',
- context: CodegenContext
+ { helper, push, newline }: CodegenContext
) {
- const resolver = context.helper(
+ const resolver = helper(
type === 'component' ? RESOLVE_COMPONENT : RESOLVE_DIRECTIVE
)
for (let i = 0; i < assets.length; i++) {
const id = assets[i]
- context.push(
+ push(
`const ${toValidAssetId(id, type)} = ${resolver}(${JSON.stringify(id)})`
)
- context.newline()
+ if (i < assets.length - 1) {
+ newline()
+ } else {
+ push(`\n`)
+ }
}
}
export { transformBind } from './transforms/vBind'
// exported for compiler-ssr
-export { MERGE_PROPS } from './runtimeHelpers'
+export * from './runtimeHelpers'
export { processIf } from './transforms/vIf'
export { processFor, createForLoopParams } from './transforms/vFor'
export {
processExpression
} from './transforms/transformExpression'
export { trackVForSlotScopes, trackSlotScopes } from './transforms/vSlot'
-export { buildProps } from './transforms/transformElement'
+export { resolveComponentType, buildProps } from './transforms/transformElement'
export { processSlotOutlet } from './transforms/transformSlotOutlet'
// utility, but need to rewrite typing to avoid dts relying on @vue/shared
} else if (
isCoreComponent(tag) ||
(options.isBuiltInComponent && options.isBuiltInComponent(tag)) ||
- /^[A-Z]/.test(tag)
+ /^[A-Z]/.test(tag) ||
+ tag === 'component'
) {
tagType = ElementTypes.COMPONENT
}
createSimpleExpression,
createObjectExpression,
Property,
- createSequenceExpression
+ createSequenceExpression,
+ ComponentNode
} from '../ast'
import { PatchFlags, PatchFlagNames, isSymbol } from '@vue/shared'
import { createCompilerError, ErrorCodes } from '../errors'
getInnerRange,
toValidAssetId,
findProp,
- isCoreComponent
+ isCoreComponent,
+ isBindKey
} from '../utils'
import { buildSlots } from './vSlot'
import { isStaticNode } from './hoistStatic'
// perform the work on exit, after all child expressions have been
// processed and merged.
return function postTransformElement() {
- const { tag, tagType, props } = node
- const builtInComponentSymbol =
- isCoreComponent(tag) || context.isBuiltInComponent(tag)
- const isComponent = tagType === ElementTypes.COMPONENT
+ const { tag, props } = node
+ const isComponent = node.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 patchFlag: number = 0
let runtimeDirectives: DirectiveNode[] | 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
if (hasProps) {
- const propsBuildResult = buildProps(
- node,
- context,
- // skip reserved "is" prop <component is>
- isProp ? node.props.filter(p => p !== isProp) : node.props
- )
+ const propsBuildResult = buildProps(node, context)
patchFlag = propsBuildResult.patchFlag
dynamicPropNames = propsBuildResult.dynamicPropNames
runtimeDirectives = propsBuildResult.directives
args.push(propsBuildResult.props)
}
}
+
// children
const hasChildren = node.children.length > 0
if (hasChildren) {
// 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
// Transition
- if (
- isComponent &&
- builtInComponentSymbol !== PORTAL &&
- builtInComponentSymbol !== KEEP_ALIVE
- ) {
+ if (isComponent && nodeType !== PORTAL && nodeType !== KEEP_ALIVE) {
const { slots, hasDynamicSlots } = buildSlots(node, context)
args.push(slots)
if (hasDynamicSlots) {
args.push(node.children)
}
}
+
// patchFlag & dynamicPropNames
if (patchFlag !== 0) {
if (!hasChildren) {
}
}
-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 += ', '
+export function resolveComponentType(
+ node: ComponentNode,
+ context: TransformContext
+) {
+ 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`)
+ }
+ }
+ // 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.$` : `$`]
+ )
+ }
}
- return propsNamesString + `]`
+
+ // 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
patchFlag: number
dynamicPropNames: string[]
} {
- const elementLoc = node.loc
+ const { tag, loc: elementLoc } = node
const isComponent = node.tagType === ElementTypes.COMPONENT
let properties: ObjectExpression['properties'] = []
const mergeArgs: PropsExpression[] = []
if (name === 'ref') {
hasRef = true
}
+ // skip :is on <component>
+ if (name === 'is' && tag === 'component') {
+ continue
+ }
properties.push(
createObjectProperty(
createSimpleExpression(
} else {
// directives
const { name, arg, exp, loc } = prop
+ const isBind = name === 'bind'
+ const isOn = name === 'on'
// skip v-slot - it is handled by its dedicated transform.
if (name === 'slot') {
}
continue
}
-
// skip v-once - it is handled by its dedicated transform.
if (name === 'once') {
continue
}
-
- const isBind = name === 'bind'
- const isOn = name === 'on'
-
+ // skip :is on <component>
+ if (isBind && tag === 'component' && isBindKey(arg, 'is')) {
+ continue
+ }
// skip v-on in SSR compilation
- if (ssr && isOn) {
+ if (isOn && ssr) {
continue
}
}
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 + `]`
+}
const eventName = arg
? arg.type === NodeTypes.SIMPLE_EXPRESSION && arg.isStatic
? `onUpdate:${arg.content}`
- : createCompoundExpression([
- '"onUpdate:" + ',
- ...(arg.type === NodeTypes.SIMPLE_EXPRESSION ? [arg] : arg.children)
- ])
+ : createCompoundExpression(['"onUpdate:" + ', arg])
: `onUpdate:modelValue`
const props = [
if (p.name === name && p.value) {
return p
}
- } else if (
- p.name === 'bind' &&
- p.arg &&
- p.arg.type === NodeTypes.SIMPLE_EXPRESSION &&
- p.arg.isStatic &&
- p.arg.content === name &&
- p.exp
- ) {
+ } else if (p.name === 'bind' && p.exp && isBindKey(p.arg, name)) {
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 {
return node.props.some(
p =>
const { vModelDynamic: _vModelDynamic, createVNode: _createVNode, withDirectives: _withDirectives, resolveDirective: _resolveDirective, createBlock: _createBlock, openBlock: _openBlock } = _Vue
const _directive_bind = _resolveDirective(\\"bind\\")
-
+
return (_openBlock(), _withDirectives(_createBlock(\\"input\\", {
modelValue: model,
\\"onUpdate:modelValue\\": $event => (model = $event)
const { vModelDynamic: _vModelDynamic, createVNode: _createVNode, withDirectives: _withDirectives, resolveDirective: _resolveDirective, createBlock: _createBlock, openBlock: _openBlock } = _Vue
const _directive_bind = _resolveDirective(\\"bind\\")
-
+
return (_openBlock(), _withDirectives(_createBlock(\\"input\\", {
modelValue: model,
\\"onUpdate:modelValue\\": $event => (model = $event)
--- /dev/null
+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)
+ }"
+ `)
+ })
+})
import { ssrProcessIf } from './transforms/ssrVIf'
import { ssrProcessFor } from './transforms/ssrVFor'
import { ssrProcessSlotOutlet } from './transforms/ssrTransformSlotOutlet'
+import { ssrProcessComponent } from './transforms/ssrTransformComponent'
// Because SSR codegen output is completely different from client-side output
// (e.g. multiple elements can be concatenated into a single template literal
context.pushStringPart(`</${child.tag}>`)
}
} else if (child.tagType === ElementTypes.COMPONENT) {
- // TODO
+ ssrProcessComponent(child, context)
} else if (child.tagType === ElementTypes.SLOT) {
ssrProcessSlotOutlet(child, context)
}
-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) => {
if (
- node.type === NodeTypes.ELEMENT &&
- node.tagType === ElementTypes.COMPONENT
+ node.type !== NodeTypes.ELEMENT ||
+ node.tagType !== ElementTypes.COMPONENT
) {
- return function ssrPostTransformComponent() {
- // generate a _push(_renderComponent) call
- // dynamic component as well
- // !check if we need to bail out for slots
- // TODO also handle scopeID here
+ return
+ }
+
+ 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!)
+}
createAssignmentExpression,
TextNode,
hasDynamicKeyVBind,
- MERGE_PROPS
+ MERGE_PROPS,
+ isBindKey
} from '@vue/compiler-dom'
import { escapeHtml, isBooleanAttr, isSSRSafeAttrName } from '@vue/shared'
import { createSSRCompilerError, SSRErrorCodes } from '../errors'
return !!(
node.tag === 'textarea' &&
prop.name === 'bind' &&
- prop.arg &&
- prop.arg.type === NodeTypes.SIMPLE_EXPRESSION &&
- prop.arg.isStatic &&
- prop.arg.content === 'value'
+ isBindKey(prop.arg, 'value')
)
}
// resolveComponent, resolveDirective) during render
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.
// If $attrs was used during render then the warning for failed attrs
// fallthrough can be suppressed.
// SSR -------------------------------------------------------------------------
import { createComponentInstance, setupComponent } from './component'
-import { renderComponentRoot } from './componentRenderUtils'
+import {
+ renderComponentRoot,
+ setCurrentRenderingInstance
+} from './componentRenderUtils'
import { isVNode, normalizeVNode } from './vnode'
// SSR utils are only exposed in cjs builds.
createComponentInstance,
setupComponent,
renderComponentRoot,
+ setCurrentRenderingInstance,
isVNode,
normalizeVNode
}
const {
isVNode,
createComponentInstance,
+ setCurrentRenderingInstance,
setupComponent,
renderComponentRoot,
normalizeVNode
} else {
if (comp.ssrRender) {
// optimized
+ // set current rendering instance for asset resoolution
+ setCurrentRenderingInstance(instance)
comp.ssrRender(instance.proxy, push, instance)
+ setCurrentRenderingInstance(null)
} else if (comp.render) {
renderVNode(push, renderComponentRoot(instance), instance)
} else {