wip(compiler-ssr): built-in component fallthrough
This commit is contained in:
parent
9cfbab0686
commit
3c27bf6133
@ -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(
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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'
|
||||
|
@ -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><!---->\`)
|
||||
}"
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -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
|
||||
|
@ -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(`<!---->`)
|
||||
}
|
||||
}
|
||||
|
@ -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,6 +93,20 @@ export function ssrProcessComponent(
|
||||
node: ComponentNode,
|
||||
context: SSRTransformContext
|
||||
) {
|
||||
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++) {
|
||||
@ -106,5 +115,6 @@ export function ssrProcessComponent(
|
||||
processChildren(children, childContext)
|
||||
fn.body = createBlockStatement(childContext.body)
|
||||
}
|
||||
context.pushStatement(node.ssrCodegenNode!)
|
||||
context.pushStatement(node.ssrCodegenNode)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
)
|
||||
|
@ -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)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user