Description
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?