gluon-framework / gluon

A new framework for creating desktop apps from websites, using system installed browsers and NodeJS
https://gluonjs.org
MIT License
3.1k stars 76 forks source link

RFC: Next-gen IPC #43

Open amiller-gh opened 1 year ago

amiller-gh commented 1 year ago

Edit: This got long, sorry for the novel!

Happy to break up this issue and close it in favor of multiple issues if any of the ideas seem valuable, or move it to Discord for a more live discussion, but it seemed helpful to present as a single concept first!

We take a very similar approach to Gluon's IPC methods / stores in our Electron app with a homebrew state management library, and all of the concepts below relate to the challenges we ran into as our code base began to scale and design decisions we made to make IPC methods / stores work better together in large codebases. I thought some of the ideas could be helpful as the Gluon API continues to evolve!


First: I love the direction of this new framework – thank you so much for creating it! Very excited to watch (and hopefully help with?) how it develops.

We build a fairly complex Electron app, and I'd love to evaluate migrating to Gluon in the near future as a lighter weight option once it hits 1.0.0.

Specifically, your built-in exposed IPC methods and state objects (forgive me, I'm going to use "state" and "store" interchangeably here), resonate perfectly with how we've ended up architecting data flow in our app. The ergonomics of IPC methods / state over Electron's more 'events only' model is something we've focused closely on too.

We actually went as far as to develop a framework that jams something similar on top of Electron's IPC events API: https://github.com/universe/electron-state

I wanted to drop a flew thoughts here and see if some of the use cases (and growing pains) we encountered using IPC methods / state at-scale might be helpful with API design.

The three biggest problems we ran into when building out our own methods / state model as our app scaled up included:

If we adopted Gluon right now, I would see us running into similar issues since all shared state and methods exist on the singe IPC object!

We built around these issues in electron-state by leaning on class syntax to couple and namespace relevant methods and state, in a way that automatically emits events from the class singleton whenever data changes so we can easily wire up any frontend framework to respond to state updates.

There are three independent ideas from our stab at IPC methods/state that may be interesting to take into account for Gluon, and conveniently can all be considered independently!

  1. Avoid API Namespace Pollution: Separate out user-defined methods from the other IPC helper methods, like you do with shared state using ipc.store. Perhaps only allow writing user-defined methods to something like ipc.methods (to mirror the store API) instead. If you encourage users to write directly to the main IPC object, once you hit 1.0.0 you can not add any more framework methods without bumping a major version: you might accidentally be overwriting a user created function! This will give Gluon more API flexibility in the long run, and allow new IPC helper methods on minor version bumps instead of major version bumps. This will also make the inevitable Typescript types for the library (written by you, or a 3rd party) much more managable.
  2. Reduce Responsive Frontend Boilerplate: Trigger user facing events when ipc.store is modified! I see you trigger an un-document 'web store change' IPC event to write data back to the main process from the renderer, but more granular event triggers would allow front-end frameworks to create hooks into state changes so the UI can be more easily reflected. It may be helpful to create a separate event channel than the current user-created event Gluon.ipc.on API for these automated events, in service of isolating the event namespaces as well. (Humble recommendation for all internal data sync events too – currently, if I for some reason wanted to create both a myFun and exposed myFunc function, I think things would not run as expected!)
    1. Allow User Provided Namespaces: Currently, all user provided methods and state appear to be written to a single, global namespace. This won't scale well as apps become more and more complex! Yes, users can cleverly nest their store objects, but then both you and the user loose change detection fidelity since the state store uses a proxy under the hood. Other than function name prefixes, there appears to be no nice way to organize the IPC methods. Our electron-state module does this by co-opting JS class syntax for for very dev-friendly namespaces, and the base class that these IPC state objects extend from is responsible for wiring up all the IPC sync event channels, ensuring it remains a singleton, etc. Notably though, this couples the concept of state and methods. I think this is a feature (it mirrors the "data down actions up" principle we find in frontend component design!) but a lower level, less opinionated, version of this for Gluon may simply allow for named IPC method collections or state objects that users can create and retrieve through the IPC API. E.g. Gluon.ipc.getState('my-state-object-name') or Gluon.ipc.getMethods('my-functionality-collection-name')

Let me know if any of these are worth exploring – I'm always happy to chat about them more if they align with how you imagine Gluon evolving!

CanadaHonk commented 1 year ago

Thanks for the comments and praise! Cool library as well. To briefly address each main point:

  1. I am aware of this now and plan to change in the next major release.
  2. Good idea, I'll likely add a dedicated ipc.store.onChange or something of the sort soon.
  3. This is a pretty neat idea although don't want to make things too complicated, will discuss the idea around to get some thoughts.

Happy to talk more on Discord too.

amiller-gh commented 1 year ago
  1. Great to hear!
  2. Awesome, that'll make frontend framework integrations a breeze. Since the state objects are also user-defined-key-dumping-grounds, it may be good to consider an api like Gluon.onStoreChange(myStore, (...args) => { /* callback */ }) to avoid polluting the namespace in the same way as point 1.
  3. An IPC State / Method object factory may be a nice internal model to take for code organization regardless, even if you never expose the factory functions and simply use it to create the single "global" state / method objects on Gluon.ipc used now. That would give the project the flexibility to choose to expose the factory functions in the future, even if it's buried in the documentation as an "advanced" feature. A lack of name-spaced objects would def block our larger app's adoption in the future, but 100% hear you on the complexity argument for the average user.

Will see you on Discord!