1

GWT compiler produces code fragments as a result of code splitting (see details here)

The initial download file is named as {alphanumeric}.cache.js (e.g 1805B2053A824F148DB6D05B2186F955.cache.js) and other fragments {number}.cache.js (e.g. 1.cache.js) in the figure below.

GWT Code fragments

I'm trying to develop a GWT app with Service Worker as I want the app to work offline. Once cached and offline, the GET request for initial download file triggers FETCH event in ServiceWorker but individual code fragments won't trigger the event. For some reason they seem to bypass the Service Worker.

For demonstration, check the app at https://gwt-pwa-demo.herokuapp.com/pwademo.html. It is a GWT app which uses Service Worker. It's not my app, I'm just using it for demonstration.

When you load the app, all the files will get cached. No go offline from chrome dev tools, and refresh the app. The app still shows and you will notice that initial download file is loaded from Service Worker. Now click on, Paris/Berlin/London, which will load fragments 1.cache.js and so on. Notice that these are coming from disk cache and not Cache Storage through ServiceWorker. If you look at console output, you don't see a log for these fragments which is printed from ServiceWorker Fetch event.

To clarify more, the app works offline only because the fragments are pulled from disk cache. If these fragment are not present in disk cache, the app won't work. Even if the fragments are present in Cache Storage, since ServiceWorker can't intercept these requests, they won't get loaded. Follow these steps to see this in action.

  1. Assuming you have loaded the app once, open dev tools -> Application -> Clear Storage and clear site data. Make sure that cache in Cache Storage is also cleared.
  2. Now load the app again https://gwt-pwa-demo.herokuapp.com/pwademo.html.
  3. The cache will be created again and you will see the fragments in the cache.
  4. Reload the app using "Empty Cache & Hard Reload" in Chrome while your Dev Tools are open.
  5. This will clear disk cache but the Cache Storage is still there.
  6. Go offline, and refresh the app and the initial app still loads from Cache Storage.
  7. But when you click on, Paris or London, which triggers loading of code fragments, it fails.

enter image description here

1
  • It's possible in GWT, but you need a custom Linker. Perhaps i have time over weekend to but a sample code on Github. Commented Nov 16, 2017 at 11:18

2 Answers 2

3

You have to load the fragments with xhr requests.

1) First you have to extend CrossSiteFrameLinker

/**
 * Created by Gernot Pansy <[email protected]> on 07.08.17.
 */
@LinkerOrder(LinkerOrder.Order.PRIMARY)
@Shardable
public class ProgressiveWebAppLinker extends CrossSiteIframeLinker {

    @Override
    public String getDescription() {
        return "Progressive-Web-App";
    }

    @Override
    protected String wrapDeferredFragment(final TreeLogger logger, final LinkerContext context, final int fragment,
                                        final String js, final ArtifactSet artifacts) {
        return js;
    }

    @Override
    protected String getJsInstallLocation(final LinkerContext context) {
        return "com/google/gwt/core/ext/linker/impl/installLocationMainWindow.js";
    }

    @Override
    protected String wrapPrimaryFragment(final TreeLogger logger, final LinkerContext context, final String script,
                                        final ArtifactSet artifacts, final CompilationResult result)
            throws UnableToCompleteException {
        return script;
    }

    @Override
    protected String getJsInstallScript(final LinkerContext context) {
        return "xxxxxxx/progressiveWebAppInstallScript.js";
    }

    @Override
    protected String getJsIsBodyLoaded(final LinkerContext context) {
        return "";
    }

    @Override
    protected String getJsWaitForBodyLoaded(final LinkerContext context) {
        return "";
    }

    @Override
    protected String getScriptChunkSeparator(TreeLogger logger, LinkerContext context) {
        return "";
    }

    @Override
    protected boolean shouldInstallCode(final LinkerContext context) {
        return true;
    }
}

2) You need a modified javascript install script (progressiveWebAppInstallScript.js):

function installScript(filename) {

    function installCode(code) {
        function removeScript(body, element) {
        // Unless we're in pretty mode, remove the tags to shrink the DOM a little.
        // It should have installed its code immediately after being added.

        __START_OBFUSCATED_ONLY__
        body.removeChild(element);
        __END_OBFUSCATED_ONLY__
        }

        var doc = getInstallLocationDoc();

        var script = doc.createElement('script');
        script.language='javascript';
        script.text = code;
        doc.body.appendChild(script);
        removeScript(doc.body, script);
    }

    sendStats('moduleStartup', 'moduleRequested');
    var xhr = new $wnd.XMLHttpRequest()
    xhr.open("GET", filename);
    xhr.onreadystatechange = function() {
        if (xhr.readyState == 4) {
        // Clearing onreadystatechange otherwise it may cause memory leak (e.g. in IE8).
        xhr.onreadystatechange = function() {}; // Clear callback
        if (xhr.status == 200) {
            installCode(xhr.responseText);
        } else if (__MODULE_FUNC__.__errFn) {
            __MODULE_FUNC__.__errFn('__MODULE_FUNC__', new Error("Failed to load " + code));
        }
        }
    };
    xhr.send(null);
}
Sign up to request clarification or add additional context in comments.

Comments

1

OK, I guess I found the chromium bug report @ https://bugs.chromium.org/p/chromium/issues/detail?id=439697 and related ServiceWorker spec issue @ https://github.com/w3c/ServiceWorker/issues/612.

GWT loads code fragments by creating iframes internally. These iframes are not controlled by parents Service Worker. The chrome bug is still open but w3c closed the spec issue @ https://github.com/w3c/ServiceWorker/issues/1163.

2 Comments

I will wait for community to verify my understanding before accepting my own answer for few days.
I recompiled the example using "PRETTY" style to see what is going on. it seems like this a known behavior of iframes since their are meant to represent an independent context inside the dom. I guess they need their own service worker, here is a related answer: stackoverflow.com/questions/43932169/…

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.