Skip to content

Commit

Permalink
fix(ssr): render from superclass (#5063)
Browse files Browse the repository at this point in the history
Co-authored-by: Nolan Lawson <[email protected]>
  • Loading branch information
cardoso and nolanlawson authored Dec 19, 2024
1 parent ff0d118 commit a6e321c
Show file tree
Hide file tree
Showing 5 changed files with 7 additions and 52 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,6 @@ export const expectedFailures = new Set([
'slot-not-at-top-level/with-adjacent-text-nodes/lwcIf/light/index.js',
'slot-not-at-top-level/with-adjacent-text-nodes/if/light/index.js',
'slot-not-at-top-level/with-adjacent-text-nodes/if-as-sibling/light/index.js',
'superclass/render-in-superclass/no-template-in-subclass/index.js',
'superclass/render-in-superclass/unused-default-in-subclass/index.js',
'superclass/render-in-superclass/unused-default-in-superclass/index.js',
'wire/errors/throws-on-computed-key/index.js',
'wire/errors/throws-when-colliding-prop-then-method/index.js',
]);
27 changes: 7 additions & 20 deletions packages/@lwc/ssr-compiler/src/compile-js/generate-markup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,27 +8,18 @@
import { parse as pathParse } from 'node:path';
import { is, builders as b } from 'estree-toolkit';
import { esTemplate } from '../estemplate';
import { isIdentOrRenderCall } from '../estree/validators';
import { bImportDeclaration } from '../estree/builders';
import { bWireAdaptersPlumbing } from './wire';

import type {
Program,
SimpleCallExpression,
Identifier,
MemberExpression,
Statement,
ExpressionStatement,
IfStatement,
FunctionDeclaration,
} from 'estree';
import type { ComponentMetaState } from './types';

/** Node representing `<something>.render()`. */
type RenderCallExpression = SimpleCallExpression & {
callee: MemberExpression & { property: Identifier & { name: 'render' } };
};

const bGenerateMarkup = esTemplate`
async function* generateMarkup(
tagName,
Expand Down Expand Up @@ -62,7 +53,10 @@ const bGenerateMarkup = esTemplate`
instance.connectedCallback();
__mutationTracker.disable(instance);
}
const tmplFn = ${isIdentOrRenderCall} ?? ${/*component class*/ 3}[__SYMBOL__DEFAULT_TEMPLATE] ?? __fallbackTmpl;
// If a render() function is defined on the class or any of its superclasses, then that takes priority.
// Next, if the class or any of its superclasses has an implicitly-associated template, then that takes
// second priority (e.g. a foo.html file alongside a foo.js file). Finally, there is a fallback empty template.
const tmplFn = instance.render?.() ?? ${/*component class*/ 3}[__SYMBOL__DEFAULT_TEMPLATE] ?? __fallbackTmpl;
yield \`<\${tagName}\`;
const hostHasScopedStylesheets =
Expand Down Expand Up @@ -109,25 +103,19 @@ export function addGenerateMarkupFunction(
tagName: string,
filename: string
) {
const { hasRenderMethod, privateFields, publicFields, tmplExplicitImports } = state;
const { privateFields, publicFields, tmplExplicitImports } = state;

// The default tag name represents the component name that's passed to the transformer.
// This is needed to generate markup for dynamic components which are invoked through
// the generateMarkup function on the constructor.
// At the time of generation, the invoker does not have reference to its tag name to pass as an argument.
const defaultTagName = b.literal(tagName);
const classIdentifier = b.identifier(state.lwcClassName!);
const tmplVar = b.identifier('tmpl');
const renderCall = hasRenderMethod
? (b.callExpression(
b.memberExpression(b.identifier('instance'), b.identifier('render')),
[]
) as RenderCallExpression)
: tmplVar;

let exposeTemplateBlock: IfStatement | null = null;
if (!tmplExplicitImports) {
const defaultTmplPath = `./${pathParse(filename).name}.html`;
const tmplVar = b.identifier('tmpl');
program.body.unshift(bImportDeclaration({ default: tmplVar.name }, defaultTmplPath));
program.body.unshift(
bImportDeclaration({ SYMBOL__DEFAULT_TEMPLATE: '__SYMBOL__DEFAULT_TEMPLATE' })
Expand Down Expand Up @@ -160,8 +148,7 @@ export function addGenerateMarkupFunction(
b.arrayExpression(publicFields.map(b.literal)),
b.arrayExpression(privateFields.map(b.literal)),
classIdentifier,
connectWireAdapterCode,
renderCall
connectWireAdapterCode
)
);

Expand Down
4 changes: 0 additions & 4 deletions packages/@lwc/ssr-compiler/src/compile-js/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,9 +173,6 @@ const visitors: Visitors = {
case 'connectedCallback':
state.hasConnectedCallback = true;
break;
case 'render':
state.hasRenderMethod = true;
break;
case 'renderedCallback':
state.hadRenderedCallback = true;
path.remove();
Expand Down Expand Up @@ -253,7 +250,6 @@ export default function compileJS(
isLWC: false,
hasConstructor: false,
hasConnectedCallback: false,
hasRenderMethod: false,
hadRenderedCallback: false,
hadDisconnectedCallback: false,
hadErrorCallback: false,
Expand Down
2 changes: 0 additions & 2 deletions packages/@lwc/ssr-compiler/src/compile-js/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,6 @@ export interface ComponentMetaState {
hasConstructor: boolean;
// indicates whether the subclass has a connectedCallback method
hasConnectedCallback: boolean;
// indicates whether the subclass has a render method
hasRenderMethod: boolean;
// indicates whether the subclass has a renderedCallback method
hadRenderedCallback: boolean;
// indicates whether the subclass has a disconnectedCallback method
Expand Down
23 changes: 0 additions & 23 deletions packages/@lwc/ssr-compiler/src/estree/validators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,32 +7,9 @@

import { is } from 'estree-toolkit';
import { entries } from '@lwc/shared';
import type { CallExpression, Identifier, MemberExpression } from 'estree';
import type { Checker } from 'estree-toolkit/dist/generated/is-type';
import type { Node } from 'estree-toolkit/dist/helpers'; // estree's `Node` is not compatible?

/** Node representing an identifier named "render". */
type RenderIdentifier = Identifier & { name: 'render' };
/** Node representing a member expression `<something>.render`. */
type RenderMemberExpression = MemberExpression & { property: RenderIdentifier };
/** Node representing a method call `<something>.render()`. */
type RenderCall = CallExpression & { callee: RenderMemberExpression };

/** Returns `true` if the node is an identifier or `<something>.render()`. */
export const isIdentOrRenderCall = (
node: Node | null | undefined
): node is Identifier | RenderCall => {
return (
is.identifier(node) ||
(is.callExpression(node) &&
is.memberExpression(node.callee) &&
is.identifier(node.callee.property) &&
node.callee.property.name === 'render')
);
};

isIdentOrRenderCall.__debugName = 'identifier or .render() call';

/** A validator that returns `true` if the node is `null`. */
type NullableChecker<T extends Node> = (node: Node | null | undefined) => node is T | null;

Expand Down

0 comments on commit a6e321c

Please sign in to comment.