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

Already on GitHub? Sign in to your account

Overhaul commands handling and make settings handling consistent #18

Merged
merged 2 commits into from
Jan 10, 2017
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
20 changes: 17 additions & 3 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,25 @@ And add it as a compiler to pipeline in your django `settings.py`::
To add source maps during development (or any other browserify args)::

if DEBUG:
PIPELINE['BROWSERIFY_ARGUMENTS'] = '-d'
PIPELINE['BROWSERIFY_ARGS'] = ['-d']

To add variable assignments before the browserify command::
Passing arguments as an array makes sure they are safely unambiguous, but the way browserify lets you pass nested arguments within brackets can make this very tedious::

# this is very unreadable, and hard to maintain!
PIPELINE['BROWSERIFY_ARGS'] = ['--transform', '[', 'babelify', '--presets', '[', 'es2015', 'react', ']', '--plugins', '[', 'transform-object-rest-spread', 'transform-class-properties', ']', ']']

To avoid this, when you know that no individual argument has a space within it, simply split the arguments yourself::

# the easy way :-)
PIPELINE['BROWSERIFY_ARGS'] = "--transform [ babelify --presets [ es2015 react ] --plugins [ transform-object-rest-spread transform-class-properties ] ]".split()


To set environment varaibles specific to the browserify command::

PIPELINE['BROWSERIFY_ENV'] = {'NODE_ENV':'production'}

(Note that for an actual production build, this example is not sufficient. You'll probably want to use a transform like loose-envify so the minifier can optimize out debug statements. Browserify doesn't usually pass environment variables like that shown above into the compiled code; but it may effect the runtime behavior of browserify itself.)

PIPELINE['BROWSERIFY_VARS'] = 'NODE_ENV=production'

**Important:** give your entry-point file a `.browserify.js` extension::

Expand Down
145 changes: 78 additions & 67 deletions pipeline_browserify/compiler.py
Original file line number Diff line number Diff line change
@@ -1,91 +1,102 @@
import json
import os

from os.path import dirname
from tempfile import NamedTemporaryFile

from django.core.exceptions import SuspiciousFileOperation

from pipeline.conf import settings as pipeline_settings
from pipeline.compilers import SubProcessCompiler
from django.conf import settings
from django.core.exceptions import SuspiciousFileOperation
from pipeline.exceptions import CompilerError
from warnings import warn

class BrowserifyCompiler(SubProcessCompiler):
output_extension = 'browserified.js'

def match_file(self, path):
if self.verbose:
print('matching file:', path)
return path.endswith('.browserify.js')


# similar to old removed in https://github.com/jazzband/django-pipeline/commit/1f6b48ae74026a12f955f2f15f9f08823d744515
def simple_execute_command(self, cmd, **kwargs):
import subprocess
pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs)
stdout, stderr = pipe.communicate()
if self.verbose:
print stdout
print stderr
if pipe.returncode != 0:
raise CompilerError("Compiler returned non-zero exit status %i" % pipe.returncode, command=cmd, error_output=stderr)
return stdout

def _get_cmd_parts(self):
pipeline_settings = getattr(settings, 'PIPELINE', {})
tool = pipeline_settings.get('BROWSERIFY_BINARY', "/usr/bin/env browserify")

old_args = pipeline_settings.get('BROWSERIFY_ARGUMENTS', '')
if old_args:
warn("You are using the string-based BROWSERIFY_ARGUMENTS setting. Please migrate to providing a list of arguments via BROWSERIFY_ARGS instead.", DeprecationWarning)
args = old_args.split()
else:
args = pipeline_settings.get('BROWSERIFY_ARGS', [])

old_env = pipeline_settings.get('BROWSERIFY_VARS', '')
if old_env:
warn("You are using the string-based BROWSERIFY_VARS setting. Please migrate to providing a dict of environment variables via BROWSERIFY_ENV instead.", DeprecationWarning)
env = dict(map(lambda s: s.split('='), old_env.split()))
else:
env = pipeline_settings.get('BROWSERIFY_ENV', None)
if env:
# when there's custom variables, we need to explicitly pass along the original environment
import os
_env = {}
_env.update(os.environ)
_env.update(env)
env = _env

return tool, args, env

def compile_file(self, infile, outfile, outdated=False, force=False):
if not force and not outdated:
return # File doesn't need to be recompiled
command = (
pipeline_settings.get('BROWSERIFY_VARS', ''),
pipeline_settings.get('BROWSERIFY_BINARY', '/usr/bin/env browserify'),
pipeline_settings.get('BROWSERIFY_ARGUMENTS', ''),
infile,
"-o",
outfile,
)

return self.execute_command(command, cwd=dirname(infile))

return

tool, args, env = self._get_cmd_parts()
args.extend([infile, '--outfile', outfile])
cmd = [tool] + args

if self.verbose:
print "compile_file command:", cmd, env
self.simple_execute_command(cmd, env=env)

def is_outdated(self, infile, outfile):
"""Check if the input file is outdated.

The difficulty with the default implementation is that any file that is
`require`d from the entry-point file will not trigger a recompile if it
is modified. This overloaded version of the method corrects this by generating
a list of all required files that are also a part of the storage manifest
and checking if they've been modified since the last compile.

The command used to generate the list of dependencies is the same as the compile
command but includes the `--deps` option.

command but uses the `--list` option instead of `--outfile`.
WARNING: It seems to me that just generating the dependencies may take just
as long as actually compiling, which would mean we would be better off just
forcing a compile every time.
"""

# Check for missing file or modified entry-point file.
# Preliminary check for simply missing file or modified entry-point file.
if super(BrowserifyCompiler, self).is_outdated(infile, outfile):
return True

# Check if we've already calculated dependencies.
deps = getattr(self, '_deps', None)
if not deps:
# Collect dependency information.
command = (
pipeline_settings.get('BROWSERIFY_VARS', ''),
pipeline_settings.get('BROWSERIFY_BINARY', '/usr/bin/env browserify'),
pipeline_settings.get('BROWSERIFY_ARGUMENTS', ''),
"--deps",
self.storage.path(infile),
)

with NamedTemporaryFile(delete=False, dir=dirname(outfile)) as dep_json:
self.execute_command(command, stdout_captured=dep_json.name)

# Process the output data. It's JSON, and the file's path is coded
# in the "file" field. We also want to save the content of each file
# so we can check if they're outdated, which is coded under "source".
deps = []
with open(dep_json.name) as command_output:
for dep in json.loads(command_output.read()):
# Is this file managed by the storage?
try:
if self.storage.exists(dep['file']):
deps.append(dep['file'])
except SuspiciousFileOperation:
pass
# dep_json must be removed afterwards
os.remove(dep_json.name)

# Cache the dependencies for the next possible run.
self._deps = deps

# Test the dependencies to see if they're out of date.
for dep in deps:
if super(BrowserifyCompiler, self).is_outdated(dep, outfile):

# Otherwise we need to see what dependencies there are now, and if they're modified.
tool, args, env = self._get_cmd_parts()
args.extend(['--list', infile])
cmd = [tool] + args
if self.verbose:
print "is_outdated command:", cmd, env
dep_list = self.simple_execute_command(cmd, env=env)
if self.verbose:
print "dep_list is:", dep_list
for dep_file in dep_list.strip().split('\n'):
if super(BrowserifyCompiler, self).is_outdated(dep_file, outfile):
if self.verbose:
print "Found dep_file \"%s\" updated." % dep_file
return True

return False