Skip to content
Closed
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,13 @@ const bGenerateMarkup = esTemplate`
value: async function* generateMarkup(
tagName,
props,
attrs,
attrs,
parent,
scopeToken,
contextfulParent,
shadowSlottedContent,
lightSlottedContent,
scopedSlottedContent,
parent,
scopeToken,
contextfulParent
) {
tagName = tagName ?? ${/*component tag name*/ is.literal};
attrs = attrs ?? Object.create(null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,13 @@ const bYieldFromChildGenerator = esTemplateWithYield`
yield* generateMarkup(
tagName,
childProps,
childAttrs,
shadowSlottedContent,
lightSlottedContentMap,
scopedSlottedContentMap,
childAttrs,
instance,
scopeToken,
contextfulParent
contextfulParent,
shadowSlottedContent,
lightSlottedContentMap,
scopedSlottedContentMap
);
} else {
yield \`<\${tagName}>\`;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,12 @@ const bYieldFromDynamicComponentConstructorGenerator = esTemplateWithYield`
null,
childProps,
childAttrs,
shadowSlottedContent,
lightSlottedContentMap,
scopedSlottedContentMap,
instance,
scopeToken,
contextfulParent
contextfulParent,
shadowSlottedContent,
lightSlottedContentMap,
scopedSlottedContentMap
);
}
`<EsStatement[]>;
Expand Down
2 changes: 1 addition & 1 deletion packages/@lwc/ssr-runtime/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export { mutationTracker } from './mutation-tracker';
export {
fallbackTmpl,
fallbackTmplNoYield,
GenerateMarkupFn,
GenerateMarkupAsyncYield as GenerateMarkupFn,
renderAttrs,
renderAttrsNoYield,
serverSideRenderComponent,
Expand Down
3 changes: 3 additions & 0 deletions packages/@lwc/ssr-runtime/src/lightning-element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ export const SYMBOL__DEFAULT_TEMPLATE = Symbol('default-template');
export class LightningElement implements PropsAvailableAtConstruction {
static renderMode?: 'light' | 'shadow';
static stylesheets?: Stylesheets;
static delegatesFocus?: boolean;
static formAssociated?: boolean;
static shadowSupportMode?: 'any' | 'reset' | 'native';
Comment on lines +53 to +55
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just bonus stuff from engine-core that I noticed was missing here.


// Using ! because these are defined by descriptors in ./reflection
accessKey!: string;
Expand Down
156 changes: 89 additions & 67 deletions packages/@lwc/ssr-runtime/src/render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,64 @@ import {
} from '@lwc/shared';
import { mutationTracker } from './mutation-tracker';
import { SYMBOL__GENERATE_MARKUP } from './lightning-element';
import type { CompilationMode } from '@lwc/shared';
import type { LightningElement, LightningElementConstructor } from './lightning-element';
import type { Attributes, Properties } from './types';

/** Parameters used by all `generateMarkup` variants that don't get transmogrified. */
type BaseGenerateMarkupParams = readonly [
tagName: string,
props: Properties | null,
attrs: Attributes | null,
// Not always null when invoked internally, but should always be
// null when invoked by ssr-runtime
parent: LightningElement | null,
scopeToken: string | null,
contextfulParent: LightningElement | null,
];

/** Text emitter used by transmogrified formats. */
type Emit = (str: string) => void;

/** Slotted content function used by `asyncYield` mode. */
type SlottedContentGenerator = (
instance: LightningElement
) => AsyncGenerator<string, void, unknown>;
/** Slotted content function used by `sync` and `async` modes. */
type SlottedContentEmitter = ($$emit: Emit, instance: LightningElement) => void;

/** Slotted content map used by `asyncYield` mode. */
type SlottedContentGeneratorMap = Record<number | string, SlottedContentGenerator[]>;
/** Slotted content map used by `sync` and `async` modes. */
type SlottedContentEmitterMap = Record<number | string, SlottedContentEmitter[]>;

/** `generateMarkup` parameters used by `asyncYield` mode. */
type GenerateMarkupGeneratorParams = readonly [
...BaseGenerateMarkupParams,
shadowSlottedContent: SlottedContentGenerator | null,
lightSlottedContent: SlottedContentGeneratorMap | null,
scopedSlottedContent: SlottedContentGeneratorMap | null,
];
/** `generateMarkup` parameters used by `sync` and `async` modes. */
type GenerateMarkupEmitterParams = readonly [
emit: Emit,
...BaseGenerateMarkupParams,
shadowSlottedContent: SlottedContentEmitter | null,
lightSlottedContent: SlottedContentEmitterMap | null,
scopedSlottedContent: SlottedContentEmitterMap | null,
];

/** Signature for `asyncYield` compilation mode. */
export type GenerateMarkupAsyncYield = (
...args: GenerateMarkupGeneratorParams
) => AsyncGenerator<string>;
/** Signature for `async` compilation mode. */
export type GenerateMarkupAsync = (...args: GenerateMarkupEmitterParams) => Promise<void>;
/** Signature for `sync` compilation mode. */
export type GenerateMarkupSync = (...args: GenerateMarkupEmitterParams) => void;

type GenerateMarkupVariants = GenerateMarkupAsyncYield | GenerateMarkupAsync | GenerateMarkupSync;

function renderAttrsPrivate(
instance: LightningElement,
attrs: Attributes,
Expand Down Expand Up @@ -100,95 +155,60 @@ export function renderAttrsNoYield(
emit(renderAttrsPrivate(instance, attrs, hostScopeToken, scopeToken));
}

export function* fallbackTmpl(
shadowSlottedContent: AsyncGeneratorFunction,
_lightSlottedContent: unknown,
_scopedSlottedContent: unknown,
export async function* fallbackTmpl(
shadowSlottedContent: SlottedContentGenerator | null,
lightSlottedContent: SlottedContentGeneratorMap | null,
_scopedSlottedContent: SlottedContentGeneratorMap | null,
Cmp: LightningElementConstructor,
instance: LightningElement
) {
): AsyncGenerator<string> {
if (Cmp.renderMode !== 'light') {
yield `<template shadowrootmode="open"></template>`;
if (shadowSlottedContent) {
yield shadowSlottedContent(instance);
yield* shadowSlottedContent(instance);
}
} else if (lightSlottedContent) {
const defaultSlot = lightSlottedContent[''];
if (defaultSlot?.length > 0) {
for (const content of defaultSlot) {
yield* content(instance);
}
}
}
}

export function fallbackTmplNoYield(
emit: (segment: string) => void,
shadowSlottedContent: AsyncGeneratorFunction | null,
_lightSlottedContent: unknown,
_scopedSlottedContent: unknown,
emit: Emit,
shadowSlottedContent: SlottedContentEmitter | null,
lightSlottedContent: SlottedContentEmitterMap | null,
_scopedSlottedContent: SlottedContentEmitterMap | null,
Cmp: LightningElementConstructor,
instance: LightningElement | null
) {
instance: LightningElement
): void {
if (Cmp.renderMode !== 'light') {
emit(`<template shadowrootmode="open"></template>`);
if (shadowSlottedContent) {
shadowSlottedContent(emit, instance);
}
} else if (lightSlottedContent) {
const defaultSlot = lightSlottedContent[''];
if (defaultSlot?.length > 0) {
for (const content of defaultSlot) {
content(emit, instance);
}
}
}
}

export type GenerateMarkupFn = (
tagName: string,
props: Properties | null,
attrs: Attributes | null,
shadowSlottedContent: AsyncGenerator<string> | null,
lightSlottedContent: Record<number | string, AsyncGenerator<string>> | null,
scopedSlottedContent: Record<number | string, AsyncGenerator<string>> | null,
// Not always null when invoked internally, but should always be
// null when invoked by ssr-runtime
parent: LightningElement | null,
scopeToken: string | null,
contextfulParent: LightningElement | null
) => AsyncGenerator<string>;

export type GenerateMarkupFnAsyncNoGen = (
emit: (segment: string) => void,
tagName: string,
props: Properties | null,
attrs: Attributes | null,
shadowSlottedContent: AsyncGenerator<string> | null,
lightSlottedContent: Record<number | string, AsyncGenerator<string>> | null,
scopedSlottedContent: Record<number | string, AsyncGenerator<string>> | null,
// Not always null when invoked internally, but should always be
// null when invoked by ssr-runtime
parent: LightningElement | null,
scopeToken: string | null,
contextfulParent: LightningElement | null
) => Promise<void>;

export type GenerateMarkupFnSyncNoGen = (
emit: (segment: string) => void,
tagName: string,
props: Properties | null,
attrs: Attributes | null,
shadowSlottedContent: AsyncGenerator<string> | null,
lightSlottedContent: Record<number | string, AsyncGenerator<string>> | null,
scopedSlottedContent: Record<number | string, AsyncGenerator<string>> | null,
// Not always null when invoked internally, but should always be
// null when invoked by ssr-runtime
parent: LightningElement | null,
scopeToken: string | null,
contextfulParent: LightningElement | null
) => void;

type GenerateMarkupFnVariants =
| GenerateMarkupFn
| GenerateMarkupFnAsyncNoGen
| GenerateMarkupFnSyncNoGen;

interface ComponentWithGenerateMarkup extends LightningElementConstructor {
[SYMBOL__GENERATE_MARKUP]?: GenerateMarkupFnVariants;
[SYMBOL__GENERATE_MARKUP]?: GenerateMarkupVariants;
}

export async function serverSideRenderComponent(
tagName: string,
Component: ComponentWithGenerateMarkup,
props: Properties = {},
mode: 'asyncYield' | 'async' | 'sync' = DEFAULT_SSR_MODE
mode: CompilationMode = DEFAULT_SSR_MODE
): Promise<string> {
if (typeof tagName !== 'string') {
throw new Error(`tagName must be a string, found: ${tagName}`);
Expand All @@ -204,13 +224,15 @@ export async function serverSideRenderComponent(
if (!generateMarkup) {
// If a non-component is accidentally provided, render an empty template
emit(`<${tagName}>`);
fallbackTmplNoYield(emit, null, null, null, Component, null);
// Using a false type assertion for the `instance` param is safe because it's only used
// if there's slotted content, which we are not providing
fallbackTmplNoYield(emit, null, null, null, Component, null as any);
emit(`</${tagName}>`);
return markup;
}

if (mode === 'asyncYield') {
for await (const segment of (generateMarkup as GenerateMarkupFn)(
for await (const segment of (generateMarkup as GenerateMarkupAsyncYield)(
tagName,
props,
null,
Expand All @@ -224,7 +246,7 @@ export async function serverSideRenderComponent(
markup += segment;
}
} else if (mode === 'async') {
await (generateMarkup as GenerateMarkupFnAsyncNoGen)(
await (generateMarkup as GenerateMarkupAsync)(
emit,
tagName,
props,
Expand All @@ -237,7 +259,7 @@ export async function serverSideRenderComponent(
null
);
} else if (mode === 'sync') {
(generateMarkup as GenerateMarkupFnSyncNoGen)(
(generateMarkup as GenerateMarkupSync)(
emit,
tagName,
props,
Expand Down
Loading