最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - How to isolate the execution of JS code, such that each script is executed within a "container"?

programmeradmin1浏览0评论

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
  • 4 Use modules and avoid global variables. ES modules were introduced in 2015 (10 years ago) and before that you had third-party solutions, e.g. require.js or system.js. Why would you use global variables in 2025? The IIFE makes no sense. It doesn't wrap the loaded code. It wraps the code that loads other code. – jabaa Commented Mar 11 at 16:31
  • It looks like a possible XY Problem. You face a pretty simple object scope problem that can be solved immediately by avoiding adding the object in the global scope and/or using modules. Instead, you look for some overcomplicated tricks. Please correct me if I miss something. – Sergey A Kryukov Commented Mar 11 at 18:44
  • "I have 2 js files - dashboard.js and statuspanel.js." - please show us the code in those files, as that is where the Wc variable appears to be declared. This is where you need to put the IIFE (also show us what your npm 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
  • @jabaa thanks for mentioning ES modules. I have simply replaced <script type="text/javascript" src="...js"/> with <script type="module" src="...js"/> and that have solved the problem. If you post your suggestion as an answer, I will accept it. To others - I do not have control over the used global variables, they are auto generated during the npm build process of the React app. These global variables are defined by React. – MinisX Commented Mar 12 at 13:51
Add a comment  | 

1 Answer 1

Reset to default 0

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>
发布评论

评论列表(0)

  1. 暂无评论