Skip to content

Add support for source maps #68

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’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Oct 5, 2015
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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