Skip to content

Commit a51e710

Browse files
committed
wip(ssr): proper scope analysis for ssr vnode slot fallback
1 parent b7a74d0 commit a51e710

File tree

11 files changed

+241
-89
lines changed

11 files changed

+241
-89
lines changed

packages/compiler-core/src/ast.ts

+19
Original file line numberDiff line numberDiff line change
@@ -546,6 +546,25 @@ export const locStub: SourceLocation = {
546546
end: { line: 1, column: 1, offset: 0 }
547547
}
548548

549+
export function createRoot(
550+
children: TemplateChildNode[],
551+
loc = locStub
552+
): RootNode {
553+
return {
554+
type: NodeTypes.ROOT,
555+
children,
556+
helpers: [],
557+
components: [],
558+
directives: [],
559+
hoists: [],
560+
imports: [],
561+
cached: 0,
562+
temps: 0,
563+
codegenNode: undefined,
564+
loc
565+
}
566+
}
567+
549568
export function createArrayExpression(
550569
elements: ArrayExpression['elements'],
551570
loc: SourceLocation = locStub

packages/compiler-core/src/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ export { baseParse, TextModes } from './parse'
1111
export {
1212
transform,
1313
TransformContext,
14+
createTransformContext,
15+
traverseNode,
1416
createStructuralDirectiveTransform,
1517
NodeTransform,
1618
StructuralDirectiveTransform,

packages/compiler-core/src/parse.ts

+6-15
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ import {
2121
SourceLocation,
2222
TextNode,
2323
TemplateChildNode,
24-
InterpolationNode
24+
InterpolationNode,
25+
createRoot
2526
} from './ast'
2627
import { extend } from '@vue/shared'
2728

@@ -72,20 +73,10 @@ export function baseParse(
7273
): RootNode {
7374
const context = createParserContext(content, options)
7475
const start = getCursor(context)
75-
76-
return {
77-
type: NodeTypes.ROOT,
78-
children: parseChildren(context, TextModes.DATA, []),
79-
helpers: [],
80-
components: [],
81-
directives: [],
82-
hoists: [],
83-
imports: [],
84-
cached: 0,
85-
temps: 0,
86-
codegenNode: undefined,
87-
loc: getSelection(context, start)
88-
}
76+
return createRoot(
77+
parseChildren(context, TextModes.DATA, []),
78+
getSelection(context, start)
79+
)
8980
}
9081

9182
function createParserContext(

packages/compiler-core/src/transform.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ export interface TransformContext extends Required<TransformOptions> {
109109
cache<T extends JSChildNode>(exp: T, isVNode?: boolean): CacheExpression | T
110110
}
111111

112-
function createTransformContext(
112+
export function createTransformContext(
113113
root: RootNode,
114114
{
115115
prefixIdentifiers = false,

packages/compiler-core/src/transforms/transformExpression.ts

+8-4
Original file line numberDiff line numberDiff line change
@@ -40,19 +40,23 @@ export const transformExpression: NodeTransform = (node, context) => {
4040
const dir = node.props[i]
4141
// do not process for v-on & v-for since they are special handled
4242
if (dir.type === NodeTypes.DIRECTIVE && dir.name !== 'for') {
43-
const exp = dir.exp as SimpleExpressionNode | undefined
44-
const arg = dir.arg as SimpleExpressionNode | undefined
43+
const exp = dir.exp
44+
const arg = dir.arg
4545
// do not process exp if this is v-on:arg - we need special handling
4646
// for wrapping inline statements.
47-
if (exp && !(dir.name === 'on' && arg)) {
47+
if (
48+
exp &&
49+
exp.type === NodeTypes.SIMPLE_EXPRESSION &&
50+
!(dir.name === 'on' && arg)
51+
) {
4852
dir.exp = processExpression(
4953
exp,
5054
context,
5155
// slot args must be processed as function params
5256
dir.name === 'slot'
5357
)
5458
}
55-
if (arg && !arg.isStatic) {
59+
if (arg && arg.type === NodeTypes.SIMPLE_EXPRESSION && !arg.isStatic) {
5660
dir.arg = processExpression(arg, context)
5761
}
5862
}

packages/compiler-dom/src/index.ts

+1-12
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import {
33
baseParse,
44
CompilerOptions,
55
CodegenResult,
6-
isBuiltInType,
76
ParserOptions,
87
RootNode,
98
noopDirectiveTransform,
@@ -18,21 +17,12 @@ import { transformVText } from './transforms/vText'
1817
import { transformModel } from './transforms/vModel'
1918
import { transformOn } from './transforms/vOn'
2019
import { transformShow } from './transforms/vShow'
21-
import { TRANSITION, TRANSITION_GROUP } from './runtimeHelpers'
2220
import { warnTransitionChildren } from './transforms/warnTransitionChildren'
2321

2422
export const parserOptions = __BROWSER__
2523
? parserOptionsMinimal
2624
: parserOptionsStandard
2725

28-
export const isBuiltInDOMComponent = (tag: string): symbol | undefined => {
29-
if (isBuiltInType(tag, `Transition`)) {
30-
return TRANSITION
31-
} else if (isBuiltInType(tag, `TransitionGroup`)) {
32-
return TRANSITION_GROUP
33-
}
34-
}
35-
3626
export function getDOMTransformPreset(
3727
prefixIdentifiers?: boolean
3828
): TransformPreset {
@@ -71,8 +61,7 @@ export function compile(
7161
directiveTransforms: {
7262
...directiveTransforms,
7363
...(options.directiveTransforms || {})
74-
},
75-
isBuiltInComponent: isBuiltInDOMComponent
64+
}
7665
})
7766
}
7867

packages/compiler-dom/src/parserOptionsMinimal.ts

+11-1
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@ import {
33
ParserOptions,
44
ElementNode,
55
Namespaces,
6-
NodeTypes
6+
NodeTypes,
7+
isBuiltInType
78
} from '@vue/compiler-core'
89
import { makeMap, isVoidTag, isHTMLTag, isSVGTag } from '@vue/shared'
10+
import { TRANSITION, TRANSITION_GROUP } from './runtimeHelpers'
911

1012
const isRawTextContainer = /*#__PURE__*/ makeMap(
1113
'style,iframe,script,noscript',
@@ -23,6 +25,14 @@ export const parserOptionsMinimal: ParserOptions = {
2325
isNativeTag: tag => isHTMLTag(tag) || isSVGTag(tag),
2426
isPreTag: tag => tag === 'pre',
2527

28+
isBuiltInComponent: (tag: string): symbol | undefined => {
29+
if (isBuiltInType(tag, `Transition`)) {
30+
return TRANSITION
31+
} else if (isBuiltInType(tag, `TransitionGroup`)) {
32+
return TRANSITION_GROUP
33+
}
34+
},
35+
2636
// https://html.spec.whatwg.org/multipage/parsing.html#tree-construction-dispatcher
2737
getNamespace(tag: string, parent: ElementNode | undefined): DOMNamespaces {
2838
let ns = parent ? parent.ns : DOMNamespaces.HTML

packages/compiler-ssr/__tests__/ssrComponent.spec.ts

+87-7
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ describe('ssr: components', () => {
4949
describe('slots', () => {
5050
test('implicit default slot', () => {
5151
expect(compile(`<foo>hello<div/></foo>`).code).toMatchInlineSnapshot(`
52-
"const { resolveComponent } = require(\\"vue\\")
52+
"const { resolveComponent, createVNode, createTextVNode } = require(\\"vue\\")
5353
const { _ssrRenderComponent } = require(\\"@vue/server-renderer\\")
5454
5555
return function ssrRender(_ctx, _push, _parent) {
@@ -75,7 +75,7 @@ describe('ssr: components', () => {
7575
test('explicit default slot', () => {
7676
expect(compile(`<foo v-slot="{ msg }">{{ msg + outer }}</foo>`).code)
7777
.toMatchInlineSnapshot(`
78-
"const { resolveComponent } = require(\\"vue\\")
78+
"const { resolveComponent, createTextVNode } = require(\\"vue\\")
7979
const { _ssrRenderComponent, _ssrInterpolate } = require(\\"@vue/server-renderer\\")
8080
8181
return function ssrRender(_ctx, _push, _parent) {
@@ -87,7 +87,7 @@ describe('ssr: components', () => {
8787
_push(\`\${_ssrInterpolate(msg + _ctx.outer)}\`)
8888
} else {
8989
return [
90-
createTextVNode(toDisplayString(_ctx.msg + _ctx.outer))
90+
createTextVNode(toDisplayString(msg + _ctx.outer))
9191
]
9292
}
9393
},
@@ -104,7 +104,7 @@ describe('ssr: components', () => {
104104
<template v-slot:named>bar</template>
105105
</foo>`).code
106106
).toMatchInlineSnapshot(`
107-
"const { resolveComponent } = require(\\"vue\\")
107+
"const { resolveComponent, createTextVNode } = require(\\"vue\\")
108108
const { _ssrRenderComponent } = require(\\"@vue/server-renderer\\")
109109
110110
return function ssrRender(_ctx, _push, _parent) {
@@ -141,7 +141,7 @@ describe('ssr: components', () => {
141141
<template v-slot:named v-if="ok">foo</template>
142142
</foo>`).code
143143
).toMatchInlineSnapshot(`
144-
"const { resolveComponent, createSlots } = require(\\"vue\\")
144+
"const { resolveComponent, createTextVNode, createSlots } = require(\\"vue\\")
145145
const { _ssrRenderComponent } = require(\\"@vue/server-renderer\\")
146146
147147
return function ssrRender(_ctx, _push, _parent) {
@@ -173,7 +173,7 @@ describe('ssr: components', () => {
173173
<template v-for="key in names" v-slot:[key]="{ msg }">{{ msg + key + bar }}</template>
174174
</foo>`).code
175175
).toMatchInlineSnapshot(`
176-
"const { resolveComponent, renderList, createSlots } = require(\\"vue\\")
176+
"const { resolveComponent, createTextVNode, renderList, createSlots } = require(\\"vue\\")
177177
const { _ssrRenderComponent, _ssrInterpolate } = require(\\"@vue/server-renderer\\")
178178
179179
return function ssrRender(_ctx, _push, _parent) {
@@ -184,7 +184,13 @@ describe('ssr: components', () => {
184184
return {
185185
name: key,
186186
fn: ({ msg }, _push, _parent, _scopeId) => {
187-
_push(\`\${_ssrInterpolate(msg + key + _ctx.bar)}\`)
187+
if (_push) {
188+
_push(\`\${_ssrInterpolate(msg + key + _ctx.bar)}\`)
189+
} else {
190+
return [
191+
createTextVNode(toDisplayString(msg + _ctx.key + _ctx.bar))
192+
]
193+
}
188194
}
189195
}
190196
})
@@ -193,6 +199,80 @@ describe('ssr: components', () => {
193199
`)
194200
})
195201

202+
test('nested transform scoping in vnode branch', () => {
203+
expect(
204+
compile(`<foo>
205+
<template v-slot:foo="{ list }">
206+
<div v-if="ok">
207+
<span v-for="i in list"></span>
208+
</div>
209+
</template>
210+
<template v-slot:bar="{ ok }">
211+
<div v-if="ok">
212+
<span v-for="i in list"></span>
213+
</div>
214+
</template>
215+
</foo>`).code
216+
).toMatchInlineSnapshot(`
217+
"const { resolveComponent, renderList, openBlock, createBlock, Fragment, createVNode, createCommentVNode } = require(\\"vue\\")
218+
const { _ssrRenderComponent, _ssrRenderList } = require(\\"@vue/server-renderer\\")
219+
220+
return function ssrRender(_ctx, _push, _parent) {
221+
const _component_foo = resolveComponent(\\"foo\\")
222+
223+
_push(_ssrRenderComponent(_component_foo, null, {
224+
foo: ({ list }, _push, _parent, _scopeId) => {
225+
if (_push) {
226+
if (_ctx.ok) {
227+
_push(\`<div\${_scopeId}><!---->\`)
228+
_ssrRenderList(list, (i) => {
229+
_push(\`<span\${_scopeId}></span>\`)
230+
})
231+
_push(\`<!----></div>\`)
232+
} else {
233+
_push(\`<!---->\`)
234+
}
235+
} else {
236+
return [
237+
(openBlock(), (_ctx.ok)
238+
? createBlock(\\"div\\", { key: 0 }, [
239+
(openBlock(false), createBlock(Fragment, null, renderList(list, (i) => {
240+
return (openBlock(), createBlock(\\"span\\"))
241+
}), 256 /* UNKEYED_FRAGMENT */))
242+
])
243+
: createCommentVNode(\\"v-if\\", true))
244+
]
245+
}
246+
},
247+
bar: ({ ok }, _push, _parent, _scopeId) => {
248+
if (_push) {
249+
if (ok) {
250+
_push(\`<div\${_scopeId}><!---->\`)
251+
_ssrRenderList(_ctx.list, (i) => {
252+
_push(\`<span\${_scopeId}></span>\`)
253+
})
254+
_push(\`<!----></div>\`)
255+
} else {
256+
_push(\`<!---->\`)
257+
}
258+
} else {
259+
return [
260+
(openBlock(), ok
261+
? createBlock(\\"div\\", { key: 0 }, [
262+
(openBlock(false), createBlock(Fragment, null, renderList(_ctx.list, (i) => {
263+
return (openBlock(), createBlock(\\"span\\"))
264+
}), 256 /* UNKEYED_FRAGMENT */))
265+
])
266+
: createCommentVNode(\\"v-if\\", true))
267+
]
268+
}
269+
},
270+
_compiled: true
271+
}, _parent))
272+
}"
273+
`)
274+
})
275+
196276
test('built-in fallthroughs', () => {
197277
// no fragment
198278
expect(compile(`<transition><div/></transition>`).code)

packages/compiler-ssr/__tests__/ssrScopeId.spec.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ describe('ssr: scopeId', () => {
2323
scopeId
2424
}).code
2525
).toMatchInlineSnapshot(`
26-
"const { resolveComponent } = require(\\"vue\\")
26+
"const { resolveComponent, createTextVNode } = require(\\"vue\\")
2727
const { _ssrRenderComponent } = require(\\"@vue/server-renderer\\")
2828
2929
return function ssrRender(_ctx, _push, _parent) {
@@ -51,7 +51,7 @@ describe('ssr: scopeId', () => {
5151
scopeId
5252
}).code
5353
).toMatchInlineSnapshot(`
54-
"const { resolveComponent } = require(\\"vue\\")
54+
"const { resolveComponent, createVNode } = require(\\"vue\\")
5555
const { _ssrRenderComponent } = require(\\"@vue/server-renderer\\")
5656
5757
return function ssrRender(_ctx, _push, _parent) {
@@ -79,12 +79,12 @@ describe('ssr: scopeId', () => {
7979
scopeId
8080
}).code
8181
).toMatchInlineSnapshot(`
82-
"const { resolveComponent } = require(\\"vue\\")
82+
"const { resolveComponent, createVNode } = require(\\"vue\\")
8383
const { _ssrRenderComponent } = require(\\"@vue/server-renderer\\")
8484
8585
return function ssrRender(_ctx, _push, _parent) {
86-
const _component_bar = resolveComponent(\\"bar\\")
8786
const _component_foo = resolveComponent(\\"foo\\")
87+
const _component_bar = resolveComponent(\\"bar\\")
8888
8989
_push(_ssrRenderComponent(_component_foo, null, {
9090
default: (_, _push, _parent, _scopeId) => {

packages/compiler-ssr/src/index.ts

+10-5
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,14 @@ import {
1010
trackSlotScopes,
1111
noopDirectiveTransform,
1212
transformBind,
13-
transformStyle,
14-
isBuiltInDOMComponent
13+
transformStyle
1514
} from '@vue/compiler-dom'
1615
import { ssrCodegenTransform } from './ssrCodegenTransform'
1716
import { ssrTransformElement } from './transforms/ssrTransformElement'
18-
import { ssrTransformComponent } from './transforms/ssrTransformComponent'
17+
import {
18+
ssrTransformComponent,
19+
rawOptionsMap
20+
} from './transforms/ssrTransformComponent'
1921
import { ssrTransformSlotOutlet } from './transforms/ssrTransformSlotOutlet'
2022
import { ssrTransformIf } from './transforms/ssrVIf'
2123
import { ssrTransformFor } from './transforms/ssrVFor'
@@ -41,6 +43,10 @@ export function compile(
4143

4244
const ast = baseParse(template, options)
4345

46+
// Save raw options for AST. This is needed when performing sub-transforms
47+
// on slot vnode branches.
48+
rawOptionsMap.set(ast, options)
49+
4450
transform(ast, {
4551
...options,
4652
nodeTransforms: [
@@ -66,8 +72,7 @@ export function compile(
6672
cloak: noopDirectiveTransform,
6773
once: noopDirectiveTransform,
6874
...(options.directiveTransforms || {}) // user transforms
69-
},
70-
isBuiltInComponent: isBuiltInDOMComponent
75+
}
7176
})
7277

7378
// traverse the template AST and convert into SSR codegen AST

0 commit comments

Comments
 (0)