[CRITICAL] Changes to Jira 9 resource bundler are creating race conditions to WRM / AJS

We’ve been working on Jira 9 compatibility for one of our apps and we’ve ran into an issue with the order in which static resources are being loaded.

Jira 9 has moved some of the <script /> tags that are generated by the web resource bundler from the <head /> into the top of the <body /> tag. In addition, it has included included defer attributes to the script includes that are in the <head />.

This is meant as a performance improvement and will probably speed up the page load significantly, where it not for the slight problem that the web resource bundler is having some issues determining which resources can be lazily loaded at the end of page rendering.

So here is the full story of what is going wrong for us.

Our app uses the atlassian-webresource-webpack-plugin to generate the web-resource tags. Which is working great. All the resources are generated correctly, with the right dependencies. However, the atlassian-webresource-webpack-plugin injects the following code into the common runtime:

if (typeof AJS !== "undefined") {
    __webpack_require__.p = AJS.contextPath() + "/s/${uuid}/_/download/resources/${assetWebresource}/";
}

Source code

That means that our common runtime now has a dependency on AJS.contextPath to be available. This is resolved by the code itself by adding a dependency to com.atlassian.plugins.atlassian-plugins-webresource-plugin:context-path. This works perfectly well as the generate web-resource for the common-runtime actually has that dependency.

  <web-resource key="common-runtime">
    <dependency>com.atlassian.plugins.atlassian-plugins-webresource-plugin:context-path</dependency>
    <resource type="download" name="runtime.3bf4b09dd62a9c0f3c05.js" location="static/runtime.3bf4b09dd62a9c0f3c05.js"/>
  </web-resource>

The bundler picks up on this as it loads the scripts in the right order:

As you can see in the screen shot, the code that is responsible for setting AJS.contextPath is loaded as one of the first in the network waterfall, before our common runtime is loaded:

Despite the fact that the bundler is splitting the resources correctly based on the dependencies, we still get an error when loading the page:

Screenshot 2022-08-10 at 16.02.04

The reason for this is that the common runtime that includes the atlassian-webresource-webpack-plugin code is executed before AJS.contextPath is initialised. The <script /> tag that includes the code that initialises AJS.contextPath is in the <head /> and should normally be executed first, however, it is marked with a defer attribute:

The batch that includes our common runtime is now part of the <body /> tag (as an almost direct descendant and not even at the end of the page) and does not have a defer attribute:

As shown here (thank you @tobias.viehweger for the link), the scripts at the top of body without a defer attribute are executed prior to the scripts in the head with defer:

Due to this, a race condition is created that breaks any add-on that uses atlassian-webresource-webpack-plugin and has an assets <web-resource />. In general it creates a race condition for any code that the bundler decides should move to the top of <body /> tag without a defer attribute and that relies on AJS.contextPath (or other AJS / WRM resources for that matter).

I would consider this a critical bug, and happy to file a report for this elsewhere. Looking forward to a solution as this blocks Jira 9 compatibility for our apps.

CC: @TomaszPrus @lvysochyn @AndrzejKotas @RomanKolosovskiy @MikolajRydzewski @GregRowinski @FilipNowak @skorytnicki

16 Likes

Hi @remie ,

I’ve brought this to the attention of the Jira Server team for their follow up.

Regards,
Dugald

2 Likes

Ok, so the change is actually documented in the Jira 9 preparation documentation.

App header updates

Status: IMPLEMENTED (eap 04)

We’ve made changes to the App header, which allow us to load it faster. We’ve updated the way Jira renders the following pages. The changes will affect your plugins if they execute .js scripts on these pages:

  • Issue View: <your Jira URL>/browse/ISSUEKEY-1
  • Project-centric Issue Navigator: <your Jira URL>/projects/MNSTR/issues/ISSUEKEY-1?filter=allopenissues
  • Global Issue Navigator: <your Jira URL>issues/?jql=
  • Dashboard (including system dashboard): <your Jira URL>/secure/Dashboard.jspa?selectPageId=1

All the .js resources on the above-listed pages are now deferred. Additionally, we’ve adjusted the Flush Head Early mechanism for these pages—it sends a partially rendered markup with resource (.css, .js) definitions and an animated app header placeholder before doing heavy work on the back-end.

This means that the issue will only exist on Issue View, Dashboard and Issue Navigation pages. There is also a recommended solution for this.

However, the reason we did not implement this recommended solution is because we do not have any dependency on AJS or other global variables in our own scripts. We unknowingly got that dependency for free from the atlassian-webresource-webpack-plugin.

It is a bit weird that anyone using the atlassian-webresource-webpack-plugin and is loading resources on the Issue View, Dashboard or Issue Navigator pages suddenly needs to add logic to defer their scripts because of an unknown and unwanted dependency on AJS.

So I’ve created [SPFE-909] - Ecosystem Jira for the atlassian-webresource-webpack-plugin to resolve this.

1 Like

As a workaround you could maybe do this.
Place a special hidden div with special class and data-property of the context path.

Then with VanillaJS read out the data attribute on DOM loaded.
I am doing it this way since 3 years and it works good for me.

(1) velocity templace

    <div
        id="my-app-root"
        data-base-url="$req.contextPath"
    ></div>

(2) in the JS file



document.addEventListener('DOMContentLoaded', function() {
  const info = document.getElementById('my-app-root');
  const getNullSafeInfo = (key: string) => {
    if (info) {
      const val = info.getAttribute(key);
      return val ? val : '';
    } else {
      return '';
    }
  };
  const baseUrl = getNullSafeInfo('data-base-url');
  // DO STUFF
});
1 Like

Sounds like a lot of hassle (and custom code to maintain) for getting the contextPath, given that there is the AJS.contextPath, the WRM/context-path and the <meta name="ajs-context-path" content=""> element in the <head /> which are all readily available on the front-end. Was there a specific reason why you chose to do this (an incident, bug, etc)?

Also, unfortunately it will not solve the current issue as the code that is broken is part of the atlassian-webresource-webpack-plugin and out of our control. We do not rely on AJS in any of our own code.

1 Like

Ok, I’ve managed to fix the Jira compatibility issue for our app by using the new com.atlassian.webresource.api.assembler.RequiredResources.requireContext that accepts a com.atlassian.webresource.api.assembler.resource.ResourcePhase as a parameter. Our scripts are now also deferred and are loaded in the correct order. That means that AJS.contextPath is now available when our script is executed.

I still believe this is something that should be fixed in atlassian-webresource-webpack-plugin instead of our app as the requirement is created by a dependency to AJS.contextPath injected in the common runtime.

I am fully independent of AJS and AUI.
I use Atlaskit with React since 3 years and bundle it with react-scripts.
I have written my own script that injects the JS/CSS lines in atlassian-plugin.xml

Why? Because I want to be independent of all those breaking changes that occured multiple times now within the 4-5 years I have been writing my Jira app.

Secondly I inject much more data via data-attributes to the frontend, currently:

    <div
        id="myapp-root"
        data-base-url="$baseurl"
        data-locale="$authcontext.locale"
        data-timezone="$userAndGroupService.remoteUserTimeZone"
        data-plugin-version="$onlineHelpLinkService.pluginVersion"
    >    
        <div id="myapp-root-react"></div>
    </div>

Secondly I need a div with id anyway to inject the React frontend.
That way I am fully independent of all AJS/AUI a.s.o and can pass user timezone, locale, contextPath and appVersion to the frontend via my own stable way that never breaks and was reliable for over 3 years.

Thirdly I have a huge e2e testssuite that has a mocked Jira backend and that runs also smoothly that way. I can simply do “yarn start” in my frontend and do not need a Jira backend to write/test/use my frontend code.

1 Like

On our end, we’ve had to add intermediate fetcher scripts to act as WRM lazy loaders for anything provided by Atlassian.
It isn’t pretty but it works well enough and doesn’t require much maintenance.

The page load isn’t that much faster and you can now see some UI manipulations you didn’t see before.
I felt it was weird to not be able to mark some scripts as deferred while keep some as not, since the front-end of our app depends on JavaScript to function anyways. We know which scripts are lazily required and which ones aren’t.
To me it felt kind of like the breaking changes for the WebAction GET annotations.

1 Like

Thanks @dmorrow, really loved having detailed discussions with the Jira team with regard to this issue they introduced. I especially appreciated their quick response and in-depth knowledge sharing, helping the community navigate the intricacies of their changes and how to work with it from a partner perspective :+1:

2 Likes

Ok, unfortunately the issue is still not resolved. The race condition is resolved when using the com.atlassian.webresource.api.assembler.RequiredResources.requireContext that accepts a com.atlassian.webresource.api.assembler.resource.ResourcePhase as a parameter.

However, this only works on the Issue View. It seems like the Issue Navigator does not pick up on resource requests from issue panel context providers / velocity templates, meaning that the code is never included. This worked in Jira 8 and is now broken in Jira 9.

1 Like

Hi @remie !
I’m a Jira developer and I’m here to help you solve the issue as fast as possible :slight_smile:

Please let me rephrase what I’ve understood so far:

  1. There is a problem with using resources generated with atlassian-webresource-webpack-plugin as they are loaded in the REQUIRE resource phase (immediately) and rely on the AJS object that is initialized in the DEFER phase. You reported an issue for this - [SPFE-909] - Ecosystem Jira (thank you!). As you said - you figured out the workaround (using com.atlassian.webresource.api.assembler.RequiredResources.requireContext with DEFER phase) - so you’re good here for now.
  2. The remainig problem is that including some resources does not work when in Issue Navigator.
    Am I correct?

My asks regarding problem 2:
Does this fail silently or do you see some errors in the browser’s console?
Could you provide me some snippets of the way you require the resources that won’t work in the Issue Navigator, please? It will allow me to reproduce the problem quicker and hopefully help you sooner :slight_smile:

Also: there are some additional mechanisms we introduced for working with the different resource phases that you might find useful:

  1. com.atlassian.jira.web.RequestAssetPhaseStore that allows you to get to know what is the resource phase for the current request on the backend (you can pass it as an argument to requireContext),
  2. We’ve also implemented resource phase checkpoints (Preparing for Jira 9.0 | Atlassian Support | Atlassian Documentation) that allow you to execute frontend code when specific resource phases happen.

I’m looking forward to hearing from you!
Cheers, Michał

@MichalCiesielski thanks for reaching out!

It fails silently, in the sense that the scripts are simply not included by the bundler. The <script /> tags that should include these resources just never show up in the generated HTML output.

I already started on creating an example repo in Github with this issue. I will hopefully be able to finish that tomorrow and share it with you!

Cheers,

Remie

1 Like