Skip to content

Commit

Permalink
refactor(ssr): add back references to esTemplate and infer types (#…
Browse files Browse the repository at this point in the history
…4660)

* refactor: list all IR nodes explicitly

* feat(ssr): implement iterator:* directive

* test(ssr): add test for multiple iterator blocks

* fix(ssr-for-of): use empty array as default for missing iterator

* fix(ssr): update error in for-of directive for invalid value

* chore(eslint): don't enforce header in spec files

* test(lwc-if): add tests for lwc:if, lwc:elseif, and lwc:else

* feat(ssr): handle lwc:elseif

* fix(ssr): add comments to lwc:if output

* fix(ssr): don't add comments for if:true and if:false

* feat(ssr): explicitly disallow lwc:dynamic

* fix(ssr): only complain about lwc:dynamic if it gets used

* refactor(ssr): update `esTemplate` for better type inferencing

* refactor(ssr): update `esTemplate` usage

* refactor(ssr): re-use replacement node validation

* refactor(ssr): update esTemplates to use back refs

* fix(ssr): ensure `esTemplate` type inferences work properly

* fix(ssr): add predicate return type

* Update packages/@lwc/ssr-compiler/src/estemplate.ts

Co-authored-by: Nolan Lawson <[email protected]>

* chore(ssr): clean up `isBool`

* chore(ssr): clean up types

* chore(ssr): clean up `ToReplacementParameters`

* chore(ssr): avoid unnecessary conditional

---------

Co-authored-by: Nolan Lawson <[email protected]>
  • Loading branch information
wjhsf and nolanlawson authored Oct 21, 2024
1 parent f005927 commit 7802e27
Show file tree
Hide file tree
Showing 13 changed files with 256 additions and 178 deletions.
39 changes: 27 additions & 12 deletions packages/@lwc/ssr-compiler/src/compile-js/generate-markup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,22 @@ import type {
Program,
ImportDeclaration,
Property,
SimpleLiteral,
SimpleCallExpression,
Identifier,
MemberExpression,
} from 'estree';
import type { ComponentMetaState } from './types';

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

/** Node representing a string literal. */
type StringLiteral = SimpleLiteral & { value: string };

const bGenerateMarkup = esTemplate`
export async function* generateMarkup(tagName, props, attrs, slotted) {
attrs = attrs ?? {};
${isNullableOf(is.expressionStatement)};
Expand All @@ -35,18 +47,18 @@ const bGenerateMarkup = esTemplate<ExportNamedDeclaration>`
yield tmplFn.stylesheetScopeTokenHostClass ?? '';
yield *__renderAttrs(instance, attrs)
yield '>';
yield* tmplFn(props, attrs, slotted, ${is.identifier}, instance);
yield* tmplFn(props, attrs, slotted, ${1}, instance);
yield \`</\${tagName}>\`;
}
`;
`<ExportNamedDeclaration>;

const bInsertFallbackTmplImport = esTemplate<ImportDeclaration>`
const bInsertFallbackTmplImport = esTemplate`
import { fallbackTmpl as __fallbackTmpl, renderAttrs as __renderAttrs } from '@lwc/ssr-runtime';
`;
`<ImportDeclaration>;

const bCreateReflectedPropArr = esTemplate<ExpressionStatement>`
const bCreateReflectedPropArr = esTemplate`
const __REFLECTED_PROPS__ = ${is.arrayExpression};
`;
`<ExpressionStatement>;

function bReflectedAttrsObj(reflectedPropNames: (keyof typeof AriaPropNameToAttrNameMap)[]) {
// This will build getter properties for each reflected property. It'll look
Expand Down Expand Up @@ -135,12 +147,17 @@ export function addGenerateMarkupExport(

const classIdentifier = b.identifier(state.lwcClassName!);
const renderCall = hasRenderMethod
? b.callExpression(b.memberExpression(b.identifier('instance'), b.identifier('render')), [])
? (b.callExpression(
b.memberExpression(b.identifier('instance'), b.identifier('render')),
[]
) as RenderCallExpression)
: b.identifier('tmpl');

if (!tmplExplicitImports) {
const defaultTmplPath = filename.replace(/\.js$/, '.html');
program.body.unshift(bImportDeclaration(b.identifier('tmpl'), b.literal(defaultTmplPath)));
program.body.unshift(
bImportDeclaration(b.identifier('tmpl'), b.literal(defaultTmplPath) as StringLiteral)
);
}

let attrsAugmentation: ExpressionStatement | null = null;
Expand All @@ -153,7 +170,5 @@ export function addGenerateMarkupExport(

program.body.unshift(bInsertFallbackTmplImport());
program.body.push(bCreateReflectedPropArr(reflectedPropArr));
program.body.push(
bGenerateMarkup(attrsAugmentation, classIdentifier, renderCall, classIdentifier)
);
program.body.push(bGenerateMarkup(attrsAugmentation, classIdentifier, renderCall));
}
24 changes: 12 additions & 12 deletions packages/@lwc/ssr-compiler/src/compile-js/stylesheet-scope-token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,29 +11,29 @@ import { builders as b } from 'estree-toolkit/dist/builders';
import { esTemplate } from '../estemplate';
import type { BlockStatement, ExportNamedDeclaration, Program, VariableDeclaration } from 'estree';

const bStylesheetTokenDeclaration = esTemplate<VariableDeclaration>`
const bStylesheetTokenDeclaration = esTemplate`
const stylesheetScopeToken = '${is.literal}';
`;
`<VariableDeclaration>;

const bAdditionalDeclarations = [
esTemplate<VariableDeclaration>`
esTemplate`
const hasScopedStylesheets = defaultScopedStylesheets && defaultScopedStylesheets.length > 0;
`,
esTemplate<ExportNamedDeclaration>`
`<VariableDeclaration>,
esTemplate`
const stylesheetScopeTokenClass = hasScopedStylesheets ? \` class="\${stylesheetScopeToken}"\` : '';
`,
esTemplate<ExportNamedDeclaration>`
`<ExportNamedDeclaration>,
esTemplate`
const stylesheetScopeTokenHostClass = hasScopedStylesheets ? \` class="\${stylesheetScopeToken}-host"\` : '';
`,
esTemplate<ExportNamedDeclaration>`
`<ExportNamedDeclaration>,
esTemplate`
const stylesheetScopeTokenClassPrefix = hasScopedStylesheets ? (stylesheetScopeToken + ' ') : '';
`,
`<ExportNamedDeclaration>,
];

// Scope tokens are associated with a given template. This is assigned here so that it can be used in `generateMarkup`.
const tmplAssignmentBlock = esTemplate<BlockStatement>`
const tmplAssignmentBlock = esTemplate`
${is.identifier}.stylesheetScopeTokenHostClass = stylesheetScopeTokenHostClass;
`;
`<BlockStatement>;

export function addScopeTokenDeclarations(
program: Program,
Expand Down
8 changes: 4 additions & 4 deletions packages/@lwc/ssr-compiler/src/compile-js/stylesheets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ import type { NodePath } from 'estree-toolkit';
import type { ImportDeclaration } from 'estree';
import type { ComponentMetaState } from './types';

const bDefaultStyleImport = esTemplate<ImportDeclaration>`
const bDefaultStyleImport = esTemplate`
import defaultStylesheets from '${is.literal}';
`;
`<ImportDeclaration>;

const bDefaultScopedStyleImport = esTemplate<ImportDeclaration>`
const bDefaultScopedStyleImport = esTemplate`
import defaultScopedStylesheets from '${is.literal}';
`;
`<ImportDeclaration>;

export function catalogStyleImport(path: NodePath<ImportDeclaration>, state: ComponentMetaState) {
const specifier = path.node!.specifiers[0];
Expand Down
4 changes: 2 additions & 2 deletions packages/@lwc/ssr-compiler/src/compile-template/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import type {
} from '@lwc/template-compiler';
import type { Transformer } from './types';

const bYieldFromChildGenerator = esTemplateWithYield<EsBlockStatement>`
const bYieldFromChildGenerator = esTemplateWithYield`
{
const childProps = ${is.objectExpression};
const childAttrs = ${is.objectExpression};
Expand All @@ -34,7 +34,7 @@ const bYieldFromChildGenerator = esTemplateWithYield<EsBlockStatement>`
};
yield* ${is.identifier}(${is.literal}, childProps, childAttrs, childSlottedContentGenerator);
}
`;
`<EsBlockStatement>;

const bImportGenerateMarkup = (localName: string, importPath: string) =>
b.importDeclaration(
Expand Down
8 changes: 4 additions & 4 deletions packages/@lwc/ssr-compiler/src/compile-template/element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import type {
import type { Transformer } from './types';

const bYield = (expr: EsExpression) => b.expressionStatement(b.yieldExpression(expr));
const bConditionalLiveYield = esTemplateWithYield<EsBlockStatement>`
const bConditionalLiveYield = esTemplateWithYield`
{
const prefix = (${/* isClass */ is.literal} && stylesheetScopeTokenClassPrefix) || '';
const attrOrPropValue = ${is.expression};
Expand All @@ -48,14 +48,14 @@ const bConditionalLiveYield = esTemplateWithYield<EsBlockStatement>`
}
}
}
`;
`<EsBlockStatement>;

const bStringLiteralYield = esTemplateWithYield<EsBlockStatement>`
const bStringLiteralYield = esTemplateWithYield`
{
const prefix = (${/* isClass */ is.literal} && stylesheetScopeTokenClassPrefix) || '';
yield ' ' + ${is.literal} + '="' + prefix + "${is.literal}" + '"'
}
`;
`<EsBlockStatement>;

function yieldAttrOrPropLiteralValue(
name: string,
Expand Down
8 changes: 2 additions & 6 deletions packages/@lwc/ssr-compiler/src/compile-template/for-each.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import type {
Expression as EsExpression,
ForOfStatement as EsForOfStatement,
Identifier as EsIdentifier,
Statement as EsStatement,
MemberExpression as EsMemberExpression,
} from 'estree';
import type { Transformer } from './types';
Expand All @@ -29,14 +28,11 @@ function getRootIdentifier(node: EsMemberExpression): EsIdentifier | null {
return is.identifier(rootMemberExpression?.object) ? rootMemberExpression.object : null;
}

const bForOfYieldFrom = esTemplate<
EsForOfStatement,
[EsIdentifier, EsIdentifier, EsExpression, EsStatement[]]
>`
const bForOfYieldFrom = esTemplate`
for (let [${is.identifier}, ${is.identifier}] of Object.entries(${is.expression} ?? {})) {
${is.statement};
}
`;
`<EsForOfStatement>;

export const ForEach: Transformer<IrForEach> = function ForEach(node, cxt): EsForOfStatement[] {
const forItemId = node.item.name;
Expand Down
9 changes: 4 additions & 5 deletions packages/@lwc/ssr-compiler/src/compile-template/for-of.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import type {
Expression as EsExpression,
ForOfStatement as EsForOfStatement,
Identifier as EsIdentifier,
Statement as EsStatement,
MemberExpression as EsMemberExpression,
ImportDeclaration as EsImportDeclaration,
} from 'estree';
Expand All @@ -30,15 +29,15 @@ function getRootIdentifier(node: EsMemberExpression): EsIdentifier | null {
return is.identifier(rootMemberExpression?.object) ? rootMemberExpression.object : null;
}

const bForOfYieldFrom = esTemplate<EsForOfStatement, [EsIdentifier, EsExpression, EsStatement[]]>`
const bForOfYieldFrom = esTemplate`
for (let ${is.identifier} of toIteratorDirective(${is.expression} ?? [])) {
${is.statement};
}
`;
`<EsForOfStatement>;

const bToIteratorDirectiveImport = esTemplate<EsImportDeclaration>`
const bToIteratorDirectiveImport = esTemplate`
import { toIteratorDirective } from '@lwc/ssr-runtime';
`;
`<EsImportDeclaration>;

export const ForOf: Transformer<IrForOf> = function ForEach(node, cxt): EsForOfStatement[] {
const id = node.iterator.name;
Expand Down
31 changes: 14 additions & 17 deletions packages/@lwc/ssr-compiler/src/compile-template/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,23 @@ import { optimizeAdjacentYieldStmts } from './shared';
import { templateIrToEsTree } from './ir-to-es';
import type {
Node as EsNode,
Statement as EsStatement,
Literal as EsLiteral,
ExportDefaultDeclaration as EsExportDefaultDeclaration,
ImportDeclaration as EsImportDeclaration,
SimpleLiteral,
} from 'estree';

const isBool = (node: EsNode | null) => is.literal(node) && typeof node.value === 'boolean';
type Nullable<T> = T | null | undefined;
type BooleanLiteral = SimpleLiteral & { value: boolean };

const bStyleValidationImport = esTemplate<EsImportDeclaration>`
const isBool = (node: Nullable<EsNode>): node is BooleanLiteral => {
return is.literal(node) && typeof node.value === 'boolean';
};

const bStyleValidationImport = esTemplate`
import { validateStyleTextContents } from '@lwc/ssr-runtime';
`;
`<EsImportDeclaration>;

const bExportTemplate = esTemplate<
EsExportDefaultDeclaration,
[EsLiteral, EsStatement[], EsLiteral]
>`
const bExportTemplate = esTemplate`
export default async function* tmpl(props, attrs, slottedContent, Cmp, instance) {
if (!${isBool} && Cmp.renderMode !== 'light') {
yield \`<template shadowrootmode="open"\${Cmp.delegatesFocus ? ' shadowrootdelegatesfocus' : ''}>\`
Expand All @@ -55,15 +56,15 @@ const bExportTemplate = esTemplate<
${is.statement};
if (!${isBool} && Cmp.renderMode !== 'light') {
if (!${0} && Cmp.renderMode !== 'light') {
yield '</template>';
}
if (slottedContent) {
yield* slottedContent();
}
}
`;
`<EsExportDefaultDeclaration>;

export default function compileTemplate(
src: string,
Expand Down Expand Up @@ -108,7 +109,7 @@ export default function compileTemplate(
const tmplRenderMode =
root.directives.find((directive) => directive.name === 'RenderMode')?.value?.value ??
'shadow';
const astShadowModeBool = tmplRenderMode === 'light' ? b.literal(true) : b.literal(false);
const astShadowModeBool = b.literal(tmplRenderMode === 'light') as BooleanLiteral;

const preserveComments = !!root.directives.find(
(directive) => directive.name === 'PreserveComments'
Expand All @@ -119,11 +120,7 @@ export default function compileTemplate(
const moduleBody = [
...hoisted,
bStyleValidationImport(),
bExportTemplate(
astShadowModeBool,
optimizeAdjacentYieldStmts(statements),
astShadowModeBool
),
bExportTemplate(astShadowModeBool, optimizeAdjacentYieldStmts(statements)),
];
const program = b.program(moduleBody, 'module');

Expand Down
4 changes: 2 additions & 2 deletions packages/@lwc/ssr-compiler/src/compile-template/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ import { esTemplate } from '../estemplate';

import type { ImportDeclaration as EsImportDeclaration, Statement as EsStatement } from 'estree';

export const bImportHtmlEscape = esTemplate<EsImportDeclaration>`
export const bImportHtmlEscape = esTemplate`
import { htmlEscape } from '@lwc/shared';
`;
`<EsImportDeclaration>;
export const importHtmlEscapeKey = 'import:htmlEscape';

// This is a mostly-correct regular expression will only match if the entire string
Expand Down
46 changes: 9 additions & 37 deletions packages/@lwc/ssr-compiler/src/compile-template/text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,7 @@ import { esTemplateWithYield } from '../estemplate';
import { bImportHtmlEscape, importHtmlEscapeKey } from './shared';
import { expressionIrToEs } from './expression';

import type {
Expression as EsExpression,
Identifier as EsIdentifier,
Literal as EsLiteral,
Statement as EsStatement,
} from 'estree';
import type { Expression as EsExpression, Statement as EsStatement } from 'estree';
import type {
ComplexExpression as IrComplexExpression,
Expression as IrExpression,
Expand All @@ -26,29 +21,16 @@ import type { Transformer } from './types';

const bYield = (expr: EsExpression) => b.expressionStatement(b.yieldExpression(expr));

const bYieldEscapedString = esTemplateWithYield<
EsStatement[],
[
EsIdentifier,
EsExpression,
EsIdentifier,
EsLiteral,
EsIdentifier,
EsIdentifier,
EsIdentifier,
EsIdentifier,
EsIdentifier,
]
>`
const bYieldEscapedString = esTemplateWithYield`
const ${is.identifier} = ${is.expression};
if (typeof ${is.identifier} === 'string') {
yield (${is.literal} && ${is.identifier} === '') ? '\\u200D' : htmlEscape(${is.identifier});
} else if (typeof ${is.identifier} === 'number') {
yield ${is.identifier}.toString();
if (typeof ${0} === 'string') {
yield (${is.literal} && ${0} === '') ? '\\u200D' : htmlEscape(${0});
} else if (typeof ${0} === 'number') {
yield ${0}.toString();
} else {
yield htmlEscape((${is.identifier} ?? '').toString());
yield ${0} ? htmlEscape(${0}.toString()) : '\\u200D';
}
`;
`<EsStatement[]>;

function isLiteral(node: IrLiteral | IrExpression | IrComplexExpression): node is IrLiteral {
return node.type === 'Literal';
Expand All @@ -68,15 +50,5 @@ export const Text: Transformer<IrText> = function Text(node, cxt): EsStatement[]
cxt.hoist(bImportHtmlEscape(), importHtmlEscapeKey);

const tempVariable = b.identifier(cxt.getUniqueVar());
return bYieldEscapedString(
tempVariable,
valueToYield,
tempVariable,
isIsolatedTextNode,
tempVariable,
tempVariable,
tempVariable,
tempVariable,
tempVariable
);
return bYieldEscapedString(tempVariable, valueToYield, isIsolatedTextNode);
};
Loading

0 comments on commit 7802e27

Please sign in to comment.