]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
wip(compiler-ssr): built-in component fallthrough
authorEvan You <yyx990803@gmail.com>
Thu, 6 Feb 2020 20:29:02 +0000 (15:29 -0500)
committerEvan You <yyx990803@gmail.com>
Thu, 6 Feb 2020 22:45:46 +0000 (17:45 -0500)
packages/compiler-core/src/codegen.ts
packages/compiler-core/src/options.ts
packages/compiler-core/src/transforms/transformElement.ts
packages/compiler-dom/src/index.ts
packages/compiler-ssr/__tests__/ssrComponent.spec.ts
packages/compiler-ssr/src/index.ts
packages/compiler-ssr/src/ssrCodegenTransform.ts
packages/compiler-ssr/src/transforms/ssrTransformComponent.ts
packages/compiler-ssr/src/transforms/ssrVFor.ts
packages/compiler-ssr/src/transforms/ssrVIf.ts

index 4f9290bedc01f5411daf62272e1adc1ff793490c..41f6e4c77edc5763ab2a611201e3e09745f56f5d 100644 (file)
@@ -79,6 +79,8 @@ function createCodegenContext(
     sourceMap = false,
     filename = `template.vue.html`,
     scopeId = null,
+    runtimeGlobalName = `Vue`,
+    runtimeModuleName = `vue`,
     ssr = false
   }: CodegenOptions
 ): CodegenContext {
@@ -88,6 +90,8 @@ function createCodegenContext(
     sourceMap,
     filename,
     scopeId,
+    runtimeGlobalName,
+    runtimeModuleName,
     ssr,
     source: ast.loc.source,
     code: ``,
@@ -275,8 +279,18 @@ export function generate(
 }
 
 function genFunctionPreamble(ast: RootNode, context: CodegenContext) {
-  const { ssr, helper, prefixIdentifiers, push, newline } = context
-  const VueBinding = ssr ? `require("vue")` : `Vue`
+  const {
+    ssr,
+    helper,
+    prefixIdentifiers,
+    push,
+    newline,
+    runtimeModuleName,
+    runtimeGlobalName
+  } = context
+  const VueBinding = ssr
+    ? `require(${JSON.stringify(runtimeModuleName)})`
+    : runtimeGlobalName
   // Generate const declaration for helpers
   // In prefix mode, we place the const declaration at top so it's done
   // only once; But if we not prefixing, we place the declaration inside the
@@ -319,7 +333,7 @@ function genModulePreamble(
   context: CodegenContext,
   genScopeId: boolean
 ) {
-  const { push, helper, newline, scopeId } = context
+  const { push, helper, newline, scopeId, runtimeModuleName } = context
   // generate import statements for helpers
   if (genScopeId) {
     ast.helpers.push(WITH_SCOPE_ID)
@@ -328,7 +342,11 @@ function genModulePreamble(
     }
   }
   if (ast.helpers.length) {
-    push(`import { ${ast.helpers.map(helper).join(', ')} } from "vue"\n`)
+    push(
+      `import { ${ast.helpers.map(helper).join(', ')} } from ${JSON.stringify(
+        runtimeModuleName
+      )}\n`
+    )
   }
   if (!__BROWSER__ && ast.ssrHelpers && ast.ssrHelpers.length) {
     push(
index f108edcf80e57133132b7116ca43024b2e1435a1..d195cf393fec23c7c010bc169471abd676d1c9b3 100644 (file)
@@ -71,6 +71,9 @@ export interface CodegenOptions {
   scopeId?: string | null
   // we need to know about this to generate proper preambles
   prefixIdentifiers?: boolean
+  // for specifying where to import helpers
+  runtimeModuleName?: string
+  runtimeGlobalName?: string
   // generate ssr-specific code?
   ssr?: boolean
 }
index 1473f428d54551063a75d1333449044437220782..7c9dd0fe8fd1399f7e06d7aad80a2b531be52ace 100644 (file)
@@ -182,7 +182,8 @@ export const transformElement: NodeTransform = (node, context) => {
 
 export function resolveComponentType(
   node: ComponentNode,
-  context: TransformContext
+  context: TransformContext,
+  ssr = false
 ) {
   const { tag } = node
 
@@ -211,7 +212,9 @@ export function resolveComponentType(
   // 2. built-in components (Portal, Transition, KeepAlive, Suspense...)
   const builtIn = isCoreComponent(tag) || context.isBuiltInComponent(tag)
   if (builtIn) {
-    context.helper(builtIn)
+    // built-ins are simply fallthroughs / have special handling during ssr
+    // no we don't need to import their runtime equivalents
+    if (!ssr) context.helper(builtIn)
     return builtIn
   }
 
index 6cecb8b2c91da08973016f2ca09f8f09b9116cac..5e1d17dfd23ddbf83f209fdb4ef31bbd85776e5f 100644 (file)
@@ -22,6 +22,14 @@ export const parserOptions = __BROWSER__
   ? parserOptionsMinimal
   : parserOptionsStandard
 
+export const isBuiltInDOMComponent = (tag: string): symbol | undefined => {
+  if (isBuiltInType(tag, `Transition`)) {
+    return TRANSITION
+  } else if (isBuiltInType(tag, `TransitionGroup`)) {
+    return TRANSITION_GROUP
+  }
+}
+
 export function compile(
   template: string,
   options: CompilerOptions = {}
@@ -39,13 +47,7 @@ export function compile(
       show: transformShow,
       ...(options.directiveTransforms || {})
     },
-    isBuiltInComponent: tag => {
-      if (isBuiltInType(tag, `Transition`)) {
-        return TRANSITION
-      } else if (isBuiltInType(tag, `TransitionGroup`)) {
-        return TRANSITION_GROUP
-      }
-    }
+    isBuiltInComponent: isBuiltInDOMComponent
   })
 }
 
@@ -56,6 +58,7 @@ export function parse(template: string, options: ParserOptions = {}): RootNode {
   })
 }
 
+export * from './runtimeHelpers'
 export { transformStyle } from './transforms/transformStyle'
 export { createDOMCompilerError, DOMErrorCodes } from './errors'
 export * from '@vue/compiler-core'
index 1f24a92444ea44426fa2f2520a5ab8a85394a69d..3fd0629f3d9ea125d1876857f8adb95698ebca9f 100644 (file)
@@ -161,5 +161,47 @@ describe('ssr: components', () => {
         }"
       `)
     })
+
+    test('built-in fallthroughs', () => {
+      // no fragment
+      expect(compile(`<transition><div/></transition>`).code)
+        .toMatchInlineSnapshot(`
+        "
+        return function ssrRender(_ctx, _push, _parent) {
+          _push(\`<div></div>\`)
+        }"
+      `)
+
+      // wrap with fragment
+      expect(compile(`<transition-group><div/></transition-group>`).code)
+        .toMatchInlineSnapshot(`
+        "
+        return function ssrRender(_ctx, _push, _parent) {
+          _push(\`<!----><div></div><!---->\`)
+        }"
+      `)
+
+      // no fragment
+      expect(compile(`<keep-alive><foo/></keep-alive>`).code)
+        .toMatchInlineSnapshot(`
+        "const { resolveComponent } = require(\\"vue\\")
+        const { _ssrRenderComponent } = require(\\"@vue/server-renderer\\")
+
+        return function ssrRender(_ctx, _push, _parent) {
+          const _component_foo = resolveComponent(\\"foo\\")
+
+          _ssrRenderComponent(_component_foo, null, null, _parent)
+        }"
+      `)
+
+      // wrap with fragment
+      expect(compile(`<suspense><div/></suspense>`).code)
+        .toMatchInlineSnapshot(`
+        "
+        return function ssrRender(_ctx, _push, _parent) {
+          _push(\`<!----><div></div><!---->\`)
+        }"
+      `)
+    })
   })
 })
index e018e2d905db285f79fe88344bf866d27307b57b..2a0ba788f37e46aa165c2a5eabe534893948a58a 100644 (file)
@@ -10,7 +10,8 @@ import {
   trackSlotScopes,
   noopDirectiveTransform,
   transformBind,
-  transformStyle
+  transformStyle,
+  isBuiltInDOMComponent
 } from '@vue/compiler-dom'
 import { ssrCodegenTransform } from './ssrCodegenTransform'
 import { ssrTransformElement } from './transforms/ssrTransformElement'
@@ -64,7 +65,8 @@ export function compile(
       cloak: noopDirectiveTransform,
       once: noopDirectiveTransform,
       ...(options.directiveTransforms || {}) // user transforms
-    }
+    },
+    isBuiltInComponent: isBuiltInDOMComponent
   })
 
   // traverse the template AST and convert into SSR codegen AST
index ed5cc1030190045f1ba2ad24a159ab06f5a6b87d..d9efb3b01f849defde915565108303634ac8924b 100644 (file)
@@ -28,17 +28,9 @@ import { ssrProcessComponent } from './transforms/ssrTransformComponent'
 
 export function ssrCodegenTransform(ast: RootNode, options: CompilerOptions) {
   const context = createSSRTransformContext(options)
-
   const isFragment =
     ast.children.length > 1 && !ast.children.every(c => isText(c))
-  if (isFragment) {
-    context.pushStringPart(`<!---->`)
-  }
-  processChildren(ast.children, context)
-  if (isFragment) {
-    context.pushStringPart(`<!---->`)
-  }
-
+  processChildren(ast.children, context, isFragment)
   ast.codegenNode = createBlockStatement(context.body)
 
   // Finalize helpers.
@@ -99,8 +91,12 @@ export function createChildContext(
 
 export function processChildren(
   children: TemplateChildNode[],
-  context: SSRTransformContext
+  context: SSRTransformContext,
+  asFragment = false
 ) {
+  if (asFragment) {
+    context.pushStringPart(`<!---->`)
+  }
   const isVoidTag = context.options.isVoidTag || NO
   for (let i = 0; i < children.length; i++) {
     const child = children[i]
@@ -135,4 +131,7 @@ export function processChildren(
       ssrProcessFor(child, context)
     }
   }
+  if (asFragment) {
+    context.pushStringPart(`<!---->`)
+  }
 }
index 6916515d80998325288a07c03a9a4c53dd3350e8..de055f3da99996609cb8d0cebed0415534825cc9 100644 (file)
@@ -6,14 +6,15 @@ import {
   resolveComponentType,
   buildProps,
   ComponentNode,
-  PORTAL,
-  SUSPENSE,
   SlotFnBuilder,
   createFunctionExpression,
   createBlockStatement,
   buildSlots,
   FunctionExpression,
-  TemplateChildNode
+  TemplateChildNode,
+  PORTAL,
+  SUSPENSE,
+  TRANSITION_GROUP
 } from '@vue/compiler-dom'
 import { SSR_RENDER_COMPONENT } from '../runtimeHelpers'
 import {
@@ -34,6 +35,8 @@ interface WIPSlotEntry {
   children: TemplateChildNode[]
 }
 
+const componentTypeMap = new WeakMap<ComponentNode, symbol>()
+
 export const ssrTransformComponent: NodeTransform = (node, context) => {
   if (
     node.type !== NodeTypes.ELEMENT ||
@@ -43,18 +46,10 @@ export const ssrTransformComponent: NodeTransform = (node, context) => {
   }
 
   return function ssrPostTransformComponent() {
-    const component = resolveComponentType(node, context)
-
+    const component = resolveComponentType(node, context, true /* ssr */)
     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
-      }
+      componentTypeMap.set(node, component)
+      return // built-in component: fallthrough
     }
 
     // note we are not passing ssr: true here because for components, v-on
@@ -98,13 +93,28 @@ export function ssrProcessComponent(
   node: ComponentNode,
   context: SSRTransformContext
 ) {
-  // finish up slot function expressions from the 1st pass.
-  const wipEntries = wipMap.get(node) || []
-  for (let i = 0; i < wipEntries.length; i++) {
-    const { fn, children } = wipEntries[i]
-    const childContext = createChildContext(context)
-    processChildren(children, childContext)
-    fn.body = createBlockStatement(childContext.body)
+  if (!node.ssrCodegenNode) {
+    // this is a built-in component that fell-through.
+    // just render its children.
+    const component = componentTypeMap.get(node)!
+
+    if (component === PORTAL) {
+      // TODO
+      return
+    }
+
+    const needFragmentWrapper =
+      component === SUSPENSE || component === TRANSITION_GROUP
+    processChildren(node.children, context, needFragmentWrapper)
+  } else {
+    // finish up slot function expressions from the 1st pass.
+    const wipEntries = wipMap.get(node) || []
+    for (let i = 0; i < wipEntries.length; i++) {
+      const { fn, children } = wipEntries[i]
+      const childContext = createChildContext(context)
+      processChildren(children, childContext)
+      fn.body = createBlockStatement(childContext.body)
+    }
+    context.pushStatement(node.ssrCodegenNode)
   }
-  context.pushStatement(node.ssrCodegenNode!)
 }
index effa8d8802af4f794e33e3ecffa9da84bf96a0eb..ec9f251ed987a5f9c344681890ec1de98715c7de 100644 (file)
@@ -27,13 +27,7 @@ export function ssrProcessFor(node: ForNode, context: SSRTransformContext) {
   const childContext = createChildContext(context)
   const needFragmentWrapper =
     node.children.length !== 1 || node.children[0].type !== NodeTypes.ELEMENT
-  if (needFragmentWrapper) {
-    childContext.pushStringPart(`<!---->`)
-  }
-  processChildren(node.children, childContext)
-  if (needFragmentWrapper) {
-    childContext.pushStringPart(`<!---->`)
-  }
+  processChildren(node.children, childContext, needFragmentWrapper)
   const renderLoop = createFunctionExpression(
     createForLoopParams(node.parseResult)
   )
index 23e06db2a86d9afae960114ca3f68ffce23174d2..a642b9fd42ece33162a10081a8870f76cbe45e78 100644 (file)
@@ -64,12 +64,6 @@ function processIfBranch(
     // optimize away nested fragments when the only child is a ForNode
     !(children.length === 1 && children[0].type === NodeTypes.FOR)
   const childContext = createChildContext(context)
-  if (needFragmentWrapper) {
-    childContext.pushStringPart(`<!---->`)
-  }
-  processChildren(children, childContext)
-  if (needFragmentWrapper) {
-    childContext.pushStringPart(`<!---->`)
-  }
+  processChildren(children, childContext, needFragmentWrapper)
   return createBlockStatement(childContext.body)
 }