How does the service worker hot reload mechanism work?

Hello, hello... Very impressed with my initial look at Plasmo. Some lovely ideas and implementations in the framework 👏 I have my own internal framework that does many of the things Plasmo does including live reload on all scripts, but I'm blocked in migrating to manifest v3 as I don't know what the mechanics would be to reload a service worker. Unfortunately a wholesale migration to Plasmo right now is not on the cards, but @louis , I wonder if you could give me a high-level overview of how you guys are handling service worker reload? @stefan was kind enough to confirm the magic is happening in this file, so perhaps if you could explain what's going on I could apply that to my own setup: https://github.com/PlasmoHQ/plasmo/blob/main/packages/parcel-runtime/src/runtimes/background-service-runtime.ts In the meantime I'm going to migrate a simpler extension to Plasmo so I can begin to get to grips with what looks like a great framework. Thanks! Dave
GitHub
plasmo/background-service-runtime.ts at main · PlasmoHQ/plasmo
🧩 The Browser Extension Framework. Contribute to PlasmoHQ/plasmo development by creating an account on GitHub.
25 Replies
YAGPDB.xyz
YAGPDB.xyz2y ago
Gave +1 Rep to @louis
lab
lab2y ago
Howdy @davestewart - reloading bgsw is pretty much the same in mv2 afaik using runtime.reload Would love to exchange notes on how we can improve the framework further :)
davestewart
davestewartOP2y ago
Ah, that makes sense now why the context invalidated error keeps popping up. I'm doing it differently. I set up a webpack plugin which triggers only the background script to reload using location.reload() when background.js is rewritten by the build. This means any content processes don't get reloaded if you only need to modify the background script, and can just continue passing messages without losing state. I was hoping that there would be a way to either reload the background service worker (I see there is an update() method on registered workers) or somehow have the files it references update, but I don't have any experience with workers or how manifest v3 references workers, so I'm starting from scratch in my investigations. Also, yes, happy to exchange notes!
lab
lab2y ago
I'm doing it differently. I set up a webpack plugin which triggers only the background script to reload using location.reload() when background.js is rewritten by the build. This means any content processes don't get reloaded if you only need to modify the background script, and can just continue passing messages without losing state.
In MV3, since the bgsw is not web document runtime (we call it page runtime in our framework), but service worker - it's inherently stateless. Keeping state is a bit futile unless there's an external store to the bgsw to rehydrate, unless you want to do some sort of eval but afaik that's very tough in mv3 If you think of bgsw as a stateless edge function, that'll help guideline what type of thing you can realistic do with it
davestewart
davestewartOP2y ago
Ah, you misunderstand me! I meant only the background is reloaded; so any content scripts hosts or other extension windows do not need to be reloaded. chrome.runtime.reload() is a bit of a sledgehammer to crack a nut
Arcane
Arcane2y ago
@davestewart has reached level 1. GG!
lab
lab2y ago
Ooh, this is not possible ATM due to the context of the still-mounted CS/Extension windows still tied to the previous bgsw runtime, but that's due to change in a future version of the webext API They are introducing a new function to bind context, hopefully a way to also watch for context invalidate/destroyed as well. It's still in proposal stage afaik: Merged proposal so might be implemented soon: https://github.com/w3c/webextensions/pull/358
Also, yes, happy to exchange notes!
@davestewart what time works for you today? Wanna hop on a call?
davestewart
davestewartOP2y ago
Yeah, why not! Appreciated. I can do any time at all 🙂
lab
lab2y ago
I'm in #co-working It's the voice channel
davestewart
davestewartOP2y ago
kk Ah, can't see that
lab
lab2y ago
https://discord.gg/YVcqkGuw It's in the /Info
davestewart
davestewartOP2y ago
OK, have been researching this a bit more, and it seems that it is going to be impossible to get some kind of reload working in a service worker. I was hoping that eval might be a possibility, but I can't get unsafe-eval to be accepted in the CSP. So here's my next idea; thought I would just sanity check it... Rather than putting all the logic in the background process, an extension in development would open a physical browser tab which did contain the logic, and the existing background service would proxy commands to that tab:
content <--> background <--> reloadable_tab
content <--> background <--> reloadable_tab
This tab could then hook into HMR or the same setup as I described before, and reload itself when the source files changed:
reloadable_tab
^
|
v
dev_server
reloadable_tab
^
|
v
dev_server
Granted, it seems like a lot of work, but it would allow content scripts to remain loaded when the "background" logic updated. Another option (in my case) would be in development, to load the background logic directly in the content scripts as some kind of service, and redirect calls to the background to the local service, directly in the script. This would mean HMR would "just work" and the background service worker could effectively be ignored. Again, I haven't thought properly about extension architecture for around 18 months, so I might be way off. Thoughts @louis
lab
lab2y ago
Rather than putting all the logic in the background process, an extension in development would open a physical browser tab which did contain the logic, and the existing background service would proxy commands to that tab:
Can you elaborate a bit more on how this tab would be spawned and what runtime would it contains? CS are internally spawned by a chrome internal sw runtime at creation that basically hook themselves to that bgsw context. One experiment I've done is that even if you manage to deliver an HMR payload, how would you apply it to the CS because eval/script injection won't work all the time due to restriction of the target page's CSP. Another behavior I've found is that a location.reload will not suffice to update content-script if it's bundled in the extension. It appears the extension must reload the full bundle (via runtime.reload) for the new code to take effect :d ....
Another option (in my case) would be in development, to load the background logic directly in the content scripts as some kind of service, and redirect calls to the background to the local service, directly in the script. This would mean HMR would "just work" and the background service worker could effectively be ignored.
This is what we do, but need to coordinate with bgsw to actually reload the bundle, otherwise the current context will only be awared of the static bundle loaded initially BTW, when you mention content, we're talking about content script right? Or you mean a "content included" page such as popup/tab? @davestewart
davestewart
davestewartOP2y ago
Ah, sorry - my fault for being ambiguous. Content tab vs content script:
chrome-extension://xxx/some-tab.html
chrome-extension://xxx/some-tab.html
This tab will have all (most of?) the functionality of background, plus a tab.id. Thus, should be able to complete the proxying as I mentioned before, calling any of the permitted chrome.* APIs that you would normally expect to use background for, via messaging.
lab
lab2y ago
Yeah that'd work, you can potentially do more stuff with a sandbox page: https://docs.plasmo.com/framework/sandbox-pages
Plasmo Docs
Sandbox Pages – Plasmo
Using sandbox pages with Plasmo to create secure context pages for browser extensions.
lab
lab2y ago
content <--> background <--> reloadable_tab ^ so in the above, you have a tab that spawned by the bgsw to do some eval and send the HMR to the "reloadable tab" which is an extension page? In extension page runtime, it's possible to do HMR - we have this for react runtime
davestewart
davestewartOP2y ago
Whether it's HMR or just reloading the tab, it should still result in new compiled code being loaded in and running, which is what you need in development. Regards the sandbox, actually I don't know if this would be strictly necessary, but thanks for the tip! I'l read up more about that. This actually gives me hope that I'll be able to build a DX friendly workflow in MV3!
lab
lab2y ago
Yeah in page runtime the way we have it is basically eval but with script injection xD, and plasmo basically swap the csp for dev mode accordingly for that @davestewart Do you know what are the components needed for vue3 HMR? Looking around and not finding much, they just add straight into bundler lol xD
davestewart
davestewartOP2y ago
Vue 3 HMR is basically Vite HMR AFAIK. But I suspect you're asking in terms of how to target content injected into the shadow DOM?
Arcane
Arcane2y ago
@davestewart has reached level 2. GG!
lab
lab2y ago
lol it seems very bundled together yeah xD... And no I'm looking for vue3 HMR plain implementation. Would be nice to incorporate vue3 HMR, either using vite as a middleware, or just extract its plugin/HMR lifecycle and make a vue runtime :-?
davestewart
davestewartOP2y ago
GitHub
GitHub - vitejs/vite-plugin-vue: Vite Vue Plugins
Vite Vue Plugins. Contribute to vitejs/vite-plugin-vue development by creating an account on GitHub.
lab
lab2y ago
I can see the transformer wrapping the vue code inside some standard hmr callback but then there's the whole vue transformation in the middle to separate out template/style etc...
lab
lab2y ago
Hmmm... seems like Plasmo can easily add HMR to vue: https://parceljs.org/languages/vue/ :-?...
Vue
Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web. Parcel supports Vue automatically using the @parcel/transformer-vue plugin. When a .vue file is detected, it will be installed into your project automatically.
davestewart
davestewartOP2y ago
Interesting!
Want results from more Discord servers?
Add your server