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.
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.
Share Improve this question edited Mar 12 at 10:17 BalusC 1.1m376 gold badges3.7k silver badges3.6k bronze badges asked Mar 11 at 16:26 MinisXMinisX 3911 silver badge15 bronze badges 4 |1 Answer
Reset to default 0You 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>
Wc
variable appears to be declared. This is where you need to put the IIFE (also show us what yournpm build
command does to create them, with all relevant configuration). Although ideally, you just shouldn't load those same scripts multiple times in the first place. – Bergi Commented Mar 12 at 10:52