OfficeDev / microsoft-teams-library-js

JavaScript library for use by Microsoft Teams apps
https://docs.microsoft.com/microsoftteams/platform/
Other
426 stars 193 forks source link

Script cannot be loaded conditionally at runtime - works only when incuded in <head> #2339

Open veodko opened 3 months ago

veodko commented 3 months ago

I have an application that shares the same codebase for web usage, Outlook addin and Teams app. Because of this, we load appropriate scripts (office.js) conditionally at runtime, especially that including office.js causes the teams app to not load at all.

Loading office.js at runtime works without any trobules, however MicrosoftTeams.js simply wont work if loaded like that, even putting the script tag into <body> causes the library to appear in the sources tab, but microsoftTeams object is undefined:

<body>
    <script src="https://res.cdn.office.net/teams-js/2.23.0/js/MicrosoftTeams.min.js"></script>
</body>

image obraz

Only putting the script to <head> tag makes it work and microsoftTeams is then defined in the global context. This disallows us from loading the script conditionally after our framework code is initialized and we need to either load the script all the time or do some ugly checks directly in the head element.

Is this by design?

veodko commented 3 months ago

More than that, I just discovered that this happens randomly that the script just doesn't load at all sometimes, even when loaded in head:

<head>
  <script>
    // assume there is some condition here, but for testing purposes I just left it like this:
    const script = document.createElement('script');
    script.type = 'text/javascript';
    script.src = 'https://res.cdn.office.net/teams-js/2.23.0/js/MicrosoftTeams.min.js?t='+Date.now();
    script.onerror = console.error;
    script.onload = () => {
      microsoftTeams.initialize();
    }
    document.head.appendChild(script);
  </script>
  <link rel="stylesheet" href="/style.css" type="text/css" />
</head>

<body>
</body>

If I keep reloading the page with location.reload() I occasionally get microsoftTeams is not defined error, even if I delay the initialization code. The property is just not created, despite scripts being loaded in the sources tab.

Update I found a workaround. The entire issue is probably caused by the teams script relies on some load event, while scripts loaded dynamically like that are not exactly synchronous despite being appended in the head element. Ugly solution is to inject the script using document.write which makes it synchronous and the script loads properly each time. But this is in my opinion something that should be fixed in the library to allow runtime loading, especially that it is not initialized implicitly but requires a call to microsoftTeams.initialize()

document.write('<script src="https://res.cdn.office.net/teams-js/2.23.0/js/MicrosoftTeams.min.js" onload="microsoftTeams.initialize()"><\/script>'); // notice the escape in ending tag, it MUST be there otherwise interpreter throws an error
Dinesh-MSFT commented 3 months ago

Hi @veodko - Thanks for raising the query. We will look into it and let you know the updates.

jadahiya-MSFT commented 3 months ago

Hi @veodko, I'm sorry you've run into this issue and thanks for making this known to us. I'll be looking into this further and wanted to thank you for your very detailed responses. If you have any questions feel free to ask them here and I will do my best to keep you updated on the status of this issue from my side as well.

jadahiya-MSFT commented 3 months ago

Hi @veodko. I looked into this further and was unable to repro this issue. In fact I was able to load the script conditionally by doing the following, please let me know if this helps:

  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <script>
      //Insert condition here, intentionally left out for testing purposes
      const script = document.createElement('script');
      script.src = 'https://res.cdn.office.net/teams-js/2.23.0/js/MicrosoftTeams.min.js';
      script.integrity = 'sha384-08XJvrutoSVYTA8PQeUgetTKn8B7JE73C5BdAfWnHZ5WnSFFQO6g1FGmSqxsrAzb';
      script.crossOrigin = 'anonymous';
      script.onerror = console.error;
      script.defer = 'async';
      script.onload = () => {
        microsoftTeams.initialize();
      };
      document.body.appendChild(script);
    </script>
    <div id="root"></div>
  </body>
veodko commented 3 months ago

Thank you for checking on this @jadahiya-MSFT I've done some more investigation and it seems that this is an issue somehow specific to the MeteorJS framework. If I put your code into a plain HTTP server, it works. If I put it directly into the main.html file of MeteorJS app, it doesn't. Or works only 1 out of 10 reloads depending on how fast it loads.

Any javascript files imported in Meteor is wrapped in closure in which you cannot easily access the global scope.

Weird thing is, Office.js works when loaded like that even after a 10 second delay. But I'll understand if you want to close this issue since it's caused by a 3rd party framework. I just cannot understand what Meteor does that even scripts loaded after framework initialization by plain <script> tags get thrown into some isolated scope.

jadahiya-MSFT commented 3 months ago

Hi @veodko, I've just tested this out and I agree with your assessment that this seems to be an issue more specific with the MeteorJS framework. I will be investigating if there's anything we can do on our side to help out there. If you feel happy with this resolution, feel free to close this issue.