diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/very-deeply-nested-each/error.txt b/packages/@lwc/engine-server/src/__tests__/fixtures/very-deeply-nested-each/error.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/very-deeply-nested-each/expected.html b/packages/@lwc/engine-server/src/__tests__/fixtures/very-deeply-nested-each/expected.html new file mode 100644 index 0000000000..0f1a1b5d5b --- /dev/null +++ b/packages/@lwc/engine-server/src/__tests__/fixtures/very-deeply-nested-each/expected.html @@ -0,0 +1,7 @@ + + + \ No newline at end of file diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/very-deeply-nested-each/index.js b/packages/@lwc/engine-server/src/__tests__/fixtures/very-deeply-nested-each/index.js new file mode 100644 index 0000000000..cd34090d2c --- /dev/null +++ b/packages/@lwc/engine-server/src/__tests__/fixtures/very-deeply-nested-each/index.js @@ -0,0 +1,3 @@ +export const tagName = 'x-component'; +export { default } from 'x/component'; +export * from 'x/component'; diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/very-deeply-nested-each/modules/x/component/component.html b/packages/@lwc/engine-server/src/__tests__/fixtures/very-deeply-nested-each/modules/x/component/component.html new file mode 100755 index 0000000000..efb27c23c0 --- /dev/null +++ b/packages/@lwc/engine-server/src/__tests__/fixtures/very-deeply-nested-each/modules/x/component/component.html @@ -0,0 +1,5 @@ + diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/very-deeply-nested-each/modules/x/component/component.js b/packages/@lwc/engine-server/src/__tests__/fixtures/very-deeply-nested-each/modules/x/component/component.js new file mode 100755 index 0000000000..4c777808d7 --- /dev/null +++ b/packages/@lwc/engine-server/src/__tests__/fixtures/very-deeply-nested-each/modules/x/component/component.js @@ -0,0 +1,14 @@ +import { LightningElement } from 'lwc'; + +export default class Component extends LightningElement { + rows = [ + { + id: 1, + foo: { + bar: { + baz: 'yolo' + } + } + } + ] +} diff --git a/packages/@lwc/rollup-plugin/src/index.ts b/packages/@lwc/rollup-plugin/src/index.ts index b504085af1..d4cafc8f47 100644 --- a/packages/@lwc/rollup-plugin/src/index.ts +++ b/packages/@lwc/rollup-plugin/src/index.ts @@ -161,6 +161,7 @@ export default function lwc(pluginOptions: RollupLwcOptions = {}): Plugin { let { rootDir, modules = [] } = pluginOptions; const { targetSSR, + ssrMode, stylesheetConfig, sourcemap = false, preserveHtmlComments, @@ -348,6 +349,7 @@ export default function lwc(pluginOptions: RollupLwcOptions = {}): Plugin { enableStaticContentOptimization: pluginOptions.enableStaticContentOptimization, }), targetSSR, + ssrMode, }); if (warnings) { diff --git a/packages/@lwc/ssr-compiler/src/compile-template/shared.ts b/packages/@lwc/ssr-compiler/src/compile-template/shared.ts index 2a3165764b..af9eb3c4e2 100644 --- a/packages/@lwc/ssr-compiler/src/compile-template/shared.ts +++ b/packages/@lwc/ssr-compiler/src/compile-template/shared.ts @@ -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'; `; export const importHtmlEscapeKey = 'import:htmlEscape'; @@ -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. `