From c6f813f4cb9c1e9f6739f19503eb3e9acec4b728 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Debongnie?= Date: Sun, 17 Oct 2021 14:50:39 +0000 Subject: [PATCH] [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) --- content/developer/reference/javascript.rst | 7 +- .../javascript/javascript_modules.rst | 390 ++++++++++++++++++ .../javascript/javascript_reference.rst | 372 +---------------- 3 files changed, 398 insertions(+), 371 deletions(-) create mode 100644 content/developer/reference/javascript/javascript_modules.rst diff --git a/content/developer/reference/javascript.rst b/content/developer/reference/javascript.rst index 066e672b2..43f2d8982 100644 --- a/content/developer/reference/javascript.rst +++ b/content/developer/reference/javascript.rst @@ -1,13 +1,14 @@ :nosearch: -================== -Javascript Modules -================== +========== +Javascript +========== .. toctree:: :titlesonly: javascript/framework_overview + javascript/javascript_modules javascript/owl_component_system javascript/javascript_cheatsheet javascript/javascript_reference diff --git a/content/developer/reference/javascript/javascript_modules.rst b/content/developer/reference/javascript/javascript_modules.rst new file mode 100644 index 000000000..a71d72ec1 --- /dev/null +++ b/content/developer/reference/javascript/javascript_modules.rst @@ -0,0 +1,390 @@ +================== +Javascript Modules +================== + +Odoo supports three different kinds of javascript files: + +- :ref:`plain javascript files ` (no module system), +- :ref:`native javascript module `. +- :ref:`Odoo modules ` (using a custom module system), + +As described in the :ref:`assets management section `, +all javascript files are bundled together and served to the browser. +Note that native javascript files are processed by the Odoo server and transformed into Odoo custom modules. + +Let us briefly explain the purpose behind each kind of javascript file. Plain +javascript files should be reserved only for external libraries and some small +specific low level purposes. All new javascript files should be created in the +native javascript module system. The custom module system is only useful for old, +not yet converted files. + +.. _javascript/plain_javascript: + +Plain Javascript files +====================== + +Plain javascript files can contain arbitrary content. It is advised to use the +*iife* :dfn:`immediately invoked function execution` style when writing such a file: + +.. code-block:: javascript + + (function () { + // some code here + let a = 1; + console.log(a); + })(); + +The advantages of such files is that we avoid leaking local variables to the +global scope. + +Clearly, plain javascript files do not offer the benefits of a module system, so +one needs to be careful about the order in the bundle (since the browser will +execute them precisely in that order). + +.. note:: + In Odoo, all external libraries are loaded as plain javascript files. + +.. _javascript/native_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 the IDE. + +There is a very important point to know: Odoo needs to know which files +should be translated into :ref:`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 and check if it contains the string *@odoo-module*. If so, it will +automatically be converted to an Odoo module. + +For example, let us consider the following module, located in :file:`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 this 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 +:file:`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 if the modules are in the same Odoo addon. So, imagine that we have +the following file structure: + +:: + + addons/ + web/ + static/ + src/ + file_a.js + file_b.js + stock/ + static/ + src/ + file_c.js + +The file :file:`file_b` can import :file:`file_a` like this: + +.. code-block:: javascript + + /** @odoo-module **/ + import {something} from `./file_a` + +But :file:`file_c` need to use the full name: + +.. code-block:: javascript + + /** @odoo-module **/ + import {something} from `@web/file_a` + + +Aliased modules +--------------- + +Because :ref:`Odoo modules ` follow a different module naming pattern, a system exists to allow a smooth +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, for + example, three names to call the same module, you would have to add a proxy manually. + 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 :file:`./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 module names with + a `/` anymore. + +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. + + +.. _javascript/odoo_module: + +Odoo Module System +=================== + +Odoo has defined a small module system (located in the file +: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 the `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 an rpc to load some data. In that case, the module can +simply return a promise. 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; + }); + }); \ No newline at end of file diff --git a/content/developer/reference/javascript/javascript_reference.rst b/content/developer/reference/javascript/javascript_reference.rst index cee45b7b4..639cea744 100644 --- a/content/developer/reference/javascript/javascript_reference.rst +++ b/content/developer/reference/javascript/javascript_reference.rst @@ -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