Learn MOAR: Lightning Message Service Generally Available in Summer ’20

Discover Summer ’20 Release features! We are sharing five release highlights for Developers and Admins, curated and published by our evangelists as part of Learn MOAR. Complete the trailmix by July 31, 2020 to get a special community badge, and unlock a $10 contribution to Libraries Without Borders (Bibliothèques Sans Frontières).

In Winter ’20, we announced the Lightning Message Service (LMS) cross DOM messaging API. In Summer ‘20 it is now generally available. This makes building seamless user experiences easier than ever, regardless of whether you’ve implemented new Lightning Web Components (LWC), or have a UI that uses a combination of LWC, existing Aura components, or even Visualforce pages. To incorporate LMS into our sample apps, I had the opportunity to get access to LMS during the developer preview and beta cycles

Building for the modern Salesforce UI

Given the evolution of the modern Salesforce UI, developers frequently deal with a blend of UI technologies. Many customers have implemented a lot of features in Visualforce and Aura that work, and reimplementing them for the sake of new technology is not practical. Some orgs have now added web components features since the launch of Lighting Web Components a year and a half ago.

But since the launch of LWC, there have been two main obstacles developers face when building a seamless user experience using modular components. The first has existed since the launch of the Lightning Experience: no supported platform feature to enable easy cross DOM events between Visualforce pages and Lightning components. The second was new: no supported platform feature to enable cross DOM events between LWCs. And while we did create the OSS c-pubsub shim component, we always stated this was a temporary solution.

LMS has smashed both of those barriers with an easy to use API implemented in LWC, Aura, and Visualforce. This allows publishing and subscribing to and from any of these technologies in the same Salesforce UI. It even allows pub/sub between the utility bar, utility bar popups, and the rest of your application’s UI.

Awesome! How does it work?

Underneath the covers, LMS is a publish/subscribe service layer that is independent of the DOM. In the browser, the built-in CustomEvent API is designed so that a dispatchEvent call bubbles up the DOM tree. But there’s no native API to publish events between two separate DOM branches. LMS solves that.

But Visualforce poses a trickier problem: crossing the iFrame boundary. Visualforce has always used the iFrame sandbox feature as a security mechanism to ensure that the composed Visualforce UI would not compromise customer security, were the Visualforce page itself be compromised. Getting a message across this boundary is done using the window.postMessage() API, but this is not trivial to implement.

Image by Pixabay.com

So the second thing you can expect from LMS is for the Visualforce implementation to abstract away the complexity of using the postMessage() API. Whether your Visualforce page needs to send a message, or subscribe to a message, wiring it into the Lightning Experience has never been easier.

Note that traversing the Visualforce iFrame boundary only works if a developer explicitly enables it in the metadata for the message channel. I’ll show you how to do this a bit later.

What’s new in Summer ’20

The primary change that will come with LMS being GA is the addition of message scopes. I spent some time with the engineering team to understand scopes better.

Simply put, scopes define the implicit context that decides which subscribers will receive messages. There are two scopes: active (default) and application.

The active scope automatically enables and disables which subscribers will receive a message based on active parts of the user interface. An active part of the UI could be a standard application tab or a Lightning console tab that is currently in the selected state. An inactive part of the UI would be one of those tabs that is visible, but not currently in the selected state. As tabs that contain components that implement LMS become active or inactive, there is a context that tracks which LMS-implementing components are currently available to receive messages.

The application scope is a global scope, allowing subscribers in inactive parts of the UI to still receive messages.

Generally, developers should implement LMS in the active scope. The primary use case for application scope is Lightning Console.

Going into the detail of using LMS scopes is beyond the reach of this blog post. In the examples below, we use the default active scope.

I’m sold. Show me how to use it.

Using LMS requires a few elements.

  • A message channel defined in metadata
  • A LWC, Aura component, or Visualforce publishing a message
  • A LWC, Aura component, or Visualforce subscribing to a message

For the examples, I’m using the implementation of LMS we’ve been adding to the LWC Recipes app. This is currently working in the prerelease/summer20 branch, but we will merge it into master once Summer ‘20 goes live. The use case is for the user to select a record from a list. This publishes a message with the record id value. Subscribers then use that record id to display the selected record. In the Winter ’20 pilot announcement blog post, there was a comprehensive look at all three technologies as both subscriber and publisher. So for the sake of brevity, I’ll show the LWC publisher, and an Aura and Visualforce subscriber. But rest assured, in the examples, you can see subscribers and publishers in all three technologies.

Let’s start with the message channel.

Create a message channel

Message channels are a pretty simple piece of metadata. As is typical with metadata, the file name is used to derive the developer name for the message channel. To reference it in code, combine the filename with a __c suffix. For example, the Record_Selected message channel shown below would be referenced as Record_Selected__c.

Some may wonder why message channels exist, when they are an entirely client-side feature. One reason is to have an artifact that you can check into version control. Another is so Salesforce can perform reference checks against any message channels used in a LWC, Aura component, or Visualforce page. If you attempt to save code that uses a message channel that doesn’t exist in metadata, you’ll be blocked and notified. Lastly, when you use a message channel (for instance when importing into a web component in VS Code) the language server tooling will kick in and help you out with intellisense.

The definition of the message channel metadata includes only one required XML element, the <masterLabel> which defines a user-friendly label. To reiterate, the channel name as referenced in code is derived from the metadata file name with an appended __c.

Any message channel that communicates across the Visualforce iFrame boundary must also set the <isExposed> tag value to true.

<?xml version="1.0" encoding="UTF-8" ?>
<LightningMessageChannel xmlns="http://soap.sforce.com/2006/04/metadata">
    <masterLabel>RecordSelected</masterLabel>
    <isExposed>true</isExposed>
    <description>Message Channel to pass a record Id</description>
    <lightningMessageFields>
        <fieldName>recordId</fieldName>
        <description>This is the record Id that changed</description>
    </lightningMessageFields>
</LightningMessageChannel>

You can also define message channel fields. This provides information as to what payload to expect from the channel. However, fields are not reference checked, nor do they limit you as to what data you can attach to a message when publishing to that channel. Your message payload is just an object, and is just as mutable as any other JavaScript object.

I expect this will receive mixed reception between those that prefer strict reference checks, and those that prefer the flexibility of JavaScript. Short of taking sides, this might be something that we Salesforce developers will need think through to define good ALM and testing best practices around projects that implement LMS.

When you need to use a message channel, each technology (LWC, Aura, Visualforce) has its own idiomatic way to get a handle to it.

A couple of final notes on the metadata. First off, you manually create this metadata XML for the moment (see the instructions on where to put it and how to get it into your org in the developer guide). But given how simple the metadata structure is, that should not be much of an obstacle. The other has to do with the __c suffix borrowed from custom Salesforce objects. That threw me at first, but the use of a message channel is so sharply distinct from that of a Salesforce object, this first impression quickly melted into the background for me.

Let’s look at a few examples of how we implement LMS with the above message channel and the LMS engine itself.

Implementing a LWC publisher

In the LWC idiom, you get access to Salesforce Platform resources through standard JavaScript module imports. LMS is no different. There are two key imports in any LWC that uses LMS.

  • The required members of the LMS engine through the lightning/messageService module. In the case of a publisher, these are publish and the MessageContext object. If you are subscribing, you’ll need to import subscribe, unsubscribe, and a few others.
  • The message channel(s) that you will use. Each message channel is surfaced as its own JavaScript module, for instance the message channel in LWC Recipes is @salesforce/messageChannel/Record_Selected__c. This import is the same regardless of whether the component is a subscriber or publisher.

Here’s the example publisher web component from the LWC Recipes example. Note that in this case we’re calling publish in an event handler that is fired when a record is selected from a list.

import { LightningElement, wire } from 'lwc';
import getContactList from '@salesforce/apex/ContactController.getContactList';

// Import message service features required for publishing and the message channel
import { publish, MessageContext } from 'lightning/messageService';
import recordSelected from '@salesforce/messageChannel/Record_Selected__c';

export default class LmsPublisherWebComponent extends LightningElement {
    @wire(getContactList)
    contacts;

    @wire(MessageContext)
    messageContext;

    // Respond to UI event by publishing message
    handleContactSelect(event) {
        const payload = { recordId: event.target.contact.Id };

        // calling publish with the message context, channel, and payload
        publish(this.messageContext, recordSelected, payload); 
    }
}

Note the messageContext object above was created with the @wire adaptor. This object serves two purposes. The first is to implicitly unhook all LMS features when the component is destroyed. The other is to make sure messages are sent only to active scoped LMS implementations.

Implementing an Aura subscriber

As you’d expect in Aura, the implementation of LMS is partly encapsulated in the component markup (.cmp) file, and partly in JavaScript either in the client-side JavaScript controller or helper. Any LMS subscriber needs to do the following:

  • Get a handle to the message service engine
  • Set the context of the service
  • Subscribe to a message channel
  • Identify the handler for that subscription

Aura encapsulates all of this in a single bit of markup, as you can see in the lmsSubscriberAuraComponent.cmp here.

<lightning:messageChannel
    type="Record_Selected__c"
    aura:id="recordSelected"
    onMessage="{!c.handleMessage}"
/>

It’s important to note that LMS, as surfaced to Aura, auto-subscribes any component implementing lightning:messageChannel. There’s no other LMS-specific code to implement in order to subscribe. The handler will receive the payload and any data passed from the publisher. Your component can then respond as necessary (refresh data, update component state, etc.). There is more on this Aura component in the Lightning component reference.

Please note that publishers in Aura use the same <lightning:messageChannel> tag, but publish is invoked from the Aura controller or helper JavaScript. There is an example in LWC Recipes.

Implementing a Visualforce subscriber

Visualforce implements LMS in two different parts of the Visualforce framework.

The LMS engine is surfaced to Visualforce on the client side through the sforce.one object. You can find the publish, subscribe, and unsubscribe LMS functions there.

The message context is handled implicitly for LMS in Visualforce.

The LMS message channel that you plan to use is surfaced through the server-side $MessageChannel global variable. For instance, the Record_Selected__c message channel shown above is accessed through $MessageChannel.Record_Selected__c. A common pattern in Visualforce is to pass data from resolved global variables into JavaScript objects for use in a static resource script. You can see this in the Visualforce subscriber page here:

window.lmsUtil = {
    messageChannel: "{!$MessageChannel.Record_Selected__c}",
    ...
};

And here you can see the call to subscribe to the message channel in the static resource.

lmsUtil.subscribeToMessageChannel = function(channel, handler) { 
    if (!lmsUtil.lmsChannelSubscription) { 
        lmsUtil.lmsChannelSubscription = sforce.one.subscribe(channel, handler); 
    }
}

One thing to look out for in the Visualforce subscriber example is that we’ve implemented two pages. The examples include both a page that uses traditional postback actions, and another page using the Visualforce remote object API. These show that there’s no need to reimplement your existing Visualforce page’s server connection just to wire it up to the LWC and Aura components you’ve built in Lightning.

Important LMS considerations

We’ve already mentioned some caveats but there are a few more to consider.

LMS is not currently supported in Lightning communities, or in Lightning Out. And LMS for Visualforce is not supported in the Salesforce Mobile app.

LMS serializes the message payload on its way to subscribers. As a technology, JavaScript does not support the serializing and deserializing of functions. For this reason, we recommend not passing functions over LMS message channels.

LMS for interactive cross technology UI development

LMS represents a watershed moment in building the Lightning UI. The number of places where you can incorporate existing investments in custom UI has just increased drastically.

The excitement from the community is showing, too. There’s even a cool infographic that was shared on Twitter by @Rajasneha_dev.

So go clone the latest version of the LWC Recipes app (currently on the prerelease/summer20 branch). Take a look at the code, and see what you can build today with LMS. If you’ve implemented a something using the unsupported c-pubsub component, now is the time to get it working with this supported platform feature.

I’d like to add a special acknowledgement to Trenton Johnson, and the whole Visualforce engineering team who were invaluable in the project to implement a set of recipes for LMS, and the writing of this blog post.

Next steps

About the author

Peter Chittum is a developer evangelist and considers himself a generalist for Salesforce platform features, trudging the endless path of JavaScript proficiency. He’s been working as a Salesforce developer helping enable Salesforce developers since 2010. He lives in England.