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

CSS files containing url(...) - rewrite path for pipeline? #38

Closed
fisharebest opened this issue Oct 2, 2014 · 6 comments
Closed

CSS files containing url(...) - rewrite path for pipeline? #38

fisharebest opened this issue Oct 2, 2014 · 6 comments

Comments

@fisharebest
Copy link
Contributor

Suppose I have a CSS file /css/style.css, which contains a rule such as #id { background-image: url(img.png); } and I use the pipeline option.

My pipeline file (e.g. /min/7c358b3d092429876a4fb752721bfca8.css) still contains the exact same rule #id { background-image: url(img.png); }. However this doesn't work, because img.png is in the css folder and not the min folder.

The pipeline file needs to contain #id { background-image: url(../css/img.png); }.

The Minify_CSS library seems to have an option to rewrite URLs like this (prependRelativePath), but I can't work out how to configure/enable it. Am I missing something obvious?

@Stolz
Copy link
Owner

Stolz commented Oct 2, 2014

@fisharebest, for those cases what you need is an assets filter/preprocessor. The library doesn't include a built in one but it offers a way to provide your custom one.

Simply use a closure in your fetch_command config option to process your assets before the get pipelined. Take a look at #23 for more details. Following that example you only need to add url replacing logic to the process() function.

I didn't know about the prependRelativePath option. To be honest, I'm using the CSS minifier with default options. I'm going to research more and see if I can make something to, somehow, pass options from the config file to the minification libraries.

@fisharebest
Copy link
Contributor Author

As a quick-and-dirty proof of concept, I have this, which works.

self::$asset_manager = new \Stolz\Assets\Manager(array(
    'css_dir' => '.',
    'js_dir' => '.',
    'pipeline' => is_writable(WT_ROOT . 'asset-pipeline'),
    'pipeline_dir' => 'asset-pipeline',
    'pipeline_gzip' => 9,
    'public_dir' => WT_ROOT,
    'fetch_command' => function ($asset) {
        $unprocessed = file_get_contents($asset);
        $prefix = str_replace(WT_ROOT . '/', '.', dirname($asset)) . '/';
        $processed = str_replace('url(', 'url(' . $prefix, $unprocessed);
        return $processed;
    },
));

Obviously, I'll need a more sophisticated processor, to rewrite only local files (not data: urls, etc.), but that should be straightforward.

Thanks!

@rajivseelam
Copy link

This is my fetch command for Laravel:

'fetch_command' => function ($asset) {
        $unprocessed = file_get_contents($asset);
        $prefix = str_replace(public_path() . '/', '/', dirname($asset)) . '/';
        $unprocessed = preg_replace("/\burl\('([^']+)'\)/", "url($1)", $unprocessed);
        $processed = str_replace('url(', 'url(' . $prefix, $unprocessed);
        return $processed;
    },

I have added this additional line:

$unprocessed = preg_replace("/\burl\('([^']+)'\)/", "url($1)", $unprocessed);

font-awesome.css has urls like:

src: url('../fonts/fontawesome-webfont.eot?v=4.1.0')

In this case the above mentioned won't work

@Stolz
Copy link
Owner

Stolz commented Dec 19, 2014

According to http://www.w3.org/TR/CSS2/syndata.html#value-def-uri:

The format of a URI value is 'url('
followed by optional white space
followed by an optional single quote (') or double quote (") character
followed by the URI itself,
followed by an optional single quote (') or double quote (") character
followed by optional white space
followed by ')'.

A non greedy PHP regex to match that specification could be '~url\(\s?[\'"]?(.*?)[\'"]?\s?\)~' (not tested).

So here is a possible approach to rewrite relative CSS URLs:

'fetch_command' => function ($asset) {

    $content = file_get_contents($asset);
    $regex = '~url\(\s?[\'"]?(.*?)[\'"]?\s?\)~';
    $filter = function ($match) {

        // Do not process absolute URLs
        if('http://' === substr($match[1], 0, 7) or 'https://' === substr($match[1], 0, 8) or '//' === substr($match[1], 0, 2))
            return $match[0];

        // Add your filter logic here
        return 'url("../'. $match[1] . '")';
    };

    // Apply filter
    return preg_replace_callback($regex, $filter, $content);
},

@rajivseelam
Copy link

I am not well versed with regex but I have faced some problems with yours (which I was facing with mine too):

I had to change my regex to:

$regex = "/\burl\(\s?[\'\"]?(([^';]+)\.(jpg|eot|jpg|jpeg|gif|png|ttf|woff|svg).*?)[\'\"]?\s?\)/";

This included all the rules you have covered and adds more conditions that the resource is an image or a font file. This way I won't touch urls which don't need any fixing. For example url('data:image/png..') and some other in javascript files like: behavior:url(#default#VML)

Thanks for your input, right now my fetch_command is:

'fetch_command' => function ($asset) {

        $content = file_get_contents($asset);
        $prefix = str_replace(public_path() . '/', '', dirname($asset)) . '/';

        $regex = "/\burl\(\s?[\'\"]?(([^';]+)\.(jpg|eot|jpg|jpeg|gif|png|ttf|woff|svg).*?)[\'\"]?\s?\)/";

        $filter = function ($match) use ($prefix) {

            // Do not process absolute URLs
            if('http://' === substr($match[1], 0, 7) or 'https://' === substr($match[1], 0, 8) or '//' === substr($match[1], 0, 2))
            {
                return $match[0];
            }

            // Add your filter logic here
            return 'url(\''. $prefix.$match[1] . '\')';
        };

        // Apply filter
        return preg_replace_callback($regex, $filter, $content);
    },

@lbausch
Copy link

lbausch commented Feb 10, 2015

I used Assetic to do the rewrite stuff. This way I don't have to mess around with RegEx and can rely on battle-tested solutions. Anyway I'm sure the following snippet can be improved:

'fetch_command' => function ($asset) {    
    $content = file_get_contents($asset);

    $info = pathinfo($asset);

    $sourceRoot = 'assets/'. $info['extension'];
    $sourcePath = $info['extension'] . DIRECTORY_SEPARATOR . $info['basename'];

    $rewrite = new Assetic\Filter\CssRewriteFilter();            
    $assetic = new Assetic\Asset\StringAsset($content, [$rewrite], $sourceRoot, $sourcePath);
    $assetic->setTargetPath('assets/min');

    return $assetic->dump();
},

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

No branches or pull requests

4 participants