Since Owl 3 is a significant change from Owl 2, it will require a good amount of work to update Odoo (and other) codebases. This document is intended as a guide and set of resources to help everyone as much as possible.
This is a work in progress!!!
- List of breaking changes
- Migration guide for each change
- Migration Plan for Odoo codebase
- List of migration Scripts
Additional info is the result of grepping in odoo code base (community/enterprise)
| # | Change | Additional info |
|---|---|---|
| 1 | useState removed |
|
| 2 | reactive removed |
240 calls |
| 3 | useEffect semantics changed |
596 calls |
| 4 | this.props removed |
|
| 5 | static props / defaultprops ignored (use the props function) |
281 default props |
| 6 | this.env removed |
161 useSubEnv |
| 7 | rendering context changes (reading from component through this) |
|
| 8 | onWillUpdateProps removed |
183 calls |
| 9 | t-esc removed |
|
| 10 | t-ref changes: takes a signal (or resource) |
1022 calls |
| 11 | t-model changes: takes a signal |
197 calls |
| 12 | onWillRender removed |
70 calls |
| 13 | onRendered removed |
20 calls |
| 14 | this.render removed |
130 calls |
| 15 | t-portal removed |
18 calls |
| 16 | useExternalListener renamed to useListener (and changed) |
210 calls |
| 17 | status changed |
64 calls |
| 18 | App has only sub roots |
20 new App calls |
| 19 | loadFile removed |
|
| 20 | t-call not allowed on tags !== t |
|
| 21 | t-call body evaluated lazily, variables passed as parameters |
|
| 22 | useComponent removed |
93 calls |
This one is pretty easy: replace all uses of useState by proxy (in import
statements and in code). This works, even though the underlying code of proxy
uses signals.
For example
// owl 2
this.state = useState({ someValue: 1 });
// owl 3
this.state = proxy({ someValue: 1 });There may be some change in behaviour, as some components will not need to be rendered in owl 3, since the reactivity system will be able to avoid subscribing to state updates in some cases (for example, in event listeners).
Almost like useState.
- all uses of
reactivewith one argument can be replaced byproxy. - for uses of
reactivewith a second argument, a manual update has to be done, probably usingeffectoruseEffect, or maybe usingcomputedif it makes sense.
// simple use
// owl 2
this.thing = reactive({...});
// owl 3
this.thing = proxy({ ... });todo
and the rest...
Roughly two main phases: a preparation phase, then we merge owl 3 in master, then a cleanup phase.
[Step 1A, Step 1B] => merge owl3 in master => Step 2
- Phase 1: preparation
- Step 1A: prepare master by adding
owl2_with_some_owl3build, and replacing/rewriting unpatchable code - Step 1B: create dev branch, add
owl_3_with_some_of_owl2, compatibility layer
- Step 1A: prepare master by adding
- merge: merge dev branch with
owl_3_with_some_of_owl2version in master - Phase 2: cleanup
- progressively remove owl2 specific code and uses of compatibility layer
- replace
owl_3_with_some_of_owl2byowl_3, celebrate
Build owl2_with_some_owl3, add it in master:
-
export
renderfunction -
useEffect:- duplicate current owl2
useEffectcode in odoouseLayoutEffect(@web/owl2_utils?) - adapt all
useEffectimports from@odoo/owlto@web/owl2_utils
- duplicate current owl2
-
rendering context: add
this.on all free variables in all templates (NEED SCRIPT) -
try to remove all uses of onWillUpdateProps
- (need useAsyncEffect in odoo)
- if successfull => remove onwillUpdateProps from owl3_with2 build
-
rename all
t-esc=>t-out(NEED SCRIPT) -
t-ref:- rename them all
t-custom-ref(NEED SCRIPT?), add directive to remap them tot-ref
- rename them all
-
t-model:- rename them all
t-custom-model(with script?), add directive to remap them tot-modelfor now
- rename them all
-
this.render: update all uses to import render from owl and uses it -
implement
useExternalListenerin owl2_utils -
adapt code to use it instead of owl useListener (script?)
-
t-call: prevent t-call on tag !== t (need script)
- create dev branch
master-owl3: - add owl3.js to web/static/libs/owl
- create owl3_compatibility.js in web/static/libs/owl <-- need to be executed before owl code
owl_3_with_some_of_owl2build is: owl 3 with additional temporary code:t-portalsupport is not removed yet- this build should have a ComponentNode.prototype.beforeSetup method that is called before the setup, to make it patchable
- keep
onWillUpdateProps - change
t-custom-refandt-custom-modeldirective to wrap content oft-refandt-modelin a signal-like function
In compatibility layer:
// useState
owl.useState = proxy;
// reactive
owl.reactive = function(value, cb) {
if (cb) {
// depreciation warning => probably require manual code update
console.warn("reactive is deprecated");
useEffect(cb());
}
return proxy(value);
}
class EnvPlugin extends Plugin {
env = {};
}
const useEnv = () => plugin(EnvPlugin).env;
owl.useEnv = useEnv;
owl.useSubEnv = function (extension) {
const env = useEnv();
const subEnv = Object.assign(Object.create(env), extension);
class SubEnvPlugin extends Plugin {
static id = "EnvPlugin";
env = subEnv;
}
providePlugins([SubEnvPlugin]);
}
owl.onWillRender = (cb) => {
// find a way to make it work
}
owl.onRendered = (cb) => {
// find a way to make it work
}
owl.useComponent = () => {
...
}
owl.useExternalListener = ... // duplicate current code from owl
owl.Component.ComponentNode.beforeSetup = function() {
if (!this.component.props) {
// only patch it if component does not define it before
this.component.props = props();
}
if (!this.component.env) {
this.component.env = useEnv();
}
}
- adapt all codes that instantiat new App and roots
rewrite code to use owl 3
- replace
useStatebyproxy(NEED A SCRIPT) - replace
reactivebyproxy(NEED A SCRIPT)- all reactive uses of 2nd argument => manual code update to use useEffect
- go trough all useEffect, and see if they can be replaced by owl useEffect
- if yes => do it
- if at the end, some useEffects cannot be converted => see if we need some notion of useLayoutEffect in owl3
- props:
- add
props = props()in all components (NEED SCRIPT) - if possible, try to convert in script static props/defaultprops => props({ ...}, {...})
- as soon as possible: remove this.props override in compatibility layer
- check that we have no remaining static props/defaultprops, maybe add a linter to prevent readding them in forward ports
- add
- env
- go through all uses of env
- replace them by plugins/provideplugins/...
- t-ref: go through all uses of
t-custom-ref, and rewrite code to use a signal - remove all uses of onWillRender: manual work
- remove all uses of onRendered: manual work
- remove all uses of the exported render function: manual work
- remove all uses of t-portal (manual work)
- check all uses of useExternalListener in owl2_utils => replace them by useListener in owl3
- remove all uses of useComponent
When we can:
- Remove compatibility layer
- replace owl3_with_some_of_owl2 by owl3
Phase 1
- replace all
t-refwitht-custom-ref - add
this.before all free variables in owl templates - rename t-esc => t-out (simple)
- replace useState => proxy in all js code
- replace reactive => proxy (except if second argument)
- add
props = props()orprops = props(type, defaultprops)in all components
Phase 2
?