mirror of
https://github.com/odoo/documentation.git
synced 2026-01-03 02:09:27 +07:00
[ADD] developer: add javascript module page
This commit moves most of the content out of the javascript reference and into a more visible page. closes odoo/documentation#1192 Signed-off-by: Antoine Vandevenne (anv) <anv@odoo.com>
This commit is contained in:
@@ -75,6 +75,8 @@ We only cover the most important files/folders.
|
||||
- *fields*: all main view field widgets are defined here
|
||||
- *views*: this is where the views are located
|
||||
|
||||
.. _javascript/assets_management:
|
||||
|
||||
Assets Management
|
||||
=================
|
||||
|
||||
@@ -421,10 +423,10 @@ For most Odoo code, we want to use a module system. Because of the way assets
|
||||
work in Odoo (and in particular, the fact that each installed odoo addon can
|
||||
modify the list of files contained in a bundle), Odoo has to resolve modules
|
||||
browser side. To do that, Odoo provides a small module system described just
|
||||
below (see :ref:`reference/javascript_reference/odoo_module`).
|
||||
below (see :ref:`javascript/odoo_module`).
|
||||
|
||||
However, Odoo also provides support for native javascript modules (see
|
||||
:ref:`reference/javascript_reference/js_module`). These modules
|
||||
:ref:`javascript/native_js_module`). These modules
|
||||
will simply be translated by the server into odoo modules. It is encouraged to
|
||||
write all javascript code as a native module, for a better IDE integration. In
|
||||
the future, the Odoo module system should be considered an implementation detail,
|
||||
@@ -433,372 +435,6 @@ not the primary way to write javascript code.
|
||||
.. note::
|
||||
Native javascript modules are the primary way to define javascript code.
|
||||
|
||||
.. _reference/javascript_reference/odoo_module:
|
||||
|
||||
Odoo Module System
|
||||
===================
|
||||
|
||||
Odoo has defined a small module system (located in the file
|
||||
*addons/web/static/src/js/boot.js*, which needs to be loaded first). The Odoo
|
||||
module system, inspired by AMD, works by defining the function ``define``
|
||||
on the global odoo object. We then define each javascript module by calling that
|
||||
function. In the Odoo framework, a module is a piece of code that will be executed
|
||||
as soon as possible. It has a name and potentially some dependencies. When its
|
||||
dependencies are loaded, a module will then be loaded as well. The value of the
|
||||
module is then the return value of the function defining the module.
|
||||
|
||||
|
||||
As an example, it may look like this:
|
||||
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
// in file a.js
|
||||
odoo.define('module.A', function (require) {
|
||||
"use strict";
|
||||
|
||||
var A = ...;
|
||||
|
||||
return A;
|
||||
});
|
||||
|
||||
// in file b.js
|
||||
odoo.define('module.B', function (require) {
|
||||
"use strict";
|
||||
|
||||
var A = require('module.A');
|
||||
|
||||
var B = ...; // something that involves A
|
||||
|
||||
return B;
|
||||
});
|
||||
|
||||
An alternative way to define a module is to give explicitly a list of dependencies
|
||||
in the second argument.
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
odoo.define('module.Something', ['module.A', 'module.B'], function (require) {
|
||||
"use strict";
|
||||
|
||||
var A = require('module.A');
|
||||
var B = require('module.B');
|
||||
|
||||
// some code
|
||||
});
|
||||
|
||||
|
||||
If some dependencies are missing/non ready, then the module will simply not be
|
||||
loaded. There will be a warning in the console after a few seconds.
|
||||
|
||||
Note that circular dependencies are not supported. It makes sense, but it means that one
|
||||
needs to be careful.
|
||||
|
||||
Defining a module
|
||||
-----------------
|
||||
|
||||
The *odoo.define* method is given three arguments:
|
||||
|
||||
- *moduleName*: the name of the javascript module. It should be a unique string.
|
||||
The convention is to have the name of the odoo addon followed by a specific
|
||||
description. For example, ``web.Widget`` describes a module defined in the *web*
|
||||
addon, which exports a ``Widget`` class (because the first letter is capitalized)
|
||||
|
||||
If the name is not unique, an exception will be thrown and` displayed in the
|
||||
console.
|
||||
|
||||
- *dependencies*: the second argument is optional. If given, it should be a list
|
||||
of strings, each corresponding to a javascript module. This describes the
|
||||
dependencies that are required to be loaded before the module is executed. If
|
||||
the dependencies are not explicitly given here, then the module system will
|
||||
extract them from the function by calling toString on it, then using a regexp
|
||||
to find all ``require`` statements.
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
odoo.define('module.Something', ['web.ajax'], function (require) {
|
||||
"use strict";
|
||||
|
||||
var ajax = require('web.ajax');
|
||||
|
||||
// some code here
|
||||
return something;
|
||||
});
|
||||
|
||||
- finally, the last argument is a function which defines the module. Its return
|
||||
value is the value of the module, which may be passed to other modules requiring
|
||||
it. Note that there is a small exception for asynchronous modules, see the
|
||||
next section.
|
||||
|
||||
If an error happens, it will be logged (in debug mode) in the console:
|
||||
|
||||
* ``Missing dependencies``:
|
||||
These modules do not appear in the page. It is possible that the JavaScript
|
||||
file is not in the page or that the module name is wrong
|
||||
* ``Failed modules``:
|
||||
A javascript error is detected
|
||||
* ``Rejected modules``:
|
||||
The module returns a rejected Promise. It (and its dependent modules) is not
|
||||
loaded.
|
||||
* ``Rejected linked modules``:
|
||||
Modules who depend on a rejected module
|
||||
* ``Non loaded modules``:
|
||||
Modules who depend on a missing or a failed module
|
||||
|
||||
|
||||
|
||||
Asynchronous modules
|
||||
---------------------
|
||||
|
||||
It can happen that a module needs to perform some work before it is ready. For
|
||||
example, it could do a rpc to load some data. In that case, the module can
|
||||
simply return a promise. In that case, the module system will simply
|
||||
wait for the promise to complete before registering the module.
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
odoo.define('module.Something', function (require) {
|
||||
"use strict";
|
||||
|
||||
var ajax = require('web.ajax');
|
||||
|
||||
return ajax.rpc(...).then(function (result) {
|
||||
// some code here
|
||||
return something;
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Best practices
|
||||
----------------
|
||||
|
||||
- remember the convention for a module name: *addon name* suffixed with *module
|
||||
name*.
|
||||
- declare all your dependencies at the top of the module. Also, they should be
|
||||
sorted alphabetically by module name. This makes it easier to understand your module.
|
||||
- declare all exported values at the end
|
||||
- try to avoid exporting too many things from one module. It is usually better
|
||||
to simply export one thing in one (small/smallish) module.
|
||||
- asynchronous modules can be used to simplify some use cases. For example,
|
||||
the *web.dom_ready* module returns a promise which will be resolved when the
|
||||
dom is actually ready. So, another module that needs the DOM could simply have
|
||||
a `require('web.dom_ready')` statement somewhere, and the code will only be
|
||||
executed when the DOM is ready.
|
||||
- try to avoid defining more than one module in one file. It may be convenient
|
||||
in the short term, but this is actually harder to maintain.
|
||||
|
||||
|
||||
.. _reference/javascript_reference/js_module:
|
||||
|
||||
Native Javascript Modules
|
||||
=========================
|
||||
|
||||
Most new Odoo javascript code should use the native javascript module system. This
|
||||
is simpler, and brings the benefits of a better developer experience with a better
|
||||
integration with IDE.
|
||||
|
||||
There is a very important point to know: since Odoo needs to know which files
|
||||
should be translated into odoo modules and which files should not be translated,
|
||||
this is an opt-in system: Odoo will look at the first line of a JS file. If it
|
||||
contains the string *@odoo-module*, then it will be automatically converted to an
|
||||
Odoo module.
|
||||
|
||||
For example, let us consider the following module, located in *web/static/src/file_a.js*:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
/** @odoo-module **/
|
||||
import { someFunction } from './file_b';
|
||||
|
||||
export function otherFunction(val) {
|
||||
return someFunction(val + 3);
|
||||
}
|
||||
|
||||
Note the comment in the first line: it describes that this file should be converted.
|
||||
Any file without that comment will be kept as-is (which will most likely be an
|
||||
error). This file will then be translated into an Odoo module that look like this:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
odoo.define('@web/file_a', function (require) {
|
||||
'use strict';
|
||||
let __exports = {};
|
||||
|
||||
const { someFunction } = require("@web/file_b");
|
||||
|
||||
__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.
|
||||
|
||||
Another important point is that the translated module has an official name:
|
||||
*@web/file_a*. This is the actual name of the module. Every relative imports
|
||||
will be converted as well. Every file located in an Odoo addon
|
||||
*some_addon/static/src/path/to/file.js* will be assigned a name prefixed by the
|
||||
addon name like this: *@some_addon/path/to/file*.
|
||||
|
||||
|
||||
Relative imports work, but only inside an odoo addon. So, imagine that we have
|
||||
the following file structure:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
addons/
|
||||
web/
|
||||
static/
|
||||
src/
|
||||
file_a.js
|
||||
file_b.js
|
||||
stock/
|
||||
static/
|
||||
src/
|
||||
file_c.js
|
||||
|
||||
The file ``file_b`` can import ``file_a`` like this:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
/** @odoo-module **/
|
||||
import {something} from `./file_a`
|
||||
|
||||
But ``file_c`` need to use the full name:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
/** @odoo-module **/
|
||||
import {something} from `@web/file_a`
|
||||
|
||||
|
||||
Aliased modules
|
||||
---------------
|
||||
|
||||
Because Odoo modules follow a different module naming pattern, a system exists to allow a smoother transition towards
|
||||
the new system. Currently, if a file is converted to a module (and therefore follow the new naming convention),
|
||||
other files not yet converted to ES6-like syntax in the project won't be able to require the module. Aliases are
|
||||
here to map old names with new ones by creating a small proxy function. The module can then be called by its new
|
||||
*and* old name.
|
||||
|
||||
To add such alias, the comment tag on top of the file should look like this:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
/** @odoo-module alias=web.someName**/
|
||||
import { someFunction } from './file_b';
|
||||
|
||||
export default function otherFunction(val) {
|
||||
return someFunction(val + 3);
|
||||
}
|
||||
|
||||
Then the translated module will also create an alias with the requested name:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
odoo.define(`web.someName`, function(require) {
|
||||
return require('@web/file_a')[Symbol.for("default")];
|
||||
});
|
||||
|
||||
The default behaviour of aliases is to re-export the ``default`` value of the
|
||||
module they alias. This is because "classic" modules generally export a single
|
||||
value which would be used directly, roughly matching the semantics of default
|
||||
exports.
|
||||
However it is also possible to delegate more directly, and follow the exact
|
||||
behaviour of the aliased module:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
/** @odoo-module alias=web.someName default=0**/
|
||||
import { someFunction } from './file_b';
|
||||
|
||||
export function otherFunction(val) {
|
||||
return someFunction(val + 3);
|
||||
}
|
||||
|
||||
In that case, this will define an alias with exactly the values exported by the
|
||||
original module:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
odoo.define(`web.someName`, function(require) {
|
||||
return require('@web/file_a');
|
||||
});
|
||||
|
||||
.. note::
|
||||
Only one alias can be defined using this method. If you were to need another one to have, by example, three
|
||||
names to call the same module, you will have to manually add yourself a proxy. This is not good practice and
|
||||
should be avoided unless there is no other options.
|
||||
|
||||
Limitations
|
||||
-----------
|
||||
|
||||
For performance reasons, Odoo does not use a full javascript
|
||||
parser to transform native modules. There are, therefore, a number of limitations including but not limited to:
|
||||
|
||||
- an ``import`` or ``export`` keyword cannot be preceded by a non space character,
|
||||
- a multiline comment or string cannot have a line starting by ``import`` or ``export``
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
// supported
|
||||
import X from "xxx";
|
||||
export X;
|
||||
export default X;
|
||||
import X from "xxx";
|
||||
|
||||
/*
|
||||
* import X ...
|
||||
*/
|
||||
|
||||
/*
|
||||
* export X
|
||||
*/
|
||||
|
||||
|
||||
// not supported
|
||||
|
||||
var a= 1;import X from "xxx";
|
||||
/*
|
||||
import X ...
|
||||
*/
|
||||
|
||||
- when you ``export`` an object, it can't contain a comment
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
// supported
|
||||
export {
|
||||
a as b,
|
||||
c,
|
||||
d,
|
||||
}
|
||||
|
||||
export {
|
||||
a
|
||||
} from "./file_a"
|
||||
|
||||
|
||||
// not supported
|
||||
export {
|
||||
a as b, // this is a comment
|
||||
c,
|
||||
d,
|
||||
}
|
||||
|
||||
export {
|
||||
a /* this is a comment */
|
||||
} from "./file_a"
|
||||
|
||||
- Odoo needs a way to determine if a module is described by a path (like ``./views/form_view``) or a name (like
|
||||
``web.FormView``). It has to use a heuristic to do just that: if there is a ``/`` in the name, it is considered
|
||||
a path. This means that Odoo does not really support anymore module names with a ``/``.
|
||||
|
||||
As "classic" modules are not deprecated and there is currently no plan to remove them, you can and should keep using
|
||||
them if you encounter issues with or are constrained by the limitations of native modules. Both styles can coexist
|
||||
within the same Odoo addon.
|
||||
|
||||
|
||||
Class System
|
||||
|
||||
Reference in New Issue
Block a user