[SalesForce] Solution to reuse containers to expose LWC to tabs for Classic app

I am considering to build a Classic app which should consist of 5 to 10 tabs, each one containing single LWC component (single different LWC component).

I want to avoid code duplication and don't want to create multiple containers.

I would like to use as few reusable containers as possible.

I am looking for the most efficient solution in terms the least amount of containers possible in the current Spring '19 Release.

So far I was considering the following approach (which doesn't work completely)

I could create a standalone Lightning App LWCContainer.app with the following code

<aura:application extends="force:slds">
    <aura:attribute name="cmpName" type="String" default="lightning:button"/>
    <aura:handler name="init" value="{!this}" action="{!c.doInit}"/>
    Dynamic content here: <br/><br/>
    {!v.body} 
</aura:application> 

and the controller

({
    doInit : function(cmp) {
        $A.createComponent(
            cmp.get("v.cmpName"),
            {
            },
            function(newButton, status, errorMessage){

                if (status === "SUCCESS") {
                    var body = cmp.get("v.body");
                    body.push(newButton);
                    cmp.set("v.body", body);
                }
                else if (status === "INCOMPLETE") {
                    console.log("No response from server or client is offline.")
                    // Show offline error
                }
                else if (status === "ERROR") {
                    console.log("Error: " + errorMessage);
                    // Show error message
                }
            }
        );
    }
})

and then I could create a Web Tab with link /c/LWCContainer.app?cmpName=c:fls and a Web Tab with link /c/LWCContainer.app?cmpName=c:ols so that I would need n or O(n) of Web Tabs and 1 or O(1) of custom Lightning App containers, whene n is the number of Tabs or LWC components I would like to expose as tabs.

However, this approach actually doesn't work due to inability of containing Lightning standalone apps or other Lightning components inside of iframes which is generating error

Refused to display
'https://flsman-dev-ed.lightning.force.com/c/LWCContainer.app?cmpName=c:fls'
in a frame because an ancestor violates the following Content Security
Policy directive: "frame-ancestors 'self'".

enter image description here


Update: source code for two LWC components mentioned above.

lwc/fls/fls.html

<template>
    FLS!
    <br/>
    FLS 
</template>

lwc/fls/fls.js

import { LightningElement } from 'lwc';

export default class Fls extends LightningElement {}

lwc/fls/fls.js-meta.xml

<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata" fqn="fls">
    <apiVersion>45.0</apiVersion>
    <isExposed>true</isExposed>
    <targets>
        <target>lightning__AppPage</target>
        <target>lightning__RecordPage</target>
        <target>lightning__HomePage</target>
    </targets>
</LightningComponentBundle>

lwc/ols/ols.html

<template>
    OLS!
</template>

lwc/ols/ols.js

import { LightningElement } from 'lwc';

export default class Ols extends LightningElement {}

lwc/ols/ols.js-meta.xml

<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata" fqn="fls">
    <apiVersion>45.0</apiVersion>
    <isExposed>true</isExposed>
    <targets>
        <target>lightning__AppPage</target>
        <target>lightning__RecordPage</target>
        <target>lightning__HomePage</target>
    </targets>
</LightningComponentBundle>

Best Answer

I think I have found the following solution which would require n tabs and 1 Visualforce page with 1 Lightning Out Container app.

Web tabs will have relative URLS referring to different LWC components like /apex/LWCCont?cmpName=c:fls or /apex/LWCCont?cmpName=c:ols.

Visualforce page will have the following code

<apex:page controller="LWCContController" sidebar="false">
    <apex:includeLightning />

    <div id="lightning" />

    <script>
        $Lightning.use("c:lwcOut",    // name of the Lightning app
            function() {                  // Callback once framework and app loaded
                $Lightning.createComponent(
                    "{!cmpName}", // top-level component of your app
                    { },                  // attributes to set on the component when created
                    "lightning",   // the DOM location to insert the component
                    function(cmp) {
                        // callback when component is created and active on the page
                    }
                );
            }
        );
    </script>
</apex:page>

with Apex Controller

public inherited sharing class LWCContController {

    public String cmpName { get{
        return ApexPages.currentPage().getParameters().get('cmpName');
    } set; }
}

Also lwcOut.app will contain code of dummy lightning-out app

<aura:application access="GLOBAL" extends="ltng:outApp"> 
</aura:application> 

In total, this approach would need 3 custom container-related component to serve any number of LWC components to be exposed ( 1 Apex Class, 1 Visualforce Page, 1 Lightning Out Standalone App).

One drawback of this approach that on switching tab currently selected tab is not marked as selected which is disappointing. However, I don't know how to fix that.

enter image description here

enter image description here

If someone finds more elegant or sophisticated solution please share.

Related Topic