Saturday, 10 June 2017

Locker Service, Lightning Components and JavaScript Libraries

Locker Service, Lightning Components and JavaScript Libraries

Window

Introduction

As I’ve previously blogged, the Summer 17 release of Salesforce allows you to turn the locker service on or off based on the API version of a component. This is clearly an awesome feature, but there is a gotcha which I came across this week while working with the Lightning Testing Service Pilot.

I have a JavaScript library containing functionality that needs to work as a singleton (so a single instance that all Lightning Components have access to). This library is a static resource that is loaded via the <ltng:require /> standard component.

One window to rule them all?

In JavaScript world there is a single Window object, representing the browser’s window, which all global objects, functions and variables are attached to. When my JavaScript library is loaded, an immediately invoked function expression executes and attaches an my library object to the Window.

In Lightning Components world, with the locker service enabled, the single Window object changes somewhat. Instead of a Window, your components see a SecureWindow object which is shared among all components in the same namespace. This SecureWindow is isolated from the real Window for security reasons. In practice, this means that if you mix locker and non-locker Lightning Components on the same page, there are two different window concepts which know nothing about each other.

Example code 

The example here is a lightning application that attaches a String to the Window object when it initialises and includes two components that each attempt to access this variable from the window, one at ApI 40 and one at Api 39. The app also attempts to access the variable, just to show that it is correctly attached.

App

<aura:application >
    <aura:handler name="init" value="{!this}" action="{!c.doInit}" />
    <button onclick="{!c.showFromWindow}">Show from in App</button> <br/>
    <c:NonLockerWindow /> <br/>
    <c:LockerWindow /> <br/>
</aura:application>

Controller

({
	doInit : function(component, event, helper) {
		window.testValue="From the Window";
	},
	showFromWindow : function(component, event, helper) {
		alert('From window = ' + window.testValue);
	}
})

Locker Component

<aura:component >
	<button onclick="{!c.showFromWindow}">Show from Locker Component</button>
</aura:component>

Controller

({
	showFromWindow : function(component, event, helper) {
		alert('From window = ' + window.testValue);
	}
})

Non Locker Component

<aura:component >
	<button onclick="{!c.showFromWindow}">Show from non-Locker Window</button>
</aura:component>

Controller

({
	showFromWindow : function(component, event, helper) {
		alert('From window = ' + window.testValue);
	}
})

Executing the example

If I set the API version of the app to 40, this enables the locker service. Click the three buttons in turns shows the following alerts:

From App

Screen Shot 2017 06 10 at 06 31 24

As expected, the variable is defined.

Non-Locker Component

Screen Shot 2017 06 10 at 06 31 37

As the application is running with the locker service enabled, the variable is attached to a secure window. The non-locker service component cannot access this so the variable is undefined.

Locker Component


Screen Shot 2017 06 10 at 06 31 44

As this component is also executing the with the locker service enabled, it has access to the secure window for it’s namespace. As the namespace between the app and this component is the same, the variable is available.

Changing the app to API version 39 allows the non-locker component to access the variable from the regular JavaScript window, while the locker component doesn’t have access as the variable is not defined on the secure window.

So what?

This had a couple of effects on my code if I mix Api versions to that my contains a combination of locker and non-locker components:

  •  I can’t rely on the library being made available by the containing component or app. Thus I have to ensure that every component loads the static resource. This is best practice anyway, so not that big a deal
  • I don’t have a singleton library any more. While this might not sound like a big deal, given that I can load it into whatever window variant I have, it means that if I change something in that library from one of my components, it only affects the version attached to the window variant that my component currently have access. For example, if I set a flag to indicate debug mode is enabled, only those components with access to the specific window variant will pick this up. I suspect I’ll solve this by having two headless components that manage the singletons, one with API 40 and one with API < 40, and send an event to each of these to carry out the same action.

Related Posts

 

 

 

7 comments:

  1. For what it's worth, we _do_ document that you shouldn't mix API versions for exactly this reason:

    https://developer.salesforce.com/docs/atlas.en-us.lightning.meta/lightning/security_ls_api_version_mix.htm

    Thanks for helping us get this particular word out to more people!

    ReplyDelete
  2. Very Interesting information shared than other blogs
    Thanks for Sharing and Keep updating us

    ReplyDelete
  3. This is very helpful information Bob. I have implemented the same and unable to show the non locker component alert as shown in the screenshot.

    The Non-Locker component with ver 39.0 is still showing the alert with the value of variable. So what may I missing here?

    Thanks

    ReplyDelete
  4. This is very helpful information Bob. I have implemented the same and unable to show the non locker component alert as shown in the screenshot.

    ReplyDelete
  5. This is very helpful information Bob. I have implemented the same and unable to show the non locker component alert as shown in the screenshot.

    ReplyDelete