From 48d6fddf8e83dd9f093ff9401fd5123290875a99 Mon Sep 17 00:00:00 2001 From: Will Harney Date: Thu, 23 Jan 2025 14:40:21 -0500 Subject: [PATCH 1/8] fix: allow export { Cmp as default } --- .../@lwc/ssr-compiler/src/compile-js/index.ts | 49 +++++++++++++++---- 1 file changed, 40 insertions(+), 9 deletions(-) diff --git a/packages/@lwc/ssr-compiler/src/compile-js/index.ts b/packages/@lwc/ssr-compiler/src/compile-js/index.ts index a6386dcadf..81ca7cb1e7 100644 --- a/packages/@lwc/ssr-compiler/src/compile-js/index.ts +++ b/packages/@lwc/ssr-compiler/src/compile-js/index.ts @@ -20,11 +20,13 @@ import { catalogWireAdapters } from './wire'; import { removeDecoratorImport } from './remove-decorator-import'; import { generateError } from './errors'; +import type { NodePath } from 'estree-toolkit'; import type { ComponentTransformOptions } from '../shared'; import type { Identifier as EsIdentifier, Program as EsProgram, Decorator as EsDecorator, + ClassDeclaration as EsClassDeclaration, } from 'estree'; import type { Visitors, ComponentMetaState } from './types'; import type { CompilationMode } from '@lwc/shared'; @@ -72,15 +74,7 @@ const visitors: Visitors = { }, ClassDeclaration(path, state) { const { node } = path; - if ( - node?.superClass && - // export default class extends LightningElement {} - (is.exportDefaultDeclaration(path.parentPath) || - // class Cmp extends LightningElement {}; export default Cmp - path.scope - ?.getBinding(node.id.name) - ?.references.some((ref) => is.exportDefaultDeclaration(ref.parent))) - ) { + if (123) { // If it's a default-exported class with a superclass, then it's an LWC component! state.isLWC = true; if (node.id) { @@ -218,6 +212,43 @@ const visitors: Visitors = { }, }; +/** + * Determines whether a class declaration is an LWC component. Returns true if the class has a + * superclass and is a default export. + */ +function isLwcComponent(path: NodePath) { + const { node } = path; + if (!node?.superClass) { + // class Cmp {} + return false; + } + if (is.exportDefaultDeclaration(path.parentPath)) { + // export default class Cmp extends LightningElement {} => true + return true; + } + const binding = path.scope?.getBinding(node.id.name); + if (!binding) { + // Never exported + return false; + } + return binding.references.some((ref) => { + const { parent } = ref; + if (is.exportDefaultDeclaration(parent)) { + // class Cmp extends LightningElement {}; export default Cmp + return true; + } + if (is.exportSpecifier(parent)) { + // class Cmp extends LightningElement {}; export { Cmp as default } + // class Cmp extends LightningElement {}; export { Cmp as 'default' } + const exported = is.identifier(parent.exported) + ? parent.exported.name + : parent.exported.value; + return exported === 'default'; + } + return false; + }); +} + function validateUniqueDecorator(decorators: EsDecorator[]) { if (decorators.length < 2) { return; From 1a8673c9ebe2f03c238acb26fcd0e8f49e02036f Mon Sep 17 00:00:00 2001 From: Will Harney Date: Fri, 24 Jan 2025 12:31:13 -0500 Subject: [PATCH 2/8] test(ssr): update component-as-default to check light and shadow --- .../exports/component-as-default/expected.html | 10 ++++++++-- .../fixtures/exports/component-as-default/index.js | 6 +++--- .../component-as-default/modules/x/light/light.html | 3 +++ .../component-as-default/modules/x/light/light.js | 7 +++++++ .../component-as-default/modules/x/parent/parent.html | 4 ++++ .../component-as-default/modules/x/parent/parent.js | 3 +++ .../modules/x/{cmp/cmp.html => shadow/shadow.html} | 0 .../modules/x/{cmp/cmp.js => shadow/shadow.js} | 1 + 8 files changed, 29 insertions(+), 5 deletions(-) create mode 100644 packages/@lwc/engine-server/src/__tests__/fixtures/exports/component-as-default/modules/x/light/light.html create mode 100644 packages/@lwc/engine-server/src/__tests__/fixtures/exports/component-as-default/modules/x/light/light.js create mode 100644 packages/@lwc/engine-server/src/__tests__/fixtures/exports/component-as-default/modules/x/parent/parent.html create mode 100644 packages/@lwc/engine-server/src/__tests__/fixtures/exports/component-as-default/modules/x/parent/parent.js rename packages/@lwc/engine-server/src/__tests__/fixtures/exports/component-as-default/modules/x/{cmp/cmp.html => shadow/shadow.html} (100%) rename packages/@lwc/engine-server/src/__tests__/fixtures/exports/component-as-default/modules/x/{cmp/cmp.js => shadow/shadow.js} (99%) diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/exports/component-as-default/expected.html b/packages/@lwc/engine-server/src/__tests__/fixtures/exports/component-as-default/expected.html index ce996e78e1..d6a688f78a 100644 --- a/packages/@lwc/engine-server/src/__tests__/fixtures/exports/component-as-default/expected.html +++ b/packages/@lwc/engine-server/src/__tests__/fixtures/exports/component-as-default/expected.html @@ -1,4 +1,10 @@ - + - \ No newline at end of file + \ No newline at end of file diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/exports/component-as-default/index.js b/packages/@lwc/engine-server/src/__tests__/fixtures/exports/component-as-default/index.js index 55d4302e11..d5a55ceefa 100644 --- a/packages/@lwc/engine-server/src/__tests__/fixtures/exports/component-as-default/index.js +++ b/packages/@lwc/engine-server/src/__tests__/fixtures/exports/component-as-default/index.js @@ -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'; diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/exports/component-as-default/modules/x/light/light.html b/packages/@lwc/engine-server/src/__tests__/fixtures/exports/component-as-default/modules/x/light/light.html new file mode 100644 index 0000000000..ef261dbb01 --- /dev/null +++ b/packages/@lwc/engine-server/src/__tests__/fixtures/exports/component-as-default/modules/x/light/light.html @@ -0,0 +1,3 @@ + diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/exports/component-as-default/modules/x/light/light.js b/packages/@lwc/engine-server/src/__tests__/fixtures/exports/component-as-default/modules/x/light/light.js new file mode 100644 index 0000000000..3902ccb766 --- /dev/null +++ b/packages/@lwc/engine-server/src/__tests__/fixtures/exports/component-as-default/modules/x/light/light.js @@ -0,0 +1,7 @@ +import { LightningElement } from 'lwc'; + +class Component extends LightningElement { + static renderMode = 'light'; +} + +export { Component as default }; diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/exports/component-as-default/modules/x/parent/parent.html b/packages/@lwc/engine-server/src/__tests__/fixtures/exports/component-as-default/modules/x/parent/parent.html new file mode 100644 index 0000000000..0c64ecaad1 --- /dev/null +++ b/packages/@lwc/engine-server/src/__tests__/fixtures/exports/component-as-default/modules/x/parent/parent.html @@ -0,0 +1,4 @@ + diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/exports/component-as-default/modules/x/parent/parent.js b/packages/@lwc/engine-server/src/__tests__/fixtures/exports/component-as-default/modules/x/parent/parent.js new file mode 100644 index 0000000000..6d3542bb2f --- /dev/null +++ b/packages/@lwc/engine-server/src/__tests__/fixtures/exports/component-as-default/modules/x/parent/parent.js @@ -0,0 +1,3 @@ +import { LightningElement } from 'lwc'; + +export default class Component extends LightningElement {} diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/exports/component-as-default/modules/x/cmp/cmp.html b/packages/@lwc/engine-server/src/__tests__/fixtures/exports/component-as-default/modules/x/shadow/shadow.html similarity index 100% rename from packages/@lwc/engine-server/src/__tests__/fixtures/exports/component-as-default/modules/x/cmp/cmp.html rename to packages/@lwc/engine-server/src/__tests__/fixtures/exports/component-as-default/modules/x/shadow/shadow.html diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/exports/component-as-default/modules/x/cmp/cmp.js b/packages/@lwc/engine-server/src/__tests__/fixtures/exports/component-as-default/modules/x/shadow/shadow.js similarity index 99% rename from packages/@lwc/engine-server/src/__tests__/fixtures/exports/component-as-default/modules/x/cmp/cmp.js rename to packages/@lwc/engine-server/src/__tests__/fixtures/exports/component-as-default/modules/x/shadow/shadow.js index e0542c7a5d..3048e5440e 100644 --- a/packages/@lwc/engine-server/src/__tests__/fixtures/exports/component-as-default/modules/x/cmp/cmp.js +++ b/packages/@lwc/engine-server/src/__tests__/fixtures/exports/component-as-default/modules/x/shadow/shadow.js @@ -1,4 +1,5 @@ import { LightningElement } from 'lwc'; class Component extends LightningElement {} + export { Component as default }; From c5432bfa8f987bb6bc158ab0604379d380ab9282 Mon Sep 17 00:00:00 2001 From: Will Harney Date: Fri, 24 Jan 2025 13:24:26 -0500 Subject: [PATCH 3/8] fix: use fallback if no generateMarkup --- .../src/__tests__/utils/expected-failures.ts | 1 - .../@lwc/ssr-compiler/src/compile-js/index.ts | 49 ++++--------------- packages/@lwc/ssr-runtime/src/render.ts | 10 +++- 3 files changed, 18 insertions(+), 42 deletions(-) diff --git a/packages/@lwc/ssr-compiler/src/__tests__/utils/expected-failures.ts b/packages/@lwc/ssr-compiler/src/__tests__/utils/expected-failures.ts index b2f8518054..ffedb6f8f9 100644 --- a/packages/@lwc/ssr-compiler/src/__tests__/utils/expected-failures.ts +++ b/packages/@lwc/ssr-compiler/src/__tests__/utils/expected-failures.ts @@ -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', 'render-dynamic-value/index.js', 'slot-forwarding/slots/mixed/index.js', diff --git a/packages/@lwc/ssr-compiler/src/compile-js/index.ts b/packages/@lwc/ssr-compiler/src/compile-js/index.ts index 81ca7cb1e7..a6386dcadf 100644 --- a/packages/@lwc/ssr-compiler/src/compile-js/index.ts +++ b/packages/@lwc/ssr-compiler/src/compile-js/index.ts @@ -20,13 +20,11 @@ import { catalogWireAdapters } from './wire'; import { removeDecoratorImport } from './remove-decorator-import'; import { generateError } from './errors'; -import type { NodePath } from 'estree-toolkit'; import type { ComponentTransformOptions } from '../shared'; import type { Identifier as EsIdentifier, Program as EsProgram, Decorator as EsDecorator, - ClassDeclaration as EsClassDeclaration, } from 'estree'; import type { Visitors, ComponentMetaState } from './types'; import type { CompilationMode } from '@lwc/shared'; @@ -74,7 +72,15 @@ const visitors: Visitors = { }, ClassDeclaration(path, state) { const { node } = path; - if (123) { + if ( + node?.superClass && + // export default class extends LightningElement {} + (is.exportDefaultDeclaration(path.parentPath) || + // class Cmp extends LightningElement {}; export default Cmp + path.scope + ?.getBinding(node.id.name) + ?.references.some((ref) => is.exportDefaultDeclaration(ref.parent))) + ) { // If it's a default-exported class with a superclass, then it's an LWC component! state.isLWC = true; if (node.id) { @@ -212,43 +218,6 @@ const visitors: Visitors = { }, }; -/** - * Determines whether a class declaration is an LWC component. Returns true if the class has a - * superclass and is a default export. - */ -function isLwcComponent(path: NodePath) { - const { node } = path; - if (!node?.superClass) { - // class Cmp {} - return false; - } - if (is.exportDefaultDeclaration(path.parentPath)) { - // export default class Cmp extends LightningElement {} => true - return true; - } - const binding = path.scope?.getBinding(node.id.name); - if (!binding) { - // Never exported - return false; - } - return binding.references.some((ref) => { - const { parent } = ref; - if (is.exportDefaultDeclaration(parent)) { - // class Cmp extends LightningElement {}; export default Cmp - return true; - } - if (is.exportSpecifier(parent)) { - // class Cmp extends LightningElement {}; export { Cmp as default } - // class Cmp extends LightningElement {}; export { Cmp as 'default' } - const exported = is.identifier(parent.exported) - ? parent.exported.name - : parent.exported.value; - return exported === 'default'; - } - return false; - }); -} - function validateUniqueDecorator(decorators: EsDecorator[]) { if (decorators.length < 2) { return; diff --git a/packages/@lwc/ssr-runtime/src/render.ts b/packages/@lwc/ssr-runtime/src/render.ts index 188c169ba7..e9de71cfcf 100644 --- a/packages/@lwc/ssr-runtime/src/render.ts +++ b/packages/@lwc/ssr-runtime/src/render.ts @@ -174,7 +174,7 @@ type GenerateMarkupFnVariants = | GenerateMarkupFnAsyncNoGen | GenerateMarkupFnSyncNoGen; -interface ComponentWithGenerateMarkup { +interface ComponentWithGenerateMarkup extends LightningElementConstructor { [SYMBOL__GENERATE_MARKUP]: GenerateMarkupFnVariants; } @@ -195,6 +195,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(``); + return markup; + } + if (mode === 'asyncYield') { for await (const segment of (generateMarkup as GenerateMarkupFn)( tagName, From 17bb3424f4a8c8c2376c4426975aa2dfb95e00c6 Mon Sep 17 00:00:00 2001 From: Will Harney Date: Wed, 29 Jan 2025 11:58:28 -0500 Subject: [PATCH 4/8] fix(ssr): support `export { Cmp as default }` --- .../modules/x/light/light.js | 4 +-- .../modules/x/parent/parent.js | 2 +- .../modules/x/shadow/shadow.js | 4 +-- .../transformers/component/component.ts | 29 +++++++++++-------- packages/@lwc/ssr-runtime/src/index.ts | 1 + packages/@lwc/ssr-runtime/src/render.ts | 14 +++++++++ 6 files changed, 37 insertions(+), 17 deletions(-) diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/exports/component-as-default/modules/x/light/light.js b/packages/@lwc/engine-server/src/__tests__/fixtures/exports/component-as-default/modules/x/light/light.js index 3902ccb766..65a040de68 100644 --- a/packages/@lwc/engine-server/src/__tests__/fixtures/exports/component-as-default/modules/x/light/light.js +++ b/packages/@lwc/engine-server/src/__tests__/fixtures/exports/component-as-default/modules/x/light/light.js @@ -1,7 +1,7 @@ import { LightningElement } from 'lwc'; -class Component extends LightningElement { +class Light extends LightningElement { static renderMode = 'light'; } -export { Component as default }; +export { Light as default }; diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/exports/component-as-default/modules/x/parent/parent.js b/packages/@lwc/engine-server/src/__tests__/fixtures/exports/component-as-default/modules/x/parent/parent.js index 6d3542bb2f..a5746a015a 100644 --- a/packages/@lwc/engine-server/src/__tests__/fixtures/exports/component-as-default/modules/x/parent/parent.js +++ b/packages/@lwc/engine-server/src/__tests__/fixtures/exports/component-as-default/modules/x/parent/parent.js @@ -1,3 +1,3 @@ import { LightningElement } from 'lwc'; -export default class Component extends LightningElement {} +export default class Parent extends LightningElement {} diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/exports/component-as-default/modules/x/shadow/shadow.js b/packages/@lwc/engine-server/src/__tests__/fixtures/exports/component-as-default/modules/x/shadow/shadow.js index 3048e5440e..098f79650f 100644 --- a/packages/@lwc/engine-server/src/__tests__/fixtures/exports/component-as-default/modules/x/shadow/shadow.js +++ b/packages/@lwc/engine-server/src/__tests__/fixtures/exports/component-as-default/modules/x/shadow/shadow.js @@ -1,5 +1,5 @@ import { LightningElement } from 'lwc'; -class Component extends LightningElement {} +class Shadow extends LightningElement {} -export { Component as default }; +export { Shadow as default }; diff --git a/packages/@lwc/ssr-compiler/src/compile-template/transformers/component/component.ts b/packages/@lwc/ssr-compiler/src/compile-template/transformers/component/component.ts index c02ffe8bf8..935c35bd11 100644 --- a/packages/@lwc/ssr-compiler/src/compile-template/transformers/component/component.ts +++ b/packages/@lwc/ssr-compiler/src/compile-template/transformers/component/component.ts @@ -37,19 +37,23 @@ const bYieldFromChildGenerator = esTemplateWithYield` } const scopeToken = hasScopedStylesheets ? stylesheetScopeToken : undefined; - const Ctor = ${/* Component */ is.identifier}; + const generateMarkup = ${/* Component */ is.identifier}[__SYMBOL__GENERATE_MARKUP]; - yield* Ctor[__SYMBOL__GENERATE_MARKUP]( - ${/* tag name */ is.literal}, - childProps, - childAttrs, - shadowSlottedContent, - lightSlottedContentMap, - scopedSlottedContentMap, - instance, - scopeToken, - contextfulParent - ); + if (!generateMarkup) { + yield __unimplementedTmpl(${/* tag name */ is.literal}, ${/* Component */ 3}); + } else { + yield* generateMarkup( + ${/* tag name */ 4}, + childProps, + childAttrs, + shadowSlottedContent, + lightSlottedContentMap, + scopedSlottedContentMap, + instance, + scopeToken, + contextfulParent + ); + } } `; @@ -60,6 +64,7 @@ export const Component: Transformer = function Component(node, cxt) cxt.import({ default: childComponentLocalName }, importPath); cxt.import({ SYMBOL__GENERATE_MARKUP: '__SYMBOL__GENERATE_MARKUP', + unimplementedTmpl: '__unimplementedTmpl', }); const childTagName = node.name; diff --git a/packages/@lwc/ssr-runtime/src/index.ts b/packages/@lwc/ssr-runtime/src/index.ts index cfa9b8dd53..3d89ed79e7 100644 --- a/packages/@lwc/ssr-runtime/src/index.ts +++ b/packages/@lwc/ssr-runtime/src/index.ts @@ -28,6 +28,7 @@ export { serverSideRenderComponent, // renderComponent is an alias for serverSideRenderComponent serverSideRenderComponent as renderComponent, + unimplementedTmpl, } from './render'; export { normalizeTextContent, renderTextContent } from './render-text-content'; export { hasScopedStaticStylesheets, renderStylesheets } from './styles'; diff --git a/packages/@lwc/ssr-runtime/src/render.ts b/packages/@lwc/ssr-runtime/src/render.ts index e9de71cfcf..ca2ee8bd83 100644 --- a/packages/@lwc/ssr-runtime/src/render.ts +++ b/packages/@lwc/ssr-runtime/src/render.ts @@ -125,6 +125,20 @@ export function fallbackTmplNoYield( } } +/** + * If a component is incorrectly implemented, and is missing a `generateMarkup` function, + * then use this template as a fallback so the world doesn't explode. + * @example export { Cmp as default } + */ +export function unimplementedTmpl(tagName: string, Cmp?: LightningElementConstructor): string { + let html = `<${tagName}>`; + if (Cmp?.renderMode !== 'light') { + html += ''; + } + html += ``; + return html; +} + export type GenerateMarkupFn = ( tagName: string, props: Properties | null, From b460d35b6a8b8b6674c9c5016079a0b4ff2eb00d Mon Sep 17 00:00:00 2001 From: John Hefferman Date: Thu, 30 Jan 2025 14:41:35 -0700 Subject: [PATCH 5/8] fix: support slotted content for unimplemented templates --- .../component-as-default/expected.html | 9 ++++ .../modules/x/parent/parent.html | 2 + .../transformers/component/component.ts | 3 +- .../@lwc/ssr-compiler/src/transmogrify.ts | 3 ++ packages/@lwc/ssr-runtime/src/index.ts | 1 + packages/@lwc/ssr-runtime/src/render.ts | 45 +++++++++++++++---- 6 files changed, 53 insertions(+), 10 deletions(-) diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/exports/component-as-default/expected.html b/packages/@lwc/engine-server/src/__tests__/fixtures/exports/component-as-default/expected.html index d6a688f78a..1dd9a11014 100644 --- a/packages/@lwc/engine-server/src/__tests__/fixtures/exports/component-as-default/expected.html +++ b/packages/@lwc/engine-server/src/__tests__/fixtures/exports/component-as-default/expected.html @@ -4,6 +4,15 @@ + + +

+ slotted content +

+
+ + diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/exports/component-as-default/modules/x/parent/parent.html b/packages/@lwc/engine-server/src/__tests__/fixtures/exports/component-as-default/modules/x/parent/parent.html index 0c64ecaad1..7ace378547 100644 --- a/packages/@lwc/engine-server/src/__tests__/fixtures/exports/component-as-default/modules/x/parent/parent.html +++ b/packages/@lwc/engine-server/src/__tests__/fixtures/exports/component-as-default/modules/x/parent/parent.html @@ -1,4 +1,6 @@ diff --git a/packages/@lwc/ssr-compiler/src/compile-template/transformers/component/component.ts b/packages/@lwc/ssr-compiler/src/compile-template/transformers/component/component.ts index 935c35bd11..d773515d14 100644 --- a/packages/@lwc/ssr-compiler/src/compile-template/transformers/component/component.ts +++ b/packages/@lwc/ssr-compiler/src/compile-template/transformers/component/component.ts @@ -38,9 +38,8 @@ const bYieldFromChildGenerator = esTemplateWithYield` const scopeToken = hasScopedStylesheets ? stylesheetScopeToken : undefined; const generateMarkup = ${/* Component */ is.identifier}[__SYMBOL__GENERATE_MARKUP]; - if (!generateMarkup) { - yield __unimplementedTmpl(${/* tag name */ is.literal}, ${/* Component */ 3}); + yield* __unimplementedTmpl(${/* tag name */ is.literal}, instance, shadowSlottedContent, ${/* Component */ 3}); } else { yield* generateMarkup( ${/* tag name */ 4}, diff --git a/packages/@lwc/ssr-compiler/src/transmogrify.ts b/packages/@lwc/ssr-compiler/src/transmogrify.ts index 6a3a89a579..aeb1f58ff5 100644 --- a/packages/@lwc/ssr-compiler/src/transmogrify.ts +++ b/packages/@lwc/ssr-compiler/src/transmogrify.ts @@ -114,6 +114,7 @@ const visitors: Visitors = { // // - renderAttrs vs renderAttrsNoYield // - fallbackTmpl vs fallbackTmplNoYield + // - unimplementedTmpl vs unimplementedTmplNoYield // // If this becomes too burdensome to maintain, we can officially deprecate the generator-based approach // and switch the @lwc/ssr-runtime implementation wholesale over to the no-generator paradigm. @@ -136,6 +137,8 @@ const visitors: Visitors = { node.imported.name = 'fallbackTmplNoYield'; } else if (node.imported.name === 'renderAttrs') { node.imported.name = 'renderAttrsNoYield'; + } else if (node.imported.name === 'unimplementedTmpl') { + node.imported.name = 'unimplementedTmplNoYield'; } }, }; diff --git a/packages/@lwc/ssr-runtime/src/index.ts b/packages/@lwc/ssr-runtime/src/index.ts index 3d89ed79e7..51416bd6af 100644 --- a/packages/@lwc/ssr-runtime/src/index.ts +++ b/packages/@lwc/ssr-runtime/src/index.ts @@ -29,6 +29,7 @@ export { // renderComponent is an alias for serverSideRenderComponent serverSideRenderComponent as renderComponent, unimplementedTmpl, + unimplementedTmplNoYield, } from './render'; export { normalizeTextContent, renderTextContent } from './render-text-content'; export { hasScopedStaticStylesheets, renderStylesheets } from './styles'; diff --git a/packages/@lwc/ssr-runtime/src/render.ts b/packages/@lwc/ssr-runtime/src/render.ts index 63cd8efcb8..6a8a03675a 100644 --- a/packages/@lwc/ssr-runtime/src/render.ts +++ b/packages/@lwc/ssr-runtime/src/render.ts @@ -105,7 +105,7 @@ export function* fallbackTmpl( _lightSlottedContent: unknown, _scopedSlottedContent: unknown, Cmp: LightningElementConstructor, - instance: unknown + instance: LightningElement ) { if (Cmp.renderMode !== 'light') { yield ``; @@ -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(``); @@ -136,13 +136,42 @@ export function fallbackTmplNoYield( * then use this template as a fallback so the world doesn't explode. * @example export { Cmp as default } */ -export function unimplementedTmpl(tagName: string, Cmp?: LightningElementConstructor): string { - let html = `<${tagName}>`; +export function* unimplementedTmpl( + tagName: string, + instance: LightningElement, + shadowSlottedContent: AsyncGeneratorFunction, + Cmp?: LightningElementConstructor +) { + yield `<${tagName}>`; if (Cmp?.renderMode !== 'light') { - html += ''; + yield ''; + if (shadowSlottedContent) { + yield shadowSlottedContent(instance); + } + } + yield ``; +} + +/** + * If a component is incorrectly implemented, and is missing a `generateMarkup` function, + * then use this template as a fallback so the world doesn't explode. + * @example export { Cmp as default } + */ +export function unimplementedTmplNoYield( + emit: (segment: string) => void, + tagName: string, + instance: LightningElement, + shadowSlottedContent: AsyncGeneratorFunction, + Cmp?: LightningElementConstructor +) { + emit(`<${tagName}>`); + if (Cmp?.renderMode !== 'light') { + emit(''); + if (shadowSlottedContent) { + shadowSlottedContent(emit, instance); + } } - html += ``; - return html; + emit(``); } export type GenerateMarkupFn = ( From b5868f17abc35b6465653441f07581297d629c29 Mon Sep 17 00:00:00 2001 From: John Hefferman Date: Fri, 31 Jan 2025 09:25:30 -0700 Subject: [PATCH 6/8] fix: use fallbackTmpl for all cases --- .../transformers/component/component.ts | 12 ++++-- .../@lwc/ssr-compiler/src/transmogrify.ts | 3 -- packages/@lwc/ssr-runtime/src/index.ts | 2 - packages/@lwc/ssr-runtime/src/render.ts | 43 ------------------- 4 files changed, 8 insertions(+), 52 deletions(-) diff --git a/packages/@lwc/ssr-compiler/src/compile-template/transformers/component/component.ts b/packages/@lwc/ssr-compiler/src/compile-template/transformers/component/component.ts index d773515d14..0d0232fb14 100644 --- a/packages/@lwc/ssr-compiler/src/compile-template/transformers/component/component.ts +++ b/packages/@lwc/ssr-compiler/src/compile-template/transformers/component/component.ts @@ -31,18 +31,22 @@ 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 generateMarkup = ${/* Component */ is.identifier}[__SYMBOL__GENERATE_MARKUP]; + const tagName = ${/* tag name */ is.literal}; + if (!generateMarkup) { - yield* __unimplementedTmpl(${/* tag name */ is.literal}, instance, shadowSlottedContent, ${/* Component */ 3}); + yield \`<\${tagName}>\`; + yield* __fallbackTmpl(shadowSlottedContent, lightSlottedContentMap, scopedSlottedContentMap, ${/* Component */ 3}, instance) + yield \`\`; } else { yield* generateMarkup( - ${/* tag name */ 4}, + tagName, childProps, childAttrs, shadowSlottedContent, @@ -63,7 +67,7 @@ export const Component: Transformer = function Component(node, cxt) cxt.import({ default: childComponentLocalName }, importPath); cxt.import({ SYMBOL__GENERATE_MARKUP: '__SYMBOL__GENERATE_MARKUP', - unimplementedTmpl: '__unimplementedTmpl', + fallbackTmpl: '__fallbackTmpl', }); const childTagName = node.name; diff --git a/packages/@lwc/ssr-compiler/src/transmogrify.ts b/packages/@lwc/ssr-compiler/src/transmogrify.ts index aeb1f58ff5..6a3a89a579 100644 --- a/packages/@lwc/ssr-compiler/src/transmogrify.ts +++ b/packages/@lwc/ssr-compiler/src/transmogrify.ts @@ -114,7 +114,6 @@ const visitors: Visitors = { // // - renderAttrs vs renderAttrsNoYield // - fallbackTmpl vs fallbackTmplNoYield - // - unimplementedTmpl vs unimplementedTmplNoYield // // If this becomes too burdensome to maintain, we can officially deprecate the generator-based approach // and switch the @lwc/ssr-runtime implementation wholesale over to the no-generator paradigm. @@ -137,8 +136,6 @@ const visitors: Visitors = { node.imported.name = 'fallbackTmplNoYield'; } else if (node.imported.name === 'renderAttrs') { node.imported.name = 'renderAttrsNoYield'; - } else if (node.imported.name === 'unimplementedTmpl') { - node.imported.name = 'unimplementedTmplNoYield'; } }, }; diff --git a/packages/@lwc/ssr-runtime/src/index.ts b/packages/@lwc/ssr-runtime/src/index.ts index 51416bd6af..cfa9b8dd53 100644 --- a/packages/@lwc/ssr-runtime/src/index.ts +++ b/packages/@lwc/ssr-runtime/src/index.ts @@ -28,8 +28,6 @@ export { serverSideRenderComponent, // renderComponent is an alias for serverSideRenderComponent serverSideRenderComponent as renderComponent, - unimplementedTmpl, - unimplementedTmplNoYield, } from './render'; export { normalizeTextContent, renderTextContent } from './render-text-content'; export { hasScopedStaticStylesheets, renderStylesheets } from './styles'; diff --git a/packages/@lwc/ssr-runtime/src/render.ts b/packages/@lwc/ssr-runtime/src/render.ts index 6a8a03675a..98e15d4f25 100644 --- a/packages/@lwc/ssr-runtime/src/render.ts +++ b/packages/@lwc/ssr-runtime/src/render.ts @@ -131,49 +131,6 @@ export function fallbackTmplNoYield( } } -/** - * If a component is incorrectly implemented, and is missing a `generateMarkup` function, - * then use this template as a fallback so the world doesn't explode. - * @example export { Cmp as default } - */ -export function* unimplementedTmpl( - tagName: string, - instance: LightningElement, - shadowSlottedContent: AsyncGeneratorFunction, - Cmp?: LightningElementConstructor -) { - yield `<${tagName}>`; - if (Cmp?.renderMode !== 'light') { - yield ''; - if (shadowSlottedContent) { - yield shadowSlottedContent(instance); - } - } - yield ``; -} - -/** - * If a component is incorrectly implemented, and is missing a `generateMarkup` function, - * then use this template as a fallback so the world doesn't explode. - * @example export { Cmp as default } - */ -export function unimplementedTmplNoYield( - emit: (segment: string) => void, - tagName: string, - instance: LightningElement, - shadowSlottedContent: AsyncGeneratorFunction, - Cmp?: LightningElementConstructor -) { - emit(`<${tagName}>`); - if (Cmp?.renderMode !== 'light') { - emit(''); - if (shadowSlottedContent) { - shadowSlottedContent(emit, instance); - } - } - emit(``); -} - export type GenerateMarkupFn = ( tagName: string, props: Properties | null, From 01af84f859cf30aeca179e253a72145da5074e81 Mon Sep 17 00:00:00 2001 From: Will Harney <62956339+wjhsf@users.noreply.github.com> Date: Fri, 31 Jan 2025 11:36:10 -0500 Subject: [PATCH 7/8] Update packages/@lwc/ssr-compiler/src/compile-template/transformers/component/component.ts --- .../transformers/component/component.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/@lwc/ssr-compiler/src/compile-template/transformers/component/component.ts b/packages/@lwc/ssr-compiler/src/compile-template/transformers/component/component.ts index 0d0232fb14..6440a18db6 100644 --- a/packages/@lwc/ssr-compiler/src/compile-template/transformers/component/component.ts +++ b/packages/@lwc/ssr-compiler/src/compile-template/transformers/component/component.ts @@ -40,12 +40,8 @@ const bYieldFromChildGenerator = esTemplateWithYield` const generateMarkup = ${/* Component */ is.identifier}[__SYMBOL__GENERATE_MARKUP]; const tagName = ${/* tag name */ is.literal}; - if (!generateMarkup) { - yield \`<\${tagName}>\`; - yield* __fallbackTmpl(shadowSlottedContent, lightSlottedContentMap, scopedSlottedContentMap, ${/* Component */ 3}, instance) - yield \`\`; - } else { - yield* generateMarkup( + if (generateMarkup) { + yield* generateMarkup( tagName, childProps, childAttrs, @@ -56,6 +52,10 @@ const bYieldFromChildGenerator = esTemplateWithYield` scopeToken, contextfulParent ); + } else { + yield \`<\${tagName}>\`; + yield* __fallbackTmpl(shadowSlottedContent, lightSlottedContentMap, scopedSlottedContentMap, ${/* Component */ 3}, instance) + yield \`\`; } } `; From e83a19e3e871e7d78e35c31054a6d132b7602e3f Mon Sep 17 00:00:00 2001 From: Will Harney <62956339+wjhsf@users.noreply.github.com> Date: Fri, 31 Jan 2025 11:36:31 -0500 Subject: [PATCH 8/8] Update packages/@lwc/ssr-compiler/src/compile-template/transformers/component/component.ts --- .../src/compile-template/transformers/component/component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@lwc/ssr-compiler/src/compile-template/transformers/component/component.ts b/packages/@lwc/ssr-compiler/src/compile-template/transformers/component/component.ts index 6440a18db6..25540e1c51 100644 --- a/packages/@lwc/ssr-compiler/src/compile-template/transformers/component/component.ts +++ b/packages/@lwc/ssr-compiler/src/compile-template/transformers/component/component.ts @@ -41,7 +41,7 @@ const bYieldFromChildGenerator = esTemplateWithYield` const tagName = ${/* tag name */ is.literal}; if (generateMarkup) { - yield* generateMarkup( + yield* generateMarkup( tagName, childProps, childAttrs,