]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
wip(ssr): basic components
authorEvan You <yyx990803@gmail.com>
Thu, 6 Feb 2020 04:07:23 +0000 (23:07 -0500)
committerEvan You <yyx990803@gmail.com>
Thu, 6 Feb 2020 04:07:23 +0000 (23:07 -0500)
20 files changed:
packages/compiler-core/__tests__/__snapshots__/codegen.spec.ts.snap
packages/compiler-core/__tests__/__snapshots__/scopeId.spec.ts.snap
packages/compiler-core/__tests__/transforms/__snapshots__/hoistStatic.spec.ts.snap
packages/compiler-core/__tests__/transforms/__snapshots__/vFor.spec.ts.snap
packages/compiler-core/__tests__/transforms/__snapshots__/vOnce.spec.ts.snap
packages/compiler-core/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap
packages/compiler-core/src/codegen.ts
packages/compiler-core/src/index.ts
packages/compiler-core/src/parse.ts
packages/compiler-core/src/transforms/transformElement.ts
packages/compiler-core/src/transforms/vModel.ts
packages/compiler-core/src/utils.ts
packages/compiler-dom/__tests__/transforms/__snapshots__/vModel.spec.ts.snap
packages/compiler-ssr/__tests__/ssrComponent.spec.ts [new file with mode: 0644]
packages/compiler-ssr/src/ssrCodegenTransform.ts
packages/compiler-ssr/src/transforms/ssrTransformComponent.ts
packages/compiler-ssr/src/transforms/ssrTransformElement.ts
packages/runtime-core/src/componentRenderUtils.ts
packages/runtime-core/src/index.ts
packages/server-renderer/src/renderToString.ts

index 1593cd631ba0ed1837655a84a97ff6bff86dbc3c..ceb8d41b2735b2c4532093664c19e619a9cf76fa 100644 (file)
@@ -79,8 +79,8 @@ return function render() {
     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
   }
 }"
index e08bf68525f0042db7e22221674ff07d10841a9c..75fec293d0ca73cae9f0e2d1c855e51873778838 100644 (file)
@@ -25,7 +25,7 @@ const withId = withScopeId(\\"test\\")
 export const render = withId(function render() {
   const _ctx = this
   const _component_Child = resolveComponent(\\"Child\\")
-  
+
   return (openBlock(), createBlock(_component_Child, null, {
     default: withId(() => [
       createVNode(\\"div\\")
@@ -42,7 +42,7 @@ const withId = withScopeId(\\"test\\")
 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)
       ? {
@@ -71,7 +71,7 @@ const withId = withScopeId(\\"test\\")
 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 */)
index 9e4a7c793dc37cfde1138e733c990b9e01ace8df..8febc4444a1931d201de639da6b7638b7705dfe0 100644 (file)
@@ -103,7 +103,7 @@ return function render() {
     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]
@@ -141,7 +141,7 @@ return function render() {
     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)
@@ -249,7 +249,7 @@ return function render() {
     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
@@ -284,7 +284,7 @@ return function render() {
     const { resolveComponent: _resolveComponent, createVNode: _createVNode, createBlock: _createBlock, openBlock: _openBlock } = _Vue
     
     const _component_Comp = _resolveComponent(\\"Comp\\")
-    
+
     return (_openBlock(), _createBlock(\\"div\\", null, [
       _createVNode(_component_Comp)
     ]))
index 8baed87fda056216917f82998e1cea62f3756e46..b8ecee145a4a8088022bc46b4746fb353802c7b6 100644 (file)
@@ -140,7 +140,7 @@ return function render() {
     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]
index 4c631279904d3a60042c7ca53357556c28a5777b..27c226d15828f7cb2720a98c7f72570e0eba7184 100644 (file)
@@ -27,7 +27,7 @@ return function render() {
     const _cache = $cache
     
     const _component_Comp = _resolveComponent(\\"Comp\\")
-    
+
     return (_openBlock(), _createBlock(\\"div\\", null, [
       _cache[1] || (
         _setBlockTracking(-1),
index ef980e09dc1aa5b7c776b1c44bd8fb27ffd8dcaf..7afd31654c611953202f9e0a2f6dd88206f56360 100644 (file)
@@ -6,7 +6,7 @@ exports[`compiler: transform component slots dynamically named slots 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)],
@@ -21,7 +21,7 @@ exports[`compiler: transform component slots implicit default slot 1`] = `
 return function render() {
   const _ctx = this
   const _component_Comp = resolveComponent(\\"Comp\\")
-  
+
   return (openBlock(), createBlock(_component_Comp, null, {
     default: () => [
       createVNode(\\"div\\")
@@ -37,7 +37,7 @@ exports[`compiler: transform component slots named slot with v-for w/ prefixIden
 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 {
@@ -55,7 +55,7 @@ exports[`compiler: transform component slots named slot with v-if + prefixIdenti
 return function render() {
   const _ctx = this
   const _component_Comp = resolveComponent(\\"Comp\\")
-  
+
   return (openBlock(), createBlock(_component_Comp, null, createSlots({ _compiled: true }, [
     (_ctx.ok)
       ? {
@@ -75,7 +75,7 @@ return function render() {
     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
         ? {
@@ -104,7 +104,7 @@ return function render() {
     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
         ? {
@@ -123,7 +123,7 @@ exports[`compiler: transform component slots named slots 1`] = `
 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)],
@@ -140,7 +140,7 @@ return function render() {
     const { createVNode: _createVNode, resolveComponent: _resolveComponent, createBlock: _createBlock, openBlock: _openBlock } = _Vue
     
     const _component_Comp = _resolveComponent(\\"Comp\\")
-    
+
     return (_openBlock(), _createBlock(_component_Comp, null, {
       one: () => [\\"foo\\"],
       default: () => [
@@ -160,7 +160,7 @@ return function render() {
   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, {
@@ -183,7 +183,7 @@ exports[`compiler: transform component slots on-component default slot 1`] = `
 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
index d58c83bb1975bfa63c7cf9e064d4119f7bbedd35..4f9290bedc01f5411daf62272e1adc1ff793490c 100644 (file)
@@ -353,17 +353,21 @@ function genModulePreamble(
 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`)
+    }
   }
 }
 
index 65f3884f4e3fe1c1492eba7b3995863dc316b6a7..8e0152f5cea41676af1197c6b92a01292cb0a534 100644 (file)
@@ -34,7 +34,7 @@ export { transformOn } from './transforms/vOn'
 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 {
@@ -42,7 +42,7 @@ 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
index 421f32a5f15313a3a691620c96e06dfee3ca1d01..3a4b8dadcb5898605d065fe3d952b131082c6781 100644 (file)
@@ -465,7 +465,8 @@ function parseTag(
     } else if (
       isCoreComponent(tag) ||
       (options.isBuiltInComponent && options.isBuiltInComponent(tag)) ||
-      /^[A-Z]/.test(tag)
+      /^[A-Z]/.test(tag) ||
+      tag === 'component'
     ) {
       tagType = ElementTypes.COMPONENT
     }
index 23ece5ed47483845310405e958c27a2828ce7b0c..1473f428d54551063a75d1333449044437220782 100644 (file)
@@ -14,7 +14,8 @@ import {
   createSimpleExpression,
   createObjectExpression,
   Property,
-  createSequenceExpression
+  createSequenceExpression,
+  ComponentNode
 } from '../ast'
 import { PatchFlags, PatchFlagNames, isSymbol } from '@vue/shared'
 import { createCompilerError, ErrorCodes } from '../errors'
@@ -35,7 +36,8 @@ import {
   getInnerRange,
   toValidAssetId,
   findProp,
-  isCoreComponent
+  isCoreComponent,
+  isBindKey
 } from '../utils'
 import { buildSlots } from './vSlot'
 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
   // 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
@@ -130,6 +93,7 @@ export const transformElement: NodeTransform = (node, context) => {
         args.push(propsBuildResult.props)
       }
     }
+
     // children
     const hasChildren = node.children.length > 0
     if (hasChildren) {
@@ -140,11 +104,7 @@ export const transformElement: NodeTransform = (node, context) => {
       // 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) {
@@ -171,6 +131,7 @@ export const transformElement: NodeTransform = (node, context) => {
         args.push(node.children)
       }
     }
+
     // patchFlag & dynamicPropNames
     if (patchFlag !== 0) {
       if (!hasChildren) {
@@ -219,13 +180,45 @@ export const transformElement: NodeTransform = (node, context) => {
   }
 }
 
-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
@@ -241,7 +234,7 @@ export function buildProps(
   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[] = []
@@ -288,6 +281,10 @@ export function buildProps(
       if (name === 'ref') {
         hasRef = true
       }
+      // skip :is on <component>
+      if (name === 'is' && tag === 'component') {
+        continue
+      }
       properties.push(
         createObjectProperty(
           createSimpleExpression(
@@ -305,6 +302,8 @@ export function buildProps(
     } 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') {
@@ -315,17 +314,16 @@ export function buildProps(
         }
         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
       }
 
@@ -518,3 +516,12 @@ function buildDirectiveArgs(
   }
   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 + `]`
+}
index 7211334c3d070a0fcfd609b83754cbebcd1c0546..26d4ec85a732a1d35f33187437a06d88c1597a93 100644 (file)
@@ -44,10 +44,7 @@ export const transformModel: DirectiveTransform = (dir, node, context) => {
   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 = [
index d160e96ee959f0d9c96f76bb0cb986b6bd4c8724..3a99ff8ea09da92c5464be206b529b0a110c34c3 100644 (file)
@@ -192,19 +192,21 @@ export function findProp(
       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 =>
index 88bba3b05953637dc0615f8f96e77a78ab213ee8..d63829a459f64ebfacf910cc4d8f5c415fc5885c 100644 (file)
@@ -25,7 +25,7 @@ return function render() {
     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)
@@ -146,7 +146,7 @@ return function render() {
     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)
diff --git a/packages/compiler-ssr/__tests__/ssrComponent.spec.ts b/packages/compiler-ssr/__tests__/ssrComponent.spec.ts
new file mode 100644 (file)
index 0000000..b47c4f9
--- /dev/null
@@ -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)
+      }"
+    `)
+  })
+})
index c0b960e628eb290353ef015c6011a34ec83f17aa..ed5cc1030190045f1ba2ad24a159ab06f5a6b87d 100644 (file)
@@ -18,6 +18,7 @@ import { SSR_INTERPOLATE, ssrHelpers } from './runtimeHelpers'
 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
@@ -118,7 +119,7 @@ export function processChildren(
           context.pushStringPart(`</${child.tag}>`)
         }
       } else if (child.tagType === ElementTypes.COMPONENT) {
-        // TODO
+        ssrProcessComponent(child, context)
       } else if (child.tagType === ElementTypes.SLOT) {
         ssrProcessSlotOutlet(child, context)
       }
index 602474d901cd0119329df651d78cd1bed57330d9..034f1efd420271557327efd5f2aef14db9c05c01 100644 (file)
@@ -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) => {
   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!)
+}
index 5345c10a469d55f29cb9884621cc47b4f68678fa..df409ef2cf07e8a0614b2f3516a004037500acb1 100644 (file)
@@ -21,7 +21,8 @@ import {
   createAssignmentExpression,
   TextNode,
   hasDynamicKeyVBind,
-  MERGE_PROPS
+  MERGE_PROPS,
+  isBindKey
 } from '@vue/compiler-dom'
 import { escapeHtml, isBooleanAttr, isSSRSafeAttrName } from '@vue/shared'
 import { createSSRCompilerError, SSRErrorCodes } from '../errors'
@@ -261,10 +262,7 @@ function isTextareaWithValue(
   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')
   )
 }
 
index 01629fd2d1a0d0bad9c5b87ec304a66928d06102..eb26c1d935925b65a8fc82887fd2353bdcf3604e 100644 (file)
@@ -19,6 +19,13 @@ import { warn } from './warning'
 // 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.
index b56ff56cecc67373ab01ac8540150890f03b301d..01ba0344ff5e12b59f6cfa48010d03228778d230 100644 (file)
@@ -101,7 +101,10 @@ export { registerRuntimeCompiler } from './component'
 
 // 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.
@@ -109,6 +112,7 @@ const _ssrUtils = {
   createComponentInstance,
   setupComponent,
   renderComponentRoot,
+  setCurrentRenderingInstance,
   isVNode,
   normalizeVNode
 }
index d7b6626bb4e2d307602cb846dd55e73a80f18b73..be62560e2caed9064fd7770780bb97996c2cefa7 100644 (file)
@@ -27,6 +27,7 @@ import { SSRSlots } from './helpers/renderSlot'
 const {
   isVNode,
   createComponentInstance,
+  setCurrentRenderingInstance,
   setupComponent,
   renderComponentRoot,
   normalizeVNode
@@ -135,7 +136,10 @@ function renderComponentSubTree(
   } 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 {