Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Toward Owl 3.0 #1669

Open
ged-odoo opened this issue Feb 21, 2025 · 2 comments
Open

Toward Owl 3.0 #1669

ged-odoo opened this issue Feb 21, 2025 · 2 comments
Labels
owl-3 features for owl 3

Comments

@ged-odoo
Copy link
Contributor

ged-odoo commented Feb 21, 2025

Toward Owl 3.0 [DRAFT!!!!!!]

Motivation

After a few years of use of Owl 2 at Odoo, I believe we have a reasonable
understanding of the strengths and weaknesses of owl.

Owl has many great qualities, we definitely want to keep that. But it can still
be improved. This document is trying to open the discussion on what owl 3.0 could
look like. Of course, at this point, it is only a discussion. Nothing concrete yet.

Main ideas/themes

Here are a few research/ideas that came up in various discussions.

  • improve various APIs (simpler, more intuitive, more explicit)
    • xml`` should return the function
    • no need for a canonical root => everything in an owl App is a Root
    • useRef is awkward
  • improve the semantics of some parts of the templating
    • lazy t-set body
    • scoping semantics
    • force using this to get the component in template expressions
    • t-call with explicit arguments
    • ... ?
  • better primitives (lazy blocks instead of slots)
    • life cycle on blocks? (maybe t-on-mounted="this.onMounted")
  • better perfs
    • some of the change above should help
    • a lazy block (or a slot) should be able to create an "effect" and be updated independently
  • revamp the reactivity system
    • probably something like effects/signals (uncouple subscription/updates)
    • allow a way to define derived reactive values
    • also, derived ASYNC reactive values, with control on restarting/stopping/... computations
  • make the rendering engine synchronous
    • remove onWillStart, onWillUpdateProps
    • move asynchrony into reactivity system
  • try to simplify owl
    • remove support for deep render, remove this.render method
    • remove t-att syntax with pairs

Many of these ideas have already issues (see https://github.com/odoo/owl/issues?q=is%3Aissue%20state%3Aopen%20label%3Aowl-3)

Async Reactive Engine

Big change. Basically, move the fiber async code out of the rendering engine
and into the reactivity system. The goal is to give access to the strong concurrency
code from owl to other code (the model, in addition to the view). At the same time,
it feels like rendering a component (or a lazy block) should be an effect, and all
reactive values should be automatically subscribed to that effect (so,
basically, effects/signals)

Synchronous Rendering

If owl rendering is synchronous, the question is what happens when a component want
to fetch something asynchronous before it is rendering?

Currently, in owl 2.0, it looks like this:

class MyComponent extends Component {
    static template = xml`<div><t t-esc="value"/></div>`

    setup() {
        onWillStart(async () => {
            this.value = await loadSomeValue();
        });
    }
}

If the rendering is synchronous, how can we do the same? An easy way is to move
the loading of the data up the component tree. It works, but it loses some
composability/ease of working with owl.

Another way would be to use async derived values:

class MyComponent extends Component {
    static template = xml`<div><t t-esc="value"/></div>`

    setup() {
        this.value = useAsyncState(() => loadSomeValues());
    }
}

Owl could then basically treat the component as an empty text node until all
async values are ready, and then rerender it.

But what happens if our fetch operation is long, and in the meantime, we want to
change some parameters? Currently, if it happens, owl has to destroy the component
and restart again. But i guess we could either do that, or just recompute the
async derived value.

class MyComponent extends Component {
    static template = xml`<div><t t-esc="value"/></div>`

    setup() {
        this.value = useAsyncState(() => loadSomeValues(this.props.recordId));
    }
}

Lazy block

Lazy block could replace slots, and named slots, but explicitely.

<t t-set-block="coucou">
    <div>this is some lazy content <t t-esc="this.someValue"/></div>
</t>
<SomeComponent content="coucou"/>

and in SomeComponent:

<!--  only evaluate its content now-->
<t t-call-block="coucou" /> 

Maybe we want to keep a simple syntax for the common case of default slot:

<SomeComponent content="?">
    <div>this is some lazy content <t t-esc="this.someValue"/></div>
</SomeComponent>

How to reference the inner content? Maybe this?

<SomeComponent t-set-block="coucou" content="coucou">
    <div>this is some lazy content <t t-esc="this.someValue"/></div>
</SomeComponent>

Incremental transition?

All changes discussed above should be done with the practical transition work in mind. So, since there are obviously breaking changes, and upgrading from owl 2 to 3 will need some work, we should have a "easy" way to proceed.

  • ideally, it should allow an incremental upgrade. For example, allowing a component written in owl 2 to work in owl 3 until it has been converted
  • maybe templates/components should be explicitely flagged as owl 2 or 3, to trigger the owl 2 or 3 compiler?
@ged-odoo ged-odoo added the owl-3 features for owl 3 label Feb 21, 2025
@sdegueldre
Copy link
Contributor

A few things that you missed or aren't currently tracked in any issues:

  • destructuring assignment (mostly for t-for(each), ideally should also be supported in t-set or other assignments I might be forgetting)
  • A better (and standard) way for components to expose an imperative API to their parent, could possibly be solved by a better ref API similar to react's useImperativeHandle.
  • This is kind of technical but it would be nice if we could use scopes instead of context objects for variables in compiled templates as it would likely generate better errors and make templates even more readable, not sure this is feasible though.
  • Keeping error handling and emission in mind while designing the new version
  • Suspense? In general I think that data-fetching needs a good amount of forethought. It would definitely be good if we somehow could get a tree of the data dependencies without having the component tree. I think a lot of research into the current state-of-the art is warranted (one library worth looking at: tanstack query)

I like the idea of tagging templates to choose the compiler as that's pretty easy to keep separate, not sure how viable it will be to have a common runtime for both compilers though but definitely worth exploring. Might also be good if the new compiler could warn (or error) on old constructs to help with conversions.

One big question that remains to be discussed and decided upon is, with a rework of the reactivity system it could be feasible to push granularity even further (eg each t-att- generates a separate render effect), similar to what's done in Solid for example. Not sure what the trade-offs are.

Something to keep in mind for the signal discussion: https://github.com/tc39/proposal-signals
While this is still likely some years away (if it ever sees the light of day), making a basic effort to keep our APIs compatible in some sense with the proposal and/or with other implementations could allow us to transition to standard signals more seamlessly if that ever happens.

@ged-odoo
Copy link
Contributor Author

i believe that solid actually group some signals together, so it is not at the absolute maximal granularity. for owl, my first intuition would be to explore the granularity of a "block", in the sense of blockdom. but this is just a thought, no real work done yet

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
owl-3 features for owl 3
Projects
None yet
Development

No branches or pull requests

2 participants