Performance Considerations with LWS

Custom Lightning components always run with either Lightning Locker or LWS. If LWS is disabled, Lightning Locker is enabled. In many cases, components exhibit better performance running with LWS than with Lightning Locker. However, sometimes LWS can have a noticeable performance impact on your components. To give you insight about why your components can run into performance issues with LWS, let’s dig a little deeper into the LWS architecture.

A Lightning page can include components created by multiple companies. For example, components created by an organization’s developer team must run concurrently with components created by Salesforce. If you distribute components in managed packages, your components must run concurrently with components created by Salesforce and the components of the customer who installs your packages.

The components from each origin have a unique namespace, which is a Salesforce requirement for components to be installed into an org. Without a security layer such as Lightning Locker or LWS, the components all have access to the same browser resources and data as if they were all in the same namespace. Such is the nature of all web applications. The chances for unintentional interference between components from different namespaces are high. For example, if a component in namespace c changes a global object, the object is mutated for all the components and for the Salesforce platform. Code that tries to use that object after it’s mutated can potentially be unable to perform the intended action. If a component creates data in the page for the use of its namespace, components in other namespaces can also access the data and change it.

For example, suppose a component changes a global function as shown in this code sample.

The change affects all other objects on the page. This other code sample that's using that mutated global function always returns the unexpected value.

To understand the reasons for performance issues that components can have when running with LWS, you must be familiar with the concepts discussed in Virtualization of the Browser.

As a browser virtualization engine, LWS creates virtual environments called sandboxes for each namespace. A namespace sandbox is a copy of the host environment that can only be accessed by components that belong to the namespace.

By default, custom components use the namespace c, and LWS creates a c sandbox for them. Let’s say that your Lightning page uses your custom components, and also uses components from a managed package that uses the namespace other. LWS isolates code that’s running in the namespace c sandbox from side effects and changes of code running in the namespace other sandbox, and vice versa. If you add components from another namespace called third to the page, LWS creates another sandbox for the third namespace, and isolates the code from the c and other namespace sandboxes.

Most importantly, LWS isolates all the sandboxes from the browser’s host environment. The client side of the Salesforce platform runs in the host environment. The platform includes technologies such as the Lightning framework for Aura and Lightning Web Components, base Lightning components, Lightning Data Service, and LWS itself. These platform technologies must be protected from malicious or inadvertent access by non-platform code.

The most critical JavaScript object that must be protected in the host environment is the DOM’s window global object. Components from namespace third must not be able to intentionally or unintentionally modify global variables that could have a negative side effect on the components from the c or other namespaces. Even more importantly, a non-platform component must not be able to make changes that affect platform code like Aura or LWC. Such changes could have an impact on the entire application.

For example, imagine that an LWC third-some-cmp modified some Array.prototype functions. If the LWS sandboxes weren’t in effect, those changes affect the host environment, and components like c-example-cmp and other-cmp-example. When code in the host environment or the other namespaces uses the modified Array.prototype functions to manipulate data in arrays, unexpected or harmful behavior can occur.

As another example, imagine some JavaScript from module other/myFunctions modifying a cookie called session. A component c-example-cmp also modifies a cookie called session. Without LWS sandboxing, code from different namespaces can accidentally read or modify the same cookie.

LWS creates a virtual browser environment in a sandbox for each namespace, and allows only namespace code to execute inside this sandbox. At the same time, LWS allows code running inside a sandbox to interact with code running in another sandbox or with the browser’s host environment and the DOM.

This diagram shows the browser host environment and objects related to it in blue. The sandboxes and objects related to them are in red.

Diagram showing proxy objects in sandboxes and host environment

In order to allow marshaled access to objects between sandboxes and the host environment, LWS uses Proxy objects. Here are a few scenarios using proxies that this diagram demonstrates.

  • The solid blue X object Object called X created in host environment is created in and owned by the host environment. LWS allows c namespace components to access this object by creating the dashed blue X proxy Proxy for X object in the sandbox. If code in sandbox c modifies the blue X proxy Proxy for X object in any way, those modifications are only reflected inside sandbox c, and not in the target blue X object Target object called X owned by the host environment.
  • The solid red Y object Object called Y created in c sandbox is created in and owned by code in namespace c. Code in sandbox c can pass this object as an argument to a function that’s owned by the host environment. For example, the host environment function can be owned by LWC or LDS. LWS provides a dashed red Y proxy object Proxy for Y object in host environment for the host environment’s function to interact with.
  • If code in sandbox c interacts with the solid red Z object Object Z created in other sandbox that’s owned by namespace other, LWS provides an indirect Z proxy Indirect Z proxy created in c sandbox for sandbox c code to interact with it. The proxy is indirect because it doesn’t interact directly with the target red Z object Target Z object. Instead, the indirect Z proxy interacts with the dashed red Z proxy Z proxy in host environment in the host environment. LWS provides the dashed red Z proxy Z proxy in host environment for code to interact with red Z object Target Z object outside of the other namespace.
  • The solid red W object Object W in third sandbox is created in and owned by code in namespace third, so the dashed red W proxy object Proxy W in host environment is created in the host environment. The indirect W proxy Indirect proxy W in other namespace in namespace other enables code in that namespace to interact with the target W object Object W in third sandbox through the W proxy Proxy W in host environment in the host environment.

In summary, LWS provides:

  • a proxy object in a sandbox whenever code in the sandbox interacts with an object that's owned by the host environment
  • a proxy object in the host environment whenever code in the host environment interacts with an object that's owned by a sandbox
  • an indirect proxy object in a sandbox whenever code in the sandbox interacts with an object that's owned by another sandbox

In any sandbox environment, when code modifies a proxy object, the proxy target remains unchanged. LWS protects the target object, whether the target object is also a proxy object or the original object.

LWS uses the proxy objects to implement the distortions to prevent behavior that’s not secure.

The use of proxies has a performance cost due to the extra processing required. This cost is negligible when there are a few thousand proxies, but as the number of proxies grows into the tens of thousands, the performance impact becomes observable.

Salesforce testing shows that if an application is already pushing limits that lead to degraded performance, LWS magnifies this effect. For instance, consider an app or component that’s handling a large collection of objects in the tens of thousands. When the large collection is traversed and processed with Lightning Locker enabled, you likely don’t notice performance issues. However, if LWS is enabled, the performance degradation can be noticeable.

This example shows component code that uses the global object Set. Global objects are owned by the host environment, so LWS creates a proxy Set object in the component's namespace sandbox. Each access of the Set object goes through the proxy object, which increases the processing cost.

Another case where LWS can degrade performance is an application that instantiates a large number of components. In particular, if all the components are rendered and present in the DOM at the same time, you can notice performance degrade. DOM operations are expensive, and you must be mindful about what you do in the DOM and how often you do it.

Suppose an app is using a third-party component that performs many DOM operations, and the app spawns many instances of this component. This activity by itself can push the limits of the browser. With LWS disabled, the performance effects can take a long time to see. With LWS enabled, you see the performance effects sooner.

See this article on web.dev How large DOM sizes affect interactivity, and what you can do about it for more information.

Salesforce continues to examine the effects of LWS on the performance of your custom components. We're working on ways to improve performance and achieve the same level of security for LWS.

In the meantime, we recommend that you examine your code to find opportunities to reduce heavy DOM usage. When creating large collections of objects, minimize the use of global objects such as Set, Map, and TypedArray. Use plain JavaScript objects instead.