From 203647329dc7e8b4b3d3c863077659327db9bdb2 Mon Sep 17 00:00:00 2001 From: Ben Rudolph Date: Fri, 18 Dec 2015 16:48:55 +0200 Subject: [PATCH 1/6] Use jsUglify --- corehq/apps/style/uglify.py | 91 +++++++++++++++++++++++++++++++++++++ package.json | 5 +- settings.py | 1 + 3 files changed, 95 insertions(+), 2 deletions(-) create mode 100644 corehq/apps/style/uglify.py diff --git a/corehq/apps/style/uglify.py b/corehq/apps/style/uglify.py new file mode 100644 index 000000000000..43055f032610 --- /dev/null +++ b/corehq/apps/style/uglify.py @@ -0,0 +1,91 @@ +import subprocess + +from compressor.exceptions import FilterError +from compressor.filters import CompilerFilter +from compressor.js import JsCompressor +from compressor.utils.stringformat import FormattableString as fstr +from django.conf import settings +from django.utils.safestring import mark_safe + + +# For use with node.js' uglifyjs minifier +class UglifySourcemapFilter(CompilerFilter): + command = ( + "uglifyjs {infiles} -o {outfile} --source-map {mapfile}" + " --source-map-url {mapurl} --source-map-root {maproot} -c -m") + + def input(self, **kwargs): + return self.content + + def output(self, **kwargs): + options = dict(self.options) + options['outfile'] = kwargs['outfile'] + + infiles = [] + for infile in kwargs['content_meta']: + # type, full_filename, relative_filename + infiles.append(infile[1]) + + options['infiles'] = ' '.join(f for f in infiles) + + options['mapfile'] = kwargs['outfile'].replace('.js', '.map.js') + + options['mapurl'] = '{}{}'.format( + settings.STATIC_URL, options['mapfile']) + + options['maproot'] = settings.STATIC_URL + + self.cwd = kwargs['root_location'] + + try: + command = fstr(self.command).format(**options) + + proc = subprocess.Popen( + command, shell=True, cwd=self.cwd, stdout=self.stdout, + stdin=self.stdin, stderr=self.stderr) + err = proc.communicate() + except (IOError, OSError), e: + raise FilterError('Unable to apply %s (%r): %s' % + (self.__class__.__name__, self.command, e)) + else: + # If the process doesn't return a 0 success code, throw an error + if proc.wait() != 0: + if not err: + err = ('Unable to apply %s (%s)' % + (self.__class__.__name__, self.command)) + raise FilterError(err) + if self.verbose: + self.logger.debug(err) + + +class JsUglifySourcemapCompressor(JsCompressor): + + def output(self, mode='file', forced=False): + content = self.filter_input(forced) + if not content: + return '' + + concatenated_content = '\n'.join( + c.encode(self.charset) for c in content) + + if settings.COMPRESS_ENABLED or forced: + filepath = self.get_filepath(concatenated_content, basename=None) + + # UglifySourcemapFilter writes the file directly, as it needs to + # output the sourcemap as well + UglifySourcemapFilter(content).output( + outfile=filepath, + content_meta=self.split_content, + root_location=self.storage.base_location) + + return self.output_file(mode, filepath) + else: + return concatenated_content + + def output_file(self, mode, new_filepath): + """ + The output method that saves the content to a file and renders + the appropriate template with the file's URL. + """ + url = mark_safe(self.storage.url(new_filepath)) + return self.render_output(mode, {"url": url}) diff --git a/package.json b/package.json index a18f33f5488b..efe6fda41ac0 100644 --- a/package.json +++ b/package.json @@ -6,8 +6,9 @@ "dependencies": { "grunt": "^0.4.5", "grunt-cli": "^0.1.13", - "grunt-mocha": "^0.4.13", "grunt-contrib-watch": "^0.6.1", - "mocha": "^2.3.3" + "grunt-mocha": "^0.4.13", + "mocha": "^2.3.3", + "uglifyjs": "^2.4.10" } } diff --git a/settings.py b/settings.py index 0bec1a1ef542..b467c41399e1 100644 --- a/settings.py +++ b/settings.py @@ -1625,6 +1625,7 @@ } COMPRESS_CSS_HASHING_METHOD = 'content' +COMPRESS_JS_COMPRESSOR = 'corehq.apps.style.uglify.JsUglifySourcemapCompressor' if 'locmem' not in CACHES: From 3025c075e24ea8d5cb0b6eb6d3a9258724f3f3f8 Mon Sep 17 00:00:00 2001 From: Ben Rudolph Date: Fri, 18 Dec 2015 17:33:44 +0200 Subject: [PATCH 2/6] make dirs if they don't exist --- corehq/apps/style/uglify.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/corehq/apps/style/uglify.py b/corehq/apps/style/uglify.py index 43055f032610..93587c0d668f 100644 --- a/corehq/apps/style/uglify.py +++ b/corehq/apps/style/uglify.py @@ -1,3 +1,4 @@ +import os import subprocess from compressor.exceptions import FilterError @@ -69,6 +70,11 @@ def output(self, mode='file', forced=False): c.encode(self.charset) for c in content) if settings.COMPRESS_ENABLED or forced: + js_compress_dir = os.path.join( + settings.STATIC_ROOT, self.output_dir, self.output_prefix + ) + if not os.path.exists(js_compress_dir): + os.makedirs(js_compress_dir, 0775) filepath = self.get_filepath(concatenated_content, basename=None) # UglifySourcemapFilter writes the file directly, as it needs to From db3ca243653b857a0f6546974750eb2bed0e89d8 Mon Sep 17 00:00:00 2001 From: Ben Rudolph Date: Thu, 7 Jan 2016 13:28:37 -0500 Subject: [PATCH 3/6] use relative filename --- corehq/apps/style/uglify.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/corehq/apps/style/uglify.py b/corehq/apps/style/uglify.py index 93587c0d668f..4f2284361dd4 100644 --- a/corehq/apps/style/uglify.py +++ b/corehq/apps/style/uglify.py @@ -25,14 +25,15 @@ def output(self, **kwargs): infiles = [] for infile in kwargs['content_meta']: # type, full_filename, relative_filename - infiles.append(infile[1]) + infiles.append(infile[2]) options['infiles'] = ' '.join(f for f in infiles) options['mapfile'] = kwargs['outfile'].replace('.js', '.map.js') options['mapurl'] = '{}{}'.format( - settings.STATIC_URL, options['mapfile']) + settings.STATIC_URL, options['mapfile'] + ) options['maproot'] = settings.STATIC_URL From 57de0cef8095f64a45d2b9d82ea3e4138e947bba Mon Sep 17 00:00:00 2001 From: Ben Rudolph Date: Thu, 7 Jan 2016 13:51:16 -0500 Subject: [PATCH 4/6] Add uglify-js to installs --- .travis/matrix-installs.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis/matrix-installs.sh b/.travis/matrix-installs.sh index 85995bbd3f66..63686e5b1374 100755 --- a/.travis/matrix-installs.sh +++ b/.travis/matrix-installs.sh @@ -20,6 +20,7 @@ else fi if [ "${BOWER:-no}" = "yes" ]; then + npm install -g uglify-js npm install -g bower bower install fi From ff28467afbf04e133584655603f4418a2ab7768b Mon Sep 17 00:00:00 2001 From: Ben Rudolph Date: Fri, 8 Jan 2016 13:30:48 -0500 Subject: [PATCH 5/6] Fix tests --- corehq/apps/style/tests/test_compress_command.py | 1 + 1 file changed, 1 insertion(+) diff --git a/corehq/apps/style/tests/test_compress_command.py b/corehq/apps/style/tests/test_compress_command.py index d068485fca4b..b672cda2bea9 100644 --- a/corehq/apps/style/tests/test_compress_command.py +++ b/corehq/apps/style/tests/test_compress_command.py @@ -67,6 +67,7 @@ def _is_b3_base_template(self, template): return False def test_compress_offline(self): + call_command('collectstatic', verbosity=0, interactive=False) with patch('sys.stdout', new_callable=StringIO) as mock_stdout: call_command('compress', force=True) From cb6189fc8309b8e5f3ebfee5a754203eb9aac284 Mon Sep 17 00:00:00 2001 From: Ben Date: Wed, 13 Jan 2016 12:09:08 -0500 Subject: [PATCH 6/6] [ci skip] Add source --- corehq/apps/style/uglify.py | 1 + 1 file changed, 1 insertion(+) diff --git a/corehq/apps/style/uglify.py b/corehq/apps/style/uglify.py index 4f2284361dd4..1f35bc6ba97c 100644 --- a/corehq/apps/style/uglify.py +++ b/corehq/apps/style/uglify.py @@ -10,6 +10,7 @@ # For use with node.js' uglifyjs minifier +# Code taken from: https://roverdotcom.github.io/blog/2014/05/28/javascript-error-reporting-with-source-maps-in-django/ class UglifySourcemapFilter(CompilerFilter): command = ( "uglifyjs {infiles} -o {outfile} --source-map {mapfile}"