Hi Rory,
My extension works across Chrome, Edge, Firefox & Safari and is compliant with both MV2 and MV3 manifests.
My solution was to have two template manifest files, a v2 one and v3 one. My webpack powered build process then output's a 'dist.mv2' and 'dist.mv3' folder which I then pack for each browser.
The only difference I found was to rename the service worker to background page and make the background page persistent and also to change the popup action type.
I'm hoping that I'll also be compliant with iOS just by removing the persistent background flag.
The key thing was to make sure that any and all access to state was serialised to and from local storage and I also keep an in memory reference to the current state so that if it's valid (ie not undefined) then that is returned instead which keeps state access super fast for background pages and compliant with the fact that the page might not exist as a service worker.
Give me a shout if you need any further help.
Jon...