wip(compiler-ssr): built-in component fallthrough
This commit is contained in:
parent
9cfbab0686
commit
3c27bf6133
@ -79,6 +79,8 @@ function createCodegenContext(
|
|||||||
sourceMap = false,
|
sourceMap = false,
|
||||||
filename = `template.vue.html`,
|
filename = `template.vue.html`,
|
||||||
scopeId = null,
|
scopeId = null,
|
||||||
|
runtimeGlobalName = `Vue`,
|
||||||
|
runtimeModuleName = `vue`,
|
||||||
ssr = false
|
ssr = false
|
||||||
}: CodegenOptions
|
}: CodegenOptions
|
||||||
): CodegenContext {
|
): CodegenContext {
|
||||||
@ -88,6 +90,8 @@ function createCodegenContext(
|
|||||||
sourceMap,
|
sourceMap,
|
||||||
filename,
|
filename,
|
||||||
scopeId,
|
scopeId,
|
||||||
|
runtimeGlobalName,
|
||||||
|
runtimeModuleName,
|
||||||
ssr,
|
ssr,
|
||||||
source: ast.loc.source,
|
source: ast.loc.source,
|
||||||
code: ``,
|
code: ``,
|
||||||
@ -275,8 +279,18 @@ export function generate(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function genFunctionPreamble(ast: RootNode, context: CodegenContext) {
|
function genFunctionPreamble(ast: RootNode, context: CodegenContext) {
|
||||||
const { ssr, helper, prefixIdentifiers, push, newline } = context
|
const {
|
||||||
const VueBinding = ssr ? `require("vue")` : `Vue`
|
ssr,
|
||||||
|
helper,
|
||||||
|
prefixIdentifiers,
|
||||||
|
push,
|
||||||
|
newline,
|
||||||
|
runtimeModuleName,
|
||||||
|
runtimeGlobalName
|
||||||
|
} = context
|
||||||
|
const VueBinding = ssr
|
||||||
|
? `require(${JSON.stringify(runtimeModuleName)})`
|
||||||
|
: runtimeGlobalName
|
||||||
// Generate const declaration for helpers
|
// Generate const declaration for helpers
|
||||||
// In prefix mode, we place the const declaration at top so it's done
|
// 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
|
// only once; But if we not prefixing, we place the declaration inside the
|
||||||
@ -319,7 +333,7 @@ function genModulePreamble(
|
|||||||
context: CodegenContext,
|
context: CodegenContext,
|
||||||
genScopeId: boolean
|
genScopeId: boolean
|
||||||
) {
|
) {
|
||||||
const { push, helper, newline, scopeId } = context
|
const { push, helper, newline, scopeId, runtimeModuleName } = context
|
||||||
// generate import statements for helpers
|
// generate import statements for helpers
|
||||||
if (genScopeId) {
|
if (genScopeId) {
|
||||||
ast.helpers.push(WITH_SCOPE_ID)
|
ast.helpers.push(WITH_SCOPE_ID)
|
||||||
@ -328,7 +342,11 @@ function genModulePreamble(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (ast.helpers.length) {
|
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) {
|
if (!__BROWSER__ && ast.ssrHelpers && ast.ssrHelpers.length) {
|
||||||
push(
|
push(
|
||||||
|
@ -71,6 +71,9 @@ export interface CodegenOptions {
|
|||||||
scopeId?: string | null
|
scopeId?: string | null
|
||||||
// we need to know about this to generate proper preambles
|
// we need to know about this to generate proper preambles
|
||||||
prefixIdentifiers?: boolean
|
prefixIdentifiers?: boolean
|
||||||
|
// for specifying where to import helpers
|
||||||
|
runtimeModuleName?: string
|
||||||
|
runtimeGlobalName?: string
|
||||||
// generate ssr-specific code?
|
// generate ssr-specific code?
|
||||||
ssr?: boolean
|
ssr?: boolean
|
||||||
}
|
}
|
||||||
|
@ -182,7 +182,8 @@ export const transformElement: NodeTransform = (node, context) => {
|
|||||||
|
|
||||||
export function resolveComponentType(
|
export function resolveComponentType(
|
||||||
node: ComponentNode,
|
node: ComponentNode,
|
||||||
context: TransformContext
|
context: TransformContext,
|
||||||
|
ssr = false
|
||||||
) {
|
) {
|
||||||
const { tag } = node
|
const { tag } = node
|
||||||
|
|
||||||
@ -211,7 +212,9 @@ export function resolveComponentType(
|
|||||||
// 2. built-in components (Portal, Transition, KeepAlive, Suspense...)
|
// 2. built-in components (Portal, Transition, KeepAlive, Suspense...)
|
||||||
const builtIn = isCoreComponent(tag) || context.isBuiltInComponent(tag)
|
const builtIn = isCoreComponent(tag) || context.isBuiltInComponent(tag)
|
||||||
if (builtIn) {
|
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
|
return builtIn
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,6 +22,14 @@ export const parserOptions = __BROWSER__
|
|||||||
? parserOptionsMinimal
|
? parserOptionsMinimal
|
||||||
: parserOptionsStandard
|
: 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(
|
export function compile(
|
||||||
template: string,
|
template: string,
|
||||||
options: CompilerOptions = {}
|
options: CompilerOptions = {}
|
||||||
@ -39,13 +47,7 @@ export function compile(
|
|||||||
show: transformShow,
|
show: transformShow,
|
||||||
...(options.directiveTransforms || {})
|
...(options.directiveTransforms || {})
|
||||||
},
|
},
|
||||||
isBuiltInComponent: tag => {
|
isBuiltInComponent: isBuiltInDOMComponent
|
||||||
if (isBuiltInType(tag, `Transition`)) {
|
|
||||||
return TRANSITION
|
|
||||||
} else if (isBuiltInType(tag, `TransitionGroup`)) {
|
|
||||||
return TRANSITION_GROUP
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,6 +58,7 @@ export function parse(template: string, options: ParserOptions = {}): RootNode {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export * from './runtimeHelpers'
|
||||||
export { transformStyle } from './transforms/transformStyle'
|
export { transformStyle } from './transforms/transformStyle'
|
||||||
export { createDOMCompilerError, DOMErrorCodes } from './errors'
|
export { createDOMCompilerError, DOMErrorCodes } from './errors'
|
||||||
export * from '@vue/compiler-core'
|
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,
|
trackSlotScopes,
|
||||||
noopDirectiveTransform,
|
noopDirectiveTransform,
|
||||||
transformBind,
|
transformBind,
|
||||||
transformStyle
|
transformStyle,
|
||||||
|
isBuiltInDOMComponent
|
||||||
} from '@vue/compiler-dom'
|
} from '@vue/compiler-dom'
|
||||||
import { ssrCodegenTransform } from './ssrCodegenTransform'
|
import { ssrCodegenTransform } from './ssrCodegenTransform'
|
||||||
import { ssrTransformElement } from './transforms/ssrTransformElement'
|
import { ssrTransformElement } from './transforms/ssrTransformElement'
|
||||||
@ -64,7 +65,8 @@ export function compile(
|
|||||||
cloak: noopDirectiveTransform,
|
cloak: noopDirectiveTransform,
|
||||||
once: noopDirectiveTransform,
|
once: noopDirectiveTransform,
|
||||||
...(options.directiveTransforms || {}) // user transforms
|
...(options.directiveTransforms || {}) // user transforms
|
||||||
}
|
},
|
||||||
|
isBuiltInComponent: isBuiltInDOMComponent
|
||||||
})
|
})
|
||||||
|
|
||||||
// traverse the template AST and convert into SSR codegen AST
|
// 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) {
|
export function ssrCodegenTransform(ast: RootNode, options: CompilerOptions) {
|
||||||
const context = createSSRTransformContext(options)
|
const context = createSSRTransformContext(options)
|
||||||
|
|
||||||
const isFragment =
|
const isFragment =
|
||||||
ast.children.length > 1 && !ast.children.every(c => isText(c))
|
ast.children.length > 1 && !ast.children.every(c => isText(c))
|
||||||
if (isFragment) {
|
processChildren(ast.children, context, isFragment)
|
||||||
context.pushStringPart(`<!---->`)
|
|
||||||
}
|
|
||||||
processChildren(ast.children, context)
|
|
||||||
if (isFragment) {
|
|
||||||
context.pushStringPart(`<!---->`)
|
|
||||||
}
|
|
||||||
|
|
||||||
ast.codegenNode = createBlockStatement(context.body)
|
ast.codegenNode = createBlockStatement(context.body)
|
||||||
|
|
||||||
// Finalize helpers.
|
// Finalize helpers.
|
||||||
@ -99,8 +91,12 @@ export function createChildContext(
|
|||||||
|
|
||||||
export function processChildren(
|
export function processChildren(
|
||||||
children: TemplateChildNode[],
|
children: TemplateChildNode[],
|
||||||
context: SSRTransformContext
|
context: SSRTransformContext,
|
||||||
|
asFragment = false
|
||||||
) {
|
) {
|
||||||
|
if (asFragment) {
|
||||||
|
context.pushStringPart(`<!---->`)
|
||||||
|
}
|
||||||
const isVoidTag = context.options.isVoidTag || NO
|
const isVoidTag = context.options.isVoidTag || NO
|
||||||
for (let i = 0; i < children.length; i++) {
|
for (let i = 0; i < children.length; i++) {
|
||||||
const child = children[i]
|
const child = children[i]
|
||||||
@ -135,4 +131,7 @@ export function processChildren(
|
|||||||
ssrProcessFor(child, context)
|
ssrProcessFor(child, context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (asFragment) {
|
||||||
|
context.pushStringPart(`<!---->`)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,14 +6,15 @@ import {
|
|||||||
resolveComponentType,
|
resolveComponentType,
|
||||||
buildProps,
|
buildProps,
|
||||||
ComponentNode,
|
ComponentNode,
|
||||||
PORTAL,
|
|
||||||
SUSPENSE,
|
|
||||||
SlotFnBuilder,
|
SlotFnBuilder,
|
||||||
createFunctionExpression,
|
createFunctionExpression,
|
||||||
createBlockStatement,
|
createBlockStatement,
|
||||||
buildSlots,
|
buildSlots,
|
||||||
FunctionExpression,
|
FunctionExpression,
|
||||||
TemplateChildNode
|
TemplateChildNode,
|
||||||
|
PORTAL,
|
||||||
|
SUSPENSE,
|
||||||
|
TRANSITION_GROUP
|
||||||
} from '@vue/compiler-dom'
|
} from '@vue/compiler-dom'
|
||||||
import { SSR_RENDER_COMPONENT } from '../runtimeHelpers'
|
import { SSR_RENDER_COMPONENT } from '../runtimeHelpers'
|
||||||
import {
|
import {
|
||||||
@ -34,6 +35,8 @@ interface WIPSlotEntry {
|
|||||||
children: TemplateChildNode[]
|
children: TemplateChildNode[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const componentTypeMap = new WeakMap<ComponentNode, symbol>()
|
||||||
|
|
||||||
export const ssrTransformComponent: NodeTransform = (node, context) => {
|
export const ssrTransformComponent: NodeTransform = (node, context) => {
|
||||||
if (
|
if (
|
||||||
node.type !== NodeTypes.ELEMENT ||
|
node.type !== NodeTypes.ELEMENT ||
|
||||||
@ -43,18 +46,10 @@ export const ssrTransformComponent: NodeTransform = (node, context) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return function ssrPostTransformComponent() {
|
return function ssrPostTransformComponent() {
|
||||||
const component = resolveComponentType(node, context)
|
const component = resolveComponentType(node, context, true /* ssr */)
|
||||||
|
|
||||||
if (isSymbol(component)) {
|
if (isSymbol(component)) {
|
||||||
// built-in compoonent
|
componentTypeMap.set(node, component)
|
||||||
if (component === PORTAL) {
|
return // built-in component: fallthrough
|
||||||
// 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
|
// note we are not passing ssr: true here because for components, v-on
|
||||||
@ -98,13 +93,28 @@ export function ssrProcessComponent(
|
|||||||
node: ComponentNode,
|
node: ComponentNode,
|
||||||
context: SSRTransformContext
|
context: SSRTransformContext
|
||||||
) {
|
) {
|
||||||
// finish up slot function expressions from the 1st pass.
|
if (!node.ssrCodegenNode) {
|
||||||
const wipEntries = wipMap.get(node) || []
|
// this is a built-in component that fell-through.
|
||||||
for (let i = 0; i < wipEntries.length; i++) {
|
// just render its children.
|
||||||
const { fn, children } = wipEntries[i]
|
const component = componentTypeMap.get(node)!
|
||||||
const childContext = createChildContext(context)
|
|
||||||
processChildren(children, childContext)
|
if (component === PORTAL) {
|
||||||
fn.body = createBlockStatement(childContext.body)
|
// 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!)
|
|
||||||
}
|
}
|
||||||
|
@ -27,13 +27,7 @@ export function ssrProcessFor(node: ForNode, context: SSRTransformContext) {
|
|||||||
const childContext = createChildContext(context)
|
const childContext = createChildContext(context)
|
||||||
const needFragmentWrapper =
|
const needFragmentWrapper =
|
||||||
node.children.length !== 1 || node.children[0].type !== NodeTypes.ELEMENT
|
node.children.length !== 1 || node.children[0].type !== NodeTypes.ELEMENT
|
||||||
if (needFragmentWrapper) {
|
processChildren(node.children, childContext, needFragmentWrapper)
|
||||||
childContext.pushStringPart(`<!---->`)
|
|
||||||
}
|
|
||||||
processChildren(node.children, childContext)
|
|
||||||
if (needFragmentWrapper) {
|
|
||||||
childContext.pushStringPart(`<!---->`)
|
|
||||||
}
|
|
||||||
const renderLoop = createFunctionExpression(
|
const renderLoop = createFunctionExpression(
|
||||||
createForLoopParams(node.parseResult)
|
createForLoopParams(node.parseResult)
|
||||||
)
|
)
|
||||||
|
@ -64,12 +64,6 @@ function processIfBranch(
|
|||||||
// optimize away nested fragments when the only child is a ForNode
|
// optimize away nested fragments when the only child is a ForNode
|
||||||
!(children.length === 1 && children[0].type === NodeTypes.FOR)
|
!(children.length === 1 && children[0].type === NodeTypes.FOR)
|
||||||
const childContext = createChildContext(context)
|
const childContext = createChildContext(context)
|
||||||
if (needFragmentWrapper) {
|
processChildren(children, childContext, needFragmentWrapper)
|
||||||
childContext.pushStringPart(`<!---->`)
|
|
||||||
}
|
|
||||||
processChildren(children, childContext)
|
|
||||||
if (needFragmentWrapper) {
|
|
||||||
childContext.pushStringPart(`<!---->`)
|
|
||||||
}
|
|
||||||
return createBlockStatement(childContext.body)
|
return createBlockStatement(childContext.body)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user