From 832c27ed18bd84b517a2b6de824cd9a96995ce7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Fri, 19 May 2017 12:28:20 +0200 Subject: [PATCH] Filter source maps from bundled assets At least for now. Might be re-evaluated in the future. URL rewrite filter for such a case already in place. --- src/octoprint/server/__init__.py | 60 ++++++--------- src/octoprint/server/util/webassets.py | 100 +++++++++++++++++++++++++ tests/server/util/test_webassets.py | 30 ++++++++ 3 files changed, 153 insertions(+), 37 deletions(-) create mode 100644 src/octoprint/server/util/webassets.py create mode 100644 tests/server/util/test_webassets.py diff --git a/src/octoprint/server/__init__.py b/src/octoprint/server/__init__.py index 3a972dfa..6b3c8cfc 100644 --- a/src/octoprint/server/__init__.py +++ b/src/octoprint/server/__init__.py @@ -1115,75 +1115,61 @@ class Server(object): less_core = list(dynamic_core_assets["less"]) + list(dynamic_plugin_assets["bundled"]["less"]) less_plugins = list(dynamic_plugin_assets["external"]["less"]) - from webassets.filter import register_filter, Filter - from webassets.filter.cssrewrite.base import PatternRewriter - import re - class LessImportRewrite(PatternRewriter): - name = "less_importrewrite" - - patterns = { - "import_rewrite": re.compile("(@import(\s+\(.*\))?\s+)\"(.*)\";") - } - - def import_rewrite(self, m): - import_with_options = m.group(1) - import_url = m.group(3) - - if not import_url.startswith("http:") and not import_url.startswith("https:") and not import_url.startswith("/"): - import_url = "../less/" + import_url - - return "{import_with_options}\"{import_url}\";".format(**locals()) - - class JsDelimiterBundler(Filter): - name = "js_delimiter_bundler" - options = {} - def input(self, _in, out, **kwargs): - out.write(_in.read()) - out.write("\n;\n") + # a couple of custom filters + from octoprint.server.util.webassets import LessImportRewrite, JsDelimiterBundler, SourceMapRewrite, SourceMapRemove + from webassets.filter import register_filter register_filter(LessImportRewrite) + register_filter(SourceMapRewrite) + register_filter(SourceMapRemove) register_filter(JsDelimiterBundler) # JS - js_libs_bundle = Bundle(*js_libs, output="webassets/packed_libs.js", filters="js_delimiter_bundler") + js_filters = ["sourcemap_remove", "js_delimiter_bundler"] - js_client_bundle = Bundle(*js_client, output="webassets/packed_client.js", filters="js_delimiter_bundler") - js_core_bundle = Bundle(*js_core, output="webassets/packed_core.js", filters="js_delimiter_bundler") + js_libs_bundle = Bundle(*js_libs, output="webassets/packed_libs.js", filters=",".join(js_filters)) + + js_client_bundle = Bundle(*js_client, output="webassets/packed_client.js", filters=",".join(js_filters)) + js_core_bundle = Bundle(*js_core, output="webassets/packed_core.js", filters=",".join(js_filters)) if len(js_plugins) == 0: js_plugins_bundle = Bundle(*[]) else: - js_plugins_bundle = Bundle(*js_plugins, output="webassets/packed_plugins.js", filters="js_delimiter_bundler") + js_plugins_bundle = Bundle(*js_plugins, output="webassets/packed_plugins.js", filters=",".join(js_filters)) - js_app_bundle = Bundle(js_plugins_bundle, js_core_bundle, output="webassets/packed_app.js", filters="js_delimiter_bundler") + js_app_bundle = Bundle(js_plugins_bundle, js_core_bundle, output="webassets/packed_app.js", filters=",".join(js_filters)) # CSS - css_libs_bundle = Bundle(*css_libs, output="webassets/packed_libs.css", filters="cssrewrite") + css_filters = ["cssrewrite"] + + css_libs_bundle = Bundle(*css_libs, output="webassets/packed_libs.css", filters=",".join(css_filters)) if len(css_core) == 0: css_core_bundle = Bundle(*[]) else: - css_core_bundle = Bundle(*css_core, output="webassets/packed_core.css", filters="cssrewrite") + css_core_bundle = Bundle(*css_core, output="webassets/packed_core.css", filters=",".join(css_filters)) if len(css_plugins) == 0: css_plugins_bundle = Bundle(*[]) else: - css_plugins_bundle = Bundle(*css_plugins, output="webassets/packed_plugins.css", filters="cssrewrite") + css_plugins_bundle = Bundle(*css_plugins, output="webassets/packed_plugins.css", filters=",".join(css_filters)) - css_app_bundle = Bundle(css_core, css_plugins, output="webassets/packed_app.css", filters="cssrewrite") + css_app_bundle = Bundle(css_core, css_plugins, output="webassets/packed_app.css", filters=",".join(css_filters)) # LESS + less_filters = ["cssrewrite", "less_importrewrite"] + if len(less_core) == 0: less_core_bundle = Bundle(*[]) else: - less_core_bundle = Bundle(*less_core, output="webassets/packed_core.less", filters="cssrewrite, less_importrewrite") + less_core_bundle = Bundle(*less_core, output="webassets/packed_core.less", filters=",".join(less_filters)) if len(less_plugins) == 0: less_plugins_bundle = Bundle(*[]) else: - less_plugins_bundle = Bundle(*less_plugins, output="webassets/packed_plugins.less", filters="cssrewrite, less_importrewrite") + less_plugins_bundle = Bundle(*less_plugins, output="webassets/packed_plugins.less", filters=",".join(less_filters)) - less_app_bundle = Bundle(less_core, less_plugins, output="webassets/packed_app.less", filters="cssrewrite, less_importrewrite") + less_app_bundle = Bundle(less_core, less_plugins, output="webassets/packed_app.less", filters=",".join(less_filters)) # asset registration assets.register("js_libs", js_libs_bundle) diff --git a/src/octoprint/server/util/webassets.py b/src/octoprint/server/util/webassets.py new file mode 100644 index 00000000..376de4ab --- /dev/null +++ b/src/octoprint/server/util/webassets.py @@ -0,0 +1,100 @@ +# coding=utf-8 +from __future__ import absolute_import, division, print_function + +__author__ = "Gina Häußge " +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' +__copyright__ = "Copyright (C) 2017 The OctoPrint Project - Released under terms of the AGPLv3 License" + +import re + +try: + from urllib import parse as urlparse +except: + import urlparse + +from webassets.filter import Filter +from webassets.filter.cssrewrite.base import PatternRewriter +import webassets.filter.cssrewrite.urlpath as urlpath + + +def replace_url(source_url, output_url, url): + # If path is an absolute one, keep it + parsed = urlparse.urlparse(url) + + if not parsed.scheme and not parsed.path.startswith('/'): + abs_source_url = urlparse.urljoin(source_url, url) + + # relpath() will not detect this case + if urlparse.urlparse(abs_source_url).scheme: + return abs_source_url + + # rewritten url: relative path from new location (output) + # to location of referenced file (source + current url) + url = urlpath.relpath(output_url, abs_source_url) + + return url + + +class UrlRewriter(PatternRewriter): + + def input(self, _in, out, **kw): + source, source_path, output, output_path = \ + kw['source'], kw['source_path'], kw['output'], kw['output_path'] + + self.source_path = source_path + self.output_path = output_path + self.source_url = self.ctx.resolver.resolve_source_to_url( + self.ctx, source_path, source) + self.output_url = self.ctx.resolver.resolve_output_to_url( + self.ctx, output) + + return super(UrlRewriter, self).input(_in, out, **kw) + + def replace_url(self, url): + return replace_url(self.source_url, self.output_url, url) + + +class LessImportRewrite(UrlRewriter): + name = "less_importrewrite" + + patterns = { + "import_rewrite": re.compile("(@import(\s+\(.*\))?\s+)\"(.*)\";") + } + + def import_rewrite(self, m): + import_with_options = m.group(1) + import_url = self.replace_url(m.group(3)) + return "{import_with_options}\"{import_url}\";".format(**locals()) + + +class SourceMapRewrite(UrlRewriter): + name = "sourcemap_urlrewrite" + + patterns = { + "url_rewrite": re.compile("(//#\s+sourceMappingURL=)(.*)") + } + + def url_rewrite(self, m): + mapping = m.group(1) + source_url = self.replace_url(m.group(2)) + return "{mapping}{source_url}".format(**locals()) + + +class SourceMapRemove(PatternRewriter): + name = "sourcemap_remove" + + patterns = { + "sourcemap_remove": re.compile("(//#\s+sourceMappingURL=)(.*)") + } + + def sourcemap_remove(self, m): + return "" + + +class JsDelimiterBundler(Filter): + name = "js_delimiter_bundler" + options = {} + + def input(self, _in, out, **kwargs): + out.write(_in.read()) + out.write("\n;\n") diff --git a/tests/server/util/test_webassets.py b/tests/server/util/test_webassets.py new file mode 100644 index 00000000..fa1aac36 --- /dev/null +++ b/tests/server/util/test_webassets.py @@ -0,0 +1,30 @@ +# coding=utf-8 +""" +Unit tests for ``octoprint.server.util.flask``. +""" + +from __future__ import absolute_import + +__author__ = "Gina Häußge " +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' +__copyright__ = "Copyright (C) 2016 The OctoPrint Project - Released under terms of the AGPLv3 License" + + +import unittest +import ddt + +from octoprint.server.util.webassets import replace_url + +@ddt.ddt +class UrlReplaceTest(unittest.TestCase): + + @ddt.data( + ("mytest/some/path/", "mytest/another/longer/path/", "http://example.com/foo.html", "http://example.com/foo.html"), + ("mytest/some/path/", "mytest/another/longer/path/", "/path/foo.html", "/path/foo.html"), + ("http://example.com/mytest/some/path/", "mytest/another/longer/path/", "../foo.html", "http://example.com/mytest/some/foo.html"), + ("mytest/some/path/", "mytest/another/longer/path/", "../foo.html", "../../../some/foo.html") + ) + @ddt.unpack + def test_replace_url(self, source_url, output_url, url, expected): + actual = replace_url(source_url, output_url, url) + self.assertEqual(actual, expected)