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

COMPRESS_PRECOMPILERS break css url() #226

Closed
domoritz opened this issue Mar 4, 2012 · 37 comments · Fixed by #653
Closed

COMPRESS_PRECOMPILERS break css url() #226

domoritz opened this issue Mar 4, 2012 · 37 comments · Fixed by #653

Comments

@domoritz
Copy link

domoritz commented Mar 4, 2012

Since the precompiled files are served from another location, relative links in css files do not work anymore.

The Problem can be solved by setting the output dir to an empty string.

COMPRESS_OUTPUT_DIR = ''

However, it would be great to have processed urls in the css file that link to the right locations of images.

@jezdez
Copy link
Member

jezdez commented Mar 5, 2012

I'm not sure I understand correctly, can you provide a test case showing the problem?

@domoritz
Copy link
Author

domoritz commented Mar 5, 2012

I have a scss precompiler for my scss files. It has to be executed even in development since the browser can't handle scss files.

COMPRESS_PRECOMPILERS = (
    ('text/x-scss', 'pyscss -C {infile}'),
)

As a result I get a precompiled css for each scss file. So far so good. however, the problem occurs when I reference images with url() inside my scss files.Since these are not processed, relative urls, such as ../images/foo.png won't work anymore because the precompiled css files are served from another location and the images cannot be accessed. The problem is for example that urls will be relative to the CACHE location and not the static location in case you are using django.contrib.staticfiles.

My config looks like this:

DEBUG = True
STATIC_URL = '/static/'
COMPRESS_ENABLED = False
STATICFILES_FINDERS = (
    'django.contrib.staticfiles.finders.FileSystemFinder',
    'django.contrib.staticfiles.finders.AppDirectoriesFinder',
    # other finders..
    'compressor.finders.CompressorFinder',
)

this works:

COMPRESS_OUTPUT_DIR = ''

this does not:

COMPRESS_OUTPUT_DIR = 'CACHE'

@reinout
Copy link

reinout commented Mar 5, 2012

Yes, this is an issue (at least, I have the same problem). I'm using twitter bootstrap as a css framework. That contains less stylesheets and I handle those with compressor.

But the sprite file with icons that's included in twitter bootstrap is missing when setting COMPRESS_ENABLED=False (as I'm developing) because the icon .png file isn't moved to the CACHE/ directory.

@domoritz
Copy link
Author

domoritz commented Mar 6, 2012

It would be a solution to serve all static content from the CACHE location while DEBUG=True.

@jezdez
Copy link
Member

jezdez commented Mar 17, 2012

Well, for the record, that's why the CssAbsoluteFilter exists, to convert relative URLs to absolute ones. If you don't want to use it, it's perfectly fine to set COMPRESS_OUTPUT_DIR to an empty string. Serving files from that directory is out of scope of compressor.

@jezdez jezdez closed this as completed Mar 17, 2012
@rvanlaar
Copy link

CssAbsoluteFilter does not run when COMPRESS_ENABLED = False.

In my use case I want to run the precompressor and the CssAbsoluteFilter, but I don't want to compress the css.
This is not possible since in base.py:Compressor.hunks: enabled = settings.COMPRESS_ENABLED or forced
And self.filters where is not called because enabled = False.

Is it possible to use CssAbsoluteFilter without compressing the css?

@jezdez
Copy link
Member

jezdez commented Mar 19, 2012

It's not possible right now, no.

@Nagyman
Copy link

Nagyman commented Mar 24, 2012

I've encountered this issue as well, and it makes development a bit troublesome. Setting the COMPRESS_OUTPUT_DIR to an empty string might work, but only in simple cases; any sort of deeper directory structure breaks all of the url paths in CSS files, since the pre-compilers output to COMPRESS_OUTPUT_DIR/css regardless of source location. With so many css urls being relative, you're forced to turn compression on or abandon pre-compilers.

One alternative I was brainstorming, was detecting in the file to be pre-compiled, whether or not compression is enabled and adjusting the image paths in that file...which would be an ugly hack if it was possible.

@Nagyman
Copy link

Nagyman commented Mar 24, 2012

Scratch my comment. It does still apply, but I think one of the pull requests (specific commit below), will allow for the CssAbsoluteFilter, thus avoiding the issue entirely. Nice.

matthewwithanm@05f3d8c

@jezdez jezdez reopened this Jun 7, 2012
@reinout
Copy link

reinout commented Jun 7, 2012

Setting COMPRESS_OUTPUT_DIR to an empty directory just removes CACHE from the resulting filename. So the file still ends up in the COMPRESS_ROOT.

Setting COMPRESS_ROOT to the empty string places CACHE/css/... in the directory where you started django.

So there's no way to currently get django-compressor to create the output file in the same directory as the source file.

@chuchiperriman
Copy link

Same problem here, I think it is important

@willhardy
Copy link

My workaround for this was to add the following to the bottom of my settings:

if not COMPRESS_ENABLED:
    COMPRESS_ENABLED = True
    COMPRESS_CSS_FILTERS = ['compressor.filters.css_default.CssAbsoluteFilter']
    COMPRESS_JS_FILTERS = []

The settings are saying don't apply filters, but you want this one to apply anyway. You want some filters to get some sort of special status that makes them run whether or not COMPRESS_ENABLED is true. I imagine an attribute on the relevant filter's class might be able to tell django-compressor to always use it. This isn't ideal, because the developer won't expect things to be changed. The above mentioned approach of attaching filters to precompiler definitions might be nicer.

@anttihirvonen
Copy link
Contributor

I'm doing the same as @willhardy at the moment. It works, but as the compressor runs, the JS files also are concatenated and that makes the debugging a bit harder, as the resulting file can be quite long.

It should be possible to run some filters even when COMPRESS_ENABLED = False. Or should the CssAbsoluteFilter be run as a precompiler, as it really doesn't do any compressing? The pull request #291 is somewhat related, as it would allow class-based precompilers.

@greghball
Copy link

Sorry to leave a "me too" comment; but I too am doing the same as @willhardy. It would be nice not to have JS or CSS files concatenated while debugging; and it's also nice if SASS/SCSS/LESS files are compiled into something with a filename that indicates the source.

@dlamotte
Copy link

+1 on this bug. Took me a while to understand this, but here is the problem.

<link type="text/less" href="/static/less/main.less">

.myclass {
    background: url(../img/myimg.png);
}

The above attempts to reference this location: /static/img/myimg.png And this would work, if the generated css was served from /static/css/main.css. However, the COMPRESSOR_OUTPUT_DIR = 'CACHE' ends up adding a directory level because something that gets precompiled gets served from the COMPRESSOR_OUTPUT_DIR.

So, the above less gets precompiled and served from:
<link type="text/less" href="/static/CACHE/css/main.css"

Which ends up looking in the full path: /static/CACHE/img/myimg.png which is pretty much guaranteed to not exist.

So, this is actually a special case for css IMO. A CSS Absolute filter is an absolute requirement when it comes to precompilers only because of where a precompiled file is served from.

This problem gets compounded with the current absolute filter. Trying to account for the CACHE directory in main.less ends up breaking things very bad.

.myclass {
    background: url(../../img/myimg.png);
}

The above attempts to account for the extra CACHE dir, but when CSS Absolute Filter comes through, it'll make the full path: /img/myimg.png (notice no /static prefix) which is actually correct given the path to the less file /static/less/main.less. However, it's not given the files final resting place in the CACHE directory.

Hopefully I've helped clear up any misunderstandings if there were any.

@greghball
Copy link

I have just submitted a pull request to address this issue, which is a big one for me too.

@treyhunner
Copy link
Contributor

I just started using SCSS with Django Compressor and this is a big problem for me also.

I'm using @willhardy's hack at the moment but it creates issues with when the value of COMPRESS_ENABLED is changed in a local settings file.

@dustinfarris
Copy link

Can the absolute URL filter just be implicit, and disabled by an optional setting? e.g.

COMPRESS_CSS_ABSOLUTE_URL_FILTER = False

in the rare instance someone wouldn't want this functionality.

@croby
Copy link

croby commented Aug 6, 2013

+1, having this issue myself as well

@anttihirvonen
Copy link
Contributor

I'm currently using a custom Css absolute filter + SCSS precompiler combination to get around this issue and it works without any problems. The main point here is that the CssAbsoluteFilter is used during precompilation, so I don't need to enable compression for it to work. This keeps the original SCSS/JS files separate and the names of the files are not obfuscated hashes, which makes for happier development :-)

(The implementation is adapted from somone's LESS-based implementation, but I can't find it right now. Anyhow, the code is very easy to convert to LESS if that's your preprocessor choice.)

In lib/scssabsolutefilter.py:

from compressor.filters.base import CompilerFilter
from compressor.filters.css_default import CssAbsoluteFilter

class SCSSFilter(CompilerFilter):
    def __init__(self, content, attrs, **kwargs):
        super(SCSSFilter, self).__init__(content, command='scss {infile} {outfile}', **kwargs)

    def input(self, **kwargs):
        content = super(SCSSFilter, self).input(**kwargs)
        return CssAbsoluteFilter(content).input(**kwargs)

and in settings

COMPRESS_PRECOMPILERS = (
    ('text/scss', 'lib.scssabsolutefilter.SCSSFilter'),
)

Anyway, it would be nice if there was a way to do this correctly without any extra code.

@sunshineo
Copy link

+1 for this issue and I'm not even using any LESS or SASS
Simply relative url in CSS file and I have to pick between "../../images/" for production to work or "../images" for development to work.
Well I picked for production to work of course then debugging is hard and localhost looks bad.

@sunshineo
Copy link

Also I'm on Amazon S3 so I cannot make a sim link like suggested at yourcelf/btb#20

@dbrgn
Copy link

dbrgn commented Sep 19, 2013

Same issue here. django-cms, django-sekizai, django-compressor and pyscss.

andrefsp pushed a commit to andrefsp/django-oscar that referenced this issue Oct 31, 2013
When the LESS files are served but compression is disabled, the relative
URL between the generated CSS and other assets is wrong (using the
default compressor settings).  This fixes changes the output directory
name to match the path of Oscar's assets, which fixes this issue.

Related to django-compressor/django-compressor#226

Fixes django-oscar#649
@honi
Copy link

honi commented Mar 6, 2014

Same issue here.

CssAbsoluteFilter should be a compressor option, as @dustinfarris said (which could be implemented as a filter internally). You already know that compressor will generate a css in a different location than the original file, so it is very likely that your relative paths are going to break. When would you not want to fix your relative paths?

@vmassuchetto
Copy link

Not elegant at all, but I solved this by creating a couple of symlinks.

@andreyshipilov
Copy link

Is this still open, really? How do people develop locally using sass/less/scss? I believe this is a very important bug.

@honi
Copy link

honi commented Jun 22, 2014

I'm using a customer LessCompiler that runs the output through the CssAbsoluteFilter, similar to @anttihirvonen, and this works pretty well.

Another solution is to use absolute urls in your LESS files, with a @static-url variable that matches django's STATIC_URL setting. You won't be needing CssAbsoluteFilter anymore, but this may not work if you are using S3. For example:
background: url("@{static-url}/img/bg.png");

@mmanneva
Copy link

+1, @anttihirvonen's solution works, but IMO it should be possible to do this through settings only.

@ngzhian
Copy link

ngzhian commented Sep 18, 2014

@woeye
Copy link

woeye commented Oct 3, 2014

I've modified @anttihirvonen's solution to work with django-libsass (which uses the faster C implementation of SASS):

from compressor.filters.css_default import CssAbsoluteFilter
from django_libsass import SassCompiler

class PatchedSCSSCompiler(SassCompiler):
    def input(self, **kwargs):
        content = super(PatchedSCSSCompiler, self).input(**kwargs)
        return CssAbsoluteFilter(content).input(**kwargs)

It would be nice, though, I've there wasn't such a workaround needed. And it took my quite a while to figure out what was going on and how to fix it.

@danielgatis
Copy link

@gasman
Copy link
Contributor

gasman commented Feb 19, 2015

@woeye Having just stumbled across this problem myself, I propose to incorporate this workaround into django-libsass: torchbox/django-libsass#8

+1 for a general solution to this within django-compressor, though!

@jsSenanga
Copy link

With django-compressor 1.5:
the fix for #467 (db731cf) changed the CSSAbsoluteFilter, which breaks the 'PatchedSCSSCompiler' workaround.
Quickfix for that is adding the filename argument to the filter call:

from compressor.filters.css_default import CssAbsoluteFilter
from django_libsass import SassCompiler

class PatchedSCSSCompiler(SassCompiler):
    def input(self, **kwargs):
        content = super(PatchedSCSSCompiler, self).input(**kwargs)
        kwargs.setdefault('filename', self.filename)
        return CssAbsoluteFilter(content).input(**kwargs)

@martyzz1
Copy link

Thanks @jsSenanga I was just trying to track this issue down. Thanks @woeye for the original patch...
I now have this working under Local dev mode and Heroku Deployment with herokuside collectstatic and compression....

@rafaponieman
Copy link

+1 for a permanent solution. In my case, I've had the problem with django-libsass, and solved it by using @jsSenanga 's solution. Thank you!

@karyon
Copy link
Contributor

karyon commented Sep 24, 2015

shoutout to @domoritz :) i thought it was really funny and random to see you here. (johannes linke speaking)

@domoritz
Copy link
Author

Haha, awesome to meet you here @karyon and thanks for working on this issue. I don't quite remember what I was working on back then but it looks like a relevant issue judging from the number of people in this thread.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet