├── .editorconfig ├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── package.json ├── src └── index.coffee ├── tea.yaml ├── test ├── fixtures │ ├── absolute.css │ ├── comma.css │ ├── comma.expected.css │ ├── data-urls-svg.css │ ├── data-urls.css │ ├── imports.css │ ├── imports.expected.css │ ├── index.css │ ├── index.expected.css │ ├── index.important.css │ ├── index.important.expected.css │ ├── index.quotes.double.css │ ├── index.quotes.single.css │ ├── index.svgPath.css │ ├── index.svgPath.expected.css │ ├── index.url.with.quotes.css │ ├── index.url.with.quotes.expected.css │ ├── index.whitespaces.css │ ├── index.windows.css │ ├── index.windows.expected.css │ ├── minified.css │ ├── minified.expected.css │ ├── opt.adaptPath.css │ ├── opt.adaptPath.expected.css │ └── relative-slash.css └── index.coffee └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 4 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.coffee] 13 | indent_size = 2 14 | 15 | [*.md] 16 | trim_trailing_whitespace = false 17 | 18 | [*.json] 19 | indent_size = 2 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # Compiled binary addons (http://nodejs.org/api/addons.html) 20 | build/Release 21 | 22 | # Dependency directory 23 | # Deployed apps should consider commenting this line out: 24 | # see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git 25 | node_modules 26 | 27 | src/*.js 28 | test/*.js 29 | lib 30 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | before_install: 3 | - curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version 1.3.2 4 | - export PATH=$HOME/.yarn/bin:$PATH 5 | language: node_js 6 | cache: 7 | yarn: true 8 | directories: 9 | - node_modules 10 | notifications: 11 | email: false 12 | node_js: 13 | - 'node' 14 | - '8' 15 | after_success: 16 | - yarn coveralls 17 | - yarn travis-deploy-once "yarn semantic-release" 18 | branches: 19 | except: 20 | - /^v\d+\.\d+\.\d+$/ 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Joscha Feth 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #[gulp](https://github.com/gulpjs/gulp)-rewrite-css 2 | 3 | [![Greenkeeper badge](https://badges.greenkeeper.io/joscha/gulp-rewrite-css.svg)](https://greenkeeper.io/) 4 | 5 | [![NPM version][npm-image]][npm-url] 6 | [![Build Status][travis-image]][travis-url] 7 | [![Coverage Status][coveralls-image]][coveralls-url] 8 | [![Dependency Status][depstat-image]][depstat-url] 9 | [![devDependency Status][devdepstat-image]][devdepstat-url] 10 | 11 | A gulp plugin that allows rewriting `url(…)` and `@import` references in CSS 12 | 13 | ## Installation 14 | 15 | ```console 16 | npm install gulp-rewrite-css --save-dev 17 | ``` 18 | 19 | ## Usage 20 | 21 | ```javascript 22 | var gulp = require('gulp'); 23 | var rewriteCSS = require('gulp-rewrite-css'); 24 | 25 | gulp.task('my-rewrite', function() { 26 | var dest = './dist/'; 27 | return gulp.src('./static/css/*.css') 28 | .pipe(rewriteCSS({destination:dest})) 29 | .pipe(gulp.dest(dest)); 30 | }); 31 | ``` 32 | ### Options 33 | * `destination` (required, String) - the target directory for the processed CSS. Paths are rewritten relatively to that directory. 34 | * `[debug]` (optional, boolean, defaults to false) - whether to log what gulp-rewrite-css is doing 35 | * `[adaptPath]` (optional, Function, defaults to the internal rewriting method of gulp-rewrite-css) - will be passed a context hash that contains the following options: 36 | 37 | | key | description | 38 | |------------------|--------------------------------------------------------------------------| 39 | | `sourceDir` | the path in which the currently processed CSS file resides in | 40 | | `sourceFile` | the path to the currently processed CSS file | 41 | | `destinationDir` | the path of the target directory where the CSS file ends in | 42 | | `targetFile` | the path of the target file (e.g. the contents of `url(…)` or `@import`) | 43 | 44 | ## License 45 | MIT (c) 2017 Joscha Feth 46 | 47 | [npm-url]: https://npmjs.org/package/gulp-rewrite-css 48 | [npm-image]: http://img.shields.io/npm/v/gulp-rewrite-css.svg 49 | 50 | [travis-url]: https://travis-ci.org/joscha/gulp-rewrite-css 51 | [travis-image]: http://img.shields.io/travis/joscha/gulp-rewrite-css.svg 52 | 53 | [coveralls-url]: https://coveralls.io/r/joscha/gulp-rewrite-css 54 | [coveralls-image]: http://img.shields.io/coveralls/joscha/gulp-rewrite-css.svg 55 | [coveralls-original-image]: https://coveralls.io/repos/joscha/gulp-rewrite-css/badge.png 56 | 57 | [depstat-url]: https://david-dm.org/joscha/gulp-rewrite-css 58 | [depstat-image]: https://david-dm.org/joscha/gulp-rewrite-css.svg?theme=shields.io 59 | 60 | [devdepstat-url]: https://david-dm.org/joscha/gulp-rewrite-css#info=devDependencies 61 | [devdepstat-image]: https://david-dm.org/joscha/gulp-rewrite-css/dev-status.svg?theme=shields.io 62 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gulp-rewrite-css", 3 | "description": "Rewrite url references in CSS", 4 | "repository": { 5 | "type": "git", 6 | "url": "https://github.com/joscha/gulp-rewrite-css.git" 7 | }, 8 | "homepage": "https://github.com/joscha/gulp-rewrite-css", 9 | "keywords": [ 10 | "gulpplugin", 11 | "rewrite", 12 | "css" 13 | ], 14 | "main": "./lib/index", 15 | "dependencies": { 16 | "ansi-green": "^0.1.1", 17 | "ansi-magenta": "^0.1.1", 18 | "bufferstreams": "~2.0.0", 19 | "event-stream": "3.3.4", 20 | "fancy-log": "^1.3.2", 21 | "plugin-error": "^1.0.0" 22 | }, 23 | "engines": { 24 | "node": ">= 4" 25 | }, 26 | "scripts": { 27 | "prepublish": "coffee -o lib src", 28 | "test": "mocha --require coffeescript/register -R spec \"test/*.coffee\"", 29 | "watch": "mocha --require coffeescript/register -R min \"test/*.coffee\" --watch --watch-extensions coffee", 30 | "coverage": "coffee -o src src && coffee -o test test && istanbul cover _mocha --report html -- -R spec -t 3000 -s 2000", 31 | "coveralls": "coffee -o src src && coffee -o test test && istanbul cover _mocha --report lcovonly -- -R spec -t 3000 -s 2000 && cat ./coverage/lcov.info | coveralls", 32 | "travis-deploy-once": "travis-deploy-once", 33 | "semantic-release": "semantic-release" 34 | }, 35 | "files": [ 36 | "lib" 37 | ], 38 | "devDependencies": { 39 | "coffeescript": "~2.2.0", 40 | "coveralls": "~3.0.0", 41 | "cz-conventional-changelog": "^2.0.0", 42 | "gulp": "~3.9.0", 43 | "istanbul": "~0.4.0", 44 | "mocha": "~5.0.0", 45 | "mocha-lcov-reporter": "1.3.0", 46 | "proxyquire": "~2.1.0", 47 | "semantic-release": "^15.12.4", 48 | "should": "~13.2.1", 49 | "sinon": "~4.2.0", 50 | "strip-ansi": "~4.0.0", 51 | "travis-deploy-once": "^4.3.2", 52 | "vinyl": "^2.1.0" 53 | }, 54 | "license": "MIT", 55 | "config": { 56 | "commitizen": { 57 | "path": "./node_modules/cz-conventional-changelog" 58 | } 59 | }, 60 | "version": "0.0.0-development" 61 | } 62 | -------------------------------------------------------------------------------- /src/index.coffee: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | es = require 'event-stream' 4 | BufferStreams = require 'bufferstreams' 5 | magenta = require 'ansi-magenta' 6 | green = require 'ansi-green' 7 | PluginError = require 'plugin-error' 8 | log = require 'fancy-log' 9 | path = require 'path' 10 | url = require 'url' 11 | 12 | PLUGIN_NAME = 'rewrite-css' 13 | 14 | URL_REGEX = /// 15 | url 16 | \s* # Arbitrary white-spaces 17 | \( # An opening bracket 18 | \s* # Arbitrary white-spaces 19 | (?!["']?data:) # explicitly don't match data-urls 20 | (.+?)\)(?=\s+(?!\))|;|}|,|!) # anything up to first closing bracket that is not followed by other closing brackets 21 | ///g # We want to replace all the matches 22 | 23 | IMPORT_REGEX = /// 24 | @import 25 | \s* # Arbitrary white-spaces 26 | (["']) # the path must be quoted 27 | \s* # Arbitrary white-spaces 28 | ([^'"]+) # Anything but a closing quote 29 | \1 # The right closing quote 30 | ///g # We want to replace all the matches 31 | 32 | cleanMatch = (url) -> 33 | url = url.trim() 34 | firstChar = url.substr 0, 1 35 | if firstChar is (url.substr -1) and (firstChar is '"' or firstChar is "'") 36 | url = url.substr 1, url.length - 2 37 | url 38 | 39 | isRelativeUrl = (u) -> 40 | parts = url.parse u, false, true 41 | not parts.protocol and not parts.host 42 | 43 | isRelativeToBase = (u) -> '/' is u.substr 0, 1 44 | 45 | adaptPath = (ctx) -> path.join (path.relative ctx.destinationDir, ctx.sourceDir), ctx.targetFile 46 | 47 | module.exports = (opt) -> 48 | opt ?= {} 49 | opt.debug ?= false 50 | opt.adaptPath ?= adaptPath 51 | 52 | unless typeof opt.adaptPath is 'function' 53 | throw new PluginError PLUGIN_NAME, 'adaptPath method is missing' 54 | 55 | unless opt.destination 56 | throw new PluginError PLUGIN_NAME, 'destination directory is missing' 57 | 58 | mungePath = (match, sourceFilePath, file) -> 59 | if (isRelativeUrl file) and not (isRelativeToBase file) 60 | destinationDir = opt.destination 61 | sourceDir = path.dirname sourceFilePath 62 | targetUrl = opt.adaptPath 63 | sourceDir: sourceDir 64 | sourceFile: sourceFilePath 65 | destinationDir: destinationDir 66 | targetFile: file 67 | 68 | if typeof targetUrl is 'string' 69 | # fix for windows paths 70 | targetUrl = targetUrl.replace ///\\///g, '/' if path.sep is '\\' 71 | return targetUrl.replace "'", "\\'" 72 | else if opt.debug 73 | log (magenta PLUGIN_NAME), 74 | 'not rewriting absolute path for', 75 | (magenta match), 76 | 'in', 77 | (magenta sourceFilePath) 78 | return 79 | 80 | logRewrite = (match, sourceFilePath, destinationFilePath) -> 81 | if opt.debug 82 | log (magenta PLUGIN_NAME), 83 | 'rewriting path for', 84 | (magenta match), 85 | 'in', 86 | (magenta sourceFilePath), 87 | 'to', 88 | (green destinationFilePath) 89 | 90 | rewriteUrls = (sourceFilePath, data) -> 91 | 92 | replaceCallback = (match, file, prefix) -> 93 | file = cleanMatch file 94 | newPath = mungePath match, sourceFilePath, file 95 | return match unless newPath 96 | 97 | ret = """#{prefix}url("#{newPath.replace('"', '\\"')}")""" 98 | logRewrite match, sourceFilePath, ret 99 | return ret 100 | 101 | data 102 | .replace URL_REGEX, (match, file) -> 103 | replaceCallback match, file, '' 104 | .replace IMPORT_REGEX, (match, _, file) -> 105 | replaceCallback match, file, '@import ' 106 | 107 | bufferReplace = (file, data) -> 108 | rewriteUrls file.path, data 109 | 110 | streamReplace = (file) -> 111 | (err, buf, cb) -> 112 | cb PluginError PLUGIN_NAME, err if err 113 | 114 | # Use the buffered content 115 | buf = Buffer bufferReplace file, String buf 116 | 117 | # Bring it back to streams 118 | cb null, buf 119 | return 120 | 121 | es.map (file, callback) -> 122 | if file.isNull() 123 | callback null, file 124 | return 125 | 126 | if file.isStream() 127 | replacementFn = streamReplace opt, file 128 | file.contents = file.contents.pipe new BufferStreams streamReplace file 129 | callback null, file 130 | return 131 | 132 | if file.isBuffer() 133 | newFile = file.clone() 134 | newContents = bufferReplace file, String newFile.contents 135 | newFile.contents = new Buffer newContents 136 | callback null, newFile 137 | return 138 | 139 | module.exports.adaptPath = adaptPath 140 | -------------------------------------------------------------------------------- /tea.yaml: -------------------------------------------------------------------------------- 1 | # https://tea.xyz/what-is-this-file 2 | --- 3 | version: 1.0.0 4 | codeOwners: 5 | - '0x9f9f7fba7ea4e8eF17674B4aCB6CaFD8F2BFc01F' 6 | quorum: 1 7 | -------------------------------------------------------------------------------- /test/fixtures/absolute.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Open Sans'; 3 | font-style: normal; 4 | font-weight: 400; 5 | src: local('Open Sans'), 6 | local('OpenSans'), 7 | url(http://www.fonts.com/OpenSans.woff) format('woff'); 8 | } 9 | -------------------------------------------------------------------------------- /test/fixtures/comma.css: -------------------------------------------------------------------------------- 1 | .baz { 2 | cursor: url(hyper.cur), auto; 3 | } -------------------------------------------------------------------------------- /test/fixtures/comma.expected.css: -------------------------------------------------------------------------------- 1 | .baz { 2 | cursor: url("../../hyper.cur"), auto; 3 | } -------------------------------------------------------------------------------- /test/fixtures/data-urls-svg.css: -------------------------------------------------------------------------------- 1 | .x { 2 | background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg%20viewBox%3D'0%200%20120%20120'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20xmlns%3Axlink%3D'http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink'%3E%3Cdefs%3E%3Cline%20id%3D'l'%20x1%3D'60'%20x2%3D'60'%20y1%3D'7'%20y2%3D'27'%20stroke%3D'%23fff'%20stroke-width%3D'11'%20stroke-linecap%3D'round'%2F%3E%3C%2Fdefs%3E%3Cg%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%20transform%3D'rotate(30%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%20transform%3D'rotate(60%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%20transform%3D'rotate(90%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%20transform%3D'rotate(120%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%20transform%3D'rotate(150%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.37'%20transform%3D'rotate(180%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.46'%20transform%3D'rotate(210%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.56'%20transform%3D'rotate(240%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.66'%20transform%3D'rotate(270%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.75'%20transform%3D'rotate(300%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.85'%20transform%3D'rotate(330%2060%2C60)'%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E"); 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/data-urls.css: -------------------------------------------------------------------------------- 1 | .x { 2 | background: url("data:image/svg+xml;...") 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/imports.css: -------------------------------------------------------------------------------- 1 | @import 'my/bla.css'; 2 | @import "my/bla.css"; 3 | @import url('my/bla.css'); 4 | @import url("my/bla.css"); 5 | -------------------------------------------------------------------------------- /test/fixtures/imports.expected.css: -------------------------------------------------------------------------------- 1 | @import url("../../my/bla.css"); 2 | @import url("../../my/bla.css"); 3 | @import url("../../my/bla.css"); 4 | @import url("../../my/bla.css"); 5 | -------------------------------------------------------------------------------- /test/fixtures/index.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Open Sans'; 3 | font-style: normal; 4 | font-weight: 400; 5 | src: local('Open Sans'), 6 | local('OpenSans'), 7 | url(fonts/OpenSans.woff) format('woff'); 8 | } 9 | -------------------------------------------------------------------------------- /test/fixtures/index.expected.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Open Sans'; 3 | font-style: normal; 4 | font-weight: 400; 5 | src: local('Open Sans'), 6 | local('OpenSans'), 7 | url("../../fonts/OpenSans.woff") format('woff'); 8 | } 9 | -------------------------------------------------------------------------------- /test/fixtures/index.important.css: -------------------------------------------------------------------------------- 1 | .test{ 2 | background-image: url(test-sprite@2x.png)!important; 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/index.important.expected.css: -------------------------------------------------------------------------------- 1 | .test{ 2 | background-image: url("../../test-sprite@2x.png")!important; 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/index.quotes.double.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Open Sans'; 3 | font-style: normal; 4 | font-weight: 400; 5 | src: local('Open Sans'), 6 | local('OpenSans'), 7 | url("fonts/OpenSans.woff") format('woff'); 8 | } 9 | -------------------------------------------------------------------------------- /test/fixtures/index.quotes.single.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Open Sans'; 3 | font-style: normal; 4 | font-weight: 400; 5 | src: local('Open Sans'), 6 | local('OpenSans'), 7 | url('fonts/OpenSans.woff') format('woff'); 8 | } 9 | -------------------------------------------------------------------------------- /test/fixtures/index.svgPath.css: -------------------------------------------------------------------------------- 1 | .test{ 2 | background-image: url(sprites/sprite.svg#svgView(viewBox(0, 0, 32, 32))); 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/index.svgPath.expected.css: -------------------------------------------------------------------------------- 1 | .test{ 2 | background-image: url("../../sprites/sprite.svg#svgView(viewBox(0, 0, 32, 32))"); 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/index.url.with.quotes.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Open Sans'; 3 | font-style: normal; 4 | font-weight: 400; 5 | src: local('Open Sans'), 6 | local('OpenSans'), 7 | url('fonts/Open"Sans.woff') format('woff'); 8 | } 9 | -------------------------------------------------------------------------------- /test/fixtures/index.url.with.quotes.expected.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Open Sans'; 3 | font-style: normal; 4 | font-weight: 400; 5 | src: local('Open Sans'), 6 | local('OpenSans'), 7 | url("../../fonts/Open\"Sans.woff") format('woff'); 8 | } 9 | -------------------------------------------------------------------------------- /test/fixtures/index.whitespaces.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Open Sans'; 3 | font-style: normal; 4 | font-weight: 400; 5 | src: local('Open Sans'), 6 | local('OpenSans'), 7 | url ( fonts/OpenSans.woff ) format('woff'); 8 | } 9 | -------------------------------------------------------------------------------- /test/fixtures/index.windows.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Open Sans'; 3 | font-style: normal; 4 | font-weight: 400; 5 | src: local('Open Sans'), 6 | local('OpenSans'), 7 | url(multiple\directories\fonts\OpenSans.woff) format('woff'); 8 | } 9 | -------------------------------------------------------------------------------- /test/fixtures/index.windows.expected.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Open Sans'; 3 | font-style: normal; 4 | font-weight: 400; 5 | src: local('Open Sans'), 6 | local('OpenSans'), 7 | url("../../multiple/directories/fonts/OpenSans.woff") format('woff'); 8 | } 9 | -------------------------------------------------------------------------------- /test/fixtures/minified.css: -------------------------------------------------------------------------------- 1 | .x{background:url(my/1)}.y{background:url(my/2);font-weight:bold} 2 | -------------------------------------------------------------------------------- /test/fixtures/minified.expected.css: -------------------------------------------------------------------------------- 1 | .x{background:url("../../my/1")}.y{background:url("../../my/2");font-weight:bold} 2 | -------------------------------------------------------------------------------- /test/fixtures/opt.adaptPath.css: -------------------------------------------------------------------------------- 1 | @import url("my/1.css"); 2 | .x { 3 | background: url("my/2.css"); 4 | } 5 | -------------------------------------------------------------------------------- /test/fixtures/opt.adaptPath.expected.css: -------------------------------------------------------------------------------- 1 | @import url("../my/arbitrary/file.css"); 2 | .x { 3 | background: url("../my/arbitrary/file.css"); 4 | } 5 | -------------------------------------------------------------------------------- /test/fixtures/relative-slash.css: -------------------------------------------------------------------------------- 1 | .x { 2 | background: url("/path/from/root") 3 | } 4 | -------------------------------------------------------------------------------- /test/index.coffee: -------------------------------------------------------------------------------- 1 | require 'mocha' 2 | require 'should' 3 | sinon = require 'sinon' 4 | gulp = require 'gulp' 5 | proxyquire = require 'proxyquire' 6 | gutilStub = 7 | log: -> 8 | #console.log(arguments) 9 | fancyLogStub = -> 10 | gutilStub.log.apply(null, arguments) 11 | path = require 'path' 12 | rewriteCss = proxyquire '../src', 13 | 'fancy-log': fancyLogStub 14 | 'path': path 15 | es = require 'event-stream' 16 | Stream = require 'stream' 17 | fs = require 'fs' 18 | Vinyl = require 'vinyl' 19 | stripAnsi = require 'strip-ansi' 20 | 21 | getFixturePath = (relativePath = '') -> 22 | path.join __dirname, 'fixtures', relativePath 23 | 24 | loadFixture = (relativePath) -> 25 | p = getFixturePath relativePath 26 | fs.readFileSync p, 'utf8' 27 | 28 | describe 'gulp-rewrite-css', -> 29 | opts = null 30 | expected = null 31 | inFile = null 32 | 33 | assert = (file, done, expectedOverride = null) -> 34 | inFile = getFixturePath file 35 | if expectedOverride 36 | expected = loadFixture expectedOverride 37 | 38 | gulp.src(inFile) 39 | .pipe(rewriteCss(opts)) 40 | .pipe es.map (file) -> 41 | file.contents.toString().should.eql expected 42 | done() 43 | 44 | beforeEach -> 45 | sinon.spy gutilStub, 'log' 46 | opts = 47 | destination: getFixturePath 'another/dir' 48 | 49 | inFile = getFixturePath 'index.css' 50 | expected = loadFixture 'index.expected.css' 51 | 52 | afterEach -> 53 | gutilStub.log.restore() 54 | 55 | describe 'params', -> 56 | it 'should throw an error if no destination path is passed', -> 57 | rewriteCss.should.throw 'destination directory is missing' 58 | 59 | it 'should give debug output if debug is set to true', (done) -> 60 | opts.debug = true 61 | true.should.be.true 62 | gulp.src(inFile) 63 | .pipe(rewriteCss(opts)) 64 | .pipe es.map (file) -> 65 | gutilStub.log.calledOnce.should.be.true 66 | log = """ 67 | rewrite-css rewriting path for url(fonts/OpenSans.woff) \ 68 | in #{inFile} to url("../../fonts/OpenSans.woff") 69 | """ 70 | stripAnsi(gutilStub.log.firstCall.args.join(' ')).should.eql log 71 | done() 72 | 73 | describe 'with null file', -> 74 | it 'should return the file as-is', (done) -> 75 | file = new Vinyl 76 | base: getFixturePath() 77 | cwd: __dirname, 78 | path: inFile 79 | contents: null 80 | 81 | stream = rewriteCss opts 82 | stream.on 'data', (processed) -> 83 | file.should.be.exactly processed 84 | done() 85 | stream.write file 86 | 87 | describe 'with buffers', -> 88 | it 'should return file.contents as a buffer', (done) -> 89 | gulp.src(inFile) 90 | .pipe(rewriteCss(opts)) 91 | .pipe es.map (file) -> 92 | file.contents.should.be.an.instanceof Buffer 93 | done() 94 | 95 | it 'should rewrite URLs', (done) -> 96 | gulp.src(inFile) 97 | .pipe(rewriteCss(opts)) 98 | .pipe es.map (file) -> 99 | expected.should.eql file.contents.toString() 100 | done() 101 | 102 | describe 'absolute URLs', -> 103 | 104 | beforeEach -> 105 | inFile = getFixturePath 'absolute.css' 106 | expected = loadFixture 'absolute.css' 107 | 108 | it 'should not rewrite absolute URLs', (done) -> 109 | gulp.src(inFile) 110 | .pipe(rewriteCss(opts)) 111 | .pipe es.map (file) -> 112 | expected.should.eql file.contents.toString() 113 | done() 114 | 115 | it 'shoud log that it did not rewrite absolute URLs', (done) -> 116 | opts.debug = true 117 | gulp.src(inFile) 118 | .pipe(rewriteCss(opts)) 119 | .pipe es.map (file) -> 120 | gutilStub.log.calledOnce.should.be.true 121 | expectedLog = """ 122 | rewrite-css not rewriting absolute path for \ 123 | url(http://www.fonts.com/OpenSans.woff) in #{inFile} 124 | """ 125 | args = stripAnsi(gutilStub.log.firstCall.args.join(' ')) 126 | args.should.eql expectedLog 127 | done() 128 | 129 | describe 'with streams', -> 130 | it 'should return file.contents as a stream', (done) -> 131 | gulp.src(inFile, { 132 | buffer: false 133 | }) 134 | .pipe(rewriteCss(opts)) 135 | .pipe es.map (file) -> 136 | file.contents.should.be.an.instanceof Stream 137 | done() 138 | 139 | it 'should rewrite URLs', (done) -> 140 | gulp.src(inFile, { 141 | buffer: false 142 | }) 143 | .pipe(rewriteCss(opts)) 144 | .pipe es.map (file) -> 145 | file.contents.pipe es.wait (err, data) -> 146 | expected.should.be.eql data.toString() 147 | done() 148 | 149 | describe 'edge cases', -> 150 | 151 | it 'should handle single quoted URLs', (done) -> 152 | assert 'index.quotes.single.css', done 153 | it 'should handle double quoted URLs', (done) -> 154 | assert 'index.quotes.double.css', done 155 | it 'should handle URLs with whitespaces', (done) -> 156 | assert 'index.whitespaces.css', done 157 | it 'should handle URLs with a double quote', (done) -> 158 | assert 'index.url.with.quotes.css', 159 | done, 160 | 'index.url.with.quotes.expected.css' 161 | it 'should leave relative URLs starting with slash alone', (done) -> 162 | assert 'relative-slash.css', done, 'relative-slash.css' 163 | it 'should leave svgView URLs alone', (done) -> 164 | assert 'index.svgPath.css', done, 'index.svgPath.expected.css' 165 | it 'should handle rewriting important rules', (done) -> 166 | assert 'index.important.css', done, 'index.important.expected.css' 167 | 168 | describe 'data-urls', -> 169 | it 'should be left alone', (done) -> 170 | assert 'data-urls.css', done, 'data-urls.css' 171 | it 'should not break on weird characters within a data-url', (done) -> 172 | assert 'data-urls-svg.css', done, 'data-urls-svg.css' 173 | 174 | describe 'Windows', -> 175 | origSeparator = path.sep 176 | 177 | beforeEach -> 178 | path.sep = '\\' 179 | 180 | afterEach -> 181 | path.sep = origSeparator 182 | 183 | it 'should fix path separators', (done) -> 184 | assert 'index.windows.css', 185 | done, 186 | 'index.windows.expected.css' 187 | 188 | describe '@import rewriting', -> 189 | beforeEach -> 190 | opts = 191 | destination: getFixturePath 'another/dir' 192 | 193 | it 'should rewrite @import statements', (done) -> 194 | assert 'imports.css', done, 'imports.expected.css' 195 | 196 | describe 'use adaptPath option', -> 197 | opts = 198 | destination: 'path/to/destination' 199 | 200 | it 'should call adaptPath with the correct parameters', (done) -> 201 | opts.adaptPath = sinon.stub().returns('../my/arbitrary/file.css') 202 | 203 | test = -> 204 | opts.adaptPath.calledTwice.should.be.true 205 | opts.adaptPath.getCall(0).args[0].should.be.eql 206 | sourceDir: getFixturePath() 207 | sourceFile: getFixturePath('opt.adaptPath.css') 208 | destinationDir: opts.destination 209 | targetFile: 'my/1.css' 210 | 211 | opts.adaptPath.getCall(1).args[0].should.be.eql 212 | sourceDir: getFixturePath() 213 | sourceFile: getFixturePath('opt.adaptPath.css') 214 | destinationDir: opts.destination 215 | targetFile: 'my/2.css' 216 | 217 | done() 218 | assert 'opt.adaptPath.css', test, 'opt.adaptPath.expected.css' 219 | 220 | it 'should be possible to use the OOTB adaptPath', (done) -> 221 | myAdaptPath = sinon.spy(rewriteCss.adaptPath) 222 | opts.adaptPath= myAdaptPath 223 | 224 | test = -> 225 | myAdaptPath.calledOnce.should.be.true 226 | done() 227 | assert 'index.css', test, 'index.expected.css' 228 | 229 | describe 'minified css', -> 230 | beforeEach -> 231 | opts = 232 | destination: getFixturePath 'another/dir' 233 | 234 | it 'should rewrite urls in minified css', (done) -> 235 | assert 'minified.css', done, 'minified.expected.css' 236 | 237 | describe 'comma after URLs', -> 238 | beforeEach -> 239 | opts = 240 | destination: getFixturePath 'another/dir' 241 | 242 | it 'should rewrite URLs that are followed by a comma', (done) -> 243 | assert 'comma.css', done, 'comma.expected.css' 244 | 245 | --------------------------------------------------------------------------------