JavaScript Web Workers

Hi everyone!
I am developing a plugin which uses a javascript files.
I am trying to use javascript web workers to make my page responsive ( I need to perform time-consuming operations and I don’t want to freeze the whole page). I’ve already made some plugins with JS but I never had to use Web Workers
Unfortunately to create a Web Worker in js I need to pass a JS file name to its constructor. But when I try to do this I am getting error that file is not found (error 404). I am including my js files in xml in web resources:

<resource type="download" name="projectCalculations.js" location="/js/projectCalculations.js"/>

and I am trying to create Worker:

var worker = new Worker('projectCalculations.js')

Is it possible to do this in this way? I’ve heard that Jira bundles all javascript files together into one file an it can cause the problem. Is it possible to avoid this bundling?
Or is there someone who uses web workers in JS files in Jira and could help?

Hi @jiraWarrior, this might be a bit hard to accomplish with the Web-Resource Manager (WRM), but I think it should be doable.

1. Pure JS code

The first option is to use only JavaScript and request the resource using WRM REST API:

/**
 * Creates a new Worker for the web-resource key
 * @param {string} resourceKey - The full web-resource keym e.g. "<<plugin.key>>:<<web-resource-key>>"
 * @returns {Worker} - WebWorker instance
 */
async function initWebWorker(resourceKey) {
    // First, we need to request for the URL for the resource from WRM
    const response = await fetch(WRM.contextPath() + '/rest/webResources/1.0/resources', {
        body: JSON.stringify({
            r: [resourceKey],
            c: [],
            xr: [],
            xc: [],
        }),
        headers: {
            'content-type': 'application/json'
        },
        method: 'POST',
    });
    const { resources } = await response.json();

   // The list might contain more than a one resource so let's find the resource we need
   const resource = resources.find(({key}) => key === resourceKey);
   if (!resource) {
        throw new Error(`Can't find a "${resourceKey}" resource in the list of received resources`);
   }

    return new Worker(WRM.contextPath() + resource.url);
}

Usage example:

const worker = await initWebWorker("com.my.plugin.name:my-worker-resource");
worker.postMessage('hello');

In the example code, I’m using async/await syntax, but you can also use the JavaScript Promises API if you prefer.

2. Servlet

The second option would be to create a custom Servlet filter that redirects to a JS file in web-resource definition e.g.:

// MyServlet.java
public class WebWorkerForwardServlet extends HttpServlet {
    @ComponentImport
    private final WebResourceUrlProvider webResourceUrlProvider;

    public ServiceWorkerForwardServlet(WebResourceUrlProvider webResourceUrlProvider) {
        this.webResourceUrlProvider = webResourceUrlProvider;
    }

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String resourceUrl = webResourceUrlProvider.getResourceUrl("com.my.plugin.name:my-worker-resource", "js/my-worker-file.js"); // The 2nd param should equal the "location" value of the `resource` definition.
        request.getRequestDispatcher(resourceUrl).forward(request, response);
    }
}
// atlassian-plugin.xml
   <servlet name="My WebWorker" key="my-web-worker-servlet" class="com.my.plugin.name.WebWorkerForwardServlet">
        <url-pattern>/my-web-worker</url-pattern> <!-- This is the URL we will use to request the JS file -->
    </servlet>

Once you have a servlet with a known URL, then you can use this URL as a param for the Worker in JS file:

// We need to use the URL from the above definition like `/plugins/servlet/<<servlet-url>>`
const worker = new Worker(WRM.contextPath() + '/plugins/servlet/my-web-worker');
worker.postMessage('hello');

It’s a bit complex, but I hope that helps.

Thanks,
Maciej Adamczak

4 Likes