[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:
@@ -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
@@ -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')]
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user