I have 2 js files - dashboard.js and statuspanel.js. They render React components inside the div that has the name of the script as it's id. For example, in my JSF application. The JS files are auto-generated by npm run build
.
<composite:implementation>
<div id="#{cc.attrs.name}"></div>
<script src="http://localhost:8900/microfrontends/#{cc.attrs.name}/#{cc.attrs.name}.js" type="text/javascript"></script>
<link rel="stylesheet" type="text/css" href="http://localhost:8900/microfrontends/#{cc.attrs.name}/#{cc.attrs.name}.css"/>
</composite:implementation>
When there is one component per page, there are no issues at all. But if I have more than one component, then there is a collision in the variable names. So If I do this
<div class="ui-g-12 ui-g-nopad">
<ic:microfrontend name="statuspanel"/>
<ic:microfrontend name="dashboard"/>
</div>
I get this error
dashboard.js:1 Uncaught SyntaxError: Identifier 'Wc' has already been declared (at dashboard.js:1:1)
I searched the internet and found 2 ways to possibly solve the problem. First approach is using the shadowDOM.
<composite:implementation>
<div id="#{cc.attrs.name}_host"></div>
<div id="#{cc.attrs.name}"></div>
<script type="text/javascript">
(function () {
const shadowHost = document.getElementById("#{cc.attrs.name}_host");
const shadowRoot = shadowHost.attachShadow({mode: 'open'});
const div = document.createElement('div');
div.id = "#{cc.attrs.name}";
shadowRoot.appendChild(div);
const script = document.createElement("script");
script.src = `http://localhost:8900/microfrontends/#{cc.attrs.name}/#{cc.attrs.name}.js`;
shadowRoot.appendChild(script);
const link = document.createElement("link");
link.rel = 'stylesheet';
link.href = `http://localhost:8900/microfrontends/#{cc.attrs.name}/#{cc.attrs.name}.css`;
shadowRoot.appendChild(link);
})();
</script>
</composite:implementation>
But I still get the same error. Also, script only finds the div outside the shadowDOM, it does not see the one inside (I did remove the div above the shadowDOM beforehand).
Uncaught SyntaxError: Identifier 'Wc' has already been declared (at dashboard.js:1:1)
So I tried a second approach - wrapping my script with a function to make global variables function scoped.
<composite:implementation>
<div id="#{cc.attrs.name}"></div>
<script type="text/javascript">
(function () {
var script = document.createElement('script');
script.src = 'http://localhost:8900/microfrontends/#{cc.attrs.name}/#{cc.attrs.name}.js';
document.head.appendChild(script);
})();
</script>
<link rel="stylesheet" type="text/css" href="http://localhost:8900/microfrontends/#{cc.attrs.name}/#{cc.attrs.name}.css"/>
</composite:implementation>
However, I still get the same error.
Uncaught SyntaxError: Identifier 'Wc' has already been declared (at dashboard.js:1:1)
I have also tried to change the auto-generated js files (dashboard and statuspanel), where I replaced var with let, but this breaks the scripts. So that is not an option.
Question: How to isolate the execution of JS code, such that each script is executed within a "container"? Using iframe is not an option.
You can isolate JavaScript code using ES modules:
<composite:implementation>
<div id="#{cc.attrs.name}"></div>
<script type="module" src="http://localhost:8900/microfrontends/#{cc.attrs.name}/#{cc.attrs.name}.js"></script>
<link rel="stylesheet" type="text/css" href="http://localhost:8900/microfrontends/#{cc.attrs.name}/#{cc.attrs.name}.css"/>
</composite:implementation>