1
1
/*
2
- * Copyright (c) 2024, salesforce.com, inc .
2
+ * Copyright (c) 2025, Salesforce, Inc .
3
3
* All rights reserved.
4
4
* SPDX-License-Identifier: MIT
5
5
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
@@ -14,9 +14,64 @@ import {
14
14
} from '@lwc/shared' ;
15
15
import { mutationTracker } from './mutation-tracker' ;
16
16
import { SYMBOL__GENERATE_MARKUP } from './lightning-element' ;
17
+ import type { CompilationMode } from '@lwc/shared' ;
17
18
import type { LightningElement , LightningElementConstructor } from './lightning-element' ;
18
19
import type { Attributes , Properties } from './types' ;
19
20
21
+ /** Parameters used by all `generateMarkup` variants that don't get transmogrified. */
22
+ type BaseGenerateMarkupParams = readonly [
23
+ tagName : string ,
24
+ props : Properties | null ,
25
+ attrs : Attributes | null ,
26
+ // Not always null when invoked internally, but should always be
27
+ // null when invoked by ssr-runtime
28
+ parent : LightningElement | null ,
29
+ scopeToken : string | null ,
30
+ contextfulParent : LightningElement | null ,
31
+ ] ;
32
+
33
+ /** Text emitter used by transmogrified formats. */
34
+ type Emit = ( str : string ) => void ;
35
+
36
+ /** Slotted content function used by `asyncYield` mode. */
37
+ type SlottedContentGenerator = (
38
+ instance : LightningElement
39
+ ) => AsyncGenerator < string , void , unknown > ;
40
+ /** Slotted content function used by `sync` and `async` modes. */
41
+ type SlottedContentEmitter = ( $$emit : Emit , instance : LightningElement ) => void ;
42
+
43
+ /** Slotted content map used by `asyncYield` mode. */
44
+ type SlottedContentGeneratorMap = Record < number | string , SlottedContentGenerator [ ] > ;
45
+ /** Slotted content map used by `sync` and `async` modes. */
46
+ type SlottedContentEmitterMap = Record < number | string , SlottedContentEmitter [ ] > ;
47
+
48
+ /** `generateMarkup` parameters used by `asyncYield` mode. */
49
+ type GenerateMarkupGeneratorParams = readonly [
50
+ ...BaseGenerateMarkupParams ,
51
+ shadowSlottedContent : SlottedContentGenerator | null ,
52
+ lightSlottedContent : SlottedContentGeneratorMap | null ,
53
+ scopedSlottedContent : SlottedContentGeneratorMap | null ,
54
+ ] ;
55
+ /** `generateMarkup` parameters used by `sync` and `async` modes. */
56
+ type GenerateMarkupEmitterParams = readonly [
57
+ emit : Emit ,
58
+ ...BaseGenerateMarkupParams ,
59
+ shadowSlottedContent : SlottedContentEmitter | null ,
60
+ lightSlottedContent : SlottedContentEmitterMap | null ,
61
+ scopedSlottedContent : SlottedContentEmitterMap | null ,
62
+ ] ;
63
+
64
+ /** Signature for `asyncYield` compilation mode. */
65
+ export type GenerateMarkupAsyncYield = (
66
+ ...args : GenerateMarkupGeneratorParams
67
+ ) => AsyncGenerator < string > ;
68
+ /** Signature for `async` compilation mode. */
69
+ export type GenerateMarkupAsync = ( ...args : GenerateMarkupEmitterParams ) => Promise < void > ;
70
+ /** Signature for `sync` compilation mode. */
71
+ export type GenerateMarkupSync = ( ...args : GenerateMarkupEmitterParams ) => void ;
72
+
73
+ type GenerateMarkupVariants = GenerateMarkupAsyncYield | GenerateMarkupAsync | GenerateMarkupSync ;
74
+
20
75
function renderAttrsPrivate (
21
76
instance : LightningElement ,
22
77
attrs : Attributes ,
@@ -100,29 +155,29 @@ export function renderAttrsNoYield(
100
155
emit ( renderAttrsPrivate ( instance , attrs , hostScopeToken , scopeToken ) ) ;
101
156
}
102
157
103
- export function * fallbackTmpl (
104
- shadowSlottedContent : AsyncGeneratorFunction ,
105
- _lightSlottedContent : unknown ,
106
- _scopedSlottedContent : unknown ,
158
+ export async function * fallbackTmpl (
159
+ shadowSlottedContent : SlottedContentGenerator | null ,
160
+ _lightSlottedContent : SlottedContentGeneratorMap | null ,
161
+ _scopedSlottedContent : SlottedContentGeneratorMap | null ,
107
162
Cmp : LightningElementConstructor ,
108
163
instance : LightningElement
109
- ) {
164
+ ) : AsyncGenerator < string > {
110
165
if ( Cmp . renderMode !== 'light' ) {
111
166
yield `<template shadowrootmode="open"></template>` ;
112
167
if ( shadowSlottedContent ) {
113
- yield shadowSlottedContent ( instance ) ;
168
+ yield * shadowSlottedContent ( instance ) ;
114
169
}
115
170
}
116
171
}
117
172
118
173
export function fallbackTmplNoYield (
119
- emit : ( segment : string ) => void ,
120
- shadowSlottedContent : AsyncGeneratorFunction | null ,
121
- _lightSlottedContent : unknown ,
122
- _scopedSlottedContent : unknown ,
174
+ emit : Emit ,
175
+ shadowSlottedContent : SlottedContentEmitter | null ,
176
+ _lightSlottedContent : SlottedContentEmitterMap | null ,
177
+ _scopedSlottedContent : SlottedContentEmitterMap | null ,
123
178
Cmp : LightningElementConstructor ,
124
- instance : LightningElement | null
125
- ) {
179
+ instance : LightningElement
180
+ ) : void {
126
181
if ( Cmp . renderMode !== 'light' ) {
127
182
emit ( `<template shadowrootmode="open"></template>` ) ;
128
183
if ( shadowSlottedContent ) {
@@ -131,64 +186,15 @@ export function fallbackTmplNoYield(
131
186
}
132
187
}
133
188
134
- export type GenerateMarkupFn = (
135
- tagName : string ,
136
- props : Properties | null ,
137
- attrs : Attributes | null ,
138
- shadowSlottedContent : AsyncGenerator < string > | null ,
139
- lightSlottedContent : Record < number | string , AsyncGenerator < string > > | null ,
140
- scopedSlottedContent : Record < number | string , AsyncGenerator < string > > | null ,
141
- // Not always null when invoked internally, but should always be
142
- // null when invoked by ssr-runtime
143
- parent : LightningElement | null ,
144
- scopeToken : string | null ,
145
- contextfulParent : LightningElement | null
146
- ) => AsyncGenerator < string > ;
147
-
148
- export type GenerateMarkupFnAsyncNoGen = (
149
- emit : ( segment : string ) => void ,
150
- tagName : string ,
151
- props : Properties | null ,
152
- attrs : Attributes | null ,
153
- shadowSlottedContent : AsyncGenerator < string > | null ,
154
- lightSlottedContent : Record < number | string , AsyncGenerator < string > > | null ,
155
- scopedSlottedContent : Record < number | string , AsyncGenerator < string > > | null ,
156
- // Not always null when invoked internally, but should always be
157
- // null when invoked by ssr-runtime
158
- parent : LightningElement | null ,
159
- scopeToken : string | null ,
160
- contextfulParent : LightningElement | null
161
- ) => Promise < void > ;
162
-
163
- export type GenerateMarkupFnSyncNoGen = (
164
- emit : ( segment : string ) => void ,
165
- tagName : string ,
166
- props : Properties | null ,
167
- attrs : Attributes | null ,
168
- shadowSlottedContent : AsyncGenerator < string > | null ,
169
- lightSlottedContent : Record < number | string , AsyncGenerator < string > > | null ,
170
- scopedSlottedContent : Record < number | string , AsyncGenerator < string > > | null ,
171
- // Not always null when invoked internally, but should always be
172
- // null when invoked by ssr-runtime
173
- parent : LightningElement | null ,
174
- scopeToken : string | null ,
175
- contextfulParent : LightningElement | null
176
- ) => void ;
177
-
178
- type GenerateMarkupFnVariants =
179
- | GenerateMarkupFn
180
- | GenerateMarkupFnAsyncNoGen
181
- | GenerateMarkupFnSyncNoGen ;
182
-
183
189
interface ComponentWithGenerateMarkup extends LightningElementConstructor {
184
- [ SYMBOL__GENERATE_MARKUP ] ?: GenerateMarkupFnVariants ;
190
+ [ SYMBOL__GENERATE_MARKUP ] ?: GenerateMarkupVariants ;
185
191
}
186
192
187
193
export async function serverSideRenderComponent (
188
194
tagName : string ,
189
195
Component : ComponentWithGenerateMarkup ,
190
196
props : Properties = { } ,
191
- mode : 'asyncYield' | 'async' | 'sync' = DEFAULT_SSR_MODE
197
+ mode : CompilationMode = DEFAULT_SSR_MODE
192
198
) : Promise < string > {
193
199
if ( typeof tagName !== 'string' ) {
194
200
throw new Error ( `tagName must be a string, found: ${ tagName } ` ) ;
@@ -204,13 +210,15 @@ export async function serverSideRenderComponent(
204
210
if ( ! generateMarkup ) {
205
211
// If a non-component is accidentally provided, render an empty template
206
212
emit ( `<${ tagName } >` ) ;
207
- fallbackTmplNoYield ( emit , null , null , null , Component , null ) ;
213
+ // Using a false type assertion for the `instance` param is safe because it's only used
214
+ // if there's slotted content, which we are not providing
215
+ fallbackTmplNoYield ( emit , null , null , null , Component , null as any ) ;
208
216
emit ( `</${ tagName } >` ) ;
209
217
return markup ;
210
218
}
211
219
212
220
if ( mode === 'asyncYield' ) {
213
- for await ( const segment of ( generateMarkup as GenerateMarkupFn ) (
221
+ for await ( const segment of ( generateMarkup as GenerateMarkupAsyncYield ) (
214
222
tagName ,
215
223
props ,
216
224
null ,
@@ -224,7 +232,7 @@ export async function serverSideRenderComponent(
224
232
markup += segment ;
225
233
}
226
234
} else if ( mode === 'async' ) {
227
- await ( generateMarkup as GenerateMarkupFnAsyncNoGen ) (
235
+ await ( generateMarkup as GenerateMarkupAsync ) (
228
236
emit ,
229
237
tagName ,
230
238
props ,
@@ -237,7 +245,7 @@ export async function serverSideRenderComponent(
237
245
null
238
246
) ;
239
247
} else if ( mode === 'sync' ) {
240
- ( generateMarkup as GenerateMarkupFnSyncNoGen ) (
248
+ ( generateMarkup as GenerateMarkupSync ) (
241
249
emit ,
242
250
tagName ,
243
251
props ,
0 commit comments