DEV Community

Discussion on: The Million.js Manifesto

Collapse
 
miketalbot profile image
Mike Talbot ⭐ • Edited

I'm interested in understanding where you see the VDOM as a distinct entity existing in the world where we can compile a program that understands implicitly which attributes to change on which DOM elements.

My naive thoughts here were that a VDOM allowed you to "do what you like 'coz its complex" and then work out what the implications of that are for the much slower real DOM, hence only updating the elements and attributes that must be changed. My understanding of compiler technology was that it would generate code that performs this minimal update by statically analysing what can happen in a code path and only including code for that. So, from this rather uneducated standpoint it would appear that a properly functioning compiler would mean that a VDOM became unnecessary while retaining the declarative patterns we now often use. I can see that static analysis may not always be possible I guess...

Collapse
 
miketalbot profile image
Mike Talbot ⭐ • Edited

BTW I did read your other article, but I didn't get a concrete feeling for the API constraints you refer to. Do we not have a constrained API in the DOM?

Collapse
 
aidenybai profile image
Aiden Bai

Thanks for reading my other articles! Constraints isn't probably the best word for the job, but I feel like it's the only one that fits right now. When you develop for the purpose of compiling down to imperative operations, your API pattern will be toward that. It's difficult to see a well optimized React API pattern compiling down to imperative operations.

Thread Thread
 
miketalbot profile image
Mike Talbot ⭐

Yes I see your point.

Collapse
 
tenbits profile image
Alex Kit

This is actually what we are doing at maskjs. Here the flow is: Template ➝ AST ➝ DOM. When the builder creates DOM nodes, it notices all observable expressions and the render context, and when later the new value arrives it changes only one part of the changed DOM.

h1 {
    '~[observe user.name]'
}
Enter fullscreen mode Exit fullscreen mode

And when the name is changed, e.g. user.name = "Foo", only textContent of the Element h1 is changed. Such approach is indeed much faster. But, if you have a huge template, a UserPage for example, and want to apply a new user model to show, which leads to dozens of such in-DOM changes, it could hurt performance. Also for VDOM. For this scenario the container DOM Element should be detached from the Live DOM, modified, and then reattached. But in most cases it can be even faster just to drop old Container Element, and to re-render the Template.

Collapse
 
aidenybai profile image
Aiden Bai

Really cool MVC project, seems undervalued and needs more attention haha. I think you have a misunderstanding of how vdom works - vdom would diff changes then apply pinpoint changes to the DOM. Whether it's faster to diff or drop and rerender the template I'm not sure about since all the jsperf links on your project are broken

Collapse
 
aidenybai profile image
Aiden Bai

This is a great point. For libraries like Svelte that use their own syntax + compiler, compiling down to near imperative operations just makes sense. The issue with that is that you have to build patterns around that sort of idea - like building a system like React with static analysis would be a pain in the butt.

In terms of whether the far future will compile down to imperative operations - probably. It might also be coupled with scheduling, WASM, workers, lazyloading, etc to maximize performance. But whether that is a realistic possiblity now or in the near future with libraries that depend on Virtual DOM-y patterns is difficult to say in my opinion.

Virtual DOM in terms of architecture is more of an "ideology" in my opinion. Ideologies vary in effectiveness, and it's important to bring pragmaticism to make it a "practical ideology."

Collapse
 
miketalbot profile image
Mike Talbot ⭐

Thanks for the clarification, I agree with your sentiment. A lot of the recent work in React has been around the scheduling of work to optimise when something should be processed and make it's updates to the VDOM (React fibre etc). This I guess is the new "reactivity" of React. Is this beyond the remit of million?

In React's case, the props of a component changing initiate a reactive update scheduled at some point in the future, until you get down to the DOM element itself.

I know a bit about Fibre and this kind of thing as it inspired my js-coroutines library, but I'm not sure if this concept is outside the remit you are looking to support and innovate in?

Thread Thread
 
aidenybai profile image
Aiden Bai

I've done a bit of scheduling implementation on Million here, but I haven't implemented any task prioritization techniques yet, and I do plan to. I'm still trying to figure out how the compiler can play a role in this.

I just checked out your article on DEV about js-coroutines, and it;'s really cool! I'm unsure whether million needs a hard dependency on js-coroutines, just because they aren't quite the same but are in the same ballpark. I'd be happy to try to adopt some elements / if you can take a look to improve million with your expertise.

Thread Thread
 
miketalbot profile image
Mike Talbot ⭐

Yeah you don't need js-coroutines itself, just some of the techniques. I'd be interested in thinking through it with you and seeing if I can help. js-coroutines is a load of helper functions for common operations wrapped around a very simple core.

export async function run(
    coroutine,
    loopWhileMsRemains = 1,
    timeout = 16 * 10
) {
    const options = {timeout}
    let terminated = false
    let resolver = null
    const result = new Promise(function (resolve, reject) {
        resolver = resolve
        const iterator = coroutine()
        window.requestIdleCallback(run)

        function run(api) {
            if (terminated) {
                iterator.return()
                return
            }
            const minTime = Math.max(0.5, loopWhileMsRemains)
            try {
                do {
                    const {value, done} = iterator.next()
                    if (done) {
                        resolve(value)
                        return
                    }
                    if (value === true) {
                        break
                    }
                } while (api.timeRemaining() > minTime)
            } catch (e) {
                reject(e)
                return
            }

            window.requestIdleCallback(run, options)
        }
    })
    result.terminate = function (result) {
        terminated = true
        if (resolver) {
            resolver.resolve(result)
        }
    }
    return result
}
Enter fullscreen mode Exit fullscreen mode

The idea of deciding on the importance of a particular update and adding it to some queues that then either get immediate updates (user input) or more casual updates that could be run in priority queues on idle seems like a plan.