diff --git a/packages/compiler-core/src/codegen.ts b/packages/compiler-core/src/codegen.ts
index 4f9290be..41f6e4c7 100644
--- a/packages/compiler-core/src/codegen.ts
+++ b/packages/compiler-core/src/codegen.ts
@@ -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(
diff --git a/packages/compiler-core/src/options.ts b/packages/compiler-core/src/options.ts
index f108edcf..d195cf39 100644
--- a/packages/compiler-core/src/options.ts
+++ b/packages/compiler-core/src/options.ts
@@ -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
}
diff --git a/packages/compiler-core/src/transforms/transformElement.ts b/packages/compiler-core/src/transforms/transformElement.ts
index 1473f428..7c9dd0fe 100644
--- a/packages/compiler-core/src/transforms/transformElement.ts
+++ b/packages/compiler-core/src/transforms/transformElement.ts
@@ -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
}
diff --git a/packages/compiler-dom/src/index.ts b/packages/compiler-dom/src/index.ts
index 6cecb8b2..5e1d17df 100644
--- a/packages/compiler-dom/src/index.ts
+++ b/packages/compiler-dom/src/index.ts
@@ -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'
diff --git a/packages/compiler-ssr/__tests__/ssrComponent.spec.ts b/packages/compiler-ssr/__tests__/ssrComponent.spec.ts
index 1f24a924..3fd0629f 100644
--- a/packages/compiler-ssr/__tests__/ssrComponent.spec.ts
+++ b/packages/compiler-ssr/__tests__/ssrComponent.spec.ts
@@ -161,5 +161,47 @@ describe('ssr: components', () => {
}"
`)
})
+
+ test('built-in fallthroughs', () => {
+ // no fragment
+ expect(compile(``).code)
+ .toMatchInlineSnapshot(`
+ "
+ return function ssrRender(_ctx, _push, _parent) {
+ _push(\`
\`)
+ }"
+ `)
+
+ // wrap with fragment
+ expect(compile(``).code)
+ .toMatchInlineSnapshot(`
+ "
+ return function ssrRender(_ctx, _push, _parent) {
+ _push(\`\`)
+ }"
+ `)
+
+ // no fragment
+ expect(compile(``).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(``).code)
+ .toMatchInlineSnapshot(`
+ "
+ return function ssrRender(_ctx, _push, _parent) {
+ _push(\`\`)
+ }"
+ `)
+ })
})
})
diff --git a/packages/compiler-ssr/src/index.ts b/packages/compiler-ssr/src/index.ts
index e018e2d9..2a0ba788 100644
--- a/packages/compiler-ssr/src/index.ts
+++ b/packages/compiler-ssr/src/index.ts
@@ -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
diff --git a/packages/compiler-ssr/src/ssrCodegenTransform.ts b/packages/compiler-ssr/src/ssrCodegenTransform.ts
index ed5cc103..d9efb3b0 100644
--- a/packages/compiler-ssr/src/ssrCodegenTransform.ts
+++ b/packages/compiler-ssr/src/ssrCodegenTransform.ts
@@ -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(``)
+ }
}
diff --git a/packages/compiler-ssr/src/transforms/ssrTransformComponent.ts b/packages/compiler-ssr/src/transforms/ssrTransformComponent.ts
index 6916515d..de055f3d 100644
--- a/packages/compiler-ssr/src/transforms/ssrTransformComponent.ts
+++ b/packages/compiler-ssr/src/transforms/ssrTransformComponent.ts
@@ -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()
+
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!)
}
diff --git a/packages/compiler-ssr/src/transforms/ssrVFor.ts b/packages/compiler-ssr/src/transforms/ssrVFor.ts
index effa8d88..ec9f251e 100644
--- a/packages/compiler-ssr/src/transforms/ssrVFor.ts
+++ b/packages/compiler-ssr/src/transforms/ssrVFor.ts
@@ -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)
)
diff --git a/packages/compiler-ssr/src/transforms/ssrVIf.ts b/packages/compiler-ssr/src/transforms/ssrVIf.ts
index 23e06db2..a642b9fd 100644
--- a/packages/compiler-ssr/src/transforms/ssrVIf.ts
+++ b/packages/compiler-ssr/src/transforms/ssrVIf.ts
@@ -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)
}