Skip to content

Commit 6eee0d1

Browse files
authored
chore(ssr): update generateMarkup type defs (#5242)
* chore(types): add missing static props SSR LightningElement * refactor(ssr): move slotted content to last params makes fancy typing easier * refactor(types): rekerjigger `generateMarkup` type defs * feat(ssr): implement light dom slotted content in fallback template * Update packages/@lwc/ssr-runtime/src/index.ts * chore: revert light DOM slots in fallback template not behavior we want
1 parent 5a137ce commit 6eee0d1

File tree

6 files changed

+93
-82
lines changed

6 files changed

+93
-82
lines changed

packages/@lwc/ssr-compiler/src/compile-js/generate-markup.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,13 @@ const bGenerateMarkup = esTemplate`
3030
value: async function* generateMarkup(
3131
tagName,
3232
props,
33-
attrs,
33+
attrs,
34+
parent,
35+
scopeToken,
36+
contextfulParent,
3437
shadowSlottedContent,
3538
lightSlottedContent,
3639
scopedSlottedContent,
37-
parent,
38-
scopeToken,
39-
contextfulParent
4040
) {
4141
tagName = tagName ?? ${/*component tag name*/ is.literal};
4242
attrs = attrs ?? Object.create(null);

packages/@lwc/ssr-compiler/src/compile-template/transformers/component/component.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,13 @@ const bYieldFromChildGenerator = esTemplateWithYield`
4444
yield* generateMarkup(
4545
tagName,
4646
childProps,
47-
childAttrs,
48-
shadowSlottedContent,
49-
lightSlottedContentMap,
50-
scopedSlottedContentMap,
47+
childAttrs,
5148
instance,
5249
scopeToken,
53-
contextfulParent
50+
contextfulParent,
51+
shadowSlottedContent,
52+
lightSlottedContentMap,
53+
scopedSlottedContentMap
5454
);
5555
} else {
5656
yield \`<\${tagName}>\`;

packages/@lwc/ssr-compiler/src/compile-template/transformers/component/lwc-component.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,12 @@ const bYieldFromDynamicComponentConstructorGenerator = esTemplateWithYield`
4848
null,
4949
childProps,
5050
childAttrs,
51-
shadowSlottedContent,
52-
lightSlottedContentMap,
53-
scopedSlottedContentMap,
5451
instance,
5552
scopeToken,
56-
contextfulParent
53+
contextfulParent,
54+
shadowSlottedContent,
55+
lightSlottedContentMap,
56+
scopedSlottedContentMap
5757
);
5858
}
5959
`<EsStatement[]>;

packages/@lwc/ssr-runtime/src/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export { mutationTracker } from './mutation-tracker';
2222
export {
2323
fallbackTmpl,
2424
fallbackTmplNoYield,
25-
GenerateMarkupFn,
25+
GenerateMarkupAsyncYield,
2626
renderAttrs,
2727
renderAttrsNoYield,
2828
serverSideRenderComponent,

packages/@lwc/ssr-runtime/src/lightning-element.ts

+3
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ export const SYMBOL__DEFAULT_TEMPLATE = Symbol('default-template');
5050
export class LightningElement implements PropsAvailableAtConstruction {
5151
static renderMode?: 'light' | 'shadow';
5252
static stylesheets?: Stylesheets;
53+
static delegatesFocus?: boolean;
54+
static formAssociated?: boolean;
55+
static shadowSupportMode?: 'any' | 'reset' | 'native';
5356

5457
// Using ! because these are defined by descriptors in ./reflection
5558
accessKey!: string;

packages/@lwc/ssr-runtime/src/render.ts

+76-68
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2024, salesforce.com, inc.
2+
* Copyright (c) 2025, Salesforce, Inc.
33
* All rights reserved.
44
* SPDX-License-Identifier: MIT
55
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
@@ -14,9 +14,64 @@ import {
1414
} from '@lwc/shared';
1515
import { mutationTracker } from './mutation-tracker';
1616
import { SYMBOL__GENERATE_MARKUP } from './lightning-element';
17+
import type { CompilationMode } from '@lwc/shared';
1718
import type { LightningElement, LightningElementConstructor } from './lightning-element';
1819
import type { Attributes, Properties } from './types';
1920

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+
2075
function renderAttrsPrivate(
2176
instance: LightningElement,
2277
attrs: Attributes,
@@ -100,29 +155,29 @@ export function renderAttrsNoYield(
100155
emit(renderAttrsPrivate(instance, attrs, hostScopeToken, scopeToken));
101156
}
102157

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,
107162
Cmp: LightningElementConstructor,
108163
instance: LightningElement
109-
) {
164+
): AsyncGenerator<string> {
110165
if (Cmp.renderMode !== 'light') {
111166
yield `<template shadowrootmode="open"></template>`;
112167
if (shadowSlottedContent) {
113-
yield shadowSlottedContent(instance);
168+
yield* shadowSlottedContent(instance);
114169
}
115170
}
116171
}
117172

118173
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,
123178
Cmp: LightningElementConstructor,
124-
instance: LightningElement | null
125-
) {
179+
instance: LightningElement
180+
): void {
126181
if (Cmp.renderMode !== 'light') {
127182
emit(`<template shadowrootmode="open"></template>`);
128183
if (shadowSlottedContent) {
@@ -131,64 +186,15 @@ export function fallbackTmplNoYield(
131186
}
132187
}
133188

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-
183189
interface ComponentWithGenerateMarkup extends LightningElementConstructor {
184-
[SYMBOL__GENERATE_MARKUP]?: GenerateMarkupFnVariants;
190+
[SYMBOL__GENERATE_MARKUP]?: GenerateMarkupVariants;
185191
}
186192

187193
export async function serverSideRenderComponent(
188194
tagName: string,
189195
Component: ComponentWithGenerateMarkup,
190196
props: Properties = {},
191-
mode: 'asyncYield' | 'async' | 'sync' = DEFAULT_SSR_MODE
197+
mode: CompilationMode = DEFAULT_SSR_MODE
192198
): Promise<string> {
193199
if (typeof tagName !== 'string') {
194200
throw new Error(`tagName must be a string, found: ${tagName}`);
@@ -204,13 +210,15 @@ export async function serverSideRenderComponent(
204210
if (!generateMarkup) {
205211
// If a non-component is accidentally provided, render an empty template
206212
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);
208216
emit(`</${tagName}>`);
209217
return markup;
210218
}
211219

212220
if (mode === 'asyncYield') {
213-
for await (const segment of (generateMarkup as GenerateMarkupFn)(
221+
for await (const segment of (generateMarkup as GenerateMarkupAsyncYield)(
214222
tagName,
215223
props,
216224
null,
@@ -224,7 +232,7 @@ export async function serverSideRenderComponent(
224232
markup += segment;
225233
}
226234
} else if (mode === 'async') {
227-
await (generateMarkup as GenerateMarkupFnAsyncNoGen)(
235+
await (generateMarkup as GenerateMarkupAsync)(
228236
emit,
229237
tagName,
230238
props,
@@ -237,7 +245,7 @@ export async function serverSideRenderComponent(
237245
null
238246
);
239247
} else if (mode === 'sync') {
240-
(generateMarkup as GenerateMarkupFnSyncNoGen)(
248+
(generateMarkup as GenerateMarkupSync)(
241249
emit,
242250
tagName,
243251
props,

0 commit comments

Comments
 (0)