[IMP] developer: rewrite the page on assets

closes odoo/documentation#1302

X-original-commit: 8c79319011
Signed-off-by: Géry Debongnie (ged) <ged@openerp.com>
This commit is contained in:
Géry Debongnie 2021-11-02 12:29:47 +00:00
parent 3dd71a4817
commit 7b4879a06c
3 changed files with 244 additions and 151 deletions

View File

@ -9,18 +9,22 @@ Managing assets in Odoo is not as straightforward as it is in some other apps.
One of the reasons is that we have a variety of situations where some, but not all
of the assets are required. For example, the needs of the web client, the point of
sale app, the website or even the mobile application are different. Also, some
assets may be large, but are seldom needed. In that case, we sometimes want them
to be loaded lazily.
assets may be large, but are seldom needed: in that case we may want them
to be :ref:`loaded lazily (on demand) <frontend/assets/lazy_loading>`.
The main idea is that we define a set of **bundles** in the module manifest. A
bundle is here defined as a **list of file paths** (xml, javascript, css, scss).
Files are declared using `glob <https://en.wikipedia.org/wiki/Glob_(programming)>`_ syntax, meaning that you can declare several asset
files using a single line. Each matching file found will be appended to the
`<head>` of the page, at most once, in the order the glob patterns are given.
Bundles
=======
As mentioned, the bundles are declared in each module's :file:`__manifest__.py`, under
a dedicated `assets` key which contains a dictionary. The dictionary will map
**bundles** (keys) to the list of **files** they contain (values). It looks like this:
Odoo assets are grouped by *bundles*. Each bundle (a *list of file paths*
of specific types: `xml`, `js`, `css` or `scss`) is listed in the
:ref:`module manifest <reference/module/manifest>`. Files can be declared using
`glob <https://en.wikipedia.org/wiki/Glob_(programming)>`_ syntax, meaning that
you can declare several asset files using a single line.
The bundles are defined in each module's :file:`__manifest__.py`,
with a dedicated `assets` key which contains a dictionary. The dictionary maps
bundle names (keys) to the list of files they contain (values). It looks
like this:
.. code-block:: py
@ -38,197 +42,217 @@ a dedicated `assets` key which contains a dictionary. The dictionary will map
],
},
The files in a bundle can then be inserted into a template by using the `t-call-assets`
directive:
.. code-block:: xml
<t t-call-assets="web.assets_common" t-js="false"/>
<t t-call-assets="web.assets_common" t-css="false"/>
Here is what happens when a template is rendered by the server with these directives:
- all the scss files described in the bundle are compiled into css files. A file
named :file:`file.scss` will be compiled in a file named :file:`file.scss.css`.
- if we are in `debug=assets` mode
- the `t-call-assets` directive with the `t-js` attribute set to false will
be replaced by a list of stylesheet tags pointing to the css files
- the `t-call-assets` directive with the `t-css` attribute set to false will
link to the non minified bundle file (which uses sourcemaps)
- if we are not in `debug=assets` mode
- the css files will be concatenated and minified, then a stylesheet tag is
generated
- the js files are concatenated and minified, then a script tag is generated
.. note::
Assets files are cached, so in theory, a browser should only load them once.
Main bundles
------------
When the Odoo server is started, it checks the timestamp of each file in a bundle
and, if necessary, create/recreate the corresponding bundles.
Here are some important bundles that most developers will need to know:
Here is a list of some important bundles that most odoo developers will need to
know:
- `web.assets_common`: this bundle contains most assets which are common to the
web client, the website, and also the point of sale. This is supposed to contain
web client, the website and also the point of sale. This is supposed to contain
lower level building blocks for the odoo framework. Note that it contains the
:file:`boot.js` file, which defines the odoo module system.
- `web.assets_backend`: this bundle contains the code specific to the web client
(notably the web client/action manager/views) and all static XML templates used
in the backend environment
(notably the web client/action manager/views)
- `web.assets_frontend`: this bundle is about all that is specific to the public
website: ecommerce, forum, blog, event management, ...
website: ecommerce, portal, forum, blog, ...
- `web.assets_qweb`: all static XML templates used in the backend environment
and in the point of sale.
- `web.qunit_suite_tests`: all javascript qunit testing code (tests, helpers, mocks)
- `web.qunit_mobile_suite_tests`: mobile specific qunit testing code
Asset types
===========
There are three different asset types: code (`js` files), style (`css` or `scss`
files) and templates (`xml` files).
Code
Odoo supports :ref:`three different kinds of javascript files<frontend/js_modules>`.
All these files are then processed (native JS modules are transformed into odoo
modules), then minified (if not in `debug=assets` :ref:`mode <frontend/framework/assets_debug_mode>`)
and concatenated. The result is then saved as a file attachment. These file
attachments are usually loaded via a `<script>` tag in the `<head>` part of
the page (as a static file).
Style
Styling can be done with either `css` or `scss <https://sass-lang.com/>`_. Like
the javascript files, these files are processed (`scss` files are converted into
`css`), then minified (again, if not in `debug=assets` :ref:`mode <frontend/framework/assets_debug_mode>`)
and concatenated. The result is then saved as a file attachment. They are
then usually loaded via a `<link>` tag in the `<head>` part of the page (as
a static file).
Template
Templates (static `xml` files) are handled in a different way: they are simply
read from the file system whenever they are needed, and concatenated.
Whenever the browser loads odoo, it calls the `/web/webclient/qweb/` controller
to fetch the :ref:`templates <reference/qweb>`.
It is useful to know that in most cases, a browser only performs a request the
first time it loads a page. This is because each of these assets are
associated with a checksum, which is injected into the page source. The checksum
is then added to the url, which means that it is possible to safely set the cache
headers to a long period.
Operations on asset bundles
---------------------------
===========================
Typically, handling assets is quite trivial: you just need to add some new files
to a frequently used bundle like 'common' or 'backend'. But there are other operations
available to cover use cases bringing additional constraints. Such cases can mostly
be covered with the following operations.
Typically, handling assets is simple: you just need to add some new files
to a frequently used bundle like `assets_common` or `assets_backend`. But there are other operations
available to cover some more specific use cases.
- Add one or multiple file(s): `append`
The proper way to add a file to a bundle in any addon is simple: it is just enough
to add a glob pattern to the bundle in the file :file:`__manifest__.py` like so:
Note that all directives targeting a certain asset file (i.e. `before`, `after`,
`replace` and `remove`) need that file to be declared beforehand, either
in manifests higher up in the hierarchy or in ``ir.asset`` records with a lower
sequence.
.. code-block:: py
`append`
--------
'web.assets_common': [
'my_addon/static/src/js/**/*',
],
This operation adds one or multiple file(s). Since it is the most common
operation, it can be done by simply using the file name:
By default, adding a simple string to a bundle will append the files matching the
glob pattern at the end of the bundle. Obviously, the pattern may also be directly
a single file path.
.. code-block:: python
- Add one or multiple file(s) at the beginning of the list: `prepend`
Sometimes you need to put a certain file before the others in a bundle, when
loading css file, for example. In this case, you can use the `prepend` directive
by replacing the path with a pair `('prepend', <path>)`,
like so:
'web.assets_common': [
'my_addon/static/src/js/**/*',
],
.. code-block:: py
By default, adding a simple string to a bundle will append the files matching the
glob pattern at the end of the bundle. Obviously, the pattern may also be directly
a single file path.
'web.assets_common': [
('prepend', 'my_addon/static/src/css/bootstrap_overridden.scss'),
],
`prepend`
---------
- Add one or multiple file(s) before a specific file: `before`
Prepending a file at the beginning of a bundle might not be precise enough. The
`before` directive can be used to add the given file(s) right *before* the target
file. It is declared by replacing the normal path with a 3-element tuple
`('before', <target>, <path>)`, like so:
Add one or multiple file(s) at the beginning of the bundle.
.. code-block:: py
Useful when you need to put a certain file before the others in a bundle (for
example with css files). The `prepend` operation is invoked with the following
syntax: `('prepend', <path>)`.
'web.assets_common': [
('before', 'web/static/src/css/bootstrap_overridden.scss', 'my_addon/static/src/css/bootstrap_overridden.scss'),
],
.. code-block:: python
- Add one or multiple file(s) after a specific file: `after`
Same as `before`, with the matching file(s) appended right *after* the target file.
It is declared by replacing the normal path with a 3-element tuple
`('after', <target>, <path>)`, like so:
'web.assets_common': [
('prepend', 'my_addon/static/src/css/bootstrap_overridden.scss'),
],
.. code-block:: py
`before`
--------
'web.assets_common': [
('after', 'web/static/src/css/list_view.scss', 'my_addon/static/src/css/list_view.scss'),
],
Add one or multiple file(s) before a specific file.
- Use nested bundles: `include`
The `include` directive is a way to use a same bundle in other bundles to minimize
the size of your manifest. In Odoo we use sub bundles (prefixed with an underscore
by convention) to batch files used in multiple other bundles. You can then
specify the sub bundle as a pair `('include', <bundle>)` like this:
Prepending a file at the beginning of a bundle might not be precise enough. The
`before` directive can be used to add the given file(s) right *before* the target
file. It is declared by replacing the normal path with a 3-element tuple
`('before', <target>, <path>)`.
.. code-block:: py
.. code-block:: python
'web.assets_common': [
('include', 'web._primary_variables'),
],
'web.assets_common': [
('before', 'web/static/src/css/bootstrap_overridden.scss', 'my_addon/static/src/css/bootstrap_overridden.scss'),
],
- Remove one or multiple file(s): `remove`
In some additional module you may want to get rid of the call of a certain asset
in a bundle. Any file can be removed from an existing bundle using the `remove`
directive by specifying a pair `('remove', <target>)`:
`after`
-------
.. code-block:: py
Add one or multiple file(s) after a specific file.
'web.assets_common': [
('remove', 'web/static/src/js/boot.js'),
],
Same as `before`, with the matching file(s) appended right *after* the target file.
It is declared by replacing the normal path with a 3-element tuple
`('after', <target>, <path>)`.
- Replace an asset file with one or multiple file(s): `replace`
Let us now say that an asset need not only to be removed, but you also want to insert
your new version of that asset at the same exact position. This can be done with
the `replace` directive, using a 3-element tuple `('replace', <target>, <path>)`:
.. code-block:: python
.. code-block:: py
'web.assets_common': [
('after', 'web/static/src/css/list_view.scss', 'my_addon/static/src/css/list_view.scss'),
],
'web.assets_common': [
('replace', 'web/static/src/js/boot.js', 'my_addon/static/src/js/boot.js'),
],
`include`
---------
Note that all directives targeting a certain asset file (i.e. `before`, `after`,
`replace` and `remove`) need that file to be declared beforehand, either
in manifests higher up in the hierarchy or in ``ir.asset`` records with a lower
sequence.
Use nested bundles.
.. note::
The `include` directive is a way to use a bundle in other bundles to minimize
the size of your manifest. In Odoo we use sub bundles (prefixed with an underscore
by convention) to batch files used in multiple other bundles. You can then
specify the sub bundle as a pair `('include', <bundle>)` like this:
Note that the files in a bundle are all loaded immediately when the user loads the
odoo web client. This means that the files are transferred through the network
every time (except when the browser cache is active). In some cases, it may be
better to lazyload some assets. For example, if a widget requires a large
library, and that widget is not a core part of the experience, then it may be
a good idea to only load the library when the widget is actually created. The
widget class has actually built-in support just for this use case. (see section
:ref:`reference/javascript_reference/qweb`)
.. code-block:: python
Assets loading order
--------------------
'web.assets_common': [
('include', 'web._primary_variables'),
],
`remove`
--------
Remove one or multiple file(s).
In some cases, you may want to remove one or multiple files from a bundle. This
can be done using the `remove` directive by specifying a pair
`('remove', <target>)`:
.. code-block:: python
'web.assets_common': [
('remove', 'web/static/src/js/boot.js'),
],
`replace`
---------
Replace an asset file with one or multiple file(s).
Let us say that an asset needs not only to be removed, but you also want to insert
your new version of that asset at the same exact position. This can be done with
the `replace` directive, using a 3-element tuple `('replace', <target>, <path>)`:
.. code-block:: python
'web.assets_common': [
('replace', 'web/static/src/js/boot.js', 'my_addon/static/src/js/boot.js'),
],
Loading order
=============
The order in which assets are loaded is sometimes critical and must be deterministic,
mostly for stylesheets priorities and setup scripts. Assets in Odoo are processed
as follows.
as follows:
1. When an asset bundle is called (e.g. `t-call-assets="web.assets_common"`), an empty
list of assets is generated
#. When an asset bundle is called (e.g. `t-call-assets="web.assets_common"`), an empty
list of assets is generated
2. All records of type ``ir.asset`` matching the bundle will be fetched and sorted
by sequence number. Then all records with a sequence strictly less than 16 will
be processed and applied to the current list of assets.
#. All records of type `ir.asset` matching the bundle are fetched and sorted
by sequence number. Then all records with a sequence strictly less than 16 are
processed and applied to the current list of assets.
3. All modules declaring assets for said bundle in their manifest will apply their
assets operations to this list. This is done following the order of modules dependencies
(e.g. 'web' assets will be processed before 'website'). If a directive tries to add
a file already present in the list, nothing is done for that file. In other word,
only the first occurrence of a file is kept in the list.
#. All modules declaring assets for said bundle in their manifest apply their
assets operations to this list. This is done following the order of modules dependencies
(e.g. `web` assets is processed before 'website'). If a directive tries to add
a file already present in the list, nothing is done for that file. In other word,
only the first occurrence of a file is kept in the list.
4. The remaining ``ir.asset`` records (those with a sequence greater than or equal
to 16) are then processed and applied as well.
#. The remaining `ir.asset` records (those with a sequence greater than or equal
to 16) are then processed and applied as well.
Assets declared in the manifest may need to be loaded in a particular order, for
example :file:`jquery.js` must be loaded before all other jquery scripts when loading the
lib folder. One solution would be to create an ``ir.asset`` record with a lower sequence
or a 'prepend' directive, but there is another simpler way to do so.
lib folder. One solution would be to create an :ref:`ir.asset <frontend/assets/ir_asset>`
record with a lower sequence or a 'prepend' directive, but there is another simpler
way to do so.
Since the unicity of each file path in the list of assets is guaranteed, you can
mention any specific file before a glob that includes it. That file will thus appear
in the list before all the others included in the glob.
.. code-block:: py
.. code-block:: python
'web.assets_common': [
'my_addon/static/lib/jquery/jquery.js',
@ -241,13 +265,62 @@ in the list before all the others included in the glob.
to depend on it. Trying to operate on assets that have yet to be declared will
result in an error.
.. _frontend/assets/lazy_loading:
Lazy loading assets
===================
It is sometimes useful to load files and/or asset bundles dynamically, for
example to only load a library once it is needed. To do that, the Odoo framework
provides a few helper functions, located in :file:`@web/core/assets`.
.. code-block:: javascript
await loadAssets({
jsLibs: ["/web/static/lib/stacktracejs/stacktrace.js"],
});
.. js:function:: loadAssets(assets)
:param Object assets: a description of various assets that should be loaded
:returns: Promise<void>
Load the assets described py the `assets` parameter. It is an object that
may contain the following keys:
.. list-table::
:widths: 20 20 60
:header-rows: 1
* - Key
- Type
- Description
* - `jsLibs`
- `string[]`
- a list of urls of javascript files
* - `cssLibs`
- `string[]`
- a list of urls of css files
.. js:function:: useAssets(assets)
:param Object assets: a description of various assets that should be loaded
This hook is useful when components need to load some assets in their
`onWillStart` method. It internally calls `loadAssets`.
.. _frontend/assets/ir_asset:
The asset model (`ir.asset`)
------------------------------
============================
In most cases the assets declared in the manifest will largely suffice. Yet for
more flexibility, the framework also supports dynamic assets declared in the
database.
This is done by creating ``ir.asset`` records. Those will be processed as if they
This is done by creating `ir.asset` records. Those will be processed as if they
were found in a module manifest, and they give the same expressive power as their
manifest counterparts.

View File

@ -20,6 +20,8 @@ documents the list of hooks provided by the Odoo web framework.
* - Name
- Short Description
* - :ref:`useAssets <frontend/hooks/useassets>`
- load assets
* - :ref:`useBus <frontend/hooks/usebus>`
- subscribe and unsubscribe to a bus
* - :ref:`usePager <frontend/hooks/usepager>`
@ -27,6 +29,22 @@ documents the list of hooks provided by the Odoo web framework.
* - :ref:`usePosition <frontend/hooks/useposition>`
- position an element relative to a target
.. _frontend/hooks/useassets:
useAssets
=========
Location
--------
`@web/core/assets`
Description
-----------
See the section on :ref:`lazy loading assets <frontend/assets/lazy_loading>` for
more details.
.. _frontend/hooks/usebus:
useBus

View File

@ -1,3 +1,5 @@
.. _frontend/js_modules:
==================
Javascript Modules
==================