Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use jsUglify and have source maps! #9666

Merged
merged 6 commits into from Jan 13, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .travis/matrix-installs.sh
Expand Up @@ -20,6 +20,7 @@ else
fi

if [ "${BOWER:-no}" = "yes" ]; then
npm install -g uglify-js
npm install -g bower
bower install
fi
1 change: 1 addition & 0 deletions corehq/apps/style/tests/test_compress_command.py
Expand Up @@ -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)

Expand Down
99 changes: 99 additions & 0 deletions corehq/apps/style/uglify.py
@@ -0,0 +1,99 @@
import os
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
# Code taken from: https://roverdotcom.github.io/blog/2014/05/28/javascript-error-reporting-with-source-maps-in-django/
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

E501 line too long (119 > 115 characters)

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[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']
)

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:
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
# 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})
5 changes: 3 additions & 2 deletions package.json
Expand Up @@ -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"
}
}
1 change: 1 addition & 0 deletions settings.py
Expand Up @@ -1625,6 +1625,7 @@
}

COMPRESS_CSS_HASHING_METHOD = 'content'
COMPRESS_JS_COMPRESSOR = 'corehq.apps.style.uglify.JsUglifySourcemapCompressor'


if 'locmem' not in CACHES:
Expand Down