Skip to content

Commit f3a1690

Browse files
author
Andrey Fedoseev
committed
Merge pull request #68 from andreyfedoseev/source-maps
Add support for source maps
2 parents aa128c4 + 415665b commit f3a1690

File tree

15 files changed

+373
-164
lines changed

15 files changed

+373
-164
lines changed

CHANGES.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,16 @@
22
Changes
33
=======
44

5+
Dev
6+
===
7+
8+
- Add source maps support for SASS/SCSS
9+
- Add source maps support for LESS
10+
- Add source maps support for CoffeeScript
11+
- Add source maps support for Stylus
12+
- Add source maps support for Babel
13+
14+
515
1.0.1
616
=====
717

README.rst

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -174,10 +174,13 @@ CoffeeScript
174174
``executable``
175175
Path to CoffeeScript compiler executable. Default: ``"coffee"``.
176176

177+
``sourcemap_enabled``
178+
Boolean. Set to ``True`` to enable source maps. Default: ``False``
179+
177180
Example::
178181

179182
STATIC_PRECOMPILER_COMPILERS = (
180-
('static_precompiler.compilers.CoffeeScript', {"executable": "/usr/bin/coffee"}),
183+
('static_precompiler.compilers.CoffeeScript', {"executable": "/usr/bin/coffee", "sourcemap_enabled": True}),
181184
)
182185

183186

@@ -187,13 +190,16 @@ Babel
187190
``executable``
188191
Path to Babel compiler executable. Default: ``"babel"``.
189192

193+
``sourcemap_enabled``
194+
Boolean. Set to ``True`` to enable source maps. Default: ``False``
195+
190196
``modules``
191197
Babel `modules <https://babeljs.io/docs/usage/modules/>`_ command line option. Default: ``None`` (uses Babel's default option).
192198

193199
Example::
194200

195201
STATIC_PRECOMPILER_COMPILERS = (
196-
('static_precompiler.compilers.Babel', {"executable": "/usr/bin/babel", "modules": "amd"}),
202+
('static_precompiler.compilers.Babel', {"executable": "/usr/bin/babel", "sourcemap_enabled": True, "modules": "amd"}),
197203
)
198204

199205

@@ -203,13 +209,16 @@ SASS / SCSS
203209
``executable``
204210
Path to SASS compiler executable. Default: "sass".
205211

212+
``sourcemap_enabled``
213+
Boolean. Set to ``True`` to enable source maps. Default: ``False``
214+
206215
``compass_enabled``
207216
Boolean. Whether to use compass or not. Compass must be installed in your system. Run "sass --compass" and if no error is shown it means that compass is installed.
208217

209218
Example::
210219

211220
STATIC_PRECOMPILER_COMPILERS = (
212-
('static_precompiler.compilers.SCSS', {"executable": "/usr/bin/sass", "compass_enabled": True}),
221+
('static_precompiler.compilers.SCSS', {"executable": "/usr/bin/sass", "sourcemap_enabled": True, "compass_enabled": True}),
213222
)
214223

215224

@@ -219,10 +228,13 @@ LESS
219228
``executable``
220229
Path to LESS compiler executable. Default: ``"lessc"``.
221230

231+
``sourcemap_enabled``
232+
Boolean. Set to ``True`` to enable source maps. Default: ``False``
233+
222234
Example::
223235

224236
STATIC_PRECOMPILER_COMPILERS = (
225-
('static_precompiler.compilers.LESS', {"executable": "/usr/bin/lessc"),
237+
('static_precompiler.compilers.LESS', {"executable": "/usr/bin/lessc", "sourcemap_enabled": True),
226238
)
227239

228240

@@ -232,10 +244,13 @@ Stylus
232244
``executable``
233245
Path to Stylus compiler executable. Default: ``"stylus"``.
234246

247+
``sourcemap_enabled``
248+
Boolean. Set to ``True`` to enable source maps. Default: ``False``
249+
235250
Example::
236251

237252
STATIC_PRECOMPILER_COMPILERS = (
238-
('static_precompiler.compilers.Stylus', {"executable": "/usr/bin/stylus"),
253+
('static_precompiler.compilers.Stylus', {"executable": "/usr/bin/stylus", "sourcemap_enabled": True),
239254
)
240255

241256

static_precompiler/compilers/babel.py

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
import json
2+
import os
3+
import posixpath
4+
15
from static_precompiler import exceptions, utils
26

37
from . import base
@@ -13,13 +17,52 @@ class Babel(base.BaseCompiler):
1317
input_extension = "es6"
1418
output_extension = "js"
1519

16-
def __init__(self, executable="babel", modules=None):
20+
def __init__(self, executable="babel", sourcemap_enabled=False, modules=None):
1721
self.executable = executable
22+
self.is_sourcemap_enabled = sourcemap_enabled
1823
self.modules = modules
1924
super(Babel, self).__init__()
2025

2126
def compile_file(self, source_path):
22-
return self.compile_source(self.get_source(source_path))
27+
args = [
28+
self.executable,
29+
]
30+
31+
if self.is_sourcemap_enabled:
32+
args.append("-s")
33+
34+
if self.modules is not None:
35+
args.extend(["--modules", self.modules])
36+
37+
full_output_path = self.get_full_output_path(source_path)
38+
39+
full_output_dirname = os.path.dirname(full_output_path)
40+
if not os.path.exists(full_output_dirname):
41+
os.makedirs(full_output_dirname)
42+
43+
args.extend(["-o", full_output_path])
44+
args.append(self.get_full_source_path(source_path))
45+
46+
out, errors = utils.run_command(args)
47+
if errors:
48+
raise exceptions.StaticCompilationError(errors)
49+
50+
if self.is_sourcemap_enabled:
51+
sourcemap_full_path = full_output_path + ".map"
52+
53+
with open(sourcemap_full_path) as sourcemap_file:
54+
sourcemap = json.loads(sourcemap_file.read())
55+
56+
# Babel can't add correct relative paths in source map when the compiled file
57+
# is not in the same dir as the source file. We fix it here.
58+
sourcemap["sourceRoot"] = "../" * len(source_path.split("/")) + posixpath.dirname(source_path)
59+
sourcemap["sources"] = [os.path.basename(source) for source in sourcemap["sources"]]
60+
sourcemap["file"] = posixpath.basename(os.path.basename(full_output_path))
61+
62+
with open(sourcemap_full_path, "w") as sourcemap_file:
63+
sourcemap_file.write(json.dumps(sourcemap))
64+
65+
return self.get_output_path(source_path)
2366

2467
def compile_source(self, source):
2568
args = [

static_precompiler/compilers/base.py

Lines changed: 6 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -164,24 +164,6 @@ def get_source(self, source_path):
164164
with open(self.get_full_source_path(source_path)) as source:
165165
return source.read()
166166

167-
def write_output(self, output, source_path):
168-
""" Write the compiled output to a file.
169-
170-
:param output: compiled code
171-
:type output: str
172-
:param source_path: relative path to a source file
173-
:type source_path: str
174-
175-
"""
176-
output_path = self.get_full_output_path(source_path)
177-
output_dir = os.path.dirname(output_path)
178-
if not os.path.exists(output_dir):
179-
os.makedirs(output_dir)
180-
181-
compiled_file = open(output_path, "w+")
182-
compiled_file.write(output)
183-
compiled_file.close()
184-
185167
def compile(self, source_path, from_management=False):
186168
""" Compile the given source path and return relative path to the compiled file.
187169
Raise ValueError is the source file type is not supported.
@@ -198,18 +180,19 @@ def compile(self, source_path, from_management=False):
198180
raise ValueError("'{0}' file type is not supported by '{1}'".format(
199181
source_path, self.__class__.__name__
200182
))
183+
184+
compliled_path = self.get_output_path(source_path)
185+
201186
if self.should_compile(source_path, from_management=from_management):
202187

203-
compiled = self.compile_file(source_path)
204-
compiled = self.postprocess(compiled, source_path)
205-
self.write_output(compiled, source_path)
188+
compliled_path = self.compile_file(source_path)
206189

207190
if self.supports_dependencies:
208191
self.update_dependencies(source_path, self.find_dependencies(source_path))
209192

210193
logging.info("Compiled: '{0}'".format(source_path))
211194

212-
return self.get_output_path(source_path)
195+
return compliled_path
213196

214197
def compile_lazy(self, source_path):
215198
""" Return a lazy object which, when translated to string, compiles the specified source path and returns
@@ -226,7 +209,7 @@ def compile_lazy(self, source_path):
226209
compile_lazy = functional.lazy(compile_lazy, six.text_type)
227210

228211
def compile_file(self, source_path):
229-
""" Compile the source file. Return the compiled code.
212+
""" Compile the source file. Return the relative path to compiled file.
230213
May raise a StaticCompilationError if something goes wrong with compilation.
231214
232215
:param source_path: path to the source file
@@ -247,18 +230,6 @@ def compile_source(self, source):
247230
"""
248231
raise NotImplementedError
249232

250-
# noinspection PyMethodMayBeStatic,PyUnusedLocal
251-
def postprocess(self, compiled, source_path):
252-
""" Post-process the compiled code.
253-
254-
:param compiled: compiled code
255-
:type compiled: str
256-
:param source_path: relative path to a source file
257-
:type source_path: str
258-
:returns: str
259-
"""
260-
return compiled
261-
262233
def find_dependencies(self, source_path):
263234
""" Find the dependencies for the given source file.
264235

static_precompiler/compilers/coffeescript.py

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
import json
2+
import os
3+
import posixpath
4+
15
from static_precompiler import exceptions, settings, utils
26

37
from . import base
@@ -13,12 +17,45 @@ class CoffeeScript(base.BaseCompiler):
1317
input_extension = "coffee"
1418
output_extension = "js"
1519

16-
def __init__(self, executable=settings.COFFEESCRIPT_EXECUTABLE):
20+
def __init__(self, executable=settings.COFFEESCRIPT_EXECUTABLE, sourcemap_enabled=False):
1721
self.executable = executable
22+
self.is_sourcemap_enabled = sourcemap_enabled
1823
super(CoffeeScript, self).__init__()
1924

2025
def compile_file(self, source_path):
21-
return self.compile_source(self.get_source(source_path))
26+
full_output_path = self.get_full_output_path(source_path)
27+
args = [
28+
self.executable,
29+
"-c",
30+
]
31+
if self.is_sourcemap_enabled:
32+
args.append("-m")
33+
args.extend([
34+
"-o", os.path.dirname(full_output_path),
35+
self.get_full_source_path(source_path),
36+
])
37+
out, errors = utils.run_command(args)
38+
39+
if errors:
40+
raise exceptions.StaticCompilationError(errors)
41+
42+
if self.is_sourcemap_enabled:
43+
# Coffeescript writes source maps to compiled.map, not compiled.js.map
44+
sourcemap_full_path = os.path.splitext(full_output_path)[0] + ".map"
45+
46+
with open(sourcemap_full_path) as sourcemap_file:
47+
sourcemap = json.loads(sourcemap_file.read())
48+
49+
# CoffeeScript, unlike SASS, can't add correct relative paths in source map when the compiled file
50+
# is not in the same dir as the source file. We fix it here.
51+
sourcemap["sourceRoot"] = "../" * len(source_path.split("/")) + posixpath.dirname(source_path)
52+
sourcemap["sources"] = [os.path.basename(source) for source in sourcemap["sources"]]
53+
sourcemap["file"] = posixpath.basename(os.path.basename(full_output_path))
54+
55+
with open(sourcemap_full_path, "w") as sourcemap_file:
56+
sourcemap_file.write(json.dumps(sourcemap))
57+
58+
return self.get_output_path(source_path)
2259

2360
def compile_source(self, source):
2461
args = [

static_precompiler/compilers/less.py

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import json
12
import os
23
import posixpath
34
import re
@@ -21,8 +22,9 @@ class LESS(base.BaseCompiler):
2122
IMPORT_RE = re.compile(r"@import\s+(.+?)\s*;", re.DOTALL)
2223
IMPORT_ITEM_RE = re.compile(r"([\"'])(.+?)\1")
2324

24-
def __init__(self, executable=settings.LESS_EXECUTABLE):
25+
def __init__(self, executable=settings.LESS_EXECUTABLE, sourcemap_enabled=False):
2526
self.executable = executable
27+
self.is_sourcemap_enabled = sourcemap_enabled
2628
super(LESS, self).__init__()
2729

2830
def should_compile(self, source_path, from_management=False):
@@ -33,18 +35,46 @@ def should_compile(self, source_path, from_management=False):
3335

3436
def compile_file(self, source_path):
3537
full_source_path = self.get_full_source_path(source_path)
36-
args = [
37-
self.executable,
38-
full_source_path,
39-
]
38+
full_output_path = self.get_full_output_path(source_path)
39+
4040
# `cwd` is a directory containing `source_path`.
4141
# Ex: source_path = '1/2/3', full_source_path = '/abc/1/2/3' -> cwd = '/abc'
4242
cwd = os.path.normpath(os.path.join(full_source_path, *([".."] * len(source_path.split("/")))))
43-
out, errors = utils.run_command(args, None, cwd=cwd)
43+
44+
args = [
45+
self.executable
46+
]
47+
if self.is_sourcemap_enabled:
48+
args.extend([
49+
"--source-map"
50+
])
51+
52+
args.extend([
53+
self.get_full_source_path(source_path),
54+
full_output_path,
55+
])
56+
out, errors = utils.run_command(args, cwd=cwd)
4457
if errors:
4558
raise exceptions.StaticCompilationError(errors)
4659

47-
return out
60+
utils.convert_urls(full_output_path, source_path)
61+
62+
if self.is_sourcemap_enabled:
63+
sourcemap_full_path = full_output_path + ".map"
64+
65+
with open(sourcemap_full_path) as sourcemap_file:
66+
sourcemap = json.loads(sourcemap_file.read())
67+
68+
# LESS, unlike SASS, can't add correct relative paths in source map when the compiled file
69+
# is not in the same dir as the source file. We fix it here.
70+
sourcemap["sourceRoot"] = "../" * len(source_path.split("/")) + posixpath.dirname(source_path)
71+
72+
sourcemap["file"] = posixpath.basename(full_output_path)
73+
74+
with open(sourcemap_full_path, "w") as sourcemap_file:
75+
sourcemap_file.write(json.dumps(sourcemap))
76+
77+
return self.get_output_path(source_path)
4878

4979
def compile_source(self, source):
5080
args = [
@@ -59,9 +89,6 @@ def compile_source(self, source):
5989

6090
return out
6191

62-
def postprocess(self, compiled, source_path):
63-
return utils.convert_urls(compiled, source_path)
64-
6592
def find_imports(self, source):
6693
""" Find the imported files in the source code.
6794

0 commit comments

Comments
 (0)