@lwc/ssr-compiler 9.2.2 → 9.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,8 +1,8 @@
1
1
  import type { NodePath } from 'estree-toolkit';
2
- import type { PropertyDefinition, MethodDefinition, Identifier, BlockStatement, Decorator, CallExpression } from 'estree';
2
+ import type { ArrayExpression, PropertyDefinition, MethodDefinition, Identifier, Decorator, CallExpression } from 'estree';
3
3
  import type { ComponentMetaState, WireAdapter } from '../types';
4
4
  export declare function catalogWireAdapters(path: NodePath<PropertyDefinition | MethodDefinition>, state: ComponentMetaState): void;
5
- export declare function bWireAdaptersPlumbing(adapters: WireAdapter[]): BlockStatement[];
5
+ export declare function bWireAdaptersPlumbing(adapters: WireAdapter[]): ArrayExpression;
6
6
  export declare function isWireDecorator(decorator: Decorator | undefined): decorator is Decorator & {
7
7
  expression: CallExpression & {
8
8
  callee: Identifier & {
@@ -1,3 +1,4 @@
1
+ import type { CompilationMode } from '@lwc/shared';
1
2
  import type { Program } from 'estree';
2
3
  import type { ComponentMetaState } from './types';
3
4
  /**
@@ -12,5 +13,5 @@ import type { ComponentMetaState } from './types';
12
13
  * - yielding the tag name & attributes
13
14
  * - deferring to the template function for yielding child content
14
15
  */
15
- export declare function addGenerateMarkupFunction(program: Program, state: ComponentMetaState, tagName: string, filename: string): void;
16
+ export declare function addGenerateMarkupFunction(program: Program, state: ComponentMetaState, tagName: string, filename: string, compilationMode: CompilationMode): void;
16
17
  //# sourceMappingURL=generate-markup.d.ts.map
@@ -1,7 +1,7 @@
1
- import { type traverse } from 'estree-toolkit';
1
+ import { type traverse, type NodePath } from 'estree-toolkit';
2
2
  import type { ImportManager } from '../imports';
3
3
  import type { ComponentTransformOptions } from '../shared';
4
- import type { ClassDeclaration, Identifier, MemberExpression, MethodDefinition, Node, ObjectExpression, PropertyDefinition } from 'estree';
4
+ import type { ClassDeclaration, ClassExpression, ExportDefaultDeclaration, Identifier, MemberExpression, MethodDefinition, Node, ObjectExpression, PropertyDefinition } from 'estree';
5
5
  export type Visitors = Parameters<typeof traverse<Node, ComponentMetaState, never>>[1];
6
6
  export interface WireAdapter {
7
7
  adapterId: Identifier | MemberExpression;
@@ -12,7 +12,7 @@ export interface ComponentMetaState {
12
12
  /** indicates whether a subclass of LightningElement is found in the JS being traversed */
13
13
  isLWC: boolean;
14
14
  /** the class declaration currently being traversed, if it is an LWC component */
15
- currentComponent: ClassDeclaration | null;
15
+ currentComponent: ClassDeclaration | ClassExpression | null;
16
16
  /** indicates whether the LightningElement subclass includes a constructor method */
17
17
  hasConstructor: boolean;
18
18
  /** indicates whether the subclass has a connectedCallback method */
@@ -27,6 +27,10 @@ export interface ComponentMetaState {
27
27
  lightningElementIdentifier: string | null;
28
28
  /** the class name of the subclass */
29
29
  lwcClassName: string | null;
30
+ /** the identifier name of the default export (may differ from lwcClassName for wrapped exports) */
31
+ lwcDefaultExportName: string | null;
32
+ /** path to an expression-form `export default <expr>` node, deferred for extraction in Program.leave */
33
+ exportDefaultExpressionPath: NodePath<ExportDefaultDeclaration> | null;
30
34
  /** ties local variable names to explicitly-imported CSS files */
31
35
  cssExplicitImports: Map<string, string> | null;
32
36
  /** the set of variable names associated with explicitly imported CSS files */
package/dist/index.cjs CHANGED
@@ -680,36 +680,26 @@ const bSetWiredProp = (esTemplate `
680
680
  const bCallWiredMethod = (esTemplate `
681
681
  instance.${ /*wire-decorated method*/estreeToolkit.is.identifier}(newValue)
682
682
  `);
683
- const bWireAdapterPlumbing = (esTemplate `{
684
- // Callable adapters are expressed as a function having an 'adapter' property, which
685
- // is the actual wire constructor.
686
- const AdapterCtor = ${ /*wire adapter constructor*/estreeToolkit.is.expression}?.adapter ?? ${ /*wire adapter constructor*/0};
687
- const wireInstance = new AdapterCtor((newValue) => {
688
- ${ /*update the decorated property or call the decorated method*/estreeToolkit.is.expressionStatement};
689
- });
690
- wireInstance.connect?.();
691
- if (wireInstance.update) {
692
- const getLiveConfig = () => {
693
- return ${ /* reactive wire config */estreeToolkit.is.objectExpression};
694
- };
695
- // This may look a bit weird, in that the 'update' function is called twice: once with
696
- // an 'undefined' value and possibly again with a context-provided value. While weird,
697
- // this preserves the behavior of the browser-side wire implementation as well as the
698
- // original SSR implementation.
699
- wireInstance.update(getLiveConfig(), undefined);
700
- __connectContext(AdapterCtor, instance, (newContextValue) => {
701
- wireInstance.update(getLiveConfig(), newContextValue);
702
- });
703
- }
704
- }`);
683
+ // Object expression must be wrapped in () to be parsed correctly,
684
+ // which turns it into an expression statement
685
+ const bWireAdapterInfo = (esTemplate `({
686
+ adapter: ${
687
+ // ideally would be or(is.memberExpression, is.identifier), but we don't have `or()`
688
+ estreeToolkit.is.expression},
689
+ dataCallback: (instance) => (newValue) => { ${estreeToolkit.is.expressionStatement} },
690
+ config: (instance) => (${estreeToolkit.is.objectExpression})
691
+ })`);
705
692
  function bWireAdaptersPlumbing(adapters) {
706
- return adapters.map(({ adapterId, config, field }) => {
693
+ const info = adapters.map(({ adapterId, config, field }) => {
707
694
  const actionUponNewValue = estreeToolkit.is.methodDefinition(field) && field.kind === 'method'
708
695
  ? // Validation in compile-js/index.ts `visitors` ensures `key` is an identifier
709
696
  bCallWiredMethod(field.key)
710
697
  : bSetWiredProp(field.key);
711
- return bWireAdapterPlumbing(adapterId, actionUponNewValue, config);
698
+ // parsed as expression statement rather than object expression, so let's unwrap
699
+ const { expression } = bWireAdapterInfo(adapterId, actionUponNewValue, config);
700
+ return expression;
712
701
  });
702
+ return estreeToolkit.builders.arrayExpression(info);
713
703
  }
714
704
  function isWireDecorator(decorator) {
715
705
  return (estreeToolkit.is.callExpression(decorator?.expression) &&
@@ -723,100 +713,14 @@ function isWireDecorator(decorator) {
723
713
  * SPDX-License-Identifier: MIT
724
714
  * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
725
715
  */
726
- const bGenerateMarkup = (esTemplate `
727
- // These variables may mix with component-authored variables, so should be reasonably unique
728
- const __lwcSuperPublicProperties__ = Array.from(Object.getPrototypeOf(${ /* Component class */estreeToolkit.is.identifier})?.__lwcPublicProperties__?.values?.() ?? []);
729
- const __lwcPublicProperties__ = new Set(${ /*public properties*/estreeToolkit.is.arrayExpression}.concat(__lwcSuperPublicProperties__));
730
-
731
- Object.defineProperty(
732
- ${ /* component class */0},
733
- __SYMBOL__GENERATE_MARKUP,
734
- {
735
- configurable: false,
736
- enumerable: false,
737
- writable: false,
738
- value: async function* __lwcGenerateMarkup(
739
- tagName,
740
- props,
741
- attrs,
742
- parent,
743
- scopeToken,
744
- contextfulParent,
745
- renderContext,
746
- shadowSlottedContent,
747
- lightSlottedContent,
748
- scopedSlottedContent,
749
- ) {
750
- tagName = tagName ?? ${ /*component tag name*/estreeToolkit.is.literal};
751
- attrs = attrs ?? Object.create(null);
752
- props = props ?? Object.create(null);
753
- const instance = new ${ /* Component class */0}({
754
- tagName: tagName.toUpperCase(),
755
- });
756
-
757
- __establishContextfulRelationship(contextfulParent, instance);
758
-
759
- instance[__SYMBOL__SET_INTERNALS](
760
- props,
761
- attrs,
762
- __lwcPublicProperties__
763
- );
764
- ${ /*connect wire*/estreeToolkit.is.statement}
765
- instance.isConnected = true;
766
- if (instance.connectedCallback) {
767
- __mutationTracker.enable(instance);
768
- instance.connectedCallback();
769
- __mutationTracker.disable(instance);
770
- }
771
- // If a render() function is defined on the class or any of its superclasses, then that takes priority.
772
- // Next, if the class or any of its superclasses has an implicitly-associated template, then that takes
773
- // second priority (e.g. a foo.html file alongside a foo.js file). Finally, there is a fallback empty template.
774
- const tmplFn = instance.render?.() ?? ${ /*component class*/0}[__SYMBOL__DEFAULT_TEMPLATE] ?? __fallbackTmpl;
775
- yield \`<\${tagName}\`;
776
-
777
- const hostHasScopedStylesheets =
778
- tmplFn.hasScopedStylesheets ||
779
- hasScopedStaticStylesheets(${ /*component class*/0});
780
- const hostScopeToken = hostHasScopedStylesheets ? tmplFn.stylesheetScopeToken + "-host" : undefined;
781
-
782
- yield* __renderAttrs(instance, attrs, hostScopeToken, scopeToken);
783
- yield '>';
784
- yield* tmplFn(
785
- shadowSlottedContent,
786
- lightSlottedContent,
787
- scopedSlottedContent,
788
- ${ /*component class*/0},
789
- instance,
790
- renderContext
791
- );
792
- yield \`</\${tagName}>\`;
793
- }
794
- });
795
- Object.defineProperty(
796
- ${ /* component class */0},
797
- '__lwcPublicProperties__',
798
- {
799
- configurable: false,
800
- enumerable: false,
801
- writable: false,
802
- value: __lwcPublicProperties__
803
- }
804
- );
805
- `);
806
- const bExposeTemplate = (esTemplate `
807
- if (${ /*template*/estreeToolkit.is.identifier}) {
808
- Object.defineProperty(
809
- ${ /* component class */estreeToolkit.is.identifier},
810
- __SYMBOL__DEFAULT_TEMPLATE,
811
- {
812
- configurable: false,
813
- enumerable: false,
814
- writable: false,
815
- value: ${ /*template*/0}
816
- }
817
- );
818
- }
819
- `);
716
+ const bSetStaticInternals = esTemplate `__setStaticInternals(
717
+ ${ /* Component */estreeToolkit.is.identifier},
718
+ ${ /* tag name */estreeToolkit.is.literal},
719
+ ${ /* public props */estreeToolkit.is.arrayExpression},
720
+ ${ /* wire adapters */estreeToolkit.is.expression} ?? null,
721
+ ${ /* compilation mode */estreeToolkit.is.literal},
722
+ ${ /* default template */estreeToolkit.is.identifier}
723
+ )`;
820
724
  /**
821
725
  * This builds a generator function `generateMarkup` and adds it to the component JS's
822
726
  * compilation output. `generateMarkup` acts as the glue between component JS and its
@@ -829,38 +733,24 @@ const bExposeTemplate = (esTemplate `
829
733
  * - yielding the tag name & attributes
830
734
  * - deferring to the template function for yielding child content
831
735
  */
832
- function addGenerateMarkupFunction(program, state, tagName, filename) {
736
+ function addGenerateMarkupFunction(program, state, tagName, filename, compilationMode) {
833
737
  const { publicProperties } = state;
834
738
  // The default tag name represents the component name that's passed to the transformer.
835
739
  // This is needed to generate markup for dynamic components which are invoked through
836
740
  // the generateMarkup function on the constructor.
837
741
  // At the time of generation, the invoker does not have reference to its tag name to pass as an argument.
838
742
  const defaultTagName = estreeToolkit.builders.literal(tagName);
839
- const classIdentifier = estreeToolkit.builders.identifier(state.lwcClassName);
743
+ // Use the default export identifier if available; fall back to the class name.
744
+ const exportedIdentifier = estreeToolkit.builders.identifier((state.lwcDefaultExportName ?? state.lwcClassName));
840
745
  const defaultTmplPath = `./${node_path.parse(filename).name}.html`;
841
746
  const tmplVar = estreeToolkit.builders.identifier('__lwcTmpl');
842
747
  program.body.unshift(bImportDeclaration({ default: tmplVar.name }, defaultTmplPath));
843
- program.body.unshift(bImportDeclaration({ SYMBOL__DEFAULT_TEMPLATE: '__SYMBOL__DEFAULT_TEMPLATE' }));
844
- const exposeTemplateBlock = bExposeTemplate(tmplVar, classIdentifier);
845
748
  // If no wire adapters are detected on the component, we don't bother injecting the wire-related code.
846
- let connectWireAdapterCode = [];
847
- if (state.wireAdapters.length) {
848
- connectWireAdapterCode = bWireAdaptersPlumbing(state.wireAdapters);
849
- program.body.unshift(bImportDeclaration({ connectContext: '__connectContext' }));
850
- }
749
+ const wireAdapterInfo = state.wireAdapters.length > 0 ? bWireAdaptersPlumbing(state.wireAdapters) : estreeToolkit.builders.literal(null);
851
750
  program.body.unshift(bImportDeclaration({
852
- fallbackTmpl: '__fallbackTmpl',
853
- hasScopedStaticStylesheets: undefined,
854
- mutationTracker: '__mutationTracker',
855
- renderAttrs: '__renderAttrs',
856
- SYMBOL__GENERATE_MARKUP: '__SYMBOL__GENERATE_MARKUP',
857
- SYMBOL__SET_INTERNALS: '__SYMBOL__SET_INTERNALS',
858
- establishContextfulRelationship: '__establishContextfulRelationship',
751
+ setStaticInternals: '__setStaticInternals',
859
752
  }));
860
- program.body.push(...bGenerateMarkup(classIdentifier, estreeToolkit.builders.arrayExpression([...publicProperties.keys()].map(estreeToolkit.builders.literal)), defaultTagName, connectWireAdapterCode));
861
- if (exposeTemplateBlock) {
862
- program.body.push(exposeTemplateBlock);
863
- }
753
+ program.body.push(bSetStaticInternals(exportedIdentifier, defaultTagName, estreeToolkit.builders.arrayExpression([...publicProperties.keys()].map(estreeToolkit.builders.literal)), wireAdapterInfo, estreeToolkit.builders.literal(compilationMode), tmplVar));
864
754
  }
865
755
 
866
756
  /*
@@ -986,6 +876,31 @@ const visitors = {
986
876
  ExportAllDeclaration(path) {
987
877
  replaceAllLwcExport(path);
988
878
  },
879
+ ExportDefaultDeclaration(path, state) {
880
+ const { node } = path;
881
+ if (!node)
882
+ return;
883
+ const decl = node.declaration;
884
+ if (decl.type === 'ClassDeclaration') {
885
+ // export default class Foo extends LE {}
886
+ // lwcClassName will be set by the ClassDeclaration visitor; mirror it here
887
+ state.lwcDefaultExportName = decl.id?.name ?? 'DefaultComponentName';
888
+ }
889
+ else if (decl.type === 'ClassExpression') {
890
+ state.lwcDefaultExportName =
891
+ decl.id?.name ?? state.lwcClassName ?? 'DefaultComponentName';
892
+ }
893
+ else if (decl.type === 'Identifier') {
894
+ // export default Foo
895
+ state.lwcDefaultExportName = decl.name;
896
+ }
897
+ else if (decl.type !== 'FunctionDeclaration' && decl.type !== 'FunctionExpression') {
898
+ // export default <expression> — store the path for deferred extraction in Program.leave,
899
+ // where we know whether this is an LWC file (state.isLWC). We don't want to mutate
900
+ // non-LWC modules (e.g. wire adapters with `export default { Adapter }`).
901
+ state.exportDefaultExpressionPath = path;
902
+ }
903
+ },
989
904
  ImportDeclaration(path, state) {
990
905
  if (!path.node || !path.node.source.value || typeof path.node.source.value !== 'string') {
991
906
  return;
@@ -1026,15 +941,16 @@ const visitors = {
1026
941
  (estreeToolkit.is.exportDefaultDeclaration(path.parentPath) ||
1027
942
  // class Cmp extends LightningElement {}; export default Cmp
1028
943
  path.scope
1029
- ?.getBinding(node.id.name)
944
+ ?.getBinding(node.id?.name ?? '')
1030
945
  ?.references.some((ref) => estreeToolkit.is.exportDefaultDeclaration(ref.parent)))) {
1031
- // If it's a default-exported class with a superclass, then it's an LWC component!
1032
946
  state.isLWC = true;
1033
947
  state.currentComponent = node;
1034
948
  if (node.id) {
1035
949
  state.lwcClassName = node.id.name;
1036
950
  }
1037
951
  else {
952
+ // A class declaration can omit a name if and only if it is default-exported.
953
+ // There is only one default export, so this won't cause collisions.
1038
954
  node.id = estreeToolkit.builders.identifier('DefaultComponentName');
1039
955
  state.lwcClassName = 'DefaultComponentName';
1040
956
  }
@@ -1060,6 +976,44 @@ const visitors = {
1060
976
  }
1061
977
  },
1062
978
  },
979
+ ClassExpression: {
980
+ enter(path, state) {
981
+ const { node } = path;
982
+ if (node?.superClass &&
983
+ estreeToolkit.is.identifier(node.superClass) &&
984
+ node.superClass.name === state.lightningElementIdentifier) {
985
+ state.isLWC = true;
986
+ state.currentComponent = node;
987
+ // Get the class name from the enclosing variable declarator, if any
988
+ // e.g. `const Component = class extends LightningElement {}`
989
+ if (estreeToolkit.is.variableDeclarator(path.parentPath?.node) &&
990
+ estreeToolkit.is.identifier(path.parentPath.node.id)) {
991
+ state.lwcClassName = path.parentPath.node.id.name;
992
+ }
993
+ else if (node.id) {
994
+ state.lwcClassName = node.id.name;
995
+ }
996
+ // There's no builder for comment nodes :\
997
+ const lwcVersionComment = {
998
+ type: 'Block',
999
+ value: shared.LWC_VERSION_COMMENT,
1000
+ };
1001
+ // Add LWC version comment to end of class body
1002
+ const { body } = node;
1003
+ if (body.trailingComments) {
1004
+ body.trailingComments.push(lwcVersionComment);
1005
+ }
1006
+ else {
1007
+ body.trailingComments = [lwcVersionComment];
1008
+ }
1009
+ }
1010
+ },
1011
+ leave(path, state) {
1012
+ if (state.currentComponent && path.node === state.currentComponent) {
1013
+ state.currentComponent = null;
1014
+ }
1015
+ },
1016
+ },
1063
1017
  PropertyDefinition(path, state) {
1064
1018
  // Don't do anything unless we're in a component
1065
1019
  if (!state.currentComponent) {
@@ -1176,6 +1130,24 @@ const visitors = {
1176
1130
  },
1177
1131
  Program: {
1178
1132
  leave(path, state) {
1133
+ // If the default export is an expression (not class/identifier), extract it into a
1134
+ // const so setStaticInternals has a stable identifier to call. Only do this for LWC
1135
+ // files — non-LWC modules (e.g. wire adapters) must not be mutated.
1136
+ if (state.isLWC && state.exportDefaultExpressionPath) {
1137
+ const exportPath = state.exportDefaultExpressionPath;
1138
+ const exportedExpr = exportPath.node.declaration;
1139
+ // Each b.identifier() call creates a distinct node object; all must be trusted
1140
+ const declId = estreeToolkit.builders.identifier('__lwcDefaultExport');
1141
+ const exportId = estreeToolkit.builders.identifier('__lwcDefaultExport');
1142
+ state.trustedLwcIdentifiers.add(declId);
1143
+ state.trustedLwcIdentifiers.add(exportId);
1144
+ // insertBefore must precede replaceWith: replaceWith marks the path as removed
1145
+ exportPath.insertBefore([
1146
+ estreeToolkit.builders.variableDeclaration('const', [estreeToolkit.builders.variableDeclarator(declId, exportedExpr)]),
1147
+ ]);
1148
+ exportPath.replaceWith(estreeToolkit.builders.exportDefaultDeclaration(exportId));
1149
+ state.lwcDefaultExportName = '__lwcDefaultExport';
1150
+ }
1179
1151
  // After parsing the whole tree, insert needed imports
1180
1152
  const importDeclarations = state.importManager.getImportDeclarations();
1181
1153
  if (importDeclarations.length > 0) {
@@ -1208,6 +1180,8 @@ function compileJS(src, filename, tagName, options, compilationMode) {
1208
1180
  hadErrorCallback: false,
1209
1181
  lightningElementIdentifier: null,
1210
1182
  lwcClassName: null,
1183
+ lwcDefaultExportName: null,
1184
+ exportDefaultExpressionPath: null,
1211
1185
  cssExplicitImports: null,
1212
1186
  staticStylesheetIds: null,
1213
1187
  publicProperties: new Map(),
@@ -1226,7 +1200,7 @@ function compileJS(src, filename, tagName, options, compilationMode) {
1226
1200
  code: astring.generate(ast, {}),
1227
1201
  };
1228
1202
  }
1229
- addGenerateMarkupFunction(ast, state, tagName, filename);
1203
+ addGenerateMarkupFunction(ast, state, tagName, filename, compilationMode);
1230
1204
  if (compilationMode === 'async' || compilationMode === 'sync') {
1231
1205
  ast = transmogrify(ast, compilationMode);
1232
1206
  }
@@ -1857,10 +1831,9 @@ estreeToolkit.is.statement}
1857
1831
 
1858
1832
  if (generateMarkup) {
1859
1833
  yield* generateMarkup(
1860
- tagName,
1861
- childProps,
1834
+ tagName,
1835
+ childProps,
1862
1836
  childAttrs,
1863
- instance,
1864
1837
  scopeToken,
1865
1838
  contextfulParent,
1866
1839
  renderContext,
@@ -1921,12 +1894,11 @@ const bYieldFromDynamicComponentConstructorGenerator = (esTemplateWithYield `
1921
1894
  estreeToolkit.is.statement}
1922
1895
 
1923
1896
  const scopeToken = hasScopedStylesheets ? stylesheetScopeToken : undefined;
1924
-
1897
+
1925
1898
  yield* Ctor[__SYMBOL__GENERATE_MARKUP](
1926
- null,
1899
+ null,
1927
1900
  childProps,
1928
1901
  childAttrs,
1929
- instance,
1930
1902
  scopeToken,
1931
1903
  contextfulParent,
1932
1904
  renderContext,
@@ -2158,6 +2130,7 @@ const Element = function Element(node, cxt) {
2158
2130
  }
2159
2131
  const isForeignSelfClosingElement = node.namespace !== shared.HTML_NAMESPACE && childContent.length === 0;
2160
2132
  const isSelfClosingElement = shared.isVoidElement(node.name, shared.HTML_NAMESPACE) || isForeignSelfClosingElement;
2133
+ cxt.import('hasScopedStaticStylesheets');
2161
2134
  return [
2162
2135
  bYield(estreeToolkit.builders.literal(`<${node.name}`)),
2163
2136
  bConditionallyYieldDanglingSlotName(),
@@ -2654,7 +2627,7 @@ function compileTemplate(src, filename, options, compilationMode) {
2654
2627
  experimentalComplexExpressions,
2655
2628
  apiVersion,
2656
2629
  });
2657
- addImport(['renderStylesheets', 'hasScopedStaticStylesheets']);
2630
+ addImport(['renderStylesheets']);
2658
2631
  for (const [imports, source] of getStylesheetImports(filename)) {
2659
2632
  addImport(imports, source);
2660
2633
  }
@@ -2715,5 +2688,5 @@ function compileTemplateForSSR(src, filename, options, mode = shared.DEFAULT_SSR
2715
2688
 
2716
2689
  exports.compileComponentForSSR = compileComponentForSSR;
2717
2690
  exports.compileTemplateForSSR = compileTemplateForSSR;
2718
- /** version: 9.2.2 */
2691
+ /** version: 9.3.4 */
2719
2692
  //# sourceMappingURL=index.cjs.map
package/dist/index.js CHANGED
@@ -676,36 +676,26 @@ const bSetWiredProp = (esTemplate `
676
676
  const bCallWiredMethod = (esTemplate `
677
677
  instance.${ /*wire-decorated method*/is.identifier}(newValue)
678
678
  `);
679
- const bWireAdapterPlumbing = (esTemplate `{
680
- // Callable adapters are expressed as a function having an 'adapter' property, which
681
- // is the actual wire constructor.
682
- const AdapterCtor = ${ /*wire adapter constructor*/is.expression}?.adapter ?? ${ /*wire adapter constructor*/0};
683
- const wireInstance = new AdapterCtor((newValue) => {
684
- ${ /*update the decorated property or call the decorated method*/is.expressionStatement};
685
- });
686
- wireInstance.connect?.();
687
- if (wireInstance.update) {
688
- const getLiveConfig = () => {
689
- return ${ /* reactive wire config */is.objectExpression};
690
- };
691
- // This may look a bit weird, in that the 'update' function is called twice: once with
692
- // an 'undefined' value and possibly again with a context-provided value. While weird,
693
- // this preserves the behavior of the browser-side wire implementation as well as the
694
- // original SSR implementation.
695
- wireInstance.update(getLiveConfig(), undefined);
696
- __connectContext(AdapterCtor, instance, (newContextValue) => {
697
- wireInstance.update(getLiveConfig(), newContextValue);
698
- });
699
- }
700
- }`);
679
+ // Object expression must be wrapped in () to be parsed correctly,
680
+ // which turns it into an expression statement
681
+ const bWireAdapterInfo = (esTemplate `({
682
+ adapter: ${
683
+ // ideally would be or(is.memberExpression, is.identifier), but we don't have `or()`
684
+ is.expression},
685
+ dataCallback: (instance) => (newValue) => { ${is.expressionStatement} },
686
+ config: (instance) => (${is.objectExpression})
687
+ })`);
701
688
  function bWireAdaptersPlumbing(adapters) {
702
- return adapters.map(({ adapterId, config, field }) => {
689
+ const info = adapters.map(({ adapterId, config, field }) => {
703
690
  const actionUponNewValue = is.methodDefinition(field) && field.kind === 'method'
704
691
  ? // Validation in compile-js/index.ts `visitors` ensures `key` is an identifier
705
692
  bCallWiredMethod(field.key)
706
693
  : bSetWiredProp(field.key);
707
- return bWireAdapterPlumbing(adapterId, actionUponNewValue, config);
694
+ // parsed as expression statement rather than object expression, so let's unwrap
695
+ const { expression } = bWireAdapterInfo(adapterId, actionUponNewValue, config);
696
+ return expression;
708
697
  });
698
+ return builders.arrayExpression(info);
709
699
  }
710
700
  function isWireDecorator(decorator) {
711
701
  return (is.callExpression(decorator?.expression) &&
@@ -719,100 +709,14 @@ function isWireDecorator(decorator) {
719
709
  * SPDX-License-Identifier: MIT
720
710
  * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
721
711
  */
722
- const bGenerateMarkup = (esTemplate `
723
- // These variables may mix with component-authored variables, so should be reasonably unique
724
- const __lwcSuperPublicProperties__ = Array.from(Object.getPrototypeOf(${ /* Component class */is.identifier})?.__lwcPublicProperties__?.values?.() ?? []);
725
- const __lwcPublicProperties__ = new Set(${ /*public properties*/is.arrayExpression}.concat(__lwcSuperPublicProperties__));
726
-
727
- Object.defineProperty(
728
- ${ /* component class */0},
729
- __SYMBOL__GENERATE_MARKUP,
730
- {
731
- configurable: false,
732
- enumerable: false,
733
- writable: false,
734
- value: async function* __lwcGenerateMarkup(
735
- tagName,
736
- props,
737
- attrs,
738
- parent,
739
- scopeToken,
740
- contextfulParent,
741
- renderContext,
742
- shadowSlottedContent,
743
- lightSlottedContent,
744
- scopedSlottedContent,
745
- ) {
746
- tagName = tagName ?? ${ /*component tag name*/is.literal};
747
- attrs = attrs ?? Object.create(null);
748
- props = props ?? Object.create(null);
749
- const instance = new ${ /* Component class */0}({
750
- tagName: tagName.toUpperCase(),
751
- });
752
-
753
- __establishContextfulRelationship(contextfulParent, instance);
754
-
755
- instance[__SYMBOL__SET_INTERNALS](
756
- props,
757
- attrs,
758
- __lwcPublicProperties__
759
- );
760
- ${ /*connect wire*/is.statement}
761
- instance.isConnected = true;
762
- if (instance.connectedCallback) {
763
- __mutationTracker.enable(instance);
764
- instance.connectedCallback();
765
- __mutationTracker.disable(instance);
766
- }
767
- // If a render() function is defined on the class or any of its superclasses, then that takes priority.
768
- // Next, if the class or any of its superclasses has an implicitly-associated template, then that takes
769
- // second priority (e.g. a foo.html file alongside a foo.js file). Finally, there is a fallback empty template.
770
- const tmplFn = instance.render?.() ?? ${ /*component class*/0}[__SYMBOL__DEFAULT_TEMPLATE] ?? __fallbackTmpl;
771
- yield \`<\${tagName}\`;
772
-
773
- const hostHasScopedStylesheets =
774
- tmplFn.hasScopedStylesheets ||
775
- hasScopedStaticStylesheets(${ /*component class*/0});
776
- const hostScopeToken = hostHasScopedStylesheets ? tmplFn.stylesheetScopeToken + "-host" : undefined;
777
-
778
- yield* __renderAttrs(instance, attrs, hostScopeToken, scopeToken);
779
- yield '>';
780
- yield* tmplFn(
781
- shadowSlottedContent,
782
- lightSlottedContent,
783
- scopedSlottedContent,
784
- ${ /*component class*/0},
785
- instance,
786
- renderContext
787
- );
788
- yield \`</\${tagName}>\`;
789
- }
790
- });
791
- Object.defineProperty(
792
- ${ /* component class */0},
793
- '__lwcPublicProperties__',
794
- {
795
- configurable: false,
796
- enumerable: false,
797
- writable: false,
798
- value: __lwcPublicProperties__
799
- }
800
- );
801
- `);
802
- const bExposeTemplate = (esTemplate `
803
- if (${ /*template*/is.identifier}) {
804
- Object.defineProperty(
805
- ${ /* component class */is.identifier},
806
- __SYMBOL__DEFAULT_TEMPLATE,
807
- {
808
- configurable: false,
809
- enumerable: false,
810
- writable: false,
811
- value: ${ /*template*/0}
812
- }
813
- );
814
- }
815
- `);
712
+ const bSetStaticInternals = esTemplate `__setStaticInternals(
713
+ ${ /* Component */is.identifier},
714
+ ${ /* tag name */is.literal},
715
+ ${ /* public props */is.arrayExpression},
716
+ ${ /* wire adapters */is.expression} ?? null,
717
+ ${ /* compilation mode */is.literal},
718
+ ${ /* default template */is.identifier}
719
+ )`;
816
720
  /**
817
721
  * This builds a generator function `generateMarkup` and adds it to the component JS's
818
722
  * compilation output. `generateMarkup` acts as the glue between component JS and its
@@ -825,38 +729,24 @@ const bExposeTemplate = (esTemplate `
825
729
  * - yielding the tag name & attributes
826
730
  * - deferring to the template function for yielding child content
827
731
  */
828
- function addGenerateMarkupFunction(program, state, tagName, filename) {
732
+ function addGenerateMarkupFunction(program, state, tagName, filename, compilationMode) {
829
733
  const { publicProperties } = state;
830
734
  // The default tag name represents the component name that's passed to the transformer.
831
735
  // This is needed to generate markup for dynamic components which are invoked through
832
736
  // the generateMarkup function on the constructor.
833
737
  // At the time of generation, the invoker does not have reference to its tag name to pass as an argument.
834
738
  const defaultTagName = builders.literal(tagName);
835
- const classIdentifier = builders.identifier(state.lwcClassName);
739
+ // Use the default export identifier if available; fall back to the class name.
740
+ const exportedIdentifier = builders.identifier((state.lwcDefaultExportName ?? state.lwcClassName));
836
741
  const defaultTmplPath = `./${parse$1(filename).name}.html`;
837
742
  const tmplVar = builders.identifier('__lwcTmpl');
838
743
  program.body.unshift(bImportDeclaration({ default: tmplVar.name }, defaultTmplPath));
839
- program.body.unshift(bImportDeclaration({ SYMBOL__DEFAULT_TEMPLATE: '__SYMBOL__DEFAULT_TEMPLATE' }));
840
- const exposeTemplateBlock = bExposeTemplate(tmplVar, classIdentifier);
841
744
  // If no wire adapters are detected on the component, we don't bother injecting the wire-related code.
842
- let connectWireAdapterCode = [];
843
- if (state.wireAdapters.length) {
844
- connectWireAdapterCode = bWireAdaptersPlumbing(state.wireAdapters);
845
- program.body.unshift(bImportDeclaration({ connectContext: '__connectContext' }));
846
- }
745
+ const wireAdapterInfo = state.wireAdapters.length > 0 ? bWireAdaptersPlumbing(state.wireAdapters) : builders.literal(null);
847
746
  program.body.unshift(bImportDeclaration({
848
- fallbackTmpl: '__fallbackTmpl',
849
- hasScopedStaticStylesheets: undefined,
850
- mutationTracker: '__mutationTracker',
851
- renderAttrs: '__renderAttrs',
852
- SYMBOL__GENERATE_MARKUP: '__SYMBOL__GENERATE_MARKUP',
853
- SYMBOL__SET_INTERNALS: '__SYMBOL__SET_INTERNALS',
854
- establishContextfulRelationship: '__establishContextfulRelationship',
747
+ setStaticInternals: '__setStaticInternals',
855
748
  }));
856
- program.body.push(...bGenerateMarkup(classIdentifier, builders.arrayExpression([...publicProperties.keys()].map(builders.literal)), defaultTagName, connectWireAdapterCode));
857
- if (exposeTemplateBlock) {
858
- program.body.push(exposeTemplateBlock);
859
- }
749
+ program.body.push(bSetStaticInternals(exportedIdentifier, defaultTagName, builders.arrayExpression([...publicProperties.keys()].map(builders.literal)), wireAdapterInfo, builders.literal(compilationMode), tmplVar));
860
750
  }
861
751
 
862
752
  /*
@@ -982,6 +872,31 @@ const visitors = {
982
872
  ExportAllDeclaration(path) {
983
873
  replaceAllLwcExport(path);
984
874
  },
875
+ ExportDefaultDeclaration(path, state) {
876
+ const { node } = path;
877
+ if (!node)
878
+ return;
879
+ const decl = node.declaration;
880
+ if (decl.type === 'ClassDeclaration') {
881
+ // export default class Foo extends LE {}
882
+ // lwcClassName will be set by the ClassDeclaration visitor; mirror it here
883
+ state.lwcDefaultExportName = decl.id?.name ?? 'DefaultComponentName';
884
+ }
885
+ else if (decl.type === 'ClassExpression') {
886
+ state.lwcDefaultExportName =
887
+ decl.id?.name ?? state.lwcClassName ?? 'DefaultComponentName';
888
+ }
889
+ else if (decl.type === 'Identifier') {
890
+ // export default Foo
891
+ state.lwcDefaultExportName = decl.name;
892
+ }
893
+ else if (decl.type !== 'FunctionDeclaration' && decl.type !== 'FunctionExpression') {
894
+ // export default <expression> — store the path for deferred extraction in Program.leave,
895
+ // where we know whether this is an LWC file (state.isLWC). We don't want to mutate
896
+ // non-LWC modules (e.g. wire adapters with `export default { Adapter }`).
897
+ state.exportDefaultExpressionPath = path;
898
+ }
899
+ },
985
900
  ImportDeclaration(path, state) {
986
901
  if (!path.node || !path.node.source.value || typeof path.node.source.value !== 'string') {
987
902
  return;
@@ -1022,15 +937,16 @@ const visitors = {
1022
937
  (is.exportDefaultDeclaration(path.parentPath) ||
1023
938
  // class Cmp extends LightningElement {}; export default Cmp
1024
939
  path.scope
1025
- ?.getBinding(node.id.name)
940
+ ?.getBinding(node.id?.name ?? '')
1026
941
  ?.references.some((ref) => is.exportDefaultDeclaration(ref.parent)))) {
1027
- // If it's a default-exported class with a superclass, then it's an LWC component!
1028
942
  state.isLWC = true;
1029
943
  state.currentComponent = node;
1030
944
  if (node.id) {
1031
945
  state.lwcClassName = node.id.name;
1032
946
  }
1033
947
  else {
948
+ // A class declaration can omit a name if and only if it is default-exported.
949
+ // There is only one default export, so this won't cause collisions.
1034
950
  node.id = builders.identifier('DefaultComponentName');
1035
951
  state.lwcClassName = 'DefaultComponentName';
1036
952
  }
@@ -1056,6 +972,44 @@ const visitors = {
1056
972
  }
1057
973
  },
1058
974
  },
975
+ ClassExpression: {
976
+ enter(path, state) {
977
+ const { node } = path;
978
+ if (node?.superClass &&
979
+ is.identifier(node.superClass) &&
980
+ node.superClass.name === state.lightningElementIdentifier) {
981
+ state.isLWC = true;
982
+ state.currentComponent = node;
983
+ // Get the class name from the enclosing variable declarator, if any
984
+ // e.g. `const Component = class extends LightningElement {}`
985
+ if (is.variableDeclarator(path.parentPath?.node) &&
986
+ is.identifier(path.parentPath.node.id)) {
987
+ state.lwcClassName = path.parentPath.node.id.name;
988
+ }
989
+ else if (node.id) {
990
+ state.lwcClassName = node.id.name;
991
+ }
992
+ // There's no builder for comment nodes :\
993
+ const lwcVersionComment = {
994
+ type: 'Block',
995
+ value: LWC_VERSION_COMMENT,
996
+ };
997
+ // Add LWC version comment to end of class body
998
+ const { body } = node;
999
+ if (body.trailingComments) {
1000
+ body.trailingComments.push(lwcVersionComment);
1001
+ }
1002
+ else {
1003
+ body.trailingComments = [lwcVersionComment];
1004
+ }
1005
+ }
1006
+ },
1007
+ leave(path, state) {
1008
+ if (state.currentComponent && path.node === state.currentComponent) {
1009
+ state.currentComponent = null;
1010
+ }
1011
+ },
1012
+ },
1059
1013
  PropertyDefinition(path, state) {
1060
1014
  // Don't do anything unless we're in a component
1061
1015
  if (!state.currentComponent) {
@@ -1172,6 +1126,24 @@ const visitors = {
1172
1126
  },
1173
1127
  Program: {
1174
1128
  leave(path, state) {
1129
+ // If the default export is an expression (not class/identifier), extract it into a
1130
+ // const so setStaticInternals has a stable identifier to call. Only do this for LWC
1131
+ // files — non-LWC modules (e.g. wire adapters) must not be mutated.
1132
+ if (state.isLWC && state.exportDefaultExpressionPath) {
1133
+ const exportPath = state.exportDefaultExpressionPath;
1134
+ const exportedExpr = exportPath.node.declaration;
1135
+ // Each b.identifier() call creates a distinct node object; all must be trusted
1136
+ const declId = builders.identifier('__lwcDefaultExport');
1137
+ const exportId = builders.identifier('__lwcDefaultExport');
1138
+ state.trustedLwcIdentifiers.add(declId);
1139
+ state.trustedLwcIdentifiers.add(exportId);
1140
+ // insertBefore must precede replaceWith: replaceWith marks the path as removed
1141
+ exportPath.insertBefore([
1142
+ builders.variableDeclaration('const', [builders.variableDeclarator(declId, exportedExpr)]),
1143
+ ]);
1144
+ exportPath.replaceWith(builders.exportDefaultDeclaration(exportId));
1145
+ state.lwcDefaultExportName = '__lwcDefaultExport';
1146
+ }
1175
1147
  // After parsing the whole tree, insert needed imports
1176
1148
  const importDeclarations = state.importManager.getImportDeclarations();
1177
1149
  if (importDeclarations.length > 0) {
@@ -1204,6 +1176,8 @@ function compileJS(src, filename, tagName, options, compilationMode) {
1204
1176
  hadErrorCallback: false,
1205
1177
  lightningElementIdentifier: null,
1206
1178
  lwcClassName: null,
1179
+ lwcDefaultExportName: null,
1180
+ exportDefaultExpressionPath: null,
1207
1181
  cssExplicitImports: null,
1208
1182
  staticStylesheetIds: null,
1209
1183
  publicProperties: new Map(),
@@ -1222,7 +1196,7 @@ function compileJS(src, filename, tagName, options, compilationMode) {
1222
1196
  code: generate(ast, {}),
1223
1197
  };
1224
1198
  }
1225
- addGenerateMarkupFunction(ast, state, tagName, filename);
1199
+ addGenerateMarkupFunction(ast, state, tagName, filename, compilationMode);
1226
1200
  if (compilationMode === 'async' || compilationMode === 'sync') {
1227
1201
  ast = transmogrify(ast, compilationMode);
1228
1202
  }
@@ -1853,10 +1827,9 @@ is.statement}
1853
1827
 
1854
1828
  if (generateMarkup) {
1855
1829
  yield* generateMarkup(
1856
- tagName,
1857
- childProps,
1830
+ tagName,
1831
+ childProps,
1858
1832
  childAttrs,
1859
- instance,
1860
1833
  scopeToken,
1861
1834
  contextfulParent,
1862
1835
  renderContext,
@@ -1917,12 +1890,11 @@ const bYieldFromDynamicComponentConstructorGenerator = (esTemplateWithYield `
1917
1890
  is.statement}
1918
1891
 
1919
1892
  const scopeToken = hasScopedStylesheets ? stylesheetScopeToken : undefined;
1920
-
1893
+
1921
1894
  yield* Ctor[__SYMBOL__GENERATE_MARKUP](
1922
- null,
1895
+ null,
1923
1896
  childProps,
1924
1897
  childAttrs,
1925
- instance,
1926
1898
  scopeToken,
1927
1899
  contextfulParent,
1928
1900
  renderContext,
@@ -2154,6 +2126,7 @@ const Element = function Element(node, cxt) {
2154
2126
  }
2155
2127
  const isForeignSelfClosingElement = node.namespace !== HTML_NAMESPACE && childContent.length === 0;
2156
2128
  const isSelfClosingElement = isVoidElement(node.name, HTML_NAMESPACE) || isForeignSelfClosingElement;
2129
+ cxt.import('hasScopedStaticStylesheets');
2157
2130
  return [
2158
2131
  bYield(builders.literal(`<${node.name}`)),
2159
2132
  bConditionallyYieldDanglingSlotName(),
@@ -2650,7 +2623,7 @@ function compileTemplate(src, filename, options, compilationMode) {
2650
2623
  experimentalComplexExpressions,
2651
2624
  apiVersion,
2652
2625
  });
2653
- addImport(['renderStylesheets', 'hasScopedStaticStylesheets']);
2626
+ addImport(['renderStylesheets']);
2654
2627
  for (const [imports, source] of getStylesheetImports(filename)) {
2655
2628
  addImport(imports, source);
2656
2629
  }
@@ -2710,5 +2683,5 @@ function compileTemplateForSSR(src, filename, options, mode = DEFAULT_SSR_MODE)
2710
2683
  }
2711
2684
 
2712
2685
  export { compileComponentForSSR, compileTemplateForSSR };
2713
- /** version: 9.2.2 */
2686
+ /** version: 9.3.4 */
2714
2687
  //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "You can safely modify dependencies, devDependencies, keywords, etc., but other props will be overwritten."
5
5
  ],
6
6
  "name": "@lwc/ssr-compiler",
7
- "version": "9.2.2",
7
+ "version": "9.3.4",
8
8
  "description": "Compile component for use during server-side rendering",
9
9
  "keywords": [
10
10
  "compiler",
@@ -53,18 +53,18 @@
53
53
  }
54
54
  },
55
55
  "dependencies": {
56
- "@babel/types": "7.29.0",
57
- "@lwc/errors": "9.2.2",
58
- "@lwc/shared": "9.2.2",
59
- "@lwc/template-compiler": "9.2.2",
56
+ "@babel/types": "7.29.7",
57
+ "@lwc/errors": "9.3.4",
58
+ "@lwc/shared": "9.3.4",
59
+ "@lwc/template-compiler": "9.3.4",
60
60
  "acorn": "8.16.0",
61
61
  "astring": "^1.9.0",
62
- "estree-toolkit": "^1.7.13",
63
- "immer": "^11.1.4",
62
+ "estree-toolkit": "^1.7.14",
63
+ "immer": "^11.1.8",
64
64
  "meriyah": "^7.1.0"
65
65
  },
66
66
  "devDependencies": {
67
- "@lwc/babel-plugin-component": "9.2.2",
67
+ "@lwc/babel-plugin-component": "9.3.4",
68
68
  "@types/estree": "^1.0.8"
69
69
  }
70
70
  }