Skip to content

Commit 3c27bf6

Browse files
committed
wip(compiler-ssr): built-in component fallthrough
1 parent 9cfbab0 commit 3c27bf6

File tree

10 files changed

+129
-61
lines changed

10 files changed

+129
-61
lines changed

packages/compiler-core/src/codegen.ts

+22-4
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ function createCodegenContext(
7979
sourceMap = false,
8080
filename = `template.vue.html`,
8181
scopeId = null,
82+
runtimeGlobalName = `Vue`,
83+
runtimeModuleName = `vue`,
8284
ssr = false
8385
}: CodegenOptions
8486
): CodegenContext {
@@ -88,6 +90,8 @@ function createCodegenContext(
8890
sourceMap,
8991
filename,
9092
scopeId,
93+
runtimeGlobalName,
94+
runtimeModuleName,
9195
ssr,
9296
source: ast.loc.source,
9397
code: ``,
@@ -275,8 +279,18 @@ export function generate(
275279
}
276280

277281
function genFunctionPreamble(ast: RootNode, context: CodegenContext) {
278-
const { ssr, helper, prefixIdentifiers, push, newline } = context
279-
const VueBinding = ssr ? `require("vue")` : `Vue`
282+
const {
283+
ssr,
284+
helper,
285+
prefixIdentifiers,
286+
push,
287+
newline,
288+
runtimeModuleName,
289+
runtimeGlobalName
290+
} = context
291+
const VueBinding = ssr
292+
? `require(${JSON.stringify(runtimeModuleName)})`
293+
: runtimeGlobalName
280294
// Generate const declaration for helpers
281295
// In prefix mode, we place the const declaration at top so it's done
282296
// only once; But if we not prefixing, we place the declaration inside the
@@ -319,7 +333,7 @@ function genModulePreamble(
319333
context: CodegenContext,
320334
genScopeId: boolean
321335
) {
322-
const { push, helper, newline, scopeId } = context
336+
const { push, helper, newline, scopeId, runtimeModuleName } = context
323337
// generate import statements for helpers
324338
if (genScopeId) {
325339
ast.helpers.push(WITH_SCOPE_ID)
@@ -328,7 +342,11 @@ function genModulePreamble(
328342
}
329343
}
330344
if (ast.helpers.length) {
331-
push(`import { ${ast.helpers.map(helper).join(', ')} } from "vue"\n`)
345+
push(
346+
`import { ${ast.helpers.map(helper).join(', ')} } from ${JSON.stringify(
347+
runtimeModuleName
348+
)}\n`
349+
)
332350
}
333351
if (!__BROWSER__ && ast.ssrHelpers && ast.ssrHelpers.length) {
334352
push(

packages/compiler-core/src/options.ts

+3
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ export interface CodegenOptions {
7171
scopeId?: string | null
7272
// we need to know about this to generate proper preambles
7373
prefixIdentifiers?: boolean
74+
// for specifying where to import helpers
75+
runtimeModuleName?: string
76+
runtimeGlobalName?: string
7477
// generate ssr-specific code?
7578
ssr?: boolean
7679
}

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

+5-2
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,8 @@ export const transformElement: NodeTransform = (node, context) => {
182182

183183
export function resolveComponentType(
184184
node: ComponentNode,
185-
context: TransformContext
185+
context: TransformContext,
186+
ssr = false
186187
) {
187188
const { tag } = node
188189

@@ -211,7 +212,9 @@ export function resolveComponentType(
211212
// 2. built-in components (Portal, Transition, KeepAlive, Suspense...)
212213
const builtIn = isCoreComponent(tag) || context.isBuiltInComponent(tag)
213214
if (builtIn) {
214-
context.helper(builtIn)
215+
// built-ins are simply fallthroughs / have special handling during ssr
216+
// no we don't need to import their runtime equivalents
217+
if (!ssr) context.helper(builtIn)
215218
return builtIn
216219
}
217220

packages/compiler-dom/src/index.ts

+10-7
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,14 @@ export const parserOptions = __BROWSER__
2222
? parserOptionsMinimal
2323
: parserOptionsStandard
2424

25+
export const isBuiltInDOMComponent = (tag: string): symbol | undefined => {
26+
if (isBuiltInType(tag, `Transition`)) {
27+
return TRANSITION
28+
} else if (isBuiltInType(tag, `TransitionGroup`)) {
29+
return TRANSITION_GROUP
30+
}
31+
}
32+
2533
export function compile(
2634
template: string,
2735
options: CompilerOptions = {}
@@ -39,13 +47,7 @@ export function compile(
3947
show: transformShow,
4048
...(options.directiveTransforms || {})
4149
},
42-
isBuiltInComponent: tag => {
43-
if (isBuiltInType(tag, `Transition`)) {
44-
return TRANSITION
45-
} else if (isBuiltInType(tag, `TransitionGroup`)) {
46-
return TRANSITION_GROUP
47-
}
48-
}
50+
isBuiltInComponent: isBuiltInDOMComponent
4951
})
5052
}
5153

@@ -56,6 +58,7 @@ export function parse(template: string, options: ParserOptions = {}): RootNode {
5658
})
5759
}
5860

61+
export * from './runtimeHelpers'
5962
export { transformStyle } from './transforms/transformStyle'
6063
export { createDOMCompilerError, DOMErrorCodes } from './errors'
6164
export * from '@vue/compiler-core'

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

+42
Original file line numberDiff line numberDiff line change
@@ -161,5 +161,47 @@ describe('ssr: components', () => {
161161
}"
162162
`)
163163
})
164+
165+
test('built-in fallthroughs', () => {
166+
// no fragment
167+
expect(compile(`<transition><div/></transition>`).code)
168+
.toMatchInlineSnapshot(`
169+
"
170+
return function ssrRender(_ctx, _push, _parent) {
171+
_push(\`<div></div>\`)
172+
}"
173+
`)
174+
175+
// wrap with fragment
176+
expect(compile(`<transition-group><div/></transition-group>`).code)
177+
.toMatchInlineSnapshot(`
178+
"
179+
return function ssrRender(_ctx, _push, _parent) {
180+
_push(\`<!----><div></div><!---->\`)
181+
}"
182+
`)
183+
184+
// no fragment
185+
expect(compile(`<keep-alive><foo/></keep-alive>`).code)
186+
.toMatchInlineSnapshot(`
187+
"const { resolveComponent } = require(\\"vue\\")
188+
const { _ssrRenderComponent } = require(\\"@vue/server-renderer\\")
189+
190+
return function ssrRender(_ctx, _push, _parent) {
191+
const _component_foo = resolveComponent(\\"foo\\")
192+
193+
_ssrRenderComponent(_component_foo, null, null, _parent)
194+
}"
195+
`)
196+
197+
// wrap with fragment
198+
expect(compile(`<suspense><div/></suspense>`).code)
199+
.toMatchInlineSnapshot(`
200+
"
201+
return function ssrRender(_ctx, _push, _parent) {
202+
_push(\`<!----><div></div><!---->\`)
203+
}"
204+
`)
205+
})
164206
})
165207
})

packages/compiler-ssr/src/index.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ import {
1010
trackSlotScopes,
1111
noopDirectiveTransform,
1212
transformBind,
13-
transformStyle
13+
transformStyle,
14+
isBuiltInDOMComponent
1415
} from '@vue/compiler-dom'
1516
import { ssrCodegenTransform } from './ssrCodegenTransform'
1617
import { ssrTransformElement } from './transforms/ssrTransformElement'
@@ -64,7 +65,8 @@ export function compile(
6465
cloak: noopDirectiveTransform,
6566
once: noopDirectiveTransform,
6667
...(options.directiveTransforms || {}) // user transforms
67-
}
68+
},
69+
isBuiltInComponent: isBuiltInDOMComponent
6870
})
6971

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

packages/compiler-ssr/src/ssrCodegenTransform.ts

+9-10
Original file line numberDiff line numberDiff line change
@@ -28,17 +28,9 @@ import { ssrProcessComponent } from './transforms/ssrTransformComponent'
2828

2929
export function ssrCodegenTransform(ast: RootNode, options: CompilerOptions) {
3030
const context = createSSRTransformContext(options)
31-
3231
const isFragment =
3332
ast.children.length > 1 && !ast.children.every(c => isText(c))
34-
if (isFragment) {
35-
context.pushStringPart(`<!---->`)
36-
}
37-
processChildren(ast.children, context)
38-
if (isFragment) {
39-
context.pushStringPart(`<!---->`)
40-
}
41-
33+
processChildren(ast.children, context, isFragment)
4234
ast.codegenNode = createBlockStatement(context.body)
4335

4436
// Finalize helpers.
@@ -99,8 +91,12 @@ export function createChildContext(
9991

10092
export function processChildren(
10193
children: TemplateChildNode[],
102-
context: SSRTransformContext
94+
context: SSRTransformContext,
95+
asFragment = false
10396
) {
97+
if (asFragment) {
98+
context.pushStringPart(`<!---->`)
99+
}
104100
const isVoidTag = context.options.isVoidTag || NO
105101
for (let i = 0; i < children.length; i++) {
106102
const child = children[i]
@@ -135,4 +131,7 @@ export function processChildren(
135131
ssrProcessFor(child, context)
136132
}
137133
}
134+
if (asFragment) {
135+
context.pushStringPart(`<!---->`)
136+
}
138137
}

packages/compiler-ssr/src/transforms/ssrTransformComponent.ts

+32-22
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,15 @@ import {
66
resolveComponentType,
77
buildProps,
88
ComponentNode,
9-
PORTAL,
10-
SUSPENSE,
119
SlotFnBuilder,
1210
createFunctionExpression,
1311
createBlockStatement,
1412
buildSlots,
1513
FunctionExpression,
16-
TemplateChildNode
14+
TemplateChildNode,
15+
PORTAL,
16+
SUSPENSE,
17+
TRANSITION_GROUP
1718
} from '@vue/compiler-dom'
1819
import { SSR_RENDER_COMPONENT } from '../runtimeHelpers'
1920
import {
@@ -34,6 +35,8 @@ interface WIPSlotEntry {
3435
children: TemplateChildNode[]
3536
}
3637

38+
const componentTypeMap = new WeakMap<ComponentNode, symbol>()
39+
3740
export const ssrTransformComponent: NodeTransform = (node, context) => {
3841
if (
3942
node.type !== NodeTypes.ELEMENT ||
@@ -43,18 +46,10 @@ export const ssrTransformComponent: NodeTransform = (node, context) => {
4346
}
4447

4548
return function ssrPostTransformComponent() {
46-
const component = resolveComponentType(node, context)
47-
49+
const component = resolveComponentType(node, context, true /* ssr */)
4850
if (isSymbol(component)) {
49-
// built-in compoonent
50-
if (component === PORTAL) {
51-
// TODO
52-
} else if (component === SUSPENSE) {
53-
// TODO fallthrough
54-
// TODO option to use fallback content and resolve on client
55-
} else {
56-
// TODO fallthrough for KeepAlive & Transition
57-
}
51+
componentTypeMap.set(node, component)
52+
return // built-in component: fallthrough
5853
}
5954

6055
// note we are not passing ssr: true here because for components, v-on
@@ -98,13 +93,28 @@ export function ssrProcessComponent(
9893
node: ComponentNode,
9994
context: SSRTransformContext
10095
) {
101-
// finish up slot function expressions from the 1st pass.
102-
const wipEntries = wipMap.get(node) || []
103-
for (let i = 0; i < wipEntries.length; i++) {
104-
const { fn, children } = wipEntries[i]
105-
const childContext = createChildContext(context)
106-
processChildren(children, childContext)
107-
fn.body = createBlockStatement(childContext.body)
96+
if (!node.ssrCodegenNode) {
97+
// this is a built-in component that fell-through.
98+
// just render its children.
99+
const component = componentTypeMap.get(node)!
100+
101+
if (component === PORTAL) {
102+
// TODO
103+
return
104+
}
105+
106+
const needFragmentWrapper =
107+
component === SUSPENSE || component === TRANSITION_GROUP
108+
processChildren(node.children, context, needFragmentWrapper)
109+
} else {
110+
// finish up slot function expressions from the 1st pass.
111+
const wipEntries = wipMap.get(node) || []
112+
for (let i = 0; i < wipEntries.length; i++) {
113+
const { fn, children } = wipEntries[i]
114+
const childContext = createChildContext(context)
115+
processChildren(children, childContext)
116+
fn.body = createBlockStatement(childContext.body)
117+
}
118+
context.pushStatement(node.ssrCodegenNode)
108119
}
109-
context.pushStatement(node.ssrCodegenNode!)
110120
}

packages/compiler-ssr/src/transforms/ssrVFor.ts

+1-7
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,7 @@ export function ssrProcessFor(node: ForNode, context: SSRTransformContext) {
2727
const childContext = createChildContext(context)
2828
const needFragmentWrapper =
2929
node.children.length !== 1 || node.children[0].type !== NodeTypes.ELEMENT
30-
if (needFragmentWrapper) {
31-
childContext.pushStringPart(`<!---->`)
32-
}
33-
processChildren(node.children, childContext)
34-
if (needFragmentWrapper) {
35-
childContext.pushStringPart(`<!---->`)
36-
}
30+
processChildren(node.children, childContext, needFragmentWrapper)
3731
const renderLoop = createFunctionExpression(
3832
createForLoopParams(node.parseResult)
3933
)

packages/compiler-ssr/src/transforms/ssrVIf.ts

+1-7
Original file line numberDiff line numberDiff line change
@@ -64,12 +64,6 @@ function processIfBranch(
6464
// optimize away nested fragments when the only child is a ForNode
6565
!(children.length === 1 && children[0].type === NodeTypes.FOR)
6666
const childContext = createChildContext(context)
67-
if (needFragmentWrapper) {
68-
childContext.pushStringPart(`<!---->`)
69-
}
70-
processChildren(children, childContext)
71-
if (needFragmentWrapper) {
72-
childContext.pushStringPart(`<!---->`)
73-
}
67+
processChildren(children, childContext, needFragmentWrapper)
7468
return createBlockStatement(childContext.body)
7569
}

0 commit comments

Comments
 (0)