From 5c1bd405504cba56d0df2b39568c7e9ae57b0db9 Mon Sep 17 00:00:00 2001 From: GermanoGuerrini Date: Wed, 4 Dec 2013 15:02:46 +0100 Subject: [PATCH 1/2] Support for async/defer Now javascripts are compressed preserving async or defer. --- compressor/js.py | 32 ++++++++++++++++++-- compressor/templates/compressor/js_file.html | 2 +- compressor/tests/static/js/three.js | 1 + compressor/tests/static/js/two.js | 1 + compressor/tests/test_base.py | 28 +++++++++++++++++ compressor/tests/test_jinja2ext.py | 3 +- 6 files changed, 61 insertions(+), 6 deletions(-) create mode 100644 compressor/tests/static/js/three.js create mode 100644 compressor/tests/static/js/two.js diff --git a/compressor/js.py b/compressor/js.py index b087804..387669c 100644 --- a/compressor/js.py +++ b/compressor/js.py @@ -12,14 +12,40 @@ class JsCompressor(Compressor): def split_contents(self): if self.split_content: return self.split_content + self.nodes = [] for elem in self.parser.js_elems(): attribs = self.parser.elem_attribs(elem) if 'src' in attribs: basename = self.get_basename(attribs['src']) filename = self.get_filename(basename) content = (SOURCE_FILE, filename, basename, elem) - self.split_content.append(content) else: - content = self.parser.elem_content(elem) - self.split_content.append((SOURCE_HUNK, content, None, elem)) + content = (SOURCE_HUNK, self.parser.elem_content(elem), None, elem) + self.split_content.append(content) + extra_attr = '' + if 'async' in attribs: + extra_attr = ' async' + elif 'defer' in attribs: + extra_attr = ' defer' + # Append to the previous node if it had the same attribute + append_to_previous = self.nodes and self.nodes[-1][0] == extra_attr + if append_to_previous and settings.COMPRESS_ENABLED: + self.nodes[-1][1].split_content.append(content) + else: + node = JsCompressor(content=self.parser.elem_str(elem), + context=self.context) + node.split_content.append(content) + self.nodes.append((extra_attr, node)) return self.split_content + + def output(self, *args, **kwargs): + if (settings.COMPRESS_ENABLED or settings.COMPRESS_PRECOMPILERS or + kwargs.get('forced', False)): + self.split_contents() + if hasattr(self, 'nodes'): + ret = [] + for extra_attr, subnode in self.nodes: + subnode.extra_context.update({'extra_attr': extra_attr}) + ret.append(subnode.output(*args, **kwargs)) + return '\n'.join(ret) + return super(JsCompressor, self).output(*args, **kwargs) diff --git a/compressor/templates/compressor/js_file.html b/compressor/templates/compressor/js_file.html index 09d6a9b..e3f5d25 100644 --- a/compressor/templates/compressor/js_file.html +++ b/compressor/templates/compressor/js_file.html @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/compressor/tests/static/js/three.js b/compressor/tests/static/js/three.js new file mode 100644 index 0000000..e01a825 --- /dev/null +++ b/compressor/tests/static/js/three.js @@ -0,0 +1 @@ +hermanos = {} \ No newline at end of file diff --git a/compressor/tests/static/js/two.js b/compressor/tests/static/js/two.js new file mode 100644 index 0000000..595f5b5 --- /dev/null +++ b/compressor/tests/static/js/two.js @@ -0,0 +1 @@ +pollos = {} \ No newline at end of file diff --git a/compressor/tests/test_base.py b/compressor/tests/test_base.py index a68db96..66cbbe7 100644 --- a/compressor/tests/test_base.py +++ b/compressor/tests/test_base.py @@ -252,3 +252,31 @@ class CacheBackendTestCase(CompressorTestCase): def test_correct_backend(self): from compressor.cache import cache self.assertEqual(cache.__class__, locmem.CacheClass) + + +class JsAsyncDeferTestCase(SimpleTestCase): + def setUp(self): + self.js = """\ + + + + + + + """ + + def test_js_output(self): + def extract_attr(tag): + if tag.has_attr('async'): + return 'async' + if tag.has_attr('defer'): + return 'defer' + js_node = JsCompressor(self.js) + output = [None, 'async', 'defer', None, 'async', None] + if six.PY3: + scripts = make_soup(js_node.output()).find_all('script') + attrs = [extract_attr(i) for i in scripts] + else: + scripts = make_soup(js_node.output()).findAll('script') + attrs = [s.get('async') or s.get('defer') for s in scripts] + self.assertEqual(output, attrs) diff --git a/compressor/tests/test_jinja2ext.py b/compressor/tests/test_jinja2ext.py index c77580f..d2ab97d 100644 --- a/compressor/tests/test_jinja2ext.py +++ b/compressor/tests/test_jinja2ext.py @@ -56,8 +56,7 @@ class TestJinja2CompressorExtension(TestCase): self.assertEqual(tag_body, template.render()) def test_empty_tag(self): - template = self.env.from_string("""{% compress js %}{% block js %} - {% endblock %}{% endcompress %}""") + template = self.env.from_string("""{% compress js %}{% block js %}{% endblock %}{% endcompress %}""") context = {'STATIC_URL': settings.COMPRESS_URL} self.assertEqual('', template.render(context)) From a327860deba935dfd847702e5927ab1dab633419 Mon Sep 17 00:00:00 2001 From: GermanoGuerrini Date: Wed, 4 Dec 2013 15:02:46 +0100 Subject: [PATCH 2/2] Support for async/defer Now javascripts are compressed preserving async or defer. --- compressor/tests/test_base.py | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/compressor/tests/test_base.py b/compressor/tests/test_base.py index 66cbbe7..8917da8 100644 --- a/compressor/tests/test_base.py +++ b/compressor/tests/test_base.py @@ -63,6 +63,22 @@ class CompressorTestCase(SimpleTestCase): """ self.js_node = JsCompressor(self.js) + def assertEqualCollapsed(self, a, b): + """ + assertEqual with internal newlines collapsed to single, and + trailing whitespace removed. + """ + collapse = lambda x: re.sub(r'\n+', '\n', x).rstrip() + self.assertEqual(collapse(a), collapse(b)) + + def assertEqualSplits(self, a, b): + """ + assertEqual for splits, particularly ignoring the presence of + a trailing newline on the content. + """ + mangle = lambda split: [(x[0], x[1], x[2], x[3].rstrip()) for x in split] + self.assertEqual(mangle(a), mangle(b)) + def test_css_split(self): out = [ ( @@ -85,7 +101,7 @@ class CompressorTestCase(SimpleTestCase): ] split = self.css_node.split_contents() split = [(x[0], x[1], x[2], self.css_node.parser.elem_str(x[3])) for x in split] - self.assertEqual(out, split) + self.assertEqualSplits(split, out) def test_css_hunks(self): out = ['body { background:#990; }', 'p { border:5px solid green;}', 'body { color:#fff; }'] @@ -104,7 +120,7 @@ class CompressorTestCase(SimpleTestCase): def test_css_return_if_off(self): settings.COMPRESS_ENABLED = False - self.assertEqual(self.css, self.css_node.output()) + self.assertEqualCollapsed(self.css, self.css_node.output()) def test_cachekey(self): is_cachekey = re.compile(r'\w{12}') @@ -132,7 +148,7 @@ class CompressorTestCase(SimpleTestCase): ] split = self.js_node.split_contents() split = [(x[0], x[1], x[2], self.js_node.parser.elem_str(x[3])) for x in split] - self.assertEqual(out, split) + self.assertEqualSplits(split, out) def test_js_hunks(self): out = ['obj = {};', 'obj.value = "value";'] @@ -154,7 +170,7 @@ class CompressorTestCase(SimpleTestCase): @override_settings(COMPRESS_PRECOMPILERS=(), COMPRESS_ENABLED=False) def test_js_return_if_off(self): - self.assertEqual(self.js, self.js_node.output()) + self.assertEqualCollapsed(self.js, self.js_node.output()) def test_js_return_if_on(self): output = '' @@ -251,7 +267,7 @@ class CacheBackendTestCase(CompressorTestCase): def test_correct_backend(self): from compressor.cache import cache - self.assertEqual(cache.__class__, locmem.CacheClass) + self.assertEqual(cache.__class__, locmem.LocMemCache) class JsAsyncDeferTestCase(SimpleTestCase):