Skip to content

Instantly share code, notes, and snippets.

@ged-odoo
Last active January 26, 2026 14:45
Show Gist options
  • Select an option

  • Save ged-odoo/0599d7cd9710428eaf97ea5b0e71f589 to your computer and use it in GitHub Desktop.

Select an option

Save ged-odoo/0599d7cd9710428eaf97ea5b0e71f589 to your computer and use it in GitHub Desktop.
owl3 migration plan

πŸ¦‰πŸ¦‰ Migration πŸ¦‰πŸ¦‰πŸ¦‰ Plan

Overview of migration strategy

[Step 1A, Step 1B] => Step 2 => Step 3 => Step 4
  • Step 1A: create dev branch, add owl_3_with_some_of_owl2, compatibility layer
  • Step 1B: prepare master by adding owl2_with_some_owl3 build, and replacing/rewriting unpatchable code
  • Step 2: merge dev branch with owl_3_with_some_of_owl2 version in master
  • Step 3: progressively remove owl2 specific code and uses of compatibility layer
  • Step 4: replace owl_3_with_some_of_owl2 by owl_3, celebrate

List of breaking changes

# 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

Step 1A: dev branch master-owl3

  • 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_owl2 build is: owl 3 with additional temporary code:
    • t-portal support 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-ref and t-custom-model directive to wrap content of t-ref and t-model in 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

Step 1B: preparations in master

Build owl2_with_some_owl3, add it in master:

  • export render function

  • useEffect:

    • duplicate current owl2 useEffect code in odoo useLayoutEffect (@web/owl2_utils?)
    • adapt all useEffect imports from @odoo/owl to @web/owl2_utils
  • 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 to t-ref
  • t-model:

    • rename them all t-custom-model (with script?), add directive to remap them to t-model for now
  • this.render: update all uses to import render from owl and uses it

  • implement useExternalListener in owl2_utils

  • adapt code to use it instead of owl useListener (script?)

  • t-call: prevent t-call on tag !== t (need script)

Step 2: merge dev branch

Not much to say. Just do it as soon as it is green

Step 3

  • replace useState by proxy (NEED A SCRIPT)
  • replace reactive by proxy (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
  • 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

Step 4

  • Remove compatibility layer
  • replace owl3_with_some_of_owl2 by owl3
  • Take a break...

List of scripts

  • replace all t-ref with t-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() or props = props(type, defaultprops) in all components
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment