Skip to content

[web] new service worker loading mechanism #75535

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 11, 2021

Conversation

yjbanov
Copy link
Contributor

@yjbanov yjbanov commented Feb 5, 2021

  • Remove ?revision=* from CORE resources.
  • Delay script until the correct version of the service loader is activated.
  • Add integration tests for service workers.

Fixes #74831.

According to my testing this also fixes the issue with having to double-reload the page to get the new app version.

@flutter-dashboard flutter-dashboard bot added c: contributor-productivity Team-specific productivity, code health, technical debt. tool Affects the "flutter" command-line tool. See also t: labels. labels Feb 5, 2021
@google-cla google-cla bot added the cla: yes label Feb 5, 2021
@azbuky
Copy link

azbuky commented Feb 5, 2021

Great to see the service worker is getting some love.

Quick question, how is the service worker tested on a local http server? I thought they require https to work.

@yjbanov yjbanov changed the title Remove revision from CORE resources; delay script until serviceWorker.ready; add test [web] new service worker loading mechanism Feb 8, 2021
@yjbanov yjbanov force-pushed the pwa-page-load branch 2 times, most recently from 82f544b to 2531654 Compare February 8, 2021 17:47
Copy link
Contributor

@jonahwilliams jonahwilliams left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like this doesn't quite work in debug mode, but could be tweaked to always fallback if the revisions are null


// If service worker doesn't succeed in a reasonable amount of time,
// fallback to plaint <script> tag.
setTimeout(() => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is always going to fail to load when you flutter run.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch! Done.

<link rel="manifest" href="manifest.json">
</head>
<body>
<!-- This script installs service_worker.js to provide PWA functionality to
application. For more information, see:
https://developers.google.com/web/fundamentals/primers/service-workers -->
<script>
var serviceWorkerVersion = null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably need to check for null since this won't be set during flutter run.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested various flutter run modes. This seems to be working fine with ?v=null. I'm open to stylistic improvements, but AFAICT it's not causing trouble.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the serviceWorkerVersion even required? Isn't the browser automatically checking the service worker file for byte differences?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could experiment with alternative approaches, but right now I'm using the version number to check that the currently active service worker is the one we want. The version is encoded in the scriptURL that I can read back from reg.active. If the versions don't match I force update. This happens when you pull down a new index.html but the worker is still from the old version of the app.

@yjbanov
Copy link
Contributor Author

yjbanov commented Feb 8, 2021

Quick question, how is the service worker tested on a local http server? I thought they require https to work.

https and localhost are both supported.

@jonahwilliams
Copy link
Contributor

chrome (and possibly edge) both support service workers over localhost for local development.

@azbuky
Copy link

azbuky commented Feb 8, 2021

https and localhost are both supported.

Thanks. Makes total sense for localhost to be supported. I somehow always thought that flutter run generates an empty service worker, even with --release, because there was no https support on localhost.

Anyway, one thing to note is that when the service worker is registered and it activates the first time, it will not automatically claim the client window that registered it so all following requests made by the app from main.dart.js will not be cached on that first page load.

@yjbanov yjbanov marked this pull request as ready for review February 8, 2021 21:26
Copy link
Contributor

@jonahwilliams jonahwilliams left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One of my concerns with shipping this change in the template is that users need to regen to get it - meaning we lose the opportunity to fix bugs that we identify in the future.

Unless we very pro-actively communicate, users might get stuck on old versions/

We do have a pattern for an automatic Migrator that can run over projects, but that is really only intended for simple changes to config files. A user may make small changes to the sw loading that prevent it from running.

@azbuky
Copy link

azbuky commented Feb 9, 2021

Hey guys, I would like to add my two cents, hope I am not overstepping. I have a Flutter web app in production for some time now and I do regular updates. It has been a great experience working with the platform but the service worker has always been kind of a pain to maintain.

I use a slightly different version than the one generated by Flutter and also use a similar approach as this PR where I modified index.html to activate the service worker and then load main.dart.js. It has worked well for my use case as I don't really care much of initial load time and I use a html splash screen. But this method adds some time to the initial page load and it seems to over complicate index.html.

The main issue users seem to face is that they do not get the latest version when they do an update because main.dart.js gets served before the service worker gets a change to update and activate.

As a workaround I was thinking to use a small javascript script above the main.dart.js script tag, that would request a small file (like version.json) with a random hash query like the service worker currently has. If the service worker is activated, when the request is made, it would compare this hash value with the one on record and if they do not match it should re-validate all future requests including main.dart.js that would be next.

Would this approach work, or am I missing something?

@jonahwilliams
Copy link
Contributor

@azbuky you're definitely not overstepping, getting feedback from Flutter users is critical for us to improve the SDK

Copy link
Contributor

@jonahwilliams jonahwilliams left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Copy link
Contributor

@harryterkelsen harryterkelsen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@azbuky
Copy link

azbuky commented Feb 11, 2021

From what I have tested this works for me and fixes all major issues I was having with deploying updates.

There is one edge case thought that I think will cause assets to not update correctly. I will use galley.flutter.dev as an example.

The server seems to be sending a response header cache-control: max-age=3600 for all requested files. I think this is the default for Firebase hosting. If you deploy an update and reload the page before the cache expires, the service worker will load the CORE files correctly, it will clear its cache of all resources that have changed based on the hashes, but when it will request the new resources, it will hit the browser's cache and still get the old version that will be added to the service worker's cache and served to the app.

@yjbanov
Copy link
Contributor Author

yjbanov commented Feb 11, 2021

@azbuky Thanks for kicking the tires of this new approach! Since this improves the situation for CORE resources, I'm going to go ahead and merge it. I will look into other resources as a follow-up task. In addition to resources, another thing that's still not solved is that we're loading CanvasKit from unpkg, which cannot be cached by the service worker. We should have a mode that bundles CanvasKit such that it can be cached.

@yjbanov yjbanov merged commit e7953b3 into flutter:master Feb 11, 2021
@peerwaya
Copy link

Hey guys, I would like to add my two cents, hope I am not overstepping. I have a Flutter web app in production for some time now and I do regular updates. It has been a great experience working with the platform but the service worker has always been kind of a pain to maintain.

I use a slightly different version than the one generated by Flutter and also use a similar approach as this PR where I modified index.html to activate the service worker and then load main.dart.js. It has worked well for my use case as I don't really care much of initial load time and I use a html splash screen. But this method adds some time to the initial page load and it seems to over complicate index.html.

The main issue users seem to face is that they do not get the latest version when they do an update because main.dart.js gets served before the service worker gets a change to update and activate.

As a workaround I was thinking to use a small javascript script above the main.dart.js script tag, that would request a small file (like version.json) with a random hash query like the service worker currently has. If the service worker is activated, when the request is made, it would compare this hash value with the one on record and if they do not match it should re-validate all future requests including main.dart.js that would be next.

Would this approach work, or am I missing something?

@azbuky great tips here, how did you go about using a html splash screen with flutter? I really need this as canvaskit takes quite some time to load. Thanks

@azbuky
Copy link

azbuky commented Feb 11, 2021

@yjbanov
Thanks for fixing this.

@peerwaya
I just added a logo image and some text in index.html.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
c: contributor-productivity Team-specific productivity, code health, technical debt. tool Affects the "flutter" command-line tool. See also t: labels.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[web] main.dart.js is downloaded and cached twice
5 participants