[IMP] base: add sourcemap support for CSS files.

Improve the development experience in debug=assets mode by reducing the
number of requests to the server. We are adapting the solution used for
the JS files to the CSS files. This solution consists of no longer
sending all the files separately, but sending only the bundles
associated with their sourcemap. This allows us to keep the same
debugging experience while drastically reducing the number of requests
to the server.

Benchmark:
saas 14.2                   917 requests    domcontentloaded after 3.76s
master (bundling du js)     299 requests    domcontentloaded after 2.03s
branch (bundling js+css)    36  requests    domcontentloaded after 1.01s

Task id : 2463840

closes odoo/odoo#66169

Related: odoo/design-themes#453
Signed-off-by: Géry Debongnie (ged) <ged@openerp.com>
This commit is contained in:
Francois (fge)
2021-02-18 08:51:02 +00:00
parent b9c776129a
commit 4627c224ef
5 changed files with 155 additions and 149 deletions
+94 -100
View File
@@ -140,56 +140,41 @@ class AssetsBundle(object):
:returns [(tagName, attributes, content)] if the tag is auto close
"""
response = []
if debug and 'assets' in debug:
if css and self.stylesheets:
is_css_preprocessed, old_attachments = self.is_css_preprocessed()
if not is_css_preprocessed:
self.preprocess_css(debug=debug, old_attachments=old_attachments)
if self.css_errors:
msg = '\n'.join(self.css_errors)
response.append(JavascriptAsset(self, inline=self.dialog_message(msg)).to_node())
response.append(StylesheetAsset(self, url="/web/static/lib/bootstrap/css/bootstrap.css").to_node())
if not self.css_errors:
for style in self.stylesheets:
response.append(style.to_node())
if js and self.javascripts:
self.js(is_minified=False)
is_debug_assets = debug and 'assets' in debug
if css and self.stylesheets:
css_attachments = self.css(is_minified=not is_debug_assets) or []
for attachment in css_attachments:
if is_debug_assets:
href = self.get_debug_asset_url(extra='rtl/' if self.user_direction == 'rtl' else '',
name=css_attachments.name,
extension='')
else:
href = attachment.url
attr = OrderedDict([
["async", "async" if async_load else None],
["defer", "defer" if defer_load or lazy_load else None],
["type", "text/javascript"],
["data-src" if lazy_load else "src", self.get_debug_asset_url(name=self.name, extension=".js")],
["type", "text/css"],
["rel", "stylesheet"],
["href", href],
['data-asset-xmlid', self.name],
['data-asset-version', self.version],
])
response.append(("script", attr, None))
else:
if css and self.stylesheets:
css_attachments = self.css() or []
for attachment in css_attachments:
attr = OrderedDict([
["type", "text/css"],
["rel", "stylesheet"],
["href", attachment.url],
['data-asset-xmlid', self.name],
['data-asset-version', self.version],
])
response.append(("link", attr, None))
if self.css_errors:
msg = '\n'.join(self.css_errors)
response.append(JavascriptAsset(self, inline=self.dialog_message(msg)).to_node())
response.append(("link", attr, None))
if self.css_errors:
msg = '\n'.join(self.css_errors)
response.append(JavascriptAsset(self, inline=self.dialog_message(msg)).to_node())
response.append(StylesheetAsset(self, url="/web/static/lib/bootstrap/css/bootstrap.css").to_node())
if js and self.javascripts:
attr = OrderedDict([
["async", "async" if async_load else None],
["defer", "defer" if defer_load or lazy_load else None],
["type", "text/javascript"],
["data-src" if lazy_load else "src", self.js(is_minified=True)[0].url],
['data-asset-xmlid', self.name],
['data-asset-version', self.version],
])
response.append(("script", attr, None))
if js and self.javascripts:
js_attachment = self.js(is_minified=not is_debug_assets)
src = self.get_debug_asset_url(name=js_attachment.name, extension='') if is_debug_assets else js_attachment[0].url
attr = OrderedDict([
["async", "async" if async_load else None],
["defer", "defer" if defer_load or lazy_load else None],
["type", "text/javascript"],
["data-src" if lazy_load else "src", src],
['data-asset-xmlid', self.name],
['data-asset-version', self.version],
])
response.append(("script", attr, None))
return response
@@ -232,9 +217,6 @@ class AssetsBundle(object):
**self._get_asset_url_values(id=id, unique=unique, extra=extra, name=name, sep=sep, extension=extension)
)
def _get_debug_asset_template_url(self):
return "/web/assets/{id}-{unique}/{extra}{name}{sep}{extension}"
def get_debug_asset_url(self, extra='', name='%', extension='%'):
return f"/web/assets/debug/{extra}{name}{extension}"
@@ -250,7 +232,7 @@ class AssetsBundle(object):
"""
ira = self.env['ir.attachment']
url = self.get_asset_url(
extra='%s' % ('rtl/' if extension == 'css' and self.user_direction == 'rtl' else ''),
extra='%s' % ('rtl/' if extension in ['css', 'min.css'] and self.user_direction == 'rtl' else ''),
name=self.name,
sep='',
extension='.%s' % extension
@@ -288,7 +270,7 @@ class AssetsBundle(object):
url_pattern = self.get_asset_url(
unique=unique,
extra='%s' % ('rtl/' if extension == 'css' and self.user_direction == 'rtl' else ''),
extra='%s' % ('rtl/' if extension in ['css', 'min.css'] and self.user_direction == 'rtl' else ''),
name=self.name,
sep='',
extension='.%s' % extension
@@ -314,7 +296,7 @@ class AssetsBundle(object):
:return the ir.attachment records for a given bundle.
"""
assert extension in ('js', 'min.js', 'js.map', 'css')
assert extension in ('js', 'min.js', 'js.map', 'css', 'min.css', 'css.map')
ira = self.env['ir.attachment']
# Set user direction in name to store two bundles
@@ -323,8 +305,8 @@ class AssetsBundle(object):
# (this applies to css bundles only)
fname = '%s.%s' % (self.name, extension)
mimetype = (
'text/css' if extension == 'css' else
'application/json' if extension == 'js.map' else
'text/css' if extension in ['css', 'min.css'] else
'application/json' if extension in ['js.map', 'css.map'] else
'application/javascript'
)
values = {
@@ -340,7 +322,7 @@ class AssetsBundle(object):
url = self.get_asset_url(
id=attachment.id,
unique=self.version,
extra='%s' % ('rtl/' if extension == 'css' and self.user_direction == 'rtl' else ''),
extra='%s' % ('rtl/' if extension in ['css', 'min.css'] and self.user_direction == 'rtl' else ''),
name=fname,
sep='', # included in fname
extension=''
@@ -379,19 +361,13 @@ class AssetsBundle(object):
return attachments[0]
def js_with_sourcemap(self):
"""Create/modify the ir.attachment representing the not-minified content of the bundleJS
and the ir.attachment representing the linked sourcemap.
To avoid the loss of break points in the development tool when the bundleJS
has to be rebuilt, the bundleJS url must not change so we will not include
'self.version' in the ir.attachment url.
If any ir.attachment exists for the bundleJS, we create a new one (bundleJS and sourcemap),
else, we modify the 'raw' in the existing ir.attachments.
"""Create the ir.attachment representing the not-minified content of the bundleJS
and create/modify the ir.attachment representing the linked sourcemap.
:return ir.attachment representing the un-minified content of the bundleJS
"""
sourcemap_attachment = self.get_attachments('js.map') \
or self.save_attachment('js.map', '')
generator = SourceMapGenerator(
source_root="/".join(
[".." for i in range(0, len(self.get_debug_asset_url(name=self.name).split("/")) - 2)]
@@ -414,41 +390,79 @@ class AssetsBundle(object):
content_line_count += len(asset.content.split("\n")) + line_header
content_bundle = ';\n'.join(content_bundle_list) + "\n//# sourceMappingURL=" + sourcemap_attachment.url
js_attachment = self.get_attachments('js')
if js_attachment:
js_attachment.write({
"raw": content_bundle.encode('utf8'),
})
else:
js_attachment = self.save_attachment('js', content_bundle)
js_attachment = self.save_attachment('js', content_bundle)
generator._file = js_attachment.url
sourcemap_attachment.write({
# Store with XSSI-prevention prefix
"raw": b")]}'\n" + json.dumps(generator.to_json()).encode('utf8'),
"raw": generator.get_content()
})
return js_attachment
def css(self):
attachments = self.get_attachments('css')
def css(self, is_minified=True):
extension = 'min.css' if is_minified else 'css'
attachments = self.get_attachments(extension)
if not attachments:
# get css content
css = self.preprocess_css()
if self.css_errors:
return self.get_attachments('css', ignore_version=True)
return self.get_attachments(extension, ignore_version=True)
# move up all @import rules to the top
matches = []
css = re.sub(self.rx_css_import, lambda matchobj: matches.append(matchobj.group(0)) and '', css)
matches.append(css)
css = u'\n'.join(matches)
self.save_attachment("css", css)
attachments = self.get_attachments('css')
if is_minified:
# move up all @import rules to the top
matches.append(css)
css = u'\n'.join(matches)
self.save_attachment(extension, css)
attachments = self.get_attachments(extension)
else:
return self.css_with_sourcemap(u'\n'.join(matches))
return attachments
def css_with_sourcemap(self, content_import_rules):
"""Create the ir.attachment representing the not-minified content of the bundleCSS
and create/modify the ir.attachment representing the linked sourcemap.
:param content_import_rules: string containing all the @import rules to put at the beginning of the bundle
:return ir.attachment representing the un-minified content of the bundleCSS
"""
sourcemap_attachment = self.get_attachments('css.map') \
or self.save_attachment('css.map', '')
debug_asset_url = self.get_debug_asset_url(name=self.name,
extra='rtl/' if self.user_direction == 'rtl' else '')
generator = SourceMapGenerator(
source_root="/".join(
[".." for i in range(0, len(debug_asset_url.split("/")) - 2)]
) + "/",
)
self.preprocess_css()
# adds the @import rules at the beginning of the bundle
content_bundle_list = [content_import_rules]
content_line_count = len(content_import_rules.split("\n"))
for asset in self.stylesheets:
if asset.content:
content = asset.with_header(asset.content)
if asset.url:
generator.add_source(asset.url, content, content_line_count)
# comments all @import rules that have been added at the beginning of the bundle
content = re.sub(self.rx_css_import, lambda matchobj: f"/* {matchobj.group(0)} */", content)
content_bundle_list.append(content)
content_line_count += len(content.split("\n"))
content_bundle = '\n'.join(content_bundle_list) + f"\n//*# sourceMappingURL={sourcemap_attachment.url} */"
css_attachment = self.save_attachment('css', content_bundle)
generator._file = css_attachment.url
sourcemap_attachment.write({
"raw": generator.get_content(),
})
return css_attachment
def dialog_message(self, message):
"""
Returns a JS script which shows a warning to the user on page load.
@@ -588,26 +602,6 @@ class AssetsBundle(object):
asset = next(asset for asset in self.stylesheets if asset.id == asset_id)
asset._content = fragments.pop(0)
if debug:
try:
fname = os.path.basename(asset.url)
url = asset.html_url
with self.env.cr.savepoint():
self.env['ir.attachment'].sudo().create(dict(
raw=asset.content.encode('utf8'),
mimetype='text/css',
type='binary',
name=fname,
url=url,
res_model=False,
res_id=False,
))
if self.env.context.get('commit_assetsbundle') is True:
self.env.cr.commit()
except psycopg2.Error:
pass
return '\n'.join(asset.minify() for asset in self.stylesheets)
def compile_css(self, compiler, source):
@@ -47,7 +47,7 @@ class TestJavascriptAssetsBundle(FileTouchable):
"""
user_direction = self.env['res.lang']._lang_get(lang or self.env.user.lang).direction
bundle = self.jsbundle_xmlid if extension in ['js', 'min.js'] else self.cssbundle_xmlid
rtl = 'rtl/' if extension == 'css' and user_direction == 'rtl' else ''
rtl = 'rtl/' if extension in ['css', 'min.css'] and user_direction == 'rtl' else ''
url = f'/web/assets/%-%/{rtl}{bundle}.{extension}'
domain = [('url', '=like', url)]
return self.env['ir.attachment'].search(domain)
@@ -249,8 +249,8 @@ class TestJavascriptAssetsBundle(FileTouchable):
# self.cssbundle_xlmid contains 3 rules
self.bundle = self._get_asset(self.cssbundle_xmlid)
self.bundle.css()
self.assertEqual(len(self._any_ira_for_bundle('css')), 1)
self.assertEqual(len(self.bundle.get_attachments('css')), 1)
self.assertEqual(len(self._any_ira_for_bundle('min.css')), 1)
self.assertEqual(len(self.bundle.get_attachments('min.css')), 1)
def test_09_css_access(self):
""" Checks that the bundle's cache is working, i.e. that a bundle creates only enough
@@ -259,19 +259,19 @@ class TestJavascriptAssetsBundle(FileTouchable):
bundle0 = self._get_asset(self.cssbundle_xmlid)
bundle0.css()
self.assertEqual(len(self._any_ira_for_bundle('css')), 1)
self.assertEqual(len(self._any_ira_for_bundle('min.css')), 1)
version0 = bundle0.version
ira0 = self._any_ira_for_bundle('css')
ira0 = self._any_ira_for_bundle('min.css')
date0 = ira0.create_date
bundle1 = self._get_asset(self.cssbundle_xmlid)
bundle1.css()
self.assertEqual(len(self._any_ira_for_bundle('css')), 1)
self.assertEqual(len(self._any_ira_for_bundle('min.css')), 1)
version1 = bundle1.version
ira1 = self._any_ira_for_bundle('css')
ira1 = self._any_ira_for_bundle('min.css')
date1 = ira1.create_date
self.assertEqual(version0, version1)
@@ -286,7 +286,7 @@ class TestJavascriptAssetsBundle(FileTouchable):
files0 = bundle0.files
version0 = bundle0.version
self.assertEqual(len(self._any_ira_for_bundle('css')), 1)
self.assertEqual(len(self._any_ira_for_bundle('min.css')), 1)
view_arch = """
<data>
@@ -312,7 +312,7 @@ class TestJavascriptAssetsBundle(FileTouchable):
self.assertNotEqual(version0, version1)
# check if the previous attachment are correctly cleaned
self.assertEqual(len(self._any_ira_for_bundle('css')), 1)
self.assertEqual(len(self._any_ira_for_bundle('min.css')), 1)
def test_12_css_debug(self):
""" Check that a bundle in debug mode outputs non-minified assets.
@@ -321,10 +321,11 @@ class TestJavascriptAssetsBundle(FileTouchable):
nodes = debug_bundle.to_node(debug='assets')
content = self._node_to_list(nodes)
# find back one of the original asset file
self.assertIn('/test_assetsbundle/static/src/css/test_cssfile1.css', content)
self.assertIn('/web/assets/debug/test_assetsbundle.bundle2.css', content)
# there shouldn't be any assets created in debug mode
self.assertEqual(len(self._any_ira_for_bundle('css')), 0)
# there should be one css asset created in debug mode
self.assertEqual(len(self._any_ira_for_bundle('css')), 1,
'there should be one css asset created in debug mode')
def test_14_duplicated_css_assets(self):
""" Checks that if the bundle's ir.attachment record is duplicated, the bundle is only sourced once. This could
@@ -332,18 +333,18 @@ class TestJavascriptAssetsBundle(FileTouchable):
"""
bundle0 = self._get_asset(self.cssbundle_xmlid)
bundle0.css()
self.assertEqual(len(self._any_ira_for_bundle('css')), 1)
self.assertEqual(len(self._any_ira_for_bundle('min.css')), 1)
# duplicate the asset bundle
ira0 = self._any_ira_for_bundle('css')
ira0 = self._any_ira_for_bundle('min.css')
ira1 = ira0.copy()
self.assertEqual(len(self._any_ira_for_bundle('css')), 2)
self.assertEqual(len(self._any_ira_for_bundle('min.css')), 2)
self.assertEqual(ira0.store_fname, ira1.store_fname)
# the ir.attachment records should be deduplicated in the bundle's content
nodes = bundle0.to_node()
content = self._node_to_list(nodes)
self.assertEqual(content[2].count('test_assetsbundle.bundle2.css'), 1)
self.assertEqual(content[2].count('test_assetsbundle.bundle2.min.css'), 1)
# Language direction specific tests
@@ -354,15 +355,15 @@ class TestJavascriptAssetsBundle(FileTouchable):
self.bundle = self._get_asset(self.cssbundle_xmlid, env=self.env(context={'lang': 'ar_SY'}))
# there shouldn't be any attachment associated to this bundle
self.assertEqual(len(self._any_ira_for_bundle('css', lang='ar_SY')), 0)
self.assertEqual(len(self.bundle.get_attachments('css')), 0)
self.assertEqual(len(self._any_ira_for_bundle('min.css', lang='ar_SY')), 0)
self.assertEqual(len(self.bundle.get_attachments('min.css')), 0)
# trigger the first generation and, thus, the first save in database
self.bundle.css()
# there should be one attachment associated to this bundle
self.assertEqual(len(self._any_ira_for_bundle('css', lang='ar_SY')), 1)
self.assertEqual(len(self.bundle.get_attachments('css')), 1)
self.assertEqual(len(self._any_ira_for_bundle('min.css', lang='ar_SY')), 1)
self.assertEqual(len(self.bundle.get_attachments('min.css')), 1)
def test_16_ltr_and_rtl_css_access(self):
""" Checks that the bundle's cache is working, i.e. that the bundle creates only one
@@ -373,19 +374,19 @@ class TestJavascriptAssetsBundle(FileTouchable):
ltr_bundle0 = self._get_asset(self.cssbundle_xmlid)
ltr_bundle0.css()
self.assertEqual(len(self._any_ira_for_bundle('css')), 1)
self.assertEqual(len(self._any_ira_for_bundle('min.css')), 1)
ltr_version0 = ltr_bundle0.version
ltr_ira0 = self._any_ira_for_bundle('css')
ltr_ira0 = self._any_ira_for_bundle('min.css')
ltr_date0 = ltr_ira0.create_date
ltr_bundle1 = self._get_asset(self.cssbundle_xmlid)
ltr_bundle1.css()
self.assertEqual(len(self._any_ira_for_bundle('css')), 1)
self.assertEqual(len(self._any_ira_for_bundle('min.css')), 1)
ltr_version1 = ltr_bundle1.version
ltr_ira1 = self._any_ira_for_bundle('css')
ltr_ira1 = self._any_ira_for_bundle('min.css')
ltr_date1 = ltr_ira1.create_date
self.assertEqual(ltr_version0, ltr_version1)
@@ -395,19 +396,19 @@ class TestJavascriptAssetsBundle(FileTouchable):
rtl_bundle0 = self._get_asset(self.cssbundle_xmlid, env=self.env(context={'lang': 'ar_SY'}))
rtl_bundle0.css()
self.assertEqual(len(self._any_ira_for_bundle('css', lang='ar_SY')), 1)
self.assertEqual(len(self._any_ira_for_bundle('min.css', lang='ar_SY')), 1)
rtl_version0 = rtl_bundle0.version
rtl_ira0 = self._any_ira_for_bundle('css', lang='ar_SY')
rtl_ira0 = self._any_ira_for_bundle('min.css', lang='ar_SY')
rtl_date0 = rtl_ira0.create_date
rtl_bundle1 = self._get_asset(self.cssbundle_xmlid, env=self.env(context={'lang': 'ar_SY'}))
rtl_bundle1.css()
self.assertEqual(len(self._any_ira_for_bundle('css', lang='ar_SY')), 1)
self.assertEqual(len(self._any_ira_for_bundle('min.css', lang='ar_SY')), 1)
rtl_version1 = rtl_bundle1.version
rtl_ira1 = self._any_ira_for_bundle('css', lang='ar_SY')
rtl_ira1 = self._any_ira_for_bundle('min.css', lang='ar_SY')
rtl_date1 = rtl_ira1.create_date
self.assertEqual(rtl_version0, rtl_version1)
@@ -418,7 +419,7 @@ class TestJavascriptAssetsBundle(FileTouchable):
# Check two bundles are available, one for ltr and one for rtl
css_bundles = self.env['ir.attachment'].search([
('url', '=like', '/web/assets/%-%/{0}%.{1}'.format(self.cssbundle_xmlid, 'css'))
('url', '=like', '/web/assets/%-%/{0}%.{1}'.format(self.cssbundle_xmlid, 'min.css'))
])
self.assertEqual(len(css_bundles), 2)
@@ -446,7 +447,7 @@ class TestJavascriptAssetsBundle(FileTouchable):
ltr_bundle1.css()
ltr_last_modified1 = ltr_bundle1.last_modified
ltr_version1 = ltr_bundle1.version
ltr_ira1 = self._any_ira_for_bundle('css')
ltr_ira1 = self._any_ira_for_bundle('min.css')
self.assertNotEqual(ltr_last_modified0, ltr_last_modified1)
self.assertNotEqual(ltr_version0, ltr_version1)
@@ -455,7 +456,7 @@ class TestJavascriptAssetsBundle(FileTouchable):
rtl_bundle1.css()
rtl_last_modified1 = rtl_bundle1.last_modified
rtl_version1 = rtl_bundle1.version
rtl_ira1 = self._any_ira_for_bundle('css', lang='ar_SY')
rtl_ira1 = self._any_ira_for_bundle('min.css', lang='ar_SY')
self.assertNotEqual(rtl_last_modified0, rtl_last_modified1)
self.assertNotEqual(rtl_version0, rtl_version1)
@@ -464,7 +465,7 @@ class TestJavascriptAssetsBundle(FileTouchable):
# check if the previous attachment is correctly cleaned
css_bundles = self.env['ir.attachment'].search([
('url', '=like', '/web/assets/%-%/{0}%.{1}'.format(self.cssbundle_xmlid, 'css'))
('url', '=like', '/web/assets/%-%/{0}%.{1}'.format(self.cssbundle_xmlid, 'min.css'))
])
self.assertEqual(len(css_bundles), 2)
@@ -484,7 +485,7 @@ class TestJavascriptAssetsBundle(FileTouchable):
rtl_version0 = rtl_bundle0.version
css_bundles = self.env['ir.attachment'].search([
('url', '=like', '/web/assets/%-%/{0}%.{1}'.format(self.cssbundle_xmlid, 'css'))
('url', '=like', '/web/assets/%-%/{0}%.{1}'.format(self.cssbundle_xmlid, 'min.css'))
])
self.assertEqual(len(css_bundles), 2)
@@ -507,7 +508,7 @@ class TestJavascriptAssetsBundle(FileTouchable):
ltr_bundle1.css()
ltr_files1 = ltr_bundle1.files
ltr_version1 = ltr_bundle1.version
ltr_ira1 = self._any_ira_for_bundle('css')
ltr_ira1 = self._any_ira_for_bundle('min.css')
self.assertNotEqual(ltr_files0, ltr_files1)
self.assertNotEqual(ltr_version0, ltr_version1)
@@ -516,7 +517,7 @@ class TestJavascriptAssetsBundle(FileTouchable):
rtl_bundle1.css()
rtl_files1 = rtl_bundle1.files
rtl_version1 = rtl_bundle1.version
rtl_ira1 = self._any_ira_for_bundle('css', lang='ar_SY')
rtl_ira1 = self._any_ira_for_bundle('min.css', lang='ar_SY')
self.assertNotEqual(rtl_files0, rtl_files1)
self.assertNotEqual(rtl_version0, rtl_version1)
@@ -526,7 +527,7 @@ class TestJavascriptAssetsBundle(FileTouchable):
# check if the previous attachment are correctly cleaned
css_bundles = self.env['ir.attachment'].search([
('url', '=like', '/web/assets/%-%/{0}%.{1}'.format(self.cssbundle_xmlid, 'css'))
('url', '=like', '/web/assets/%-%/{0}%.{1}'.format(self.cssbundle_xmlid, 'min.css'))
])
self.assertEqual(len(css_bundles), 2)
@@ -537,14 +538,16 @@ class TestJavascriptAssetsBundle(FileTouchable):
nodes = debug_bundle.to_node(debug='assets')
content = self._node_to_list(nodes)
# css file should be available in assets bundle as user's lang direction is rtl
self.assertIn('/test_assetsbundle/static/src/css/test_cssfile1/rtl/{0}.css'.format(self.cssbundle_xmlid), content)
# there should be an css assets bundle in /debug/rtl if user's lang direction is rtl and debug=assets
self.assertIn('/web/assets/debug/rtl/{0}.css'.format(self.cssbundle_xmlid), content,
"there should be an css assets bundle in /debug/rtl if user's lang direction is rtl and debug=assets")
# there should be assets(css) created in debug mode as user's lang direction is rtl
# there should be an css assets bundle created in /rtl if user's lang direction is rtl and debug=assets
css_bundle = self.env['ir.attachment'].search([
('url', '=', '/test_assetsbundle/static/src/css/test_cssfile1/rtl/{0}.css'.format(self.cssbundle_xmlid))
('url', '=like', '/web/assets/%-%/rtl/{0}.css'.format(self.cssbundle_xmlid))
])
self.assertEqual(len(css_bundle), 1)
self.assertEqual(len(css_bundle), 1,
"there should be an css assets bundle created in /rtl if user's lang direction is rtl and debug=assets")
def test_20_exteral_lib_assets(self):
html = self.env['ir.ui.view']._render_template('test_assetsbundle.template2')
@@ -585,13 +588,14 @@ class TestJavascriptAssetsBundle(FileTouchable):
self.assertEqual(len(attachments), 1)
asset_data_css = etree.HTML(html).xpath('//*[@data-asset-xmlid]')[0]
asset_data_js = etree.HTML(html).xpath('//*[@data-asset-xmlid]')[2]
asset_data_js = etree.HTML(html).xpath('//*[@data-asset-xmlid]')[1]
format_data = {
"asset_xmlid_css": asset_data_css.attrib.get('data-asset-xmlid'),
"asset_version_css": asset_data_css.attrib.get('data-asset-version'),
"asset_xmlid_js": asset_data_js.attrib.get('data-asset-xmlid'),
"asset_version_js": asset_data_js.attrib.get('data-asset-version'),
"css": '/web/assets/debug/test_assetsbundle.bundle4.css',
"js": '/web/assets/debug/test_assetsbundle.bundle4.js',
}
self.assertEqual(html.strip(), ("""<!DOCTYPE html>
@@ -599,8 +603,7 @@ class TestJavascriptAssetsBundle(FileTouchable):
<head>
<link rel="stylesheet" href="http://test.external.link/style1.css"/>
<link rel="stylesheet" href="http://test.external.link/style2.css"/>
<link type="text/css" rel="stylesheet" href="/test_assetsbundle/static/src/css/test_cssfile1.css" data-asset-xmlid="%(asset_xmlid_css)s" data-asset-version="%(asset_version_css)s"/>
<link type="text/css" rel="stylesheet" href="/test_assetsbundle/static/src/css/test_cssfile2.css" data-asset-xmlid="%(asset_xmlid_css)s" data-asset-version="%(asset_version_css)s"/>
<link type="text/css" rel="stylesheet" href="%(css)s" data-asset-xmlid="%(asset_xmlid_js)s" data-asset-version="%(asset_version_css)s"/>
<meta/>
<script type="text/javascript" src="http://test.external.link/javascript1.js"></script>
<script type="text/javascript" src="http://test.external.link/javascript2.js"></script>
@@ -678,8 +681,8 @@ class TestAssetsBundleWithIRAMock(FileTouchable):
def _bundle(self, asset, should_create, should_unlink):
self.counter.clear()
asset.to_node(debug='assets')
self.assertEqual(self.counter['create'], int(should_create))
self.assertEqual(self.counter['unlink'], int(should_unlink))
self.assertEqual(self.counter['create'], 2 if should_create else 0)
self.assertEqual(self.counter['unlink'], 2 if should_unlink else 0)
def test_01_debug_mode_assets(self):
""" Checks that the ir.attachments records created for compiled assets in debug mode
+1 -1
View File
@@ -1252,7 +1252,7 @@ class DisableCacheMiddleware(object):
root.setup_session(req)
if req.session and req.session.debug and not 'wkhtmltopdf' in req.headers.get('User-Agent'):
if "assets" in req.session.debug and ".js" in req.base_url:
if "assets" in req.session.debug and (".js" in req.base_url or ".css" in req.base_url):
new_headers = [('Cache-Control', 'no-store')]
else:
new_headers = [('Cache-Control', 'no-cache')]
+1 -1
View File
@@ -22,4 +22,4 @@ from .convert import *
from .template_inheritance import *
from . import osutil
from .js_transpiler import transpile_javascript, is_odoo_module, URL_RE, ODOO_MODULE_RE
from .js_sourcemap import SourceMapGenerator
from .sourcemap_generator import SourceMapGenerator
@@ -1,9 +1,10 @@
from functools import lru_cache
import json
class SourceMapGenerator:
"""
The SourceMapGenerator creates the sourcemap maps the asset bundle to the js files.
The SourceMapGenerator creates the sourcemap maps the asset bundle to the js/css files.
What is a sourcemap ? (https://developer.mozilla.org/en-US/docs/Tools/Debugger/How_to/Use_a_source_map)
In brief: a source map is what makes possible to debug your processed/compiled/minified code as if you were
@@ -83,6 +84,14 @@ class SourceMapGenerator:
return mapping
def get_content(self):
"""Generates the content of the sourcemap.
:return the content of the sourcemap as a string encoded in UTF-8.
"""
# Store with XSSI-prevention prefix
return b")]}'\n" + json.dumps(self.to_json()).encode('utf8')
def add_source(self, source_name, source_content, last_index, start_offset=0):
"""Adds a new source file in the sourcemap. All the lines of the source file will be mapped line by line
to the generated file from the (last_index + start_offset). All lines between