[IMP] assets: switch to optimized rjsmin minification

The C implementation of the JS minification gives speedups between 6
and 55 times faster than the regex-based Python port, depending on
how compressed the input it (which is what our default implementation
does).

This is measurable when generating compiled assets bundle from scratch,
e.g. after installing/updating modules or source code.

As an illustration, the minification of a 2MB JS bundle can be 50x
faster:

```py
import rjsmin
from odoo.addons.base.models.assetsbundle import rjsmin as rjsm
js_source = open("web.assets_common_lazy.js").read() # 2MB JS
%timeit rjsm(js_source)
 # -> 339 ms ± 495 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit rjsmin.jsmin(js_source)
 # -> 6.88 ms ± 213 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
```

It's also a drop-in replacement, as long as you rjsmin 1.1.0 or better
is available (to support format strings properly, a.o.).

See also the documentation of rjsmin: http://opensource.perlig.de/rjsmin/

closes odoo/odoo#104283

Signed-off-by: Xavier Dollé (xdo) <xdo@odoo.com>
This commit is contained in:
Olivier Dony
2023-06-05 16:16:57 +02:00
parent f6814d9c6e
commit 3db7956b4c
7 changed files with 9 additions and 59 deletions
+1
View File
@@ -44,6 +44,7 @@ Depends:
python3-pydot,
python3-openssl,
python3-pypdf2,
python3-rjsmin,
python3-qrcode,
python3-renderpm,
python3-reportlab,
+2 -59
View File
@@ -24,6 +24,8 @@ except ImportError:
# `sassc` executable in the path.
libsass = None
from rjsmin import jsmin as rjsmin
from odoo import release, SUPERUSER_ID, _
from odoo.http import request
from odoo.modules.module import get_resource_path
@@ -39,65 +41,6 @@ EXTENSIONS = (".js", ".css", ".scss", ".sass", ".less", ".xml")
class CompileError(RuntimeError): pass
def rjsmin(script):
""" Minify js with a clever regex.
Taken from http://opensource.perlig.de/rjsmin (version 1.1.0)
Apache License, Version 2.0 """
def subber(match):
""" Substitution callback """
groups = match.groups()
return (
groups[0] or
groups[1] or
(groups[3] and (groups[2] + '\n')) or
groups[2] or
(groups[5] and "%s%s%s" % (
groups[4] and '\n' or '',
groups[5],
groups[6] and '\n' or '',
)) or
(groups[7] and '\n') or
(groups[8] and ' ') or
(groups[9] and ' ') or
(groups[10] and ' ') or
''
)
result = re.sub(
r'([^\047"\140/\000-\040]+)|((?:(?:\047[^\047\\\r\n]*(?:\\(?:[^'
r'\r\n]|\r?\n|\r)[^\047\\\r\n]*)*\047)|(?:"[^"\\\r\n]*(?:\\(?:[^'
r'\r\n]|\r?\n|\r)[^"\\\r\n]*)*")|(?:\140[^\140\\]*(?:\\(?:[^\r\n'
r']|\r?\n|\r)[^\140\\]*)*\140))[^\047"\140/\000-\040]*)|(?<=[(,='
r':\[!&|?{};\r\n+*-])(?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*'
r'\*+(?:[^/*][^*]*\*+)*/))*(?:(?:(?://[^\r\n]*)?[\r\n])(?:[\000-'
r'\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*)*('
r'(?:/(?![\r\n/*])[^/\\\[\r\n]*(?:(?:\\[^\r\n]|(?:\[[^\\\]\r\n]*'
r'(?:\\[^\r\n][^\\\]\r\n]*)*\]))[^/\\\[\r\n]*)*/))((?:[\000-\011'
r'\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*(?:(?:('
r'?://[^\r\n]*)?[\r\n])(?:[\000-\011\013\014\016-\040]|(?:/\*[^*'
r']*\*+(?:[^/*][^*]*\*+)*/))*)+(?=[^\000-\040&)+,.:;=?\]|}-]))?|'
r'(?<=[\000-#%-,./:-@\[-^\140{-~-]return)(?:[\000-\011\013\014\0'
r'16-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*(?:((?:(?://[^\r'
r'\n]*)?[\r\n]))(?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?'
r':[^/*][^*]*\*+)*/))*)*((?:/(?![\r\n/*])[^/\\\[\r\n]*(?:(?:\\[^'
r'\r\n]|(?:\[[^\\\]\r\n]*(?:\\[^\r\n][^\\\]\r\n]*)*\]))[^/\\\[\r'
r'\n]*)*/))((?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/'
r'*][^*]*\*+)*/))*(?:(?:(?://[^\r\n]*)?[\r\n])(?:[\000-\011\013'
r'\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*)+(?=[^\000'
r'-\040&)+,.:;=?\]|}-]))?|(?<=[^\000-!#%&(*,./:-@\[\\^{|~])(?:['
r'\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)'
r')*(?:((?:(?://[^\r\n]*)?[\r\n]))(?:[\000-\011\013\014\016-\040'
r']|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*)+(?=[^\000-\040"#%-\047'
r')*,./:-@\\-^\140|-~])|(?<=[^\000-#%-,./:-@\[-^\140{-~-])((?:['
r'\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)'
r'))+(?=[^\000-#%-,./:-@\[-^\140{-~-])|(?<=\+)((?:[\000-\011\013'
r'\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)))+(?=\+)|(?<'
r'=-)((?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]'
r'*\*+)*/)))+(?=-)|(?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*'
r'+(?:[^/*][^*]*\*+)*/))+|(?:(?:(?://[^\r\n]*)?[\r\n])(?:[\000-'
r'\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*)+', subber, '\n%s\n' % script
).strip()
return result
class AssetError(Exception):
pass
+2
View File
@@ -166,6 +166,8 @@ def init_logger():
]:
warnings.filterwarnings('ignore', category=DeprecationWarning, module=module)
# rsjmin triggers this with Python 3.10+ (that warning comes from the C code and has no `module`)
warnings.filterwarnings('ignore', r'^PyUnicode_FromUnicode\(NULL, size\) is deprecated', category=DeprecationWarning)
# the SVG guesser thing always compares str and bytes, ignore it
warnings.filterwarnings('ignore', category=BytesWarning, module='odoo.tools.image')
# reportlab does a bunch of bytes/str mixing in a hashmap
+1
View File
@@ -50,6 +50,7 @@ qrcode==6.1
reportlab==3.5.59 ; python_version <= '3.10' # version < 3.5.54 are not compatible with Pillow 8.1.2 and 3.5.59 is bullseye
reportlab==3.6.12 ; python_version > '3.10'
requests==2.25.1 # versions < 2.25 aren't compatible w/ urllib3 1.26. Bullseye = 2.25.1. min version = 2.22.0 (Focal)
rjsmin==1.1.0
urllib3==1.26.5 # indirect / min version = 1.25.8 (Focal with security backports)
vobject==0.9.6.1
Werkzeug==0.16.1 ; python_version <= '3.9'
+1
View File
@@ -53,6 +53,7 @@ setup(
'pyusb >= 1.0.0b1',
'qrcode',
'reportlab', # windows binary pypi.python.org/pypi/reportlab
'rjsmin',
'requests',
'urllib3',
'vobject',
+1
View File
@@ -41,6 +41,7 @@ RUN apt-get update -qq && \
python3-pydot \
python3-openssl \
python3-pypdf2 \
python3-rjsmin \
python3-qrcode \
python3-renderpm \
python3-reportlab \
+1
View File
@@ -47,6 +47,7 @@ RUN dnf update -d 0 -e 0 -y && \
python3-qrcode \
python3-reportlab \
python3-requests \
python3-rjsmin \
python3-six \
python3-stdnum \
python3-vobject \