Skip to content

Commit c641db1

Browse files
fkantelbergjans23
authored andcommitted
[ADD] web_assets_manager: Module to manage the used assets bundles for specific routes to improve SEO ranking and performance
(cherry picked from commit e8a1ede)
1 parent 7992d1f commit c641db1

20 files changed

Lines changed: 1108 additions & 0 deletions

File tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../../../web_assets_manager

setup/web_assets_manager/setup.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import setuptools
2+
3+
setuptools.setup(
4+
setup_requires=['setuptools-odoo'],
5+
odoo_addon=True,
6+
)

web_assets_manager/README.rst

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
==================
2+
Web Assets Manager
3+
==================
4+
5+
..
6+
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
7+
!! This file is generated by oca-gen-addon-readme !!
8+
!! changes will be overwritten. !!
9+
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
10+
!! source digest: sha256:c4d0a9e8bf76b428a36e31361a4742d8ee1921327f0ccd2d3538dd780d9970c8
11+
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
12+
13+
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
14+
:target: https://odoo-community.org/page/development-status
15+
:alt: Beta
16+
.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png
17+
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
18+
:alt: License: AGPL-3
19+
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fweb-lightgray.png?logo=github
20+
:target: https://github.com/OCA/web/tree/15.0/web_assets_manager
21+
:alt: OCA/web
22+
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
23+
:target: https://translation.odoo-community.org/projects/web-15-0/web-15-0-web_assets_manager
24+
:alt: Translate me on Weblate
25+
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
26+
:target: https://runboat.odoo-community.org/builds?repo=OCA/web&target_branch=15.0
27+
:alt: Try me on Runboat
28+
29+
|badge1| |badge2| |badge3| |badge4| |badge5|
30+
31+
Manage web assets for specific pages. By default Odoo website visitors download many large asset bundles which is bad for their performance. This module allows to customize the asset bundles to improve website performance. This can be useful for static webpages (other than portal users and online shop) in particular.
32+
33+
The web assets can exclude specific files only for a few URLs while other parts of the website can still load them. To temporarily disable the filtering you can add `debug=unfiltered`.
34+
35+
**Table of contents**
36+
37+
.. contents::
38+
:local:
39+
40+
Usage
41+
=====
42+
43+
1. Create a static webpage which contains all website blocks (widgets) you may want to use on static webpages. You may want to do this on a test system.
44+
45+
2. Go to `Settings -> Technical -> Web Assets` and create entries of bundles which you want to modify. Typically you may want to modify the bundles `web.assets_frontend`, `web.assets_frontend_minimal`, `web.assets_frontend_lazy`, `web.assets_common`, `web.assets_common_lazy`, and `web.assets_common_minimal`. Define a Regex rule to match one or multiple pages (e.g. `/test` to match the webpage with URL `example.com/test`).
46+
47+
3. Open a website which matches the defined Regex with the URL parameter "debug=assets" to generate the file lists.
48+
49+
4. Start excluding files from certain bundles. You may sort by size and start with the largest files. Or you group by module which should give a good overview which modules and their assets may not be required (and thus may be excluded).
50+
51+
5. Open and test the webpage again and check if it still works as expected. Repeat this and the previous steps again and again until your assets bundels are as minimal as desired.
52+
53+
6. Modified assets can be downloaded from one system (e.g. test system) and uploaded to another system (e.g. production system)
54+
55+
Bug Tracker
56+
===========
57+
58+
Bugs are tracked on `GitHub Issues <https://github.com/OCA/web/issues>`_.
59+
In case of trouble, please check there if your issue has already been reported.
60+
If you spotted it first, help us to smash it by providing a detailed and welcomed
61+
`feedback <https://github.com/OCA/web/issues/new?body=module:%20web_assets_manager%0Aversion:%2015.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
62+
63+
Do not contact contributors directly about support or help with technical issues.
64+
65+
Credits
66+
=======
67+
68+
Authors
69+
~~~~~~~
70+
71+
* initOS GmbH
72+
* Nitrokey GmbH
73+
74+
Contributors
75+
~~~~~~~~~~~~
76+
77+
* initOS GmbH
78+
* Nitrokey GmbH
79+
80+
Maintainers
81+
~~~~~~~~~~~
82+
83+
This module is maintained by the OCA.
84+
85+
.. image:: https://odoo-community.org/logo.png
86+
:alt: Odoo Community Association
87+
:target: https://odoo-community.org
88+
89+
OCA, or the Odoo Community Association, is a nonprofit organization whose
90+
mission is to support the collaborative development of Odoo features and
91+
promote its widespread use.
92+
93+
This module is part of the `OCA/web <https://github.com/OCA/web/tree/15.0/web_assets_manager>`_ project on GitHub.
94+
95+
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

web_assets_manager/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# © 2024 initOS GmbH
2+
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
3+
4+
from . import controllers, models, wizards

web_assets_manager/__manifest__.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# © 2024 initOS GmbH
2+
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
3+
4+
{
5+
"name": "Web Assets Manager",
6+
"version": "15.0.1.0.0",
7+
"category": "Hidden",
8+
"author": "initOS GmbH, Nitrokey GmbH, Odoo Community Association (OCA)",
9+
"website": "https://github.com/OCA/web",
10+
"license": "AGPL-3",
11+
"summary": """
12+
Odoo uses a few bundles for web assets which can grow quite large with
13+
more and more modules in use. This reduces SEO rankings for static pages
14+
because a lot of not necessary code is being loaded by the clients.
15+
16+
This module allows to control a bit further which file of the bundles is
17+
delivered for specific pages.
18+
""",
19+
"depends": [
20+
"web",
21+
"website",
22+
],
23+
"data": [
24+
"security/ir.model.access.csv",
25+
"views/web_assets_views.xml",
26+
"wizards/assets_wizard_views.xml",
27+
],
28+
"installable": True,
29+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# © 2024 initOS GmbH
2+
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
3+
4+
from . import main
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# © 2024 initOS GmbH
2+
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
3+
4+
from werkzeug.exceptions import NotFound
5+
6+
from odoo import http
7+
from odoo.http import content_disposition, request
8+
9+
10+
class Controller(http.Controller):
11+
@http.route("/web/binary/assets/<int:asset_id>", type="http", auth="user")
12+
def _download_web_assets(self, asset_id):
13+
assets = request.env["web.assets"].search([("id", "=", asset_id)])
14+
if not assets:
15+
raise NotFound()
16+
17+
return request.make_response(
18+
assets.generate_filelist(),
19+
headers=[
20+
("Content-Type", "text/plain"),
21+
("Content-Disposition", content_disposition(f"{assets.name}.txt")),
22+
],
23+
)
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# © 2024 initOS GmbH
2+
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
3+
4+
from . import ir_qweb, web_assets, web_assets_file
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
# © 2024 initOS GmbH
2+
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
3+
import logging
4+
import os
5+
import re
6+
7+
from odoo import models
8+
from odoo.http import ALLOWED_DEBUG_MODES
9+
10+
_logger = logging.getLogger(__name__)
11+
12+
# Failsafe because it has the potential to lock everyone out of the UI
13+
ALLOWED_DEBUG_MODES.append("unfiltered")
14+
15+
16+
def filesize(filename):
17+
if not filename:
18+
return 0
19+
20+
if not os.path.isfile(filename):
21+
return 0
22+
23+
try:
24+
with open(filename, "rb") as fp:
25+
fp.seek(0, 2)
26+
return fp.tell()
27+
except Exception:
28+
return 0
29+
30+
31+
class IrQweb(models.AbstractModel):
32+
_inherit = "ir.qweb"
33+
34+
def _generate_asset_nodes(
35+
self,
36+
bundle,
37+
css=True,
38+
js=True,
39+
debug=False,
40+
async_load=False,
41+
defer_load=False,
42+
lazy_load=False,
43+
media=None,
44+
):
45+
46+
if "|" in bundle:
47+
bundle = bundle.rsplit("|", 1)[0]
48+
49+
return super()._generate_asset_nodes(
50+
bundle,
51+
css=css,
52+
js=js,
53+
debug=debug,
54+
async_load=async_load,
55+
defer_load=defer_load,
56+
lazy_load=lazy_load,
57+
media=media,
58+
)
59+
60+
def get_asset_bundle(self, bundle_name, files, env=None, css=True, js=True):
61+
if "|" in bundle_name:
62+
bundle_name = bundle_name.rsplit("|", 1)[0]
63+
64+
hashsum = self.env.context.get("bundle_hashsum")
65+
skip = self.env.context.get("bundle_skip_filtering")
66+
if not hashsum or skip:
67+
return super().get_asset_bundle(bundle_name, files, env=env, css=css, js=js)
68+
69+
urls = {a["url"]: a for a in files}
70+
71+
# Synchronize assets files
72+
assets = self.env["web.assets"].sudo().search([("bundle", "=", bundle_name)])
73+
assets.mapped("file_ids").filtered_domain(
74+
[("name", "not in", list(urls))]
75+
).unlink()
76+
77+
asset_files = assets.mapped("file_ids")
78+
for url, data in urls.items():
79+
asset_files.filtered_domain([("name", "=", url)]).write(
80+
{"size": filesize(data.get("filename", ""))}
81+
)
82+
83+
for asset in assets:
84+
for file in set(urls) - set(asset.mapped("file_ids.name")):
85+
data = urls[file]
86+
87+
asset.file_ids.create(
88+
{
89+
"asset_id": asset.id,
90+
"name": file,
91+
"mimetype": data["atype"],
92+
"include": True,
93+
"size": filesize(data.get("filename", "")),
94+
}
95+
)
96+
97+
# Filter the assets files
98+
domain = [
99+
("bundle", "=", bundle_name),
100+
("hashsum", "=", hashsum),
101+
("active", "=", True),
102+
]
103+
assets = assets.search(domain, limit=1)
104+
names = set(assets.mapped("file_ids").filtered("include").mapped("name"))
105+
return super().get_asset_bundle(
106+
bundle_name,
107+
[file for file in files if file["url"] in names],
108+
env=env,
109+
css=css,
110+
js=js,
111+
)
112+
113+
def _get_asset_nodes(
114+
self,
115+
bundle,
116+
css=True,
117+
js=True,
118+
debug=False,
119+
async_load=False,
120+
defer_load=False,
121+
lazy_load=False,
122+
media=None,
123+
):
124+
path = None
125+
assets = self.env["web.assets"].sudo()
126+
try:
127+
from odoo.http import request
128+
129+
path = request.httprequest.path
130+
except RuntimeError: # pylint: disable=except-pass
131+
pass
132+
133+
if path:
134+
domain = [
135+
("bundle", "=", bundle),
136+
("active", "=", True),
137+
("path_regex", "!=", False),
138+
]
139+
for rec in assets.search(domain):
140+
pattern = r"^" + rec.path_regex.strip(r"^")
141+
if re.match(pattern, path):
142+
assets = rec
143+
break
144+
145+
if assets:
146+
bundle += f"|{assets.hashsum}"
147+
148+
return super(
149+
IrQweb,
150+
self.with_context(
151+
bundle_hashsum=assets.hashsum,
152+
bundle_skip_filtering=debug and "unfiltered" in debug,
153+
),
154+
)._get_asset_nodes(
155+
bundle,
156+
css=css,
157+
js=js,
158+
debug=debug,
159+
async_load=async_load,
160+
defer_load=defer_load,
161+
lazy_load=lazy_load,
162+
media=media,
163+
)

0 commit comments

Comments
 (0)