WebReflection / linkedom

A triple-linked lists based DOM implementation.
https://webreflection.medium.com/linkedom-a-jsdom-alternative-53dd8f699311
ISC License
1.7k stars 83 forks source link

Rendering / updates client side #95

Closed Ontopic closed 3 years ago

Ontopic commented 3 years ago

First I'd like to say it may be a good idea to list somewhat clearer this is a SSR-ONLY lib. If I understand correctly by now πŸ˜… In the end I think most devs read SSR these days as "it also renders on the server", not "is JUST renders on the server". While this technically does render to the dom, there is no implementation except for innerHTML right now as far as I can tell.

-- Edit: I mean to say, there's not really a client side seriaizer / renderer except setting innerHTML. So this is just as much SSR as PHP or Python is. Same API as real DOM, but the curren README.md to me felt like a rendering lib with custom-elements was included, wrapping the actual DOM in the browser.

Assuming this is indeed meant to allow server side manipulation as though you're in a browser, to convert later to pure serialized HTML or JSON to be send to the client;

I've been in love with wrapping the DOM in proxies. Dating back to compiling node myself before it was ready and only using deadly canary version of chrome. You all did a lovely job here of getting that right. The ability to rerender native dom based on value changes in a state object with some sugared web-components got me addicted to proxies even more when they landed in Chrome for real. I would like to see if I can use you lib in a module /worker (last updates fixed that πŸ‘), send the proxy for the "proxied dom" linkedom created, hook that up to the actual DOM (bare bones as possible), then diff, strategy to be determined, but leaning toward creating a "render-side" proxy or custom-element, could also go for sprinkling vdom. The result could potentially be an immer.js like solution, but for the dom.

So first I hope I understood right. If it makes no sense at all, please let me know. Lots to experiment and have fun with anyway, so I'll be trying to see what works (at least for rehydration I think this might be useful). Would love to know if this might be something you'd interested in integrating or if there're any plans for that.

Then some technical questions / requests;

  1. Would it be possible (well I know it's possible, but would you consider) allowing adding events to the proxy handler? I'd personally prefer a very low level we a pre-, post-, callback on get, set, apply, construct et cetera. That way I could could very quickly make my hydration better and might even give some extra opportunities to increase speed and getting closer to "real" DOM implmentation through the use of plugins for specific cases (or for new elements, features being released.
  2. What would be the best way to "unwrap" the proxy and would a revocable proxy (through a setting) maybe be something you'd consider?

Could make a setup for a PR to discuss and evolve into a solution with your input what you'd be willing to integrate.

---- TLDR

Lot to read, a quick reply to confirm this is server-side intended and if you think I'm crazy for trying to explore using it in the browser for rerenders / diffing is enough. Would love to help out on the project as well if needed.

WebReflection commented 3 years ago

will read the rest later, but to answer your first point, jsdon works on the client/browser side too, and it's mentioned in the README.

WebReflection commented 3 years ago

P.S. jumped to the TL;DR ... this library works already in Workers, either SSR workers, or Web Workers. There is indeed the linkedom/worker export

Ontopic commented 3 years ago

Thanks! That's enough for me to continue exploring. Read some of your blog posts, completely get the small-module post, will keep that in mind.

My request for access to the proxy handler without forking stands, should be trivial with lots of possibilities opening up for extensions in (small 😎) modules or plugins in different repos for whatever needs or fun extensions people want to try. The power of proxies on the dom... πŸ₯³

Same goes for revocable proxies, especially in use cases where the proxied-dom (my term for this "realm", like we also have "shadow-dom") generated in a submodule could technically be unwrapped in the main document (probably with and importNode or something). This seems to be possible already in proxied-dom space, so unwrap in submodule and import in main.

If this is outside of the scope I'll see what I can achieve without forking, any hints would be appreciated. If you're open to a PR I'll start working on it, unless you already have something planned. General direction on how you'd approach this (feasible or not, I'm gonna try), greately appreciated.

Ontopic commented 3 years ago

Of course for really taking over the proxy handler would transfer some responsibilities to the end user. Still, when communicated clearly very powerful to create your own subset, freeze things, create interfaces et cetera.

Just wrapping all proxy-handler methods with a pre- and post-event would already allow for very efficient call and DOM update tracking, allowing the ability to track changes on the proxied-dom and repeat necessary steps (after filtering, grouping or waiting for a certain callback) on the final rendering destinatio [destination! could be anywhere. we get to build a dom-tree with dom-interface to be rendered as whatever with the right adapter])

The last option would already make this an insanely efficient v-dom / native-dom framework πŸ˜…

Edit: insanely is a little too dramatic, but it will at least allow to directly diff dom operations without too much effort in an extension / worker per custom-element, f.e.

Ontopic commented 3 years ago

Not sure if you implemented any kind of batching or event logging already, then perhaps a very simple export, getter on that would already keep me from asking more questions for days ;). Or basically any api into the module would.

WebReflection commented 3 years ago

I'll read everything over the weekend and come back πŸ‘‹

Ontopic commented 3 years ago

More then I could ask for. Don't forget to take me up on my offer to contribute somewhere to thank you at least!

WebReflection commented 3 years ago

@Ontopic I did read everything you wrote but I am very confused about a few points:

Now, I am not sure I have answered any of your question, but my last one would be: how can I help making it more explicit in the README that this library has nothing to do with vDOM or anything non-standard?

Ontopic commented 3 years ago

β€”β€”β€” TLDR; see two point at the bottom ;)

I understand it can be confusing, proxies can get kinda complex to talk about quickly, sorry.

The implication for me are mostly related to a rich text editor for editing markdown (mdx). I keep a tree of the original markdown in a json structured AST. Two editors - one rendered with rich html, one rendered without styling (plain text) - that can both update this JSON tree, updates are reflected in both editors. The ability to have your "proxied dom" makes this process a whole lot easier, especially if you want to use the very powerful, but somewhat unreliable, contenteditable.

I'll refrain from trying to explain too much an drown again, because I'm mostly exploring what is possible with this anyway and was looking for your ideas, but that distracts from the main points;

  1. Could we get hooks into the proxy handler for pre- and post- get set apply construct etc events? Feels within scope, even just for SSR it would be nice to track what is being called during rendering - f,e, - to load appropriate resources or find bottlenecks serverside
  2. Ability to make the proxies revocable. This would allow to imitate a documentfragament to be manupulated in a way that is similar to how immer.js uses proxies for immutable state. You edit through whatever method you prefer and diff at the end to create the next immutable state, all "debounced". Batching reads and write to the actual dom becomes a whole lot easier and as you know very important to performance, vdom or real.

Hope questions / requests are at least clear. Sorry for my ramblings , proxies mess with my head ;)

Ontopic commented 3 years ago

To answer your question; mentioning you are using proxies to mimick the DOM server-side (or worker, yet not real dom) would be a great start. Currently, although cutely written, it's not at all clear what exactly this is doing. Perhaps I miss the background from your other libraries, but that should not really be the goal of the first paragraph - which now contains all the keywords you can imagine to think of the actual DOM, yet nowhere utters server or proxy, yet boasts its rendering performance and how it's better than the DOM. I'm 100% an idiot and I completely get that when you're deep into the material you'd write it like that, but it really took me quite a lot longer to figure out what is going on here than I think is necessary (and from other issues I get the same feeling...)

Thanks a ton, really, though. Not just for this repo (although proxies win), but all your stuff. You technically also didn't reply to my 3rd point; let me know if you need help somewhere! Perhaps I should post my Webcomponents and proxy code somewhere from years ago, so I can more clearly show what I'm after and perhaps some experiments that could spark some new ideas.

Edit: React is also DOM-less and has higher performance than the actual dom updates and deliver ssr. See where I'm going with this? They could put up the exact same intro you have, it even sounds like it could be.

WebReflection commented 3 years ago

I think we need to clarify this bit before discussing anything else ...

The ability to have your "proxied dom"

there is no proxied DOM here ... Proxy in JS has a meaning, and if you want hooks into the Proxy handler I'll tell you one more time: there is no such thing as proxied DOM in here.

The only Proxy is the window, which is exactly how it's implemented natively, but elements are no proxied whatsoever, there is no proxy, hence no hook you can hook into.

There is some other proxied thing but it's never about elements or nodes.

Once this is clear, I'd like to make something else also clear: anything that is not standard and could slow down consistently this project won't be part of this project, feel free to fork though.

I hope we can re-start the conversation from here.

P.S. are you talking about MutationObserver? if you want to observe nodes changes, you can use that ... yet, that's not Proxy.

Ontopic commented 3 years ago

That has been perfectly clear to me for years, hence the " and clarification on my usage of terms in earlier messages πŸ˜„. Guess I never really talked about proxies with anyone, but I studied them better than anything else. I noticed you start by wrapping just the window, but the wrapping continues in several directions through other methode and when you don't there's a nice surrogate for the original DOM object (element, node). Those are all very worthwhile pieces that I feel would not just benefit my use-case, but those of others completely unrelated to mine as well (the beauty of proxies) when exposed. The implications on the codebase would be minimal, but with immense options since if the "root" element is wrapped so could all it's children easily become when given access to the hooks.

This means also that the delay for people not opting for events have close to no overhead. Your decision though, I'll for myself focus on my own "proxied-dom", since that's never gonna make it into any library ;) This was just as an idea to open up experiments like treating linkedom as a virtual dom with event sourcing.

I completely agree with the "starting over". I intended to start my first message with "since the discussion board is not open, I'm putting it in an issue...". Perhaps I should have done that to make clearer that I'm just throwing around some ideas, hoping to start a discussion about what's possible. The usage of facades and proxies has as far as I know not been done to this extend, yet a lot more to discover.

MutationObservers was on my list of things to better look into before firing questions too quickly again ;) Difference is though that a MutatationObserver is just - well - an observer. A proxy can reroute calls, block, emit for through the ownKeys, has, get, getPrototypeOf and who know what else. Not the same as receiving the mutation after it happened. For my use case blocking the actual real-dom but passing on the events to a "proxied-dom" is more likely.

Ontopic commented 3 years ago

Your "facade-dom" to me is like a perfect "model" for rendering against when I don't have access to a real DOM element. The methods that are needed are there and they have effect in the underlying proxy target. Validation of method / getter calls, event sourced and with a cache in the form of you "facade-dom" tree. Kinda perfect...

Ontopic commented 3 years ago

Perhaps I also got confused with what you were doing since I was actively trying to proxy the protoype of custom elements, to catch all potential sugar event handlers or property setters with shared state over different runtimes. And your example was mostly about custom elements in my mind at the time

WebReflection commented 3 years ago

I surely welcome ideas aligned with the scope of this project, but here I’m afraid I still don’t understand what you’re after, or why. Any basic use case to explain how would you solve … whatever it is that you’re trying to solve?

Ontopic commented 3 years ago

Alright, trying an example, but to be honest I can think of a lot more. Let's say we have a server side / worker (no access to real DOM) process that receives an initial value in HTML in plain-text. This is processed in different ways and passed on to an actual rendering interface (with native DOM capabilities). Later it receives either a batch of events (f.e. a co-editor) or a completely new document (which probably overlaps with the previous input). In such cases I'd like to limit the amount of work the DOM / renderer has to do, so bring it down to a minimal set of updates and do as much as possible in a "virtual" environment (proxied/facade-dom as I called it, since virtual-dom was taken) that can run elsewhere.

const storage = new WeakMap
const targetToProxy = new WeakMap
const proxyToTarget = new WeakMap

const methods = new class {
  next = (target) => {
    return target?.nextElementSibling || null
  }

  parent = (target) => {
    return target?.parentElement || null
  }

  prev = (target) => {
    return target?.previousElementSibling || null
  }
}

class Handler {
  consturctor(original) {
    const store = storage.set(super(), {
      // state, meta ...
    })

    return new Proxy(methods, this)
  }

  get = (target, prop, proxy) => {
    // save request, for logging, batching, applying to "proxied-dom", rerouting calls, setting right "this" context et cetera
  }

  set = (t, p, v, r) => {

  }
}

const domProxy = (target) => {
  if (targetToProxy.has(target)) {
    return proxyToTarget.get(target)
  }

  const proxy = new Handler(target)

  proxyToTarget.set(proxy, target)
  targetToProxy.set(target, proxy)

  return proxy
}

This is untested semi pseudocode, but I hope it's somewhat clear (although reading someone else's proxies is supposed to be a nightmare). So this could all run outside of the actual browser, while pretending it's in a real browser with Linkedom.

I guess my request could also be brought back to; can we have a SMALLER library, that doesn't proxy the root node, but relies on the (large) subset of the DOM you built to outside of the browser "fake" sideffects.

WebReflection commented 3 years ago

can we have a SMALLER library, that doesn't proxy the root node

once again ... there is no proxy here, only the window, which is not the document, hence not the root node.

LinkeDOM is already small enough, but you can try to see if undom works for you.

Your pseudo code didn't really help ... a super() without an extend, and nothing actually happens there ... I'm afraid I still have no idea what you are asking, what you are after, or what you are trying to do.

I do DOM diffing in LinkeDOM via udomdiff though, I can use template literals too ... so, I can do pretty much everything I need to do in a DOM-less environment.