Skip to content

Commit ec660fe

Browse files
committed
Add sourcemap support
1 parent 6f661f0 commit ec660fe

File tree

5 files changed

+112
-18
lines changed

5 files changed

+112
-18
lines changed

pipeline/compressors/__init__.py

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import posixpath
66
import re
77
import subprocess
8+
import warnings
89

910
from itertools import takewhile
1011

@@ -57,29 +58,46 @@ def css_compressor(self):
5758

5859
def compress_js(self, paths, templates=None, **kwargs):
5960
"""Concatenate and compress JS files"""
61+
compressor = self.js_compressor
62+
63+
if settings.OUTPUT_SOURCEMAPS:
64+
if hasattr(compressor, 'compress_js_with_source_map'):
65+
if templates:
66+
warnings.warn("Source maps are not supported with javascript templates")
67+
else:
68+
return compressor(verbose=self.verbose).compress_js_with_source_map(paths)
69+
6070
js = self.concatenate(paths)
6171
if templates:
6272
js = js + self.compile_templates(templates)
6373

6474
if not settings.DISABLE_WRAPPER:
6575
js = "(function() {\n%s\n}).call(this);" % js
6676

67-
compressor = self.js_compressor
6877
if compressor:
6978
js = getattr(compressor(verbose=self.verbose), 'compress_js')(js)
7079

71-
return js
80+
return js, None
7281

7382
def compress_css(self, paths, output_filename, variant=None, **kwargs):
7483
"""Concatenate and compress CSS files"""
75-
css = self.concatenate_and_rewrite(paths, output_filename, variant)
7684
compressor = self.css_compressor
85+
86+
if settings.OUTPUT_SOURCEMAPS:
87+
if hasattr(compressor, 'compress_css_with_source_map'):
88+
if variant == "datauri":
89+
warnings.warn("Source maps are not supported with datauri variant")
90+
else:
91+
return (compressor(verbose=self.verbose)
92+
.compress_css_with_source_map(paths, output_filename))
93+
94+
css = self.concatenate_and_rewrite(paths, output_filename, variant)
7795
if compressor:
7896
css = getattr(compressor(verbose=self.verbose), 'compress_css')(css)
7997
if not variant:
80-
return css
98+
return css, None
8199
elif variant == "datauri":
82-
return self.with_data_uri(css)
100+
return self.with_data_uri(css), None
83101
else:
84102
raise CompressorError("\"%s\" is not a valid variant" % variant)
85103

@@ -242,9 +260,10 @@ def execute_command(self, command, content):
242260
argument_list.append(flattening_arg)
243261
else:
244262
argument_list.extend(flattening_arg)
263+
stdin = subprocess.PIPE if content else None
245264

246265
pipe = subprocess.Popen(argument_list, stdout=subprocess.PIPE,
247-
stdin=subprocess.PIPE, stderr=subprocess.PIPE)
266+
stdin=stdin, stderr=subprocess.PIPE)
248267
if content:
249268
content = smart_bytes(content)
250269
stdout, stderr = pipe.communicate(content)

pipeline/compressors/closure.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,64 @@
11
from __future__ import unicode_literals
22

3+
import os
4+
import re
5+
import tempfile
6+
7+
from django.contrib.staticfiles.storage import staticfiles_storage
8+
39
from pipeline.conf import settings
410
from pipeline.compressors import SubProcessCompressor
511

612

13+
source_map_re = re.compile((
14+
"(?:"
15+
"/\\*"
16+
"(?:\\s*\r?\n(?://)?)?"
17+
"(?:%(inner)s)"
18+
"\\s*"
19+
"\\*/"
20+
"|"
21+
"//(?:%(inner)s)"
22+
")"
23+
"\\s*$") % {'inner': r"""[#@] sourceMappingURL=([^\s'"]*)"""})
24+
25+
726
class ClosureCompressor(SubProcessCompressor):
27+
828
def compress_js(self, js):
929
command = (settings.CLOSURE_BINARY, settings.CLOSURE_ARGUMENTS)
1030
return self.execute_command(command, js)
31+
32+
def compress_js_with_source_map(self, paths):
33+
args = [settings.CLOSURE_BINARY, settings.CLOSURE_ARGUMENTS]
34+
abs_paths = []
35+
for path in paths:
36+
abs_path = staticfiles_storage.path(path)
37+
args += [
38+
'--source_map_location_mapping',
39+
"%s|%s" % (abs_path, staticfiles_storage.url(path))]
40+
abs_paths.append(abs_path)
41+
with open(abs_path) as f:
42+
content = f.read()
43+
matches = source_map_re.search(content)
44+
if matches:
45+
input_source_map = filter(None, matches.groups())[0]
46+
input_source_map_file = os.path.join(os.path.dirname(abs_path), input_source_map)
47+
args += [
48+
'--source_map_input',
49+
"%s|%s" % (abs_path, input_source_map_file)]
50+
51+
temp_file = tempfile.NamedTemporaryFile()
52+
53+
args += ["--create_source_map", temp_file.name]
54+
for path in abs_paths:
55+
args += ["--js", path]
56+
57+
js = self.execute_command(args, None)
58+
59+
with open(temp_file.name) as f:
60+
source_map = f.read()
61+
62+
temp_file.close()
63+
64+
return js, source_map

pipeline/conf.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030

3131
'DISABLE_WRAPPER': False,
3232

33+
'OUTPUT_SOURCEMAPS': False,
34+
3335
'CSSTIDY_BINARY': '/usr/bin/env csstidy',
3436
'CSSTIDY_ARGUMENTS': '--template=highest',
3537

pipeline/packager.py

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from __future__ import unicode_literals
2+
import os.path
23

34
from django.contrib.staticfiles.storage import staticfiles_storage
45
from django.contrib.staticfiles.finders import find
@@ -92,24 +93,38 @@ def individual_url(self, filename):
9293

9394
def pack_stylesheets(self, package, **kwargs):
9495
return self.pack(package, self.compressor.compress_css, css_compressed,
96+
compress_type='css',
9597
output_filename=package.output_filename,
9698
variant=package.variant, **kwargs)
9799

98100
def compile(self, paths, force=False):
99101
return self.compiler.compile(paths, force=force)
100102

101-
def pack(self, package, compress, signal, **kwargs):
103+
def pack(self, package, compress, signal, compress_type, **kwargs):
102104
output_filename = package.output_filename
103105
if self.verbose:
104106
print("Saving: %s" % output_filename)
105107
paths = self.compile(package.paths, force=True)
106-
content = compress(paths, **kwargs)
108+
content, source_map = compress(paths, **kwargs)
109+
if source_map is not None:
110+
source_map_output_filename = output_filename + '.map'
111+
if self.verbose:
112+
print("Saving: %s" % source_map_output_filename)
113+
self.save_file(source_map_output_filename, source_map)
114+
source_map_comment = "sourceMappingURL=%s" % (
115+
os.path.basename(staticfiles_storage.url(source_map_output_filename)))
116+
if compress_type == 'js':
117+
content += "\n//# %s" % source_map_comment
118+
else:
119+
content += "\n/*# %s */" % source_map_comment
120+
yield source_map_output_filename
107121
self.save_file(output_filename, content)
108122
signal.send(sender=self, package=package, **kwargs)
109-
return output_filename
123+
yield output_filename
110124

111125
def pack_javascripts(self, package, **kwargs):
112-
return self.pack(package, self.compressor.compress_js, js_compressed, templates=package.templates, **kwargs)
126+
return self.pack(package, self.compressor.compress_js, js_compressed,
127+
compress_type='js', templates=package.templates, **kwargs)
113128

114129
def pack_templates(self, package):
115130
return self.compressor.compile_templates(package.templates)

pipeline/storage.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,18 +21,22 @@ def post_process(self, paths, dry_run=False, **options):
2121
packager = Packager(storage=self)
2222
for package_name in packager.packages['css']:
2323
package = packager.package_for('css', package_name)
24-
output_file = package.output_filename
2524
if self.packing:
26-
packager.pack_stylesheets(package)
27-
paths[output_file] = (self, output_file)
28-
yield output_file, output_file, True
25+
output_files = packager.pack_stylesheets(package)
26+
else:
27+
output_files = [package.output_filename]
28+
for output_file in output_files:
29+
paths[output_file] = (self, output_file)
30+
yield output_file, output_file, True
2931
for package_name in packager.packages['js']:
3032
package = packager.package_for('js', package_name)
31-
output_file = package.output_filename
3233
if self.packing:
33-
packager.pack_javascripts(package)
34-
paths[output_file] = (self, output_file)
35-
yield output_file, output_file, True
34+
output_files = packager.pack_javascripts(package)
35+
else:
36+
output_files = [package.output_filename]
37+
for output_file in output_files:
38+
paths[output_file] = (self, output_file)
39+
yield output_file, output_file, True
3640

3741
super_class = super(PipelineMixin, self)
3842
if hasattr(super_class, 'post_process'):

0 commit comments

Comments
 (0)