Skip to content

Comments

RFC: Private Methods in LWC#96

Open
a-chabot wants to merge 25 commits intosalesforce:masterfrom
a-chabot:a-chabot-private-methods
Open

RFC: Private Methods in LWC#96
a-chabot wants to merge 25 commits intosalesforce:masterfrom
a-chabot:a-chabot-private-methods

Conversation

@a-chabot
Copy link

@a-chabot a-chabot commented Feb 3, 2026

RFC: Private Methods in LWC

This approach delegates to native browser behavior of private methods and is a backwards compatible solution. [You can find a POC PR here.](https://github.com/salesforce/lwc/pull/5477)

**Section of Random Facts Relating to the Design**
* We do not need to transform call expressions within a component, this is already functional
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't get this part, can you explain?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm guessing that the current compiler can handle call expressions (this.#privateMethod()), and it only breaks on method declarations (#privateMethod() { ... }).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wjhsf is correct

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feels yucky to have an intermediate compiled state where the javascript is intentionally broken...just me?

Copy link
Author

@a-chabot a-chabot Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ekashida Do you mean when intermediate moment when the method declaration is _internal_only_private_privateMethodCall() but the call expression is still this.#privateMethod()?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is definitely merit to that "yucky" feeling. The primary concern is the creation of an
invalid intermediate AST where the declaration and invocation are temporarily out of sync.

This leads to a few specific risks:

Plugin Conflicts: If a linter, minifier, or tree-shaker runs during that intermediate "window,"
it may see a call to #privateMethod with no corresponding declaration and either crash or
produce incorrect optimizations.

Shadowing & Collisions: Transforming to a standard string like _internal_only_... risks
shadowing existing properties or global variables. An intermediate transform might
misinterpret the intent of the renamed method and "optimize" it in a way that breaks the
link to the original source.

Technical Debt in the Pipeline: If the transforms aren't strictly atomic and encapsulated,
the compiler is essentially "lying" to the rest of the pipeline about the class structure,
making the build process fragile and difficult to debug when stack traces show "fake"
method names.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These points are pretty convincing. Let's ensure that the output of the transform maintains valid structure? We could collect all the private method names when we enter ClassBody, and then use those in the visitors for ClassPrivateMethod and MemberExpression for mangling.

@wjhsf wjhsf force-pushed the a-chabot-private-methods branch from e0971e9 to 0575ab3 Compare February 4, 2026 17:09
@ekashida
Copy link
Member

ekashida commented Feb 6, 2026

@caridy @ravijayaramappa can you take a look at this?

#privateMethod() { ... }
```

Private methods can be referenced using dot notation:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To highlight the utility of private methods, it might be good to include the rest of the class context, and to contrast that with an example of disallowed syntax (like (arg) => arg.#privateMethod()).

* This feature is limited to implementing only private methods. It does not include not private properties or private accessors (getter/setters).
* Both of these transforms only get applied if enabled, using a compiler option

Private class members can only be accessed from within the class body itself, as defined by JavaScript's private fields specification. You cannot call `instance.#privateMethod()` from external code because it attempts to access the private method from outside the class context. Similarly, referencing `{#privateMethod}` in an LWC template violates this same encapsulation rule because the template exists outside the JavaScript class body. Both result in a SyntaxError caught before the code ever runs.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are the existing errors for instance.#privateMethod() and {#privateMethod}. Particularly for the latter, should we change them to provide more clear guidance?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For instance.#privateMethod():
image

For {#privateMethod}:
image

Both warnings could definitely be improved to be more explicit.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The first one is completely self-explanatory but it's a typescript error. The javascript error ends up being the same as the second example: SyntaxError: Private field '#bar' must be declared in an enclosing class.

Not as user-friendly, but it's a native syntax error that is easily searchable. Assuming it would be simple to hook into the exception and provide an error message similar to the typescript error, but I'm ok with keeping it as-is.


## Drawbacks
**Drawback #1:** Performance.
This design requires AST traversal twice, once to rename the private functions to regular functions & then again to re-rename them as private functions. There is performance overhead to this AST traversal.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔 Does it require separate traversals or just an extra plugin call within the single traversal?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As implemented in the POC PR, there is an extra manual traversal of the tree for the Program visitor.

Copy link
Contributor

@ravijayaramappa ravijayaramappa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall seems like a good move. Had some questions about the implementation strategy.


**Drawback #2:** Complexity to LWC Compiler.
As with all expansions, this project adds complexity and technical debt to the LWC compiler. This project is implementing two new babel transforms that will need to be maintained.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume the transformation will apply to other classes that a component might have, for example quill library. Should we account for that risk exposure.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The transformation is really a two-step non-transformation. Private method in => private method out. If our transformations are #methodName => __lwc__private_method_methodName => #methodName, then it should be fairly low risk. The bigger risk is probably how our intermediate stage might interact with other babel plugins.

```

## Motivation
The largest motivation for this feature is improving our security approach. Enabling private methods helps maintain the expected behavior of a component by restricting access to its internal methods. This will prevent unexpected context abuse and shadowing methods that alter the behavior of a component. Private methods are inherently secure by design, and using them eliminates entire classes of potential component vulnerabilities.
Copy link
Contributor

@ravijayaramappa ravijayaramappa Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unexpected context abuse and shadowing methods

  1. How much of a problem is this? Since this RFC is restricted to methods only.
  2. What's our stance for customers (internally & externally) who have taken a dependency on existing behavior? It could be we will follow this for net new components but cannot break existing exposed components.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How much of a problem is this? Since this RFC is restricted to methods only.

We don't know the scope of the problem, but it will be gated with a flag within the component.

How much of a problem is this? Since this RFC is restricted to methods only.
What's our stance for customers (internally & externally) who have taken a dependency on existing behavior? It could be we will follow this for net new components but cannot break existing exposed components.

This will likely be a case-by-case basis. The guidance within base components will be exactly that--use private methods for net-new, but gate usage in existing. We didn't include this in the RFC since it's not directly relevant to the feature.


## Drawbacks
**Drawback #1:** Performance.
This design requires AST traversal twice, once to rename the private functions to regular functions & then again to re-rename them as private functions. There is performance overhead to this AST traversal.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As implemented in the POC PR, there is an extra manual traversal of the tree for the Program visitor.


As you can see in the screenshot above, the linter suggests to “Please add @babel/plugin-transform-private methods to your configuration” when using private methods in an LWC. This Babel transform lets you use JavaScript private methods in environments that don’t support them yet by transforming them into older, compatible JavaScript. In our use case, the Babel transform would perform compile time validation on private properties (anything without LWC decorators, such as @wire, @api, and @track).

This design causes issues because class properties get renamed, thus becoming unusable in the template. While this solution does remove the error, it does not resolve the security issues within the components. It is also adding a polyfill for something that is already natively supported in browsers.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only private class properties seem to be renamed. Earlier in the RFC, we asserted that private fields are not accessible from template. So I am not quite gauging the issue here, other than the fact that we are polyfiling?

Image


The design spelled out above is backwards compatible and does not break existing LWC code. It will be adopted internally before it is enabled and exposed for external customers.

A simple LWC example could be written for demonstration purposes.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds like a TODO :)

**Drawback #2:** Complexity to LWC Compiler.
As with all expansions, this project adds complexity and technical debt to the LWC compiler. This project is implementing two new babel transforms that will need to be maintained.

## Alternatives
Copy link
Contributor

@gaurav-rk9 gaurav-rk9 Feb 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: Why does @babel/plugin-transform-class-properties block private methods? seems arbitrary that a plugin that only transforms class "fields" complains when it sees a private "method". If the reason does not actually affect us, can we fork @babel/plugin-transform-class-properties and modify for our needs?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess it's a DX feature going off the assumption that you're using the plugin because your client doesn't support private features, and it doesn't make sense to transform private fields but not private methods?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Babel aligns its plugins with proposed language features. Class properties and private fields were separate proposals, so they were created as separate plugins.

**Drawback #2:** Complexity to LWC Compiler.
As with all expansions, this project adds complexity and technical debt to the LWC compiler. This project is implementing two new babel transforms that will need to be maintained.

## Alternatives
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven’t evaluated the trade-offs or risks for this, just thinking out loud
can we use decorators with accessors, to implement reactivity? That is the compiler adds accessor keyword and a decorator on all class fields, and the engine defines that decorator to observe for changes and re-render when needed.
Babel has a plugin for transpilation until we get native support

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Decorators were a bet that didn't pay off, and we've seen from other features (like synthetic shadow) that relying on non-native implementations of things is just a headache down the road.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants