Skip to content

Commit

Permalink
fix(ssr): fix expression scoping and other small fixes (#4702)
Browse files Browse the repository at this point in the history
Co-authored-by: Eugene Kashida <ekashida@gmail.com>
  • Loading branch information
nolanlawson and ekashida authored Oct 25, 2024
1 parent 75ff766 commit cd41384
Show file tree
Hide file tree
Showing 11 changed files with 77 additions and 36 deletions.
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<x-component>
<template shadowrootmode="open">
<div data-value="yolo">
yolo
</div>
</template>
</x-component>
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const tagName = 'x-component';
export { default } from 'x/component';
export * from 'x/component';
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<template>
<template for:each={rows} for:item="row">
<div key={row.id} data-value={row.foo.bar.baz}>{row.foo.bar.baz}</div>
</template>
</template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { LightningElement } from 'lwc';

export default class Component extends LightningElement {
rows = [
{
id: 1,
foo: {
bar: {
baz: 'yolo'
}
}
}
]
}
2 changes: 2 additions & 0 deletions packages/@lwc/rollup-plugin/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ export default function lwc(pluginOptions: RollupLwcOptions = {}): Plugin {
let { rootDir, modules = [] } = pluginOptions;
const {
targetSSR,
ssrMode,
stylesheetConfig,
sourcemap = false,
preserveHtmlComments,
Expand Down Expand Up @@ -348,6 +349,7 @@ export default function lwc(pluginOptions: RollupLwcOptions = {}): Plugin {
enableStaticContentOptimization: pluginOptions.enableStaticContentOptimization,
}),
targetSSR,
ssrMode,
});

if (warnings) {
Expand Down
30 changes: 29 additions & 1 deletion packages/@lwc/ssr-compiler/src/compile-template/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,17 @@ import { reservedKeywords } from '@lwc/shared';
import { Node as IrNode } from '@lwc/template-compiler';
import { esTemplate } from '../estemplate';

import { TransformerContext } from './types';
import type {
ImportDeclaration as EsImportDeclaration,
Statement as EsStatement,
Expression as EsExpression,
MemberExpression as EsMemberExpression,
Identifier as EsIdentifier,
} from 'estree';

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

Expand Down Expand Up @@ -75,3 +78,28 @@ export function bAttributeValue(node: IrNode, attrName: string): EsExpression {
return b.memberExpression(b.literal('instance'), nameAttrValue as EsExpression);
}
}

function getRootMemberExpression(node: EsMemberExpression): EsMemberExpression {
return node.object.type === 'MemberExpression' ? getRootMemberExpression(node.object) : node;
}

function getRootIdentifier(node: EsMemberExpression): EsIdentifier | null {
const rootMemberExpression = getRootMemberExpression(node);
return is.identifier(rootMemberExpression?.object) ? rootMemberExpression.object : null;
}

/**
* Given an expression in a context, return an expression that may be scoped to that context.
* For example, for the expression `foo`, it will typically be `instance.foo`, but if we're
* inside a `for:each` block then the `foo` variable may refer to the scoped `foo`,
* e.g. `<template for:each={foos} for:item="foo">`
* @param expression
*/
export function getScopedExpression(expression: EsExpression, cxt: TransformerContext) {
const scopeReferencedId = is.memberExpression(expression)
? getRootIdentifier(expression)
: null;
return cxt.isLocalVar(scopeReferencedId?.name)
? expression
: b.memberExpression(b.identifier('instance'), expression);
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,26 +25,26 @@ import {
import { esTemplateWithYield } from '../../estemplate';
import { expressionIrToEs } from '../expression';
import { irChildrenToEs } from '../ir-to-es';
import { bImportHtmlEscape, importHtmlEscapeKey } from '../shared';
import { bImportHtmlEscape, getScopedExpression, importHtmlEscapeKey } from '../shared';

import type {
BinaryExpression,
BlockStatement as EsBlockStatement,
Expression as EsExpression,
Statement as EsStatement,
} from 'estree';
import type { Transformer } from '../types';
import type { Transformer, TransformerContext } from '../types';

const bYield = (expr: EsExpression) => b.expressionStatement(b.yieldExpression(expr));
const bConditionalLiveYield = esTemplateWithYield`
{
const prefix = (${/* isClass */ is.literal} && stylesheetScopeTokenClassPrefix) || '';
const attrOrPropValue = ${is.expression};
const valueType = typeof attrOrPropValue;
if (attrOrPropValue && (valueType === 'string' || valueType === 'boolean')) {
yield ' ' + ${is.literal};
const attrValue = ${/* attribute value expression */ is.expression};
const valueType = typeof attrValue;
if (attrValue && (valueType === 'string' || valueType === 'boolean')) {
yield ' ' + ${/* attribute name */ is.literal};
if (valueType === 'string') {
yield \`="\${prefix}\${htmlEscape(attrOrPropValue, true)}"\`;
yield \`="\${prefix}\${htmlEscape(attrValue, true)}"\`;
}
}
}
Expand Down Expand Up @@ -83,10 +83,11 @@ function yieldAttrOrPropLiteralValue(
function yieldAttrOrPropLiveValue(
name: string,
value: IrExpression | BinaryExpression,
isClass: boolean
isClass: boolean,
cxt: TransformerContext
): EsStatement[] {
const instanceMemberRef = b.memberExpression(b.identifier('instance'), value as EsExpression);
return [bConditionalLiveYield(b.literal(isClass), instanceMemberRef, b.literal(name))];
const scopedExpression = getScopedExpression(value as EsExpression, cxt);
return [bConditionalLiveYield(b.literal(isClass), scopedExpression, b.literal(name))];
}

function reorderAttributes(
Expand Down Expand Up @@ -142,7 +143,7 @@ export const Element: Transformer<IrElement | IrExternalComponent | IrSlot> = fu
if (value.type === 'Literal') {
return yieldAttrOrPropLiteralValue(name, value, isClass);
} else {
return yieldAttrOrPropLiveValue(name, value, isClass);
return yieldAttrOrPropLiveValue(name, value, isClass, cxt);
}
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,12 @@
import { builders as b, is } from 'estree-toolkit';
import { esTemplate } from '../../estemplate';
import { irToEs } from '../ir-to-es';
import { optimizeAdjacentYieldStmts } from '../shared';
import { getScopedExpression, optimizeAdjacentYieldStmts } from '../shared';

import type { ForEach as IrForEach } from '@lwc/template-compiler';
import type {
Expression as EsExpression,
ForOfStatement as EsForOfStatement,
Identifier as EsIdentifier,
MemberExpression as EsMemberExpression,
} from 'estree';
import type { Expression as EsExpression, ForOfStatement as EsForOfStatement } from 'estree';
import type { Transformer } from '../types';

function getRootMemberExpression(node: EsMemberExpression): EsMemberExpression {
return node.object.type === 'MemberExpression' ? getRootMemberExpression(node.object) : node;
}

function getRootIdentifier(node: EsMemberExpression): EsIdentifier | null {
const rootMemberExpression = getRootMemberExpression(node);
return is.identifier(rootMemberExpression?.object) ? rootMemberExpression.object : null;
}

const bForOfYieldFrom = esTemplate`
for (let [${is.identifier}, ${is.identifier}] of Object.entries(${is.expression} ?? {})) {
${is.statement};
Expand All @@ -45,13 +31,7 @@ export const ForEach: Transformer<IrForEach> = function ForEach(node, cxt): EsFo
cxt.popLocalVars();

const expression = node.expression as EsExpression;

const scopeReferencedId = is.memberExpression(expression)
? getRootIdentifier(expression)
: null;
const iterable = cxt.isLocalVar(scopeReferencedId?.name)
? (node.expression as EsExpression)
: b.memberExpression(b.identifier('instance'), node.expression as EsExpression);
const iterable = getScopedExpression(expression, cxt);

return [
bForOfYieldFrom(
Expand Down
2 changes: 1 addition & 1 deletion packages/@lwc/ssr-runtime/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
}
}
},
"dependencies": {
"devDependencies": {
"@lwc/shared": "8.3.0"
}
}
1 change: 1 addition & 0 deletions packages/@lwc/ssr-runtime/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@ export {
export * from './stubs';
export { toIteratorDirective } from './to-iterator-directive';
export { validateStyleTextContents } from './validate-style-text-contents';
export { htmlEscape } from '@lwc/shared';

0 comments on commit cd41384

Please sign in to comment.