Skip to content
10 changes: 10 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@
Changes
=======

Dev
===

- Add source maps support for SASS/SCSS
- Add source maps support for LESS
- Add source maps support for CoffeeScript
- Add source maps support for Stylus
- Add source maps support for Babel


1.0.1
=====

Expand Down
25 changes: 20 additions & 5 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -174,10 +174,13 @@ CoffeeScript
``executable``
Path to CoffeeScript compiler executable. Default: ``"coffee"``.

``sourcemap_enabled``
Boolean. Set to ``True`` to enable source maps. Default: ``False``

Example::

STATIC_PRECOMPILER_COMPILERS = (
('static_precompiler.compilers.CoffeeScript', {"executable": "/usr/bin/coffee"}),
('static_precompiler.compilers.CoffeeScript', {"executable": "/usr/bin/coffee", "sourcemap_enabled": True}),
)


Expand All @@ -187,13 +190,16 @@ Babel
``executable``
Path to Babel compiler executable. Default: ``"babel"``.

``sourcemap_enabled``
Boolean. Set to ``True`` to enable source maps. Default: ``False``

``modules``
Babel `modules <https://babeljs.io/docs/usage/modules/>`_ command line option. Default: ``None`` (uses Babel's default option).

Example::

STATIC_PRECOMPILER_COMPILERS = (
('static_precompiler.compilers.Babel', {"executable": "/usr/bin/babel", "modules": "amd"}),
('static_precompiler.compilers.Babel', {"executable": "/usr/bin/babel", "sourcemap_enabled": True, "modules": "amd"}),
)


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

``sourcemap_enabled``
Boolean. Set to ``True`` to enable source maps. Default: ``False``

``compass_enabled``
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.

Example::

STATIC_PRECOMPILER_COMPILERS = (
('static_precompiler.compilers.SCSS', {"executable": "/usr/bin/sass", "compass_enabled": True}),
('static_precompiler.compilers.SCSS', {"executable": "/usr/bin/sass", "sourcemap_enabled": True, "compass_enabled": True}),
)


Expand All @@ -219,10 +228,13 @@ LESS
``executable``
Path to LESS compiler executable. Default: ``"lessc"``.

``sourcemap_enabled``
Boolean. Set to ``True`` to enable source maps. Default: ``False``

Example::

STATIC_PRECOMPILER_COMPILERS = (
('static_precompiler.compilers.LESS', {"executable": "/usr/bin/lessc"),
('static_precompiler.compilers.LESS', {"executable": "/usr/bin/lessc", "sourcemap_enabled": True),
)


Expand All @@ -232,10 +244,13 @@ Stylus
``executable``
Path to Stylus compiler executable. Default: ``"stylus"``.

``sourcemap_enabled``
Boolean. Set to ``True`` to enable source maps. Default: ``False``

Example::

STATIC_PRECOMPILER_COMPILERS = (
('static_precompiler.compilers.Stylus', {"executable": "/usr/bin/stylus"),
('static_precompiler.compilers.Stylus', {"executable": "/usr/bin/stylus", "sourcemap_enabled": True),
)


Expand Down
47 changes: 45 additions & 2 deletions static_precompiler/compilers/babel.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import json
import os
import posixpath

from static_precompiler import exceptions, utils

from . import base
Expand All @@ -13,13 +17,52 @@ class Babel(base.BaseCompiler):
input_extension = "es6"
output_extension = "js"

def __init__(self, executable="babel", modules=None):
def __init__(self, executable="babel", sourcemap_enabled=False, modules=None):
self.executable = executable
self.is_sourcemap_enabled = sourcemap_enabled
self.modules = modules
super(Babel, self).__init__()

def compile_file(self, source_path):
return self.compile_source(self.get_source(source_path))
args = [
self.executable,
]

if self.is_sourcemap_enabled:
args.append("-s")

if self.modules is not None:
args.extend(["--modules", self.modules])

full_output_path = self.get_full_output_path(source_path)

full_output_dirname = os.path.dirname(full_output_path)
if not os.path.exists(full_output_dirname):
os.makedirs(full_output_dirname)

args.extend(["-o", full_output_path])
args.append(self.get_full_source_path(source_path))

out, errors = utils.run_command(args)
if errors:
raise exceptions.StaticCompilationError(errors)

if self.is_sourcemap_enabled:
sourcemap_full_path = full_output_path + ".map"

with open(sourcemap_full_path) as sourcemap_file:
sourcemap = json.loads(sourcemap_file.read())

# Babel can't add correct relative paths in source map when the compiled file
# is not in the same dir as the source file. We fix it here.
sourcemap["sourceRoot"] = "../" * len(source_path.split("/")) + posixpath.dirname(source_path)
sourcemap["sources"] = [os.path.basename(source) for source in sourcemap["sources"]]
sourcemap["file"] = posixpath.basename(os.path.basename(full_output_path))

with open(sourcemap_full_path, "w") as sourcemap_file:
sourcemap_file.write(json.dumps(sourcemap))

return self.get_output_path(source_path)

def compile_source(self, source):
args = [
Expand Down
41 changes: 6 additions & 35 deletions static_precompiler/compilers/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,24 +164,6 @@ def get_source(self, source_path):
with open(self.get_full_source_path(source_path)) as source:
return source.read()

def write_output(self, output, source_path):
""" Write the compiled output to a file.

:param output: compiled code
:type output: str
:param source_path: relative path to a source file
:type source_path: str

"""
output_path = self.get_full_output_path(source_path)
output_dir = os.path.dirname(output_path)
if not os.path.exists(output_dir):
os.makedirs(output_dir)

compiled_file = open(output_path, "w+")
compiled_file.write(output)
compiled_file.close()

def compile(self, source_path, from_management=False):
""" Compile the given source path and return relative path to the compiled file.
Raise ValueError is the source file type is not supported.
Expand All @@ -198,18 +180,19 @@ def compile(self, source_path, from_management=False):
raise ValueError("'{0}' file type is not supported by '{1}'".format(
source_path, self.__class__.__name__
))

compliled_path = self.get_output_path(source_path)

if self.should_compile(source_path, from_management=from_management):

compiled = self.compile_file(source_path)
compiled = self.postprocess(compiled, source_path)
self.write_output(compiled, source_path)
compliled_path = self.compile_file(source_path)

if self.supports_dependencies:
self.update_dependencies(source_path, self.find_dependencies(source_path))

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

return self.get_output_path(source_path)
return compliled_path

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

def compile_file(self, source_path):
""" Compile the source file. Return the compiled code.
""" Compile the source file. Return the relative path to compiled file.
May raise a StaticCompilationError if something goes wrong with compilation.

:param source_path: path to the source file
Expand All @@ -247,18 +230,6 @@ def compile_source(self, source):
"""
raise NotImplementedError

# noinspection PyMethodMayBeStatic,PyUnusedLocal
def postprocess(self, compiled, source_path):
""" Post-process the compiled code.

:param compiled: compiled code
:type compiled: str
:param source_path: relative path to a source file
:type source_path: str
:returns: str
"""
return compiled

def find_dependencies(self, source_path):
""" Find the dependencies for the given source file.

Expand Down
41 changes: 39 additions & 2 deletions static_precompiler/compilers/coffeescript.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import json
import os
import posixpath

from static_precompiler import exceptions, settings, utils

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

def __init__(self, executable=settings.COFFEESCRIPT_EXECUTABLE):
def __init__(self, executable=settings.COFFEESCRIPT_EXECUTABLE, sourcemap_enabled=False):
self.executable = executable
self.is_sourcemap_enabled = sourcemap_enabled
super(CoffeeScript, self).__init__()

def compile_file(self, source_path):
return self.compile_source(self.get_source(source_path))
full_output_path = self.get_full_output_path(source_path)
args = [
self.executable,
"-c",
]
if self.is_sourcemap_enabled:
args.append("-m")
args.extend([
"-o", os.path.dirname(full_output_path),
self.get_full_source_path(source_path),
])
out, errors = utils.run_command(args)

if errors:
raise exceptions.StaticCompilationError(errors)

if self.is_sourcemap_enabled:
# Coffeescript writes source maps to compiled.map, not compiled.js.map
sourcemap_full_path = os.path.splitext(full_output_path)[0] + ".map"

with open(sourcemap_full_path) as sourcemap_file:
sourcemap = json.loads(sourcemap_file.read())

# CoffeeScript, unlike SASS, can't add correct relative paths in source map when the compiled file
# is not in the same dir as the source file. We fix it here.
sourcemap["sourceRoot"] = "../" * len(source_path.split("/")) + posixpath.dirname(source_path)
sourcemap["sources"] = [os.path.basename(source) for source in sourcemap["sources"]]
sourcemap["file"] = posixpath.basename(os.path.basename(full_output_path))

with open(sourcemap_full_path, "w") as sourcemap_file:
sourcemap_file.write(json.dumps(sourcemap))

return self.get_output_path(source_path)

def compile_source(self, source):
args = [
Expand Down
47 changes: 37 additions & 10 deletions static_precompiler/compilers/less.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import json
import os
import posixpath
import re
Expand All @@ -21,8 +22,9 @@ class LESS(base.BaseCompiler):
IMPORT_RE = re.compile(r"@import\s+(.+?)\s*;", re.DOTALL)
IMPORT_ITEM_RE = re.compile(r"([\"'])(.+?)\1")

def __init__(self, executable=settings.LESS_EXECUTABLE):
def __init__(self, executable=settings.LESS_EXECUTABLE, sourcemap_enabled=False):
self.executable = executable
self.is_sourcemap_enabled = sourcemap_enabled
super(LESS, self).__init__()

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

def compile_file(self, source_path):
full_source_path = self.get_full_source_path(source_path)
args = [
self.executable,
full_source_path,
]
full_output_path = self.get_full_output_path(source_path)

# `cwd` is a directory containing `source_path`.
# Ex: source_path = '1/2/3', full_source_path = '/abc/1/2/3' -> cwd = '/abc'
cwd = os.path.normpath(os.path.join(full_source_path, *([".."] * len(source_path.split("/")))))
out, errors = utils.run_command(args, None, cwd=cwd)

args = [
self.executable
]
if self.is_sourcemap_enabled:
args.extend([
"--source-map"
])

args.extend([
self.get_full_source_path(source_path),
full_output_path,
])
out, errors = utils.run_command(args, cwd=cwd)
if errors:
raise exceptions.StaticCompilationError(errors)

return out
utils.convert_urls(full_output_path, source_path)

if self.is_sourcemap_enabled:
sourcemap_full_path = full_output_path + ".map"

with open(sourcemap_full_path) as sourcemap_file:
sourcemap = json.loads(sourcemap_file.read())

# LESS, unlike SASS, can't add correct relative paths in source map when the compiled file
# is not in the same dir as the source file. We fix it here.
sourcemap["sourceRoot"] = "../" * len(source_path.split("/")) + posixpath.dirname(source_path)

sourcemap["file"] = posixpath.basename(full_output_path)

with open(sourcemap_full_path, "w") as sourcemap_file:
sourcemap_file.write(json.dumps(sourcemap))

return self.get_output_path(source_path)

def compile_source(self, source):
args = [
Expand All @@ -59,9 +89,6 @@ def compile_source(self, source):

return out

def postprocess(self, compiled, source_path):
return utils.convert_urls(compiled, source_path)

def find_imports(self, source):
""" Find the imported files in the source code.

Expand Down
Loading