QwikDev / qwik

Instant-loading web apps, without effort
https://qwik.dev
MIT License
20.52k stars 1.27k forks source link

[✨] add serviceWorker$ #6346

Open serbyxp opened 2 months ago

serbyxp commented 2 months ago

Is your feature request related to a problem?

No

Describe the solution you'd like

A service-worker hook. For example serviceWorker$(…)

A hook that can be used anywhere in the project similar to worker$ or server$, but for the current service-worker ( qwikcity). The hook / wrapper function would act as a means for the optimizer to bundle the functions in it, to be ran on the service worker.

export const mySWFunc = serviceWorker$(()=> / some function/s that will be bundled with the service worker. And executed on the service-worker when called, could be chunked and have their own QRL to be fetched later on when they are needed (same as normal $ but execute on the service-worker) /)

Describe alternatives you've considered

Currently adding functions to the service-worker.ts file.

Additional context

Currently there are no hooks to the optimizer / bundler. Adding a listener directly onto service-worker.ts does not take into account the current (built into setupServiceWorker() ) listener which could cause conflicts in the order of execution.

The service-worker does all the fetching for the chunks, acting as a proxy, if something is already cached in the service-worker , there is no entry point or “interception” point to modify the request between the main thread and the service-worker. Nor is there a mechanism to bundle code that should be executed on the service-worker… for example say a normal QRL $ The service-worker has a chunk for that, fetches it as needed, let’s say you want to manipulate it first on the service-worker, wrap it first…

  const domFunc = serviceWorker$(()=> {
  /* this runs on service-worker scope / thread,
     can do some “work” on the service-worker 
     before returning its QRL 
  */
    return $(()=> {
      /* this would run on the main thread
         Doesn’t have to be $ can just be an
         object with data or anything that
         can be serialized…
      */
    });
  });

  onClick$={domFunc}

There is currently an event$() in qwik that is similar to the above that is a QRL that returns a QRL with an implicit first arg which could be a similar way to set the serviceWorker$ … the key difference would be the “scope” of en vocation

Since it’s a QRL it’s not needed right of way…

First func$: runs on service worker if it returns a QRL… it will need to be fetched before the main thread QRL is needed, so it can do its work before the main thread QRL is fetched and stay in an “awaiting” state to finish and returns the second (main thread) QRL

Second func$: runs in main thread

I think this is a sort of callback but the callback is present on the service-worker so the second func$ can do some main thread work and return to the service worker

serbyxp commented 2 months ago

so when the build happens you get a map with all the QRLs : chunks so anything in the serviceWorker$ would need to be parsed a second time to make a sub node with its own QRL : chunks

QRL/chunk1.js Qrl/chunk2.js QRL/swchunk1.js … swchunk1.js … swchunk1/QRL/chunk3.js swchunk1/QRL/chunk4.js

something along those lines, that it has its own map for what chunks it needs to handle..

I think by doing it in such a way would… Create a ref or callback ref to the service-worker and more specifically the function that called it

This can lead to more exciting things like having the service worker act like a shared worker

ie executing more worker$ from the service-worker itself

serviceWorker$(worker$()=>…)

serviceWorker$(server$()=>…)

<input bind:value={z} />
<input bind:value={y} />
<button type=“submit” onClick$={[
  serviceWorker$(()=> {
    return y.value + z.value
  }),
  server$((x) => {
    return db.insert().values(x)
        return notification(x + “saved successfully“)
  }),
  // do a thing
]}>submit</button>
JerryWu1234 commented 2 months ago

@PatrickJS Maybe I can pick this. looks a bit complicate,

gioboa commented 2 months ago

This is an enhancement, the service worker story is changed during this month Here is the new implementation The output to solve this issue should be a cookbook example and not a new core API. @JerryWu1234 feel free to play with this. Thanks

gioboa commented 2 months ago

You can take inspiration from qwik-worker

PatrickJS commented 2 months ago

yeah @JerryWu1234 feel free to experiment. I was thinking we can pass make a fetch request and in the service worker detect this and run the code in service-worker and return the results rather than returning the code. I was planning on experimenting with this in a week or so but if you want to try then I'll be happy to help 🙏

PatrickJS commented 2 months ago

it would be better to have this which is similar to server$ but worker$ might be closer

const getDataFromServiceWorker = serviceWorker$((args) => {
  // do a thing
  return data
});

<button
  onClick$=[
    $(() => {

      const data = await getDataFromServiceWorker(args)
    })
  ]}
>
  Click
</button>
seanperez-xyz commented 2 months ago

You can take inspiration from qwik-worker

yeah the qwik worker$ last time i saw it did something similar... of event$ that is has a first arg and returns another function... ( if i remember correct) ...

but the worker$ is fine it can work universally ... the only thing I remember * was that worker$ was using a ref off the q:container?? to "message" but if we hone it down... that yes random worker$ ref whatever to spawn a new worker... but IF qwik-city is the micro framework for routing THEN hook into that instead of q:container (or w.e it is) but thats not good enough... as worker$ should / could be worker$({...}:{type:'service-worker" | "worker" | "shared"; port?; , client?; etc..?; }

similar to worker$ it autos to q:container ?? as its ref, but for service-worker it refs off the qwik-city service-worker ? qwik-city[sw] : q:container

seanperez-xyz commented 2 months ago

its hard to explain, like if there is qwik city and there is a qwik city service-worker (throw it on there )... unless its specified to put it on a specific "port" or "client"or worker which behind the scenes would be an object of | worker , service-worker, shared-worker | whatever it is in "worker" lingo

so... its not changing anything to how it works now. its hooking into how it works now, and inject it as a ref based on the object its referencing

but all that has to happen behind the scenes ...

this is not helpful at all for normal I want to add this or that... but its helpful if I want to add a "web API" like transitions or streaming API , storage API ( only one that works on main and worker threads is ? indexDB ? )... but its not so much yes I can add this to service-worker.ts but how can it be injected into the optimization process so that it can be a QRL in itself... ( QRL is handled by the service worker right... but a QRL that runs on the service worker) ... it can* fetch its own chunks/ bundles to run before the bundle needs to be fetched... its exactly what its doing now but a pre pre fetch that if you use the party-town method could be a pre-fetch to huge amounts of class methods that can serialize themselves in the background...( while streaming new qrl in..)

seanperez-xyz commented 2 months ago

like take a class for example... I cant serialize this class across $ boundries... and I dont need this class or module to run right now... but I do need it to run eventually... i can defer it , or like party-town I can just dump it to ?? some random fault ,,, and scoop it up later... but we already HAVE the service worker already its already doing things... but at the same time that its doing things it can do alllot more things... its no like the service worker is any different than any other thread... it can pump out just as much work as any other thread... the key is not clogging up the main thread which is the UX ... but now think about the class barrier problem , its not that the class cant be broken across the $ barrier because I can make a type of string and call it type MyStringType= string;... and its going to complain ( bad example) but not really because all the web3 stuff complains because of a custom Hex type it cant serialize between that... for whatever reason... ( its a lint or w/e debacle) but they are both just strings

so the idea is okay forget about the hard part of figuring all that out ... lets just let the service worker to do it for us

class MyClass ... constructor...

its not going to serialize and there are 10000000s of libs like this that people spend 10000 of hours making but we cant use them in qwik ( we can the opt easy out no serialize method and useVisibleTask method ... but whats the point then...

but if we use the service worker it can hold alll that and it can hold it all in a stateful manner""

the page can change but the scope to the service worker doesn't so it can run all that class madness back there in that thread.. while still being scoped to signals and all the serialized methods qwik uses, the difference is the SSR is not SSR but SWSR

seanperez-xyz commented 2 months ago

probably should copywrite that SWSR ... thats the extra$ for the sause$

serbyxp commented 2 months ago

You can take inspiration from qwik-worker

Like worker$ is great as I mentioned serviceWorker$ doesn’t need to exist it worker$ just gets an “upgrade” to reference the type of worker and give it a name to reference it in other files, or if the service-worker from qwik-city is in the scaffold that it gets imported to it

But right now as it is worker$ can be used, if for example you have a section of code that is no serialize | has the issue with a lot of class methods , if you get

const serializedOnWorkerData = worker$(() => // implement class methods | no serialize here );

now you got serializedOnWorkerData to pass around, the problem with plain “workers” vs “service-workers” is there life cycle it closed with the tab page scope changes so it needs to be re executed, so holding state there while navigating might not be ideal. But on the service-worker as you navigate the scope doesn’t change so any objects that were created there can be referenced still?

— edit + technically what can be done is you make a store, signal, and or context in the main thread where the data you get from the workers gets stored in like normal, so for example if the worker is calculating in the background and sends new data the on message or fetch listener can be ignored ? Since the data is going into the signal / store ? Which would trigger the re render of that component ?

shairez commented 1 month ago

Thanks everyone for the thoughtful discussion! Our plan is to switch to the newer service worker implementation that can be updated with new graph data over time.

Until we'll be done with V2, we won't have time to allocate for a proper design for a feature like serviceWorker$ but we'll definitely get back to it after V2 is out (which is coming soon).

So writing this here just so you know we're not ignoring this, we're just pausing... and later we'll resume 😊