From fbd367bcdda77a0be87156a0f18ae6b8a02124fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Luk=C3=A1cs?= Date: Fri, 14 Feb 2025 19:17:04 +0100 Subject: [PATCH 1/2] Added new confinfiguration option `HTML_COMPRESSOR` and implemented new HTML compressors: `csshtmljsminify`, `djangohtml` and `minifyhtml` --- docs/compressors.rst | 41 ++++++++++++++++++++++++- docs/configuration.rst | 9 ++++++ pipeline/compressors/__init__.py | 14 +++++++++ pipeline/compressors/csshtmljsminify.py | 5 +++ pipeline/compressors/djangohtml.py | 12 ++++++++ pipeline/compressors/minifyhtml.py | 14 +++++++++ pipeline/conf.py | 8 +++++ pipeline/middleware.py | 6 ++-- 8 files changed, 106 insertions(+), 3 deletions(-) create mode 100644 pipeline/compressors/djangohtml.py create mode 100644 pipeline/compressors/minifyhtml.py diff --git a/docs/compressors.rst b/docs/compressors.rst index 72977140..b433aebe 100644 --- a/docs/compressors.rst +++ b/docs/compressors.rst @@ -241,7 +241,7 @@ css-html-js-minify compressor ============================= The css-html-js-minify is full Python compressor using `css-html-js-minify `_ -for compressing javascript and stylesheets. +for compressing javascript, stylesheets and HTML. To use it for your stylesheets add this to your ``PIPELINE['CSS_COMPRESSOR']`` :: @@ -251,11 +251,49 @@ To use it for your javascripts add this to your ``PIPELINE['JS_COMPRESSOR']`` :: PIPELINE['JS_COMPRESSOR'] = 'pipeline.compressors.csshtmljsminify.CssHtmlJsMinifyCompressor' +To use it for your HTML add this to your ``PIPELINE['HTML_COMPRESSOR']`` :: + + PIPELINE['HTML_COMPRESSOR'] = 'pipeline.compressors.csshtmljsminify.CssHtmlJsMinifyCompressor' + Install the css-html-js-minify library with your favorite Python package manager :: pip install css-html-js-minify +minify-html compressor +====================== + +The minify-html is a Rust HTML minifier with binding to Python +`minify-html `_ +for compressing HTML. + +To use it for your HTML add this to your ``PIPELINE['HTML_COMPRESSOR']`` :: + + PIPELINE['HTML_COMPRESSOR'] = 'pipeline.compressors.minifyhtml.MinifyHtmlCompressor' + +Install the minify-html library with your favorite Python package manager :: + + pip install minify-html + +``MINIFYHTML_PARAMS`` +--------------------- + +Additional parameters to use when ``minify_html.minify()`` is called. + +Defaults to ``{'do_not_minify_doctype': True, 'ensure_spec_compliant_unquoted_attribute_values': True, 'keep_spaces_between_attributes': True, 'minify_css': True, 'minify_js': True}`` + + +Django HTML compressor +====================== + +Django's ``strip_spaces_between_tags()`` from ``django.utils.html`` to simply +strip spaces between HTML tags. + +To use it for your HTML add this to your ``PIPELINE['HTML_COMPRESSOR']`` :: + + PIPELINE['HTML_COMPRESSOR'] = 'pipeline.compressors.djangohtml.DjangoHtmlCompressor' + + No-Op Compressors ================= @@ -266,6 +304,7 @@ To use it, add this to your settings :: PIPELINE['CSS_COMPRESSOR'] = 'pipeline.compressors.NoopCompressor' PIPELINE['JS_COMPRESSOR'] = 'pipeline.compressors.NoopCompressor' + PIPELINE['HTML_COMPRESSOR'] = 'pipeline.compressors.NoopCompressor' Write your own compressor class diff --git a/docs/configuration.rst b/docs/configuration.rst index 13c807c9..f8caa619 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -188,6 +188,15 @@ Defaults to ``'pipeline.compressors.yuglify.YuglifyCompressor'`` Please note that in order to use Yuglify compressor, you need to install Yuglify (see :doc:`installation` for more details). +``HTML_COMPRESSOR`` +................... + +Compressor class to be applied to HTML responses. + +If empty or ``None``, HTML responses won't be compressed. + +Defaults to ``'pipeline.compressors.djangohtml.DjangoHtmlCompressor'`` + ``TEMPLATE_NAMESPACE`` ...................... diff --git a/pipeline/compressors/__init__.py b/pipeline/compressors/__init__.py index c8f74665..a5475f3a 100644 --- a/pipeline/compressors/__init__.py +++ b/pipeline/compressors/__init__.py @@ -105,6 +105,10 @@ def js_compressor(self): def css_compressor(self): return to_class(settings.CSS_COMPRESSOR) + @property + def html_compressor(self): + return to_class(settings.HTML_COMPRESSOR) + def compress_js( self, paths: Sequence[str], @@ -157,6 +161,13 @@ def compress_css(self, paths, output_filename, variant=None, **kwargs): else: raise CompressorError(f'"{variant}" is not a valid variant') + def compress_html(self, html, **kwargs): + """Minify HTML response""" + compressor = self.html_compressor + if compressor: + html = getattr(compressor(verbose=self.verbose), "compress_html")(html) + return html + def compile_templates(self, paths): compiled = [] if not paths: @@ -441,3 +452,6 @@ def compress_js(self, js): def compress_css(self, css): return css + + def compress_html(self, html): + return html diff --git a/pipeline/compressors/csshtmljsminify.py b/pipeline/compressors/csshtmljsminify.py index 7e306a83..ef2d75a7 100644 --- a/pipeline/compressors/csshtmljsminify.py +++ b/pipeline/compressors/csshtmljsminify.py @@ -16,3 +16,8 @@ def compress_js(self, js): from css_html_js_minify import js_minify return js_minify(js) + + def compress_html(self, html): + from css_html_js_minify import html_minify + + return html_minify(html) diff --git a/pipeline/compressors/djangohtml.py b/pipeline/compressors/djangohtml.py new file mode 100644 index 00000000..cc2617aa --- /dev/null +++ b/pipeline/compressors/djangohtml.py @@ -0,0 +1,12 @@ +from pipeline.compressors import CompressorBase + + +class DjangoHtmlCompressor(CompressorBase): + """ + Simple Django HTML minifier using 'django.utils.html.strip_spaces_between_tags()' + """ + + def compress_html(self, html): + from django.utils.html import strip_spaces_between_tags as minify_html + + return minify_html(html.strip()) diff --git a/pipeline/compressors/minifyhtml.py b/pipeline/compressors/minifyhtml.py new file mode 100644 index 00000000..eaa8d943 --- /dev/null +++ b/pipeline/compressors/minifyhtml.py @@ -0,0 +1,14 @@ +from pipeline.compressors import CompressorBase +from pipeline.conf import settings + + +class MinifyHtmlCompressor(CompressorBase): + """ + HTML compressor based on the Python library minify-html + (https://pypi.org/project/minify-html). + """ + + def compress_html(self, html): + import minify_html + + return minify_html.minify(html, **settings.MINIFYHTML_PARAMS) diff --git a/pipeline/conf.py b/pipeline/conf.py index a36643e5..abfab3c4 100644 --- a/pipeline/conf.py +++ b/pipeline/conf.py @@ -14,6 +14,7 @@ "SHOW_ERRORS_INLINE": _settings.DEBUG, "CSS_COMPRESSOR": "pipeline.compressors.yuglify.YuglifyCompressor", "JS_COMPRESSOR": "pipeline.compressors.yuglify.YuglifyCompressor", + "HTML_COMPRESSOR": "pipeline.compressors.djangohtml.DjangoHtmlCompressor", "COMPILERS": [], "STYLESHEETS": {}, "JAVASCRIPT": {}, @@ -53,6 +54,13 @@ "STYLUS_ARGUMENTS": "", "LESS_BINARY": "/usr/bin/env lessc", "LESS_ARGUMENTS": "", + "MINIFYHTML_PARAMS": { + 'do_not_minify_doctype': True, + 'ensure_spec_compliant_unquoted_attribute_values': True, + 'keep_spaces_between_attributes': True, + 'minify_css': True, + 'minify_js': True, + }, "MIMETYPES": ( (("text/coffeescript"), (".coffee")), (("text/less"), (".less")), diff --git a/pipeline/middleware.py b/pipeline/middleware.py index a37eb01b..36cf422b 100644 --- a/pipeline/middleware.py +++ b/pipeline/middleware.py @@ -1,8 +1,9 @@ from django.core.exceptions import MiddlewareNotUsed from django.utils.deprecation import MiddlewareMixin from django.utils.encoding import DjangoUnicodeDecodeError -from django.utils.html import strip_spaces_between_tags as minify_html +from django.contrib.staticfiles.storage import staticfiles_storage +from pipeline.compressors import Compressor from pipeline.conf import settings @@ -17,8 +18,9 @@ def process_response(self, request, response): response.has_header("Content-Type") and "text/html" in response["Content-Type"] ): + compressor = Compressor(storage=staticfiles_storage, verbose=False) try: - response.content = minify_html(response.content.decode("utf-8").strip()) + response.content = compressor.compress_html(response.content.decode("utf-8")) response["Content-Length"] = str(len(response.content)) except DjangoUnicodeDecodeError: pass From 45a4b13632493d31d0e3ea103aafbcd9c4f047eb Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 14 Feb 2025 18:19:22 +0000 Subject: [PATCH 2/2] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- pipeline/conf.py | 10 +++++----- pipeline/middleware.py | 6 ++++-- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/pipeline/conf.py b/pipeline/conf.py index abfab3c4..b04f8e20 100644 --- a/pipeline/conf.py +++ b/pipeline/conf.py @@ -55,11 +55,11 @@ "LESS_BINARY": "/usr/bin/env lessc", "LESS_ARGUMENTS": "", "MINIFYHTML_PARAMS": { - 'do_not_minify_doctype': True, - 'ensure_spec_compliant_unquoted_attribute_values': True, - 'keep_spaces_between_attributes': True, - 'minify_css': True, - 'minify_js': True, + "do_not_minify_doctype": True, + "ensure_spec_compliant_unquoted_attribute_values": True, + "keep_spaces_between_attributes": True, + "minify_css": True, + "minify_js": True, }, "MIMETYPES": ( (("text/coffeescript"), (".coffee")), diff --git a/pipeline/middleware.py b/pipeline/middleware.py index 36cf422b..a59fc0b8 100644 --- a/pipeline/middleware.py +++ b/pipeline/middleware.py @@ -1,7 +1,7 @@ +from django.contrib.staticfiles.storage import staticfiles_storage from django.core.exceptions import MiddlewareNotUsed from django.utils.deprecation import MiddlewareMixin from django.utils.encoding import DjangoUnicodeDecodeError -from django.contrib.staticfiles.storage import staticfiles_storage from pipeline.compressors import Compressor from pipeline.conf import settings @@ -20,7 +20,9 @@ def process_response(self, request, response): ): compressor = Compressor(storage=staticfiles_storage, verbose=False) try: - response.content = compressor.compress_html(response.content.decode("utf-8")) + response.content = compressor.compress_html( + response.content.decode("utf-8") + ) response["Content-Length"] = str(len(response.content)) except DjangoUnicodeDecodeError: pass