How to improve page load by managing Web Resource Manager and Custom App Dependencies

In Jira, Web Resource Manager (WRM) handles the preparation of necessary JavaScript and CSS resources for particular pages to ensure apps function correctly. When creating an app, you define its dependencies in <web-resource> and set the context where your app will run in Jira. According to this definition, WRM creates batch.js and batch.css files by collating all required dependencies for all apps that are running in that particular context.

Depending on the context registry and how the dependencies are defined, the batch files may end up with very large file sizes. Simply downloading and loading the batch file might take seconds, which results in very low page load performance.

There are some steps you can follow to improve page load performance:

Code splitting

Using Atlassian Web-Resource webpack Plugin in your app allows you to easily reuse libraries provided by Jira. This plugin generates <web-resource> definitions for Webpack code chunks, helping to reduce batch file sizes.

Deferring script loading

Not all resources in apps need to be part of the render-blocking version of the batch script. In many cases, apps can load after the critical resources for the page have been loaded lazily. This improves the performance and perceived loading speed for the users.

WRM supports loading phases for loading web resources starting from version 9.0. These phases are listed below.

Execution order Resource phase Description
1 Sync phase Code is put inline and runs immediately when the browser parses the page. A product uses this phase to inject any bootstrap code.
2 Require phase Code loads and runs immediately when the browser parses the <script> or <link> tag.
3 Defer phase Code loads asynchronously and runs in order when the page has finished parsing.
4 Interaction phase Code loads and runs after the require and defer phase scripts as well as DomContentLoaded (DCL) handlers have been completed.

To use loading phases in your apps, complete the following updates (if you haven’t already):

  • Use WRM 5 API or a newer version (the latest version is 8 at the time this document was created) to defer the scripts your plugin requires on the pages with deferred .js resources.
  • Use inline scripts (no src attribute) with type="module" attribute.
  • Postpone the execution of the script until DOMContentLoaded or later event occurs.
  • It’s even more effective to use the interaction phase when the logic can be initialized after the DOMContentLoaded event.

For a general phased loading approach, only load the application init scripts in the require phase. Then, prefer async loading of your critical resources in defer phase. Put all remaining code to interaction page to lazy load them until they’re requested with the user’s interaction.

To execute .js resources on pages with deferred scripts, use methods com.atlassian.webresource.api.assembler.RequiredResources that accept com.atlassian.webresource.api.assembler.resource.ResourcePhase.

To get the ResourcePhase that’s currently used on the page, use com.atlassian.jira.web.RequestAssetPhaseStore so that the proper default resource phase will be used for a given page.

For these APIs usage examples, check the com.atlassian.jira.web.action.Dashboard#doExecute.

On the client-side, you can use checkpoints to execute your front-end code in the proper resource phrase:

  • DEFER phase resources are available. To execute logic once defer checkpoint is hit, use resourcePhaseCheckpoint.defer.then(deferPhaseCallback);.
  • During the INTERACTION resource phase. To execute logic once interaction checkpoint is hit, use: resourcePhaseCheckpoint.interaction.then(interactionPhaseCallback);.

The checkpoint promises are provided by Jira pages by default. However, if your newly created page doesn’t have them, you can add these checkpoints as follows:

  • requireWebResource(ResourcePhase.INLINE, "jira.webresources:resource-phase-checkpoint-init");. This should be added at the top of the page so that it starts executing as soon as possible.
  • requireWebResource(ResourcePhase.DEFER, "jira.webresources:resource-phase-checkpoint-hit");. This should be added at the bottom of the page so that it starts executing as late as possible.

This phased usage will also reduce the initial batch file sizes since majority of your code will go to the defer or async loading stage. As a result, the render-blocking execution time may become negligible..

Compressing files

With the changes mentioned above, the batch file sizes can already be reduced. Currently, by default, the batch files we analyzed from various instances are served with Gzip compression, reducing their size with a range from 2:1 to 7:1 . Reducing the size will improve both download and script execution times. Furthermore, we can explore Brotli compression, which may further reduce file sizes by an additional 10 to 20% with respect to Gzip.

More about compression and Brotli

Further optimisation for custom apps

Instead of using web resource context, the custom apps can require a web resource key and run their code through defined web panels.

In this scenario, the steps could include creating a soy file, registering the template file in the appropriate panel location, and calling the needed webResourceManager_requireResource inside this soy file.

Some of the panels you can use are:

  • atl.header that adds code at the beginning of the header
  • atl.header.after.scripts that adds code Jira/WRM-created scripts, at the end of the header
  • atl.footer that adds code to the footer

If all the above optimization actions aren’t enough, try serving essential resources for custom apps from a CDN as an alternative approach.

You can achieve this programmatically by using downloadable plugin resources. Instead of adding your resources to batch.js and increasing the batch file, you can serve those static resources separately.

<!-- Resources may contain arbitrary key/value pairs -->
<resource type="download" name="custom.app.entry.js" location="com/custom-app/app/entry.js">
   <property key="content-type" value="application/js"/>
</resource>

By this, some common files shared across custom apps can be shared locally or from a CDN.

Additionally, if you wish to add javascript to every page, you can add your script tag code with proper CDN URL to following files

  • stylesheettag.jsp
  • <atlassian-jira>/includes/decorators/header.jsp
  • <atlassian-jira>/atlassian-jira/includes/decorators/aui-layout/header.jsp
<script src="<path-to-cdn-file>" defer />

This makes your JS and CSS resources available for every page and reduces the need for re-downloading similar resources from different batch files on different pages. Please make sure to use defer or async when using CDN for common files. This will prevent render-blocking script executions.

9 Likes

Hi Bilal,

Can you please clarify whether this is mandatory, or how plugins will be impacted if they do not implement your recommendations? Also, what is the timeline?

Thank you

1 Like

Thanks for the guide, Bilal!

Regarding the atlassian-web-resource-manager plugin and code splitting, are there any plans to support more modern build tools besides Webpack, such as Vite (https://v3.vitejs.dev/)?

Thanks again!

3 Likes

Thanks for the guide. I just created a internal task to check it for one of our next versions.

2 Likes

@aragot , those are not mandatory. We wanted to share this guide for improving the performance.

Since many apps are using WebResourceManager, unoptimised resource usage like putting all resources to require stage, using general context rather than the needed context, dramatically increases batch files.

In the recent months, I’ve analysed different Jira instances and found out due to above mentioned behaviour, those instances had 70 MB, 100 MB, 50 MB like batch file sizes (when not-gzipped). Just downloading and executing these large files takes a couple seconds to 10-15 seconds of page load time. Which gives unpleasant experience for the users.

From a single app perspective, we might consider, “our app resource JS code is just 2 MB and when gzipped it is just 100KB”, so it will not effect the performance. However when we load 10 marketplace or custom app in the same context around similar size, the batch file size becomes at least 20 MB and even more like I gave as an example above.

1 Like

@julien , it may need additional architectural changes of how WRM works, but it might be a thing to consider and explore possibilities.

Also from how you serve your code we don’t have limitation. I haven’t tested though, but after adding your main chunk to defer phase that is code-splitted with Vite or other bundlers, and define the web-resource for your app, it should work and load the split-chunks as expected.

1 Like

Hi Bilal,

Then, thanks for the guide!

If you really want to help us improve:

  • Publish a guide explaining what is the correct way to do TS in Jira,
  • Ideas: how to inject TS on a Jira page, how to hook to events (using the proper AMD thing?), how to write TS for a custom field, how to write a full-page, how to use React in Jira,
  • All previous guides relied on private repos at Atlassian, so they were unusable (like the server-fe repository, if I remember), and there is no proper source of truth today.

The ecosystem doesn’t know anymore what’s recommended, so any single sentence of advice you provide in this direction is leaps ahead in terms of vendor practices. I’m not surprised you say Jira pages take 15s to load with 100MB of JS, given the lack of coordination in writing good javascript. Today, a metaphor would be that every vendor is shipping their own version of React, because that’s the only stable way to deliver features to our customer.

Again: Thank you + any additional sentence of advice is leaps ahead + any tutorial / a demo repository that works would be a revolution in the area of plugins.

Best regards,
Adrien

1 Like

Thanks for your reply Bilal,

My understanding is that this is the main purpose of the atlassian-web-resource-manager Webpack plugin: to translate Webpack chunks into WRM declarations+search & replace import statements, so that WRM can locate and load the chunks. (so for Vite, I would expect something similar, a plugin capable of understand Vite resource tree, generate definitions and rewrite import with WRM, which should not require any change to WRM itself.)

Now, are you saying that by declaring a web-resource only for the main entry point, WRM will be able to resolve all other asynchronously loaded chunks automatically, without requiring additional configuration in the Jira plugin manifest?

Manually maintaining a complete list of web-resource entries for every split chunk is not a realistic approach.

1 Like

Yes manually maintaining the list of web-resources are not ideal. A vite plugin that works as you explained should handle all the heavy lifting. We will explore it if we can provide one.

Regarding to having main entry point in web-resource, in theory, if you define chunk require paths as absolute path, you should be able to load them asynchronously. Like I mentioned above, I haven’t tested it.

1 Like