wip(ssr): proper scope analysis for ssr vnode slot fallback
This commit is contained in:
parent
b7a74d0439
commit
a51e710396
@ -546,6 +546,25 @@ export const locStub: SourceLocation = {
|
|||||||
end: { line: 1, column: 1, offset: 0 }
|
end: { line: 1, column: 1, offset: 0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function createRoot(
|
||||||
|
children: TemplateChildNode[],
|
||||||
|
loc = locStub
|
||||||
|
): RootNode {
|
||||||
|
return {
|
||||||
|
type: NodeTypes.ROOT,
|
||||||
|
children,
|
||||||
|
helpers: [],
|
||||||
|
components: [],
|
||||||
|
directives: [],
|
||||||
|
hoists: [],
|
||||||
|
imports: [],
|
||||||
|
cached: 0,
|
||||||
|
temps: 0,
|
||||||
|
codegenNode: undefined,
|
||||||
|
loc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function createArrayExpression(
|
export function createArrayExpression(
|
||||||
elements: ArrayExpression['elements'],
|
elements: ArrayExpression['elements'],
|
||||||
loc: SourceLocation = locStub
|
loc: SourceLocation = locStub
|
||||||
|
@ -11,6 +11,8 @@ export { baseParse, TextModes } from './parse'
|
|||||||
export {
|
export {
|
||||||
transform,
|
transform,
|
||||||
TransformContext,
|
TransformContext,
|
||||||
|
createTransformContext,
|
||||||
|
traverseNode,
|
||||||
createStructuralDirectiveTransform,
|
createStructuralDirectiveTransform,
|
||||||
NodeTransform,
|
NodeTransform,
|
||||||
StructuralDirectiveTransform,
|
StructuralDirectiveTransform,
|
||||||
|
@ -21,7 +21,8 @@ import {
|
|||||||
SourceLocation,
|
SourceLocation,
|
||||||
TextNode,
|
TextNode,
|
||||||
TemplateChildNode,
|
TemplateChildNode,
|
||||||
InterpolationNode
|
InterpolationNode,
|
||||||
|
createRoot
|
||||||
} from './ast'
|
} from './ast'
|
||||||
import { extend } from '@vue/shared'
|
import { extend } from '@vue/shared'
|
||||||
|
|
||||||
@ -72,20 +73,10 @@ export function baseParse(
|
|||||||
): RootNode {
|
): RootNode {
|
||||||
const context = createParserContext(content, options)
|
const context = createParserContext(content, options)
|
||||||
const start = getCursor(context)
|
const start = getCursor(context)
|
||||||
|
return createRoot(
|
||||||
return {
|
parseChildren(context, TextModes.DATA, []),
|
||||||
type: NodeTypes.ROOT,
|
getSelection(context, start)
|
||||||
children: parseChildren(context, TextModes.DATA, []),
|
)
|
||||||
helpers: [],
|
|
||||||
components: [],
|
|
||||||
directives: [],
|
|
||||||
hoists: [],
|
|
||||||
imports: [],
|
|
||||||
cached: 0,
|
|
||||||
temps: 0,
|
|
||||||
codegenNode: undefined,
|
|
||||||
loc: getSelection(context, start)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function createParserContext(
|
function createParserContext(
|
||||||
|
@ -109,7 +109,7 @@ export interface TransformContext extends Required<TransformOptions> {
|
|||||||
cache<T extends JSChildNode>(exp: T, isVNode?: boolean): CacheExpression | T
|
cache<T extends JSChildNode>(exp: T, isVNode?: boolean): CacheExpression | T
|
||||||
}
|
}
|
||||||
|
|
||||||
function createTransformContext(
|
export function createTransformContext(
|
||||||
root: RootNode,
|
root: RootNode,
|
||||||
{
|
{
|
||||||
prefixIdentifiers = false,
|
prefixIdentifiers = false,
|
||||||
|
@ -40,11 +40,15 @@ export const transformExpression: NodeTransform = (node, context) => {
|
|||||||
const dir = node.props[i]
|
const dir = node.props[i]
|
||||||
// do not process for v-on & v-for since they are special handled
|
// do not process for v-on & v-for since they are special handled
|
||||||
if (dir.type === NodeTypes.DIRECTIVE && dir.name !== 'for') {
|
if (dir.type === NodeTypes.DIRECTIVE && dir.name !== 'for') {
|
||||||
const exp = dir.exp as SimpleExpressionNode | undefined
|
const exp = dir.exp
|
||||||
const arg = dir.arg as SimpleExpressionNode | undefined
|
const arg = dir.arg
|
||||||
// do not process exp if this is v-on:arg - we need special handling
|
// do not process exp if this is v-on:arg - we need special handling
|
||||||
// for wrapping inline statements.
|
// for wrapping inline statements.
|
||||||
if (exp && !(dir.name === 'on' && arg)) {
|
if (
|
||||||
|
exp &&
|
||||||
|
exp.type === NodeTypes.SIMPLE_EXPRESSION &&
|
||||||
|
!(dir.name === 'on' && arg)
|
||||||
|
) {
|
||||||
dir.exp = processExpression(
|
dir.exp = processExpression(
|
||||||
exp,
|
exp,
|
||||||
context,
|
context,
|
||||||
@ -52,7 +56,7 @@ export const transformExpression: NodeTransform = (node, context) => {
|
|||||||
dir.name === 'slot'
|
dir.name === 'slot'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (arg && !arg.isStatic) {
|
if (arg && arg.type === NodeTypes.SIMPLE_EXPRESSION && !arg.isStatic) {
|
||||||
dir.arg = processExpression(arg, context)
|
dir.arg = processExpression(arg, context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,6 @@ import {
|
|||||||
baseParse,
|
baseParse,
|
||||||
CompilerOptions,
|
CompilerOptions,
|
||||||
CodegenResult,
|
CodegenResult,
|
||||||
isBuiltInType,
|
|
||||||
ParserOptions,
|
ParserOptions,
|
||||||
RootNode,
|
RootNode,
|
||||||
noopDirectiveTransform,
|
noopDirectiveTransform,
|
||||||
@ -18,21 +17,12 @@ import { transformVText } from './transforms/vText'
|
|||||||
import { transformModel } from './transforms/vModel'
|
import { transformModel } from './transforms/vModel'
|
||||||
import { transformOn } from './transforms/vOn'
|
import { transformOn } from './transforms/vOn'
|
||||||
import { transformShow } from './transforms/vShow'
|
import { transformShow } from './transforms/vShow'
|
||||||
import { TRANSITION, TRANSITION_GROUP } from './runtimeHelpers'
|
|
||||||
import { warnTransitionChildren } from './transforms/warnTransitionChildren'
|
import { warnTransitionChildren } from './transforms/warnTransitionChildren'
|
||||||
|
|
||||||
export const parserOptions = __BROWSER__
|
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 getDOMTransformPreset(
|
export function getDOMTransformPreset(
|
||||||
prefixIdentifiers?: boolean
|
prefixIdentifiers?: boolean
|
||||||
): TransformPreset {
|
): TransformPreset {
|
||||||
@ -71,8 +61,7 @@ export function compile(
|
|||||||
directiveTransforms: {
|
directiveTransforms: {
|
||||||
...directiveTransforms,
|
...directiveTransforms,
|
||||||
...(options.directiveTransforms || {})
|
...(options.directiveTransforms || {})
|
||||||
},
|
}
|
||||||
isBuiltInComponent: isBuiltInDOMComponent
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,9 +3,11 @@ import {
|
|||||||
ParserOptions,
|
ParserOptions,
|
||||||
ElementNode,
|
ElementNode,
|
||||||
Namespaces,
|
Namespaces,
|
||||||
NodeTypes
|
NodeTypes,
|
||||||
|
isBuiltInType
|
||||||
} from '@vue/compiler-core'
|
} from '@vue/compiler-core'
|
||||||
import { makeMap, isVoidTag, isHTMLTag, isSVGTag } from '@vue/shared'
|
import { makeMap, isVoidTag, isHTMLTag, isSVGTag } from '@vue/shared'
|
||||||
|
import { TRANSITION, TRANSITION_GROUP } from './runtimeHelpers'
|
||||||
|
|
||||||
const isRawTextContainer = /*#__PURE__*/ makeMap(
|
const isRawTextContainer = /*#__PURE__*/ makeMap(
|
||||||
'style,iframe,script,noscript',
|
'style,iframe,script,noscript',
|
||||||
@ -23,6 +25,14 @@ export const parserOptionsMinimal: ParserOptions = {
|
|||||||
isNativeTag: tag => isHTMLTag(tag) || isSVGTag(tag),
|
isNativeTag: tag => isHTMLTag(tag) || isSVGTag(tag),
|
||||||
isPreTag: tag => tag === 'pre',
|
isPreTag: tag => tag === 'pre',
|
||||||
|
|
||||||
|
isBuiltInComponent: (tag: string): symbol | undefined => {
|
||||||
|
if (isBuiltInType(tag, `Transition`)) {
|
||||||
|
return TRANSITION
|
||||||
|
} else if (isBuiltInType(tag, `TransitionGroup`)) {
|
||||||
|
return TRANSITION_GROUP
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/parsing.html#tree-construction-dispatcher
|
// https://html.spec.whatwg.org/multipage/parsing.html#tree-construction-dispatcher
|
||||||
getNamespace(tag: string, parent: ElementNode | undefined): DOMNamespaces {
|
getNamespace(tag: string, parent: ElementNode | undefined): DOMNamespaces {
|
||||||
let ns = parent ? parent.ns : DOMNamespaces.HTML
|
let ns = parent ? parent.ns : DOMNamespaces.HTML
|
||||||
|
@ -49,7 +49,7 @@ describe('ssr: components', () => {
|
|||||||
describe('slots', () => {
|
describe('slots', () => {
|
||||||
test('implicit default slot', () => {
|
test('implicit default slot', () => {
|
||||||
expect(compile(`<foo>hello<div/></foo>`).code).toMatchInlineSnapshot(`
|
expect(compile(`<foo>hello<div/></foo>`).code).toMatchInlineSnapshot(`
|
||||||
"const { resolveComponent } = require(\\"vue\\")
|
"const { resolveComponent, createVNode, createTextVNode } = require(\\"vue\\")
|
||||||
const { _ssrRenderComponent } = require(\\"@vue/server-renderer\\")
|
const { _ssrRenderComponent } = require(\\"@vue/server-renderer\\")
|
||||||
|
|
||||||
return function ssrRender(_ctx, _push, _parent) {
|
return function ssrRender(_ctx, _push, _parent) {
|
||||||
@ -75,7 +75,7 @@ describe('ssr: components', () => {
|
|||||||
test('explicit default slot', () => {
|
test('explicit default slot', () => {
|
||||||
expect(compile(`<foo v-slot="{ msg }">{{ msg + outer }}</foo>`).code)
|
expect(compile(`<foo v-slot="{ msg }">{{ msg + outer }}</foo>`).code)
|
||||||
.toMatchInlineSnapshot(`
|
.toMatchInlineSnapshot(`
|
||||||
"const { resolveComponent } = require(\\"vue\\")
|
"const { resolveComponent, createTextVNode } = require(\\"vue\\")
|
||||||
const { _ssrRenderComponent, _ssrInterpolate } = require(\\"@vue/server-renderer\\")
|
const { _ssrRenderComponent, _ssrInterpolate } = require(\\"@vue/server-renderer\\")
|
||||||
|
|
||||||
return function ssrRender(_ctx, _push, _parent) {
|
return function ssrRender(_ctx, _push, _parent) {
|
||||||
@ -87,7 +87,7 @@ describe('ssr: components', () => {
|
|||||||
_push(\`\${_ssrInterpolate(msg + _ctx.outer)}\`)
|
_push(\`\${_ssrInterpolate(msg + _ctx.outer)}\`)
|
||||||
} else {
|
} else {
|
||||||
return [
|
return [
|
||||||
createTextVNode(toDisplayString(_ctx.msg + _ctx.outer))
|
createTextVNode(toDisplayString(msg + _ctx.outer))
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -104,7 +104,7 @@ describe('ssr: components', () => {
|
|||||||
<template v-slot:named>bar</template>
|
<template v-slot:named>bar</template>
|
||||||
</foo>`).code
|
</foo>`).code
|
||||||
).toMatchInlineSnapshot(`
|
).toMatchInlineSnapshot(`
|
||||||
"const { resolveComponent } = require(\\"vue\\")
|
"const { resolveComponent, createTextVNode } = require(\\"vue\\")
|
||||||
const { _ssrRenderComponent } = require(\\"@vue/server-renderer\\")
|
const { _ssrRenderComponent } = require(\\"@vue/server-renderer\\")
|
||||||
|
|
||||||
return function ssrRender(_ctx, _push, _parent) {
|
return function ssrRender(_ctx, _push, _parent) {
|
||||||
@ -141,7 +141,7 @@ describe('ssr: components', () => {
|
|||||||
<template v-slot:named v-if="ok">foo</template>
|
<template v-slot:named v-if="ok">foo</template>
|
||||||
</foo>`).code
|
</foo>`).code
|
||||||
).toMatchInlineSnapshot(`
|
).toMatchInlineSnapshot(`
|
||||||
"const { resolveComponent, createSlots } = require(\\"vue\\")
|
"const { resolveComponent, createTextVNode, createSlots } = require(\\"vue\\")
|
||||||
const { _ssrRenderComponent } = require(\\"@vue/server-renderer\\")
|
const { _ssrRenderComponent } = require(\\"@vue/server-renderer\\")
|
||||||
|
|
||||||
return function ssrRender(_ctx, _push, _parent) {
|
return function ssrRender(_ctx, _push, _parent) {
|
||||||
@ -173,7 +173,7 @@ describe('ssr: components', () => {
|
|||||||
<template v-for="key in names" v-slot:[key]="{ msg }">{{ msg + key + bar }}</template>
|
<template v-for="key in names" v-slot:[key]="{ msg }">{{ msg + key + bar }}</template>
|
||||||
</foo>`).code
|
</foo>`).code
|
||||||
).toMatchInlineSnapshot(`
|
).toMatchInlineSnapshot(`
|
||||||
"const { resolveComponent, renderList, createSlots } = require(\\"vue\\")
|
"const { resolveComponent, createTextVNode, renderList, createSlots } = require(\\"vue\\")
|
||||||
const { _ssrRenderComponent, _ssrInterpolate } = require(\\"@vue/server-renderer\\")
|
const { _ssrRenderComponent, _ssrInterpolate } = require(\\"@vue/server-renderer\\")
|
||||||
|
|
||||||
return function ssrRender(_ctx, _push, _parent) {
|
return function ssrRender(_ctx, _push, _parent) {
|
||||||
@ -184,7 +184,13 @@ describe('ssr: components', () => {
|
|||||||
return {
|
return {
|
||||||
name: key,
|
name: key,
|
||||||
fn: ({ msg }, _push, _parent, _scopeId) => {
|
fn: ({ msg }, _push, _parent, _scopeId) => {
|
||||||
_push(\`\${_ssrInterpolate(msg + key + _ctx.bar)}\`)
|
if (_push) {
|
||||||
|
_push(\`\${_ssrInterpolate(msg + key + _ctx.bar)}\`)
|
||||||
|
} else {
|
||||||
|
return [
|
||||||
|
createTextVNode(toDisplayString(msg + _ctx.key + _ctx.bar))
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -193,6 +199,80 @@ describe('ssr: components', () => {
|
|||||||
`)
|
`)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('nested transform scoping in vnode branch', () => {
|
||||||
|
expect(
|
||||||
|
compile(`<foo>
|
||||||
|
<template v-slot:foo="{ list }">
|
||||||
|
<div v-if="ok">
|
||||||
|
<span v-for="i in list"></span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-slot:bar="{ ok }">
|
||||||
|
<div v-if="ok">
|
||||||
|
<span v-for="i in list"></span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</foo>`).code
|
||||||
|
).toMatchInlineSnapshot(`
|
||||||
|
"const { resolveComponent, renderList, openBlock, createBlock, Fragment, createVNode, createCommentVNode } = require(\\"vue\\")
|
||||||
|
const { _ssrRenderComponent, _ssrRenderList } = require(\\"@vue/server-renderer\\")
|
||||||
|
|
||||||
|
return function ssrRender(_ctx, _push, _parent) {
|
||||||
|
const _component_foo = resolveComponent(\\"foo\\")
|
||||||
|
|
||||||
|
_push(_ssrRenderComponent(_component_foo, null, {
|
||||||
|
foo: ({ list }, _push, _parent, _scopeId) => {
|
||||||
|
if (_push) {
|
||||||
|
if (_ctx.ok) {
|
||||||
|
_push(\`<div\${_scopeId}><!---->\`)
|
||||||
|
_ssrRenderList(list, (i) => {
|
||||||
|
_push(\`<span\${_scopeId}></span>\`)
|
||||||
|
})
|
||||||
|
_push(\`<!----></div>\`)
|
||||||
|
} else {
|
||||||
|
_push(\`<!---->\`)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return [
|
||||||
|
(openBlock(), (_ctx.ok)
|
||||||
|
? createBlock(\\"div\\", { key: 0 }, [
|
||||||
|
(openBlock(false), createBlock(Fragment, null, renderList(list, (i) => {
|
||||||
|
return (openBlock(), createBlock(\\"span\\"))
|
||||||
|
}), 256 /* UNKEYED_FRAGMENT */))
|
||||||
|
])
|
||||||
|
: createCommentVNode(\\"v-if\\", true))
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
bar: ({ ok }, _push, _parent, _scopeId) => {
|
||||||
|
if (_push) {
|
||||||
|
if (ok) {
|
||||||
|
_push(\`<div\${_scopeId}><!---->\`)
|
||||||
|
_ssrRenderList(_ctx.list, (i) => {
|
||||||
|
_push(\`<span\${_scopeId}></span>\`)
|
||||||
|
})
|
||||||
|
_push(\`<!----></div>\`)
|
||||||
|
} else {
|
||||||
|
_push(\`<!---->\`)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return [
|
||||||
|
(openBlock(), ok
|
||||||
|
? createBlock(\\"div\\", { key: 0 }, [
|
||||||
|
(openBlock(false), createBlock(Fragment, null, renderList(_ctx.list, (i) => {
|
||||||
|
return (openBlock(), createBlock(\\"span\\"))
|
||||||
|
}), 256 /* UNKEYED_FRAGMENT */))
|
||||||
|
])
|
||||||
|
: createCommentVNode(\\"v-if\\", true))
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_compiled: true
|
||||||
|
}, _parent))
|
||||||
|
}"
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
test('built-in fallthroughs', () => {
|
test('built-in fallthroughs', () => {
|
||||||
// no fragment
|
// no fragment
|
||||||
expect(compile(`<transition><div/></transition>`).code)
|
expect(compile(`<transition><div/></transition>`).code)
|
||||||
|
@ -23,7 +23,7 @@ describe('ssr: scopeId', () => {
|
|||||||
scopeId
|
scopeId
|
||||||
}).code
|
}).code
|
||||||
).toMatchInlineSnapshot(`
|
).toMatchInlineSnapshot(`
|
||||||
"const { resolveComponent } = require(\\"vue\\")
|
"const { resolveComponent, createTextVNode } = require(\\"vue\\")
|
||||||
const { _ssrRenderComponent } = require(\\"@vue/server-renderer\\")
|
const { _ssrRenderComponent } = require(\\"@vue/server-renderer\\")
|
||||||
|
|
||||||
return function ssrRender(_ctx, _push, _parent) {
|
return function ssrRender(_ctx, _push, _parent) {
|
||||||
@ -51,7 +51,7 @@ describe('ssr: scopeId', () => {
|
|||||||
scopeId
|
scopeId
|
||||||
}).code
|
}).code
|
||||||
).toMatchInlineSnapshot(`
|
).toMatchInlineSnapshot(`
|
||||||
"const { resolveComponent } = require(\\"vue\\")
|
"const { resolveComponent, createVNode } = require(\\"vue\\")
|
||||||
const { _ssrRenderComponent } = require(\\"@vue/server-renderer\\")
|
const { _ssrRenderComponent } = require(\\"@vue/server-renderer\\")
|
||||||
|
|
||||||
return function ssrRender(_ctx, _push, _parent) {
|
return function ssrRender(_ctx, _push, _parent) {
|
||||||
@ -79,12 +79,12 @@ describe('ssr: scopeId', () => {
|
|||||||
scopeId
|
scopeId
|
||||||
}).code
|
}).code
|
||||||
).toMatchInlineSnapshot(`
|
).toMatchInlineSnapshot(`
|
||||||
"const { resolveComponent } = require(\\"vue\\")
|
"const { resolveComponent, createVNode } = require(\\"vue\\")
|
||||||
const { _ssrRenderComponent } = require(\\"@vue/server-renderer\\")
|
const { _ssrRenderComponent } = require(\\"@vue/server-renderer\\")
|
||||||
|
|
||||||
return function ssrRender(_ctx, _push, _parent) {
|
return function ssrRender(_ctx, _push, _parent) {
|
||||||
const _component_bar = resolveComponent(\\"bar\\")
|
|
||||||
const _component_foo = resolveComponent(\\"foo\\")
|
const _component_foo = resolveComponent(\\"foo\\")
|
||||||
|
const _component_bar = resolveComponent(\\"bar\\")
|
||||||
|
|
||||||
_push(_ssrRenderComponent(_component_foo, null, {
|
_push(_ssrRenderComponent(_component_foo, null, {
|
||||||
default: (_, _push, _parent, _scopeId) => {
|
default: (_, _push, _parent, _scopeId) => {
|
||||||
|
@ -10,12 +10,14 @@ 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'
|
||||||
import { ssrTransformComponent } from './transforms/ssrTransformComponent'
|
import {
|
||||||
|
ssrTransformComponent,
|
||||||
|
rawOptionsMap
|
||||||
|
} from './transforms/ssrTransformComponent'
|
||||||
import { ssrTransformSlotOutlet } from './transforms/ssrTransformSlotOutlet'
|
import { ssrTransformSlotOutlet } from './transforms/ssrTransformSlotOutlet'
|
||||||
import { ssrTransformIf } from './transforms/ssrVIf'
|
import { ssrTransformIf } from './transforms/ssrVIf'
|
||||||
import { ssrTransformFor } from './transforms/ssrVFor'
|
import { ssrTransformFor } from './transforms/ssrVFor'
|
||||||
@ -41,6 +43,10 @@ export function compile(
|
|||||||
|
|
||||||
const ast = baseParse(template, options)
|
const ast = baseParse(template, options)
|
||||||
|
|
||||||
|
// Save raw options for AST. This is needed when performing sub-transforms
|
||||||
|
// on slot vnode branches.
|
||||||
|
rawOptionsMap.set(ast, options)
|
||||||
|
|
||||||
transform(ast, {
|
transform(ast, {
|
||||||
...options,
|
...options,
|
||||||
nodeTransforms: [
|
nodeTransforms: [
|
||||||
@ -66,8 +72,7 @@ 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
|
||||||
|
@ -17,13 +17,19 @@ import {
|
|||||||
createIfStatement,
|
createIfStatement,
|
||||||
createSimpleExpression,
|
createSimpleExpression,
|
||||||
getDOMTransformPreset,
|
getDOMTransformPreset,
|
||||||
transform,
|
|
||||||
createReturnStatement,
|
createReturnStatement,
|
||||||
ReturnStatement,
|
ReturnStatement,
|
||||||
Namespaces,
|
Namespaces,
|
||||||
locStub,
|
locStub,
|
||||||
RootNode,
|
RootNode,
|
||||||
TransformContext
|
TransformContext,
|
||||||
|
CompilerOptions,
|
||||||
|
TransformOptions,
|
||||||
|
createRoot,
|
||||||
|
createTransformContext,
|
||||||
|
traverseNode,
|
||||||
|
ExpressionNode,
|
||||||
|
TemplateNode
|
||||||
} from '@vue/compiler-dom'
|
} from '@vue/compiler-dom'
|
||||||
import { SSR_RENDER_COMPONENT } from '../runtimeHelpers'
|
import { SSR_RENDER_COMPONENT } from '../runtimeHelpers'
|
||||||
import {
|
import {
|
||||||
@ -55,12 +61,26 @@ export const ssrTransformComponent: NodeTransform = (node, context) => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const component = resolveComponentType(node, context, true /* ssr */)
|
||||||
|
if (isSymbol(component)) {
|
||||||
|
componentTypeMap.set(node, component)
|
||||||
|
return // built-in component: fallthrough
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the fallback vnode-based branch for the component's slots.
|
||||||
|
// We need to clone the node into a fresh copy and use the buildSlots' logic
|
||||||
|
// to get access to the children of each slot. We then compile them with
|
||||||
|
// a child transform pipeline using vnode-based transforms (instead of ssr-
|
||||||
|
// based ones), and save the result branch (a ReturnStatement) in an array.
|
||||||
|
// The branch is retrieved when processing slots again in ssr mode.
|
||||||
|
const vnodeBranches: ReturnStatement[] = []
|
||||||
|
const clonedNode = clone(node)
|
||||||
|
|
||||||
return function ssrPostTransformComponent() {
|
return function ssrPostTransformComponent() {
|
||||||
const component = resolveComponentType(node, context, true /* ssr */)
|
buildSlots(clonedNode, context, (props, children) => {
|
||||||
if (isSymbol(component)) {
|
vnodeBranches.push(createVNodeSlotBranch(props, children, context))
|
||||||
componentTypeMap.set(node, component)
|
return createFunctionExpression(undefined)
|
||||||
return // built-in component: fallthrough
|
})
|
||||||
}
|
|
||||||
|
|
||||||
const props =
|
const props =
|
||||||
node.props.length > 0
|
node.props.length > 0
|
||||||
@ -86,7 +106,7 @@ export const ssrTransformComponent: NodeTransform = (node, context) => {
|
|||||||
// build the children using normal vnode-based transforms
|
// build the children using normal vnode-based transforms
|
||||||
// TODO fixme: `children` here has already been mutated at this point
|
// TODO fixme: `children` here has already been mutated at this point
|
||||||
// so the sub-transform runs into errors :/
|
// so the sub-transform runs into errors :/
|
||||||
vnodeBranch: createVNodeSlotBranch(clone(children), context)
|
vnodeBranch: vnodeBranches[wipEntries.length]
|
||||||
})
|
})
|
||||||
return fn
|
return fn
|
||||||
}
|
}
|
||||||
@ -143,47 +163,79 @@ export function ssrProcessComponent(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function createVNodeSlotBranch(
|
export const rawOptionsMap = new WeakMap<RootNode, CompilerOptions>()
|
||||||
children: TemplateChildNode[],
|
|
||||||
context: TransformContext
|
|
||||||
): ReturnStatement {
|
|
||||||
// we need to process the slot children using client-side transforms.
|
|
||||||
// in order to do that we need to construct a fresh root.
|
|
||||||
// in addition, wrap the children with a wrapper template for proper child
|
|
||||||
// treatment.
|
|
||||||
const { root } = context
|
|
||||||
const childRoot: RootNode = {
|
|
||||||
...root,
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
type: NodeTypes.ELEMENT,
|
|
||||||
ns: Namespaces.HTML,
|
|
||||||
tag: 'template',
|
|
||||||
tagType: ElementTypes.TEMPLATE,
|
|
||||||
isSelfClosing: false,
|
|
||||||
props: [],
|
|
||||||
children,
|
|
||||||
loc: locStub,
|
|
||||||
codegenNode: undefined
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
const [nodeTransforms, directiveTransforms] = getDOMTransformPreset(true)
|
|
||||||
transform(childRoot, {
|
|
||||||
...context, // copy transform options on context
|
|
||||||
nodeTransforms,
|
|
||||||
directiveTransforms
|
|
||||||
})
|
|
||||||
|
|
||||||
// merge helpers/components/directives/imports from the childRoot
|
const [vnodeNodeTransforms, vnodeDirectiveTransforms] = getDOMTransformPreset(
|
||||||
// back to current root
|
true
|
||||||
|
)
|
||||||
|
|
||||||
|
function createVNodeSlotBranch(
|
||||||
|
props: ExpressionNode | undefined,
|
||||||
|
children: TemplateChildNode[],
|
||||||
|
parentContext: TransformContext
|
||||||
|
): ReturnStatement {
|
||||||
|
// apply a sub-transform using vnode-based transforms.
|
||||||
|
const rawOptions = rawOptionsMap.get(parentContext.root)!
|
||||||
|
const subOptions = {
|
||||||
|
...rawOptions,
|
||||||
|
// overwrite with vnode-based transforms
|
||||||
|
nodeTransforms: [
|
||||||
|
...vnodeNodeTransforms,
|
||||||
|
...(rawOptions.nodeTransforms || [])
|
||||||
|
],
|
||||||
|
directiveTransforms: {
|
||||||
|
...vnodeDirectiveTransforms,
|
||||||
|
...(rawOptions.directiveTransforms || {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// wrap the children with a wrapper template for proper children treatment.
|
||||||
|
const wrapperNode: TemplateNode = {
|
||||||
|
type: NodeTypes.ELEMENT,
|
||||||
|
ns: Namespaces.HTML,
|
||||||
|
tag: 'template',
|
||||||
|
tagType: ElementTypes.TEMPLATE,
|
||||||
|
isSelfClosing: false,
|
||||||
|
// important: provide v-slot="props" on the wrapper for proper
|
||||||
|
// scope analysis
|
||||||
|
props: [
|
||||||
|
{
|
||||||
|
type: NodeTypes.DIRECTIVE,
|
||||||
|
name: 'slot',
|
||||||
|
exp: props,
|
||||||
|
arg: undefined,
|
||||||
|
modifiers: [],
|
||||||
|
loc: locStub
|
||||||
|
}
|
||||||
|
],
|
||||||
|
children,
|
||||||
|
loc: locStub,
|
||||||
|
codegenNode: undefined
|
||||||
|
}
|
||||||
|
subTransform(wrapperNode, subOptions, parentContext)
|
||||||
|
return createReturnStatement(children)
|
||||||
|
}
|
||||||
|
|
||||||
|
function subTransform(
|
||||||
|
node: TemplateChildNode,
|
||||||
|
options: TransformOptions,
|
||||||
|
parentContext: TransformContext
|
||||||
|
) {
|
||||||
|
const childRoot = createRoot([node])
|
||||||
|
const childContext = createTransformContext(childRoot, options)
|
||||||
|
// inherit parent scope analysis state
|
||||||
|
childContext.scopes = { ...parentContext.scopes }
|
||||||
|
childContext.identifiers = { ...parentContext.identifiers }
|
||||||
|
// traverse
|
||||||
|
traverseNode(childRoot, childContext)
|
||||||
|
// merge helpers/components/directives/imports into parent context
|
||||||
;(['helpers', 'components', 'directives', 'imports'] as const).forEach(
|
;(['helpers', 'components', 'directives', 'imports'] as const).forEach(
|
||||||
key => {
|
key => {
|
||||||
root[key] = [...new Set([...root[key], ...childRoot[key]])] as any
|
childContext[key].forEach((value: any) => {
|
||||||
|
;(parentContext[key] as any).add(value)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
return createReturnStatement(children)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function clone(v: any): any {
|
function clone(v: any): any {
|
||||||
@ -192,7 +244,7 @@ function clone(v: any): any {
|
|||||||
} else if (isObject(v)) {
|
} else if (isObject(v)) {
|
||||||
const res: any = {}
|
const res: any = {}
|
||||||
for (const key in v) {
|
for (const key in v) {
|
||||||
res[key] = v[key]
|
res[key] = clone(v[key])
|
||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
} else {
|
} else {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user