Skip to content

Commit

Permalink
fix(ssr): handle export { Cmp as default } @W-17655297 (#5176)
Browse files Browse the repository at this point in the history
* fix: allow export { Cmp as default }

* test(ssr): update component-as-default to check light and shadow

* fix: use fallback if no generateMarkup

* fix(ssr): support `export { Cmp as default }`

* fix: support slotted content for unimplemented templates

* fix: use fallbackTmpl for all cases

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

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

---------

Co-authored-by: John Hefferman <[email protected]>
  • Loading branch information
wjhsf and jhefferman-sfdc authored Jan 31, 2025
1 parent d78c977 commit 8eec50f
Show file tree
Hide file tree
Showing 12 changed files with 77 additions and 27 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,19 @@
<x-cmp>
<x-parent>
<template shadowrootmode="open">
<x-shadow>
<template shadowrootmode="open">
</template>
</x-shadow>
<x-shadow>
<template shadowrootmode="open">
</template>
<h1>
slotted content
</h1>
</x-shadow>
<x-light>
</x-light>
<x-light>
</x-light>
</template>
</x-cmp>
</x-parent>
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export const tagName = 'x-cmp';
export { default } from 'x/cmp';
export * from 'x/cmp';
export const tagName = 'x-parent';
export { default } from 'x/parent';
export * from 'x/parent';

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<template lwc:render-mode="light">
This template isn't actually used because `export {Component as default}` isn't recognized as an LWC component.
</template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { LightningElement } from 'lwc';

class Light extends LightningElement {
static renderMode = 'light';
}

export { Light as default };
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<template>
<x-shadow></x-shadow>
<x-shadow><h1>slotted content</h1></x-shadow>
<x-light></x-light>
<x-light><h1>slotted content</h1></x-light>
</template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { LightningElement } from 'lwc';

export default class Parent extends LightningElement {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { LightningElement } from 'lwc';

class Shadow extends LightningElement {}

export { Shadow as default };
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
export const expectedFailures = new Set([
'attribute-global-html/as-component-prop/undeclared/index.js',
'attribute-global-html/as-component-prop/without-@api/index.js',
'exports/component-as-default/index.js',
'known-boolean-attributes/default-def-html-attributes/static-on-component/index.js',
'wire/errors/throws-when-colliding-prop-then-method/index.js',
]);
Original file line number Diff line number Diff line change
Expand Up @@ -31,25 +31,32 @@ const bYieldFromChildGenerator = esTemplateWithYield`
Slotted content is inserted here.
Note that the slotted content will be stored in variables named
`shadowSlottedContent`/`lightSlottedContentMap / scopedSlottedContentMap` which are used below
when the child's generateMarkup function is invoked.
when the child's generateMarkup function is invoked.
*/
is.statement
}
const scopeToken = hasScopedStylesheets ? stylesheetScopeToken : undefined;
const Ctor = ${/* Component */ is.identifier};
const generateMarkup = ${/* Component */ is.identifier}[__SYMBOL__GENERATE_MARKUP];
const tagName = ${/* tag name */ is.literal};
yield* Ctor[__SYMBOL__GENERATE_MARKUP](
${/* tag name */ is.literal},
childProps,
childAttrs,
shadowSlottedContent,
lightSlottedContentMap,
scopedSlottedContentMap,
instance,
scopeToken,
contextfulParent
);
if (generateMarkup) {
yield* generateMarkup(
tagName,
childProps,
childAttrs,
shadowSlottedContent,
lightSlottedContentMap,
scopedSlottedContentMap,
instance,
scopeToken,
contextfulParent
);
} else {
yield \`<\${tagName}>\`;
yield* __fallbackTmpl(shadowSlottedContent, lightSlottedContentMap, scopedSlottedContentMap, ${/* Component */ 3}, instance)
yield \`</\${tagName}>\`;
}
}
`<EsBlockStatement>;

Expand All @@ -60,6 +67,7 @@ export const Component: Transformer<IrComponent> = function Component(node, cxt)
cxt.import({ default: childComponentLocalName }, importPath);
cxt.import({
SYMBOL__GENERATE_MARKUP: '__SYMBOL__GENERATE_MARKUP',
fallbackTmpl: '__fallbackTmpl',
});
const childTagName = node.name;

Expand Down
16 changes: 12 additions & 4 deletions packages/@lwc/ssr-runtime/src/render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ export function* fallbackTmpl(
_lightSlottedContent: unknown,
_scopedSlottedContent: unknown,
Cmp: LightningElementConstructor,
instance: unknown
instance: LightningElement
) {
if (Cmp.renderMode !== 'light') {
yield `<template shadowrootmode="open"></template>`;
Expand All @@ -117,11 +117,11 @@ export function* fallbackTmpl(

export function fallbackTmplNoYield(
emit: (segment: string) => void,
shadowSlottedContent: AsyncGeneratorFunction,
shadowSlottedContent: AsyncGeneratorFunction | null,
_lightSlottedContent: unknown,
_scopedSlottedContent: unknown,
Cmp: LightningElementConstructor,
instance: unknown
instance: LightningElement | null
) {
if (Cmp.renderMode !== 'light') {
emit(`<template shadowrootmode="open"></template>`);
Expand Down Expand Up @@ -180,7 +180,7 @@ type GenerateMarkupFnVariants =
| GenerateMarkupFnAsyncNoGen
| GenerateMarkupFnSyncNoGen;

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

Expand All @@ -201,6 +201,14 @@ export async function serverSideRenderComponent(
markup += segment;
};

if (!generateMarkup) {
// If a non-component is accidentally provided, render an empty template
emit(`<${tagName}>`);
fallbackTmplNoYield(emit, null, null, null, Component, null);
emit(`</${tagName}>`);
return markup;
}

if (mode === 'asyncYield') {
for await (const segment of (generateMarkup as GenerateMarkupFn)(
tagName,
Expand Down

0 comments on commit 8eec50f

Please sign in to comment.