15

I am currently breaking a monolithic 1GP Managed package into more modular 2GP packages.

While doing this I also want to decompose chunky LWC components in a way so they can be reused more flexibly and split across multiple packages (same or different namespace).

I am now looking for a set of ground rules and architecture patterns that are proposed by Salesforce AND have proven to work in ISV practice regarding:

  • Which communication channel to use (PubSub, Message Service,...)
  • How to access data (Wire Service etc)
  • When to use Apex and when not

I am especially keen on learning from people who have broken down monolithic LWCs into cross-package modules.

0

2 Answers 2

19
+50

This is a complex topic and I’m going to do my best to give some quick pointers but we could fill an entire white paper on this theme and I’m sure that are some things that I might overlook.

Inter-component communication

Data access and Apex

When working with LWC there are two main things that create coupling: child components and imports (JS modules such as wires, labels, object/field references...).

You can’t do much to limit coupling from child components because LWC does not support dynamic component creation (unlike Aura). Some devs use Aura wrappers as a hack to introduce decoupling for LWC but it’s messy and it’s likely not future proof.

That being said, let’s focus on what you can control: imports. We’ll focus on the data aspect which is the more prevalent.

You have two ways to retrieve data in LWC: using Apex @AuraEnabled endpoints (via wires or imperatively, it doesn’t make a difference from a dependency standpoint) or using the UI API wires and functions.

The best option in terms of decoupling is to use the UI API wires and functions because it does not introduce a dependency with Apex. However, there are some limits on what you do with those APIs when it gets to querying multiple records. For example, you can’t run a SOQL query but you can CRUD on a record and benefit from the Lightning Data Service cache. See the UI API wires and functions reference and my UI API playground for an extensive and up to date list of what’s available with some interactive examples.

On top of the UI API wires you can also try to use the base components if you have simple UI requirements: lightning-record-form, lightning-view-form, lightning-edit-form...

Then, when you need more flexibility, use Apex @AuraEnabled endpoint. When you do this, you’ll need to work on breaking runtime dependencies on the Apex side (see my post on Apex dependency injection). With this technique, you can separate the Apex controllers from the business logic by using services and dependency injection.

LWC (coupled with Apex)
  Apex controller for this LWC
    Service that is dynamically injected (decoupling)

However, there’s a basic tip that can help you reduce this burden in your LWC design. Try moving data access up the component hierarchy when possible so that your low level components remain reusable and uncoupled.

Top level LWC with data access (data coupling)
  Sub component with @api and events
    Sub component with @api and events

With this pattern, sub components are not coupled with data endpoints.


That's it for my recommendations. I hope that it helps. Again, this list is probably not exhaustive so everybody feel free to contribute.

1
5

Note: With permission from the original author, Salesforce's own Aditya Naag, I am reposting here some highlights of his ApexHours session. The slides can be downloaded here.


enter image description here

We rarely write code that is abstract enough to be reusable everywhere. You should write components not to be reused out of the box, but to be abstract and composable enough to serve that purpose in a variety of implementations.

Attributes / Parameters

  • Passing attributes becomes a pain when you need to pass lots of inputs to the component Consider passing objects as parameters instead of having multiple attributes.
  • Pay extra attention to public properties/function/events naming so that you don’t break the signature of shared components
  • Avoid booleans when there could be more options in the future.

Server Communication

  • Don’t lock a low-level component with an Apex endpoint.
  • Try to move server communication in upper layers when possible.
  • A reusable component should be as dumb as possible.

Over-modularizing components for App Builder leads to unnecessary usage of the publish-subscribe pattern. Compromise between the number of components in which you divide the page and the overhead of their communication mechanisms

Inter-Component Eventing

  • Avoid using LMS or pubsub when not needed
  • Use LMS or pubsub only when there is no common ancestor that you control
  • Use UI API Wire Adapters and Lightning Base Components
    • Gives the admin the opportunity to configure your components without modifying code by configuring lists views, and record layouts.
    • If required don’t hesitate to restrict your component's visibility to certain objects.
  • Think whether the component needs to be tied to an object, or can it be object agnostic
    • Don’t use static references
    • If needed, create object agnostic classes

Best Practises

  • Extract utility methods outside your LWC components
    • LWC components should only deal with UI-related logic.
  • Favor public properties over public methods
    • Properties can be set directly in the template, while a method requires the consumer to render the component first, retrieve it back from the DOM, and invoke the method.

Component Variations

  • For component variations that are structurally different, use multiple templates
  • Use Slots to create component variants, when the structure is dynamic.

Styling

  • Spend some time at design time to think about possible variants of your component.
  • Use CSS Custom Properties for custom styles
  • Use Styling Hooks to override styling of Base Components
  • Favor Styling Hooks and CSS Custom Properties over component attributes

Error Handling

  • Adopt a common error reporting strategy for your components.
  • If your component is not standalone, throw errors to the parent component.
  • Create a reusable errorPanel component across the entire project.

Accessibility

  • Reflect properties as attributes
  • Ensure the components expose relevant ARIA Attributes
  • If you’re using IDs for any HTML Elements in your component, accept it as an attribute.

Distribution

  • LWC components can be added to Managed, Unmanaged, and Unlocked packages.
  • Currently, you cannot embed an LWC from a Managed package in your own component.
  • Use Unlocked packages without namespace to share reusable components.
1
  • 1
    Currently, you cannot embed an LWC from a Managed package in your own component. --> You can with LWS!
    – Alba Rivas
    Jun 28 at 10:24

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.