Skip to content

Instantly share code, notes, and snippets.

@ged-odoo
Last active November 22, 2021 07:50
Show Gist options
  • Save ged-odoo/64062d2af641986517f8f8fbb6e603ef to your computer and use it in GitHub Desktop.
Save ged-odoo/64062d2af641986517f8f8fbb6e603ef to your computer and use it in GitHub Desktop.
Odoo Javascript Modules

Odoo Javascript Modules

This gist describes our current work at using native JS modules in Odoo, and what it implies for the source code and for the developer experience.

PR: odoo/odoo#63177 (still work in progress)

Very brief summary

The current PR allows us to use native JS modules in odoo. It works. It is backward-compatible.

The benefits are:

  • autocompletion across JS files
  • intellisense (docstring are displayed when typing function, ...)
  • ease of refactoring (IDEs can jump to symbol definition, can rename across files, ...)
  • new developers will not need to learn odoo.define semantics

Other changes:

  • we can no longer serve static files, so we always use a big bundle, even in debug=assets
  • in debug=1 and debug=assets, bundle is non minified
  • in debug=assets, we generate sourcemaps to make the debugging experience nicer
  • it is faster: in debug=assets, we go from 5.86s/900requests to 3.27s/300 requests (cold start). Even faster if we do not need to regenerate sourcemaps.

From odoo module to JS module

What this PR does is converting a file written in the new style into a old-style module. So, for example:

/** @odoo-module **/
import { someFunction } from './some_file';

export function otherFunction(val) {
    return someFunction(val + 3);
}

will be rewritten into something like:

/********************************************************
*  Filepath: /web/static/src/some_file.js               *
*  Bundle: web.assets_backend                           *
*  Lines: 448                                           *
********************************************************/
odoo.define('@web/some_file', function (require) {                
'use strict';                
let __exports = {};                

const { someFunction } = require("web.ActionModel");

__exports.otherFunction = function otherFunction(val) {
    return someFunction(val + 3);
};

return __exports;
};

So, as you can see, the transformation is basically adding odoo.define on top, and updating the import/export statements. Also, note the first line comment: it describes that this file should be converted. Any file without that comment will be kept as-is.

But then, the original file is no longer a standalone JS file that can be included in a script tag like we did so far. We can no longer simply load them in the browser in debug=assets. So, what we do is then serve the bundle, but non minified.

With this PR, we then create two versions for each bundle. For example, assets_backend.min.js and assets_backend.js. The minified version is the version where all comments and (useless) spaces have been removed, and will be served for normal usecases. The non minified version is only generated and used in debug=assets mode. It is really the same file, except that we keep all the comments/spaces.

We also generate sourcemap files for the non minified file.

Alias system

To help with backward compatibility, and also to avoid breaking code, we also provide a way to define an alias. If the comment tag on top of the file looks like this:

/** @odoo-module alias=web.AbstractAction **/

The generated bundle will also export an additional module, named web.AbstractAction:

odoo.define(`web.AbstractAction`, function(require) {
    return require('@web/js/chrome/abstract_action')[Symbol.for("default")];
});

Using alias makes sure that all existing code importing the previous JS module will still work. Note that it is unclear if that feature will be removed in the future with a deprecation warning, or it if will stay as is.

Note that the normal behaviour of alias modules is to export the default value of the module. This is necessary, because many old odoo modules simply returned a value that was then imported by other module. It was therefore a default value.

However, sometimes, we want the aliased module to behave exactly like the original module. In that case, we can just use the default argument like this:

/** @odoo-module alias=web.AbstractAction default=0**/

This will define an alias with exactly the values exported by the original module:

odoo.define(`web.AbstractAction`, function(require) {
    return require('@web/js/chrome/abstract_action');
});

Path, files, importing cross addons, ...

Each file/module has an official name: web/static/src/file_a.js has the name @web/file_a. So, to import that file, one can simply use the following code:

import {something} from `@web/file_a` 

Relative imports work, but only inside an odoo addon. So, imagine that we have the following file structure:

addons/
    web/
        file_a.js
        file_b.js
    stock/
        file_c.js

The file file_b can import file_a like this:

import {something} from `./file_a` 

But file_c need to use the full name:

import {something} from `@web/file_a` 

Note that we will provide a way to generate a tsconfig.json file to help IDE make the mapping between those complete name and the actual file it refers to, so we can still benefits from intellisense.

@SimonGenin
Copy link

We probably should mention the alias system ?

@alexkuhn
Copy link

alexkuhn commented Jan 8, 2021

The change on debugging experience using dev tools is a bit annoying, but IMO a quite small cost compared to the benefits.
I suppose the goal is to eventually remove the conversion layer and just use native JS modules, right?
If so, this problem would exist in master just for some time, like a year or so?

@Railabo
Copy link

Railabo commented Nov 21, 2021

@ged-odoo, Many thanks for this explantations.. I have started to analysie Odoo 15 core and immediately noticed the new notation..
This is now clarifying a lot. I do assume that from now on we should write extending modules with the new style, right?

I do only not understand one major thing... how can I extend a view, for example inject one html element? Xpath? Probably not working here... so how? for example, lets say I want to modify this template + add one element
addons/mail/static/src/components/attachment_list/attachment_list.xml

Do you have any further documentation on this? I will be grateful!
Best regards! Thanks a lot for your work, Gery ( @ged-odoo )

@ged-odoo
Copy link
Author

Hey @Railabo

Thanks for the positive comment. I had forgotten about this gist :).
Since then, we updated the official documentation with information about javascript files: https://www.odoo.com/documentation/15.0/developer/reference/frontend/javascript_modules.html

Now, I am not exactly sure about your question about views. If you mean modifying a view arch (like the form view), then, you can do it with xpaths, like usual. But from you question, I suppose you mean modifying the (static) template of some owl component, then yes, it also works. This is kind of new, and I just noticed that this is not documented (will do it).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment