├── .gitignore ├── .jshint ├── .travis.yml ├── LICENSE.md ├── README.md ├── expected ├── montserrat-light-webfont.woff ├── montserrat-light-webfont.woff2 ├── rev-manifest.json ├── script.js └── styles.css ├── fixtures ├── fonts │ ├── montserrat-light-webfont.woff │ └── montserrat-light-webfont.woff2 ├── images │ └── dummy.jpg ├── scripts │ ├── application.js │ └── script.js └── styles │ └── styles.css ├── gulpfile.js ├── index.js ├── package.json └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules 3 | results -------------------------------------------------------------------------------- /.jshint: -------------------------------------------------------------------------------- 1 | { 2 | // JSHint Default Configuration File (as on JSHint website) 3 | // See http://jshint.com/docs/ for more details 4 | 5 | "maxerr" : 10, // {int} Maximum error before stopping 6 | 7 | // Enforcing 8 | "bitwise" : false, // true: Prohibit bitwise operators (&, |, ^, etc.) 9 | "camelcase" : false, // true: Identifiers must be in camelCase 10 | "curly" : true, // true: Require {} for every new block or scope 11 | "eqeqeq" : true, // true: Require triple equals (===) for comparison 12 | "forin" : true, // true: Require filtering for..in loops with obj.hasOwnProperty() 13 | "immed" : true, // true: Require immediate invocations to be wrapped in parens e.g. `(function () { } ());` 14 | "indent" : 4, // {int} Number of spaces to use for indentation 15 | "latedef" : true, // true: Require variables/functions to be defined before being used 16 | "newcap" : true, // true: Require capitalization of all constructor functions e.g. `new F()` 17 | "noarg" : true, // true: Prohibit use of `arguments.caller` and `arguments.callee` 18 | "noempty" : true, // true: Prohibit use of empty blocks 19 | "nonew" : false, // true: Prohibit use of constructors for side-effects (without assignment) 20 | "plusplus" : false, // true: Prohibit use of `++` & `--` 21 | "quotmark" : "single", // Quotation mark consistency: 22 | // false : do nothing (default) 23 | // true : ensure whatever is used is consistent 24 | // "single" : require single quotes 25 | // "double" : require double quotes 26 | "undef" : true, // true: Require all non-global variables to be declared (prevents global leaks) 27 | "unused" : true, // true: Require all defined variables be used 28 | "strict" : true, // true: Requires all functions run in ES5 Strict Mode 29 | "trailing" : true, // true: Prohibit trailing whitespaces 30 | "maxparams" : false, // {int} Max number of formal params allowed per function 31 | "maxdepth" : false, // {int} Max depth of nested blocks (within functions) 32 | "maxstatements" : false, // {int} Max number statements per function 33 | "maxcomplexity" : false, // {int} Max cyclomatic complexity per function 34 | "maxlen" : 120, // {int} Max number of characters per line 35 | 36 | // Relaxing 37 | "asi" : false, // true: Tolerate Automatic Semicolon Insertion (no semicolons) 38 | "boss" : false, // true: Tolerate assignments where comparisons would be expected 39 | "debug" : false, // true: Allow debugger statements e.g. browser breakpoints. 40 | "eqnull" : true, // true: Tolerate use of `== null` 41 | "esnext" : false, // true: Allow ES.next (ES6) syntax (ex: `const`) 42 | "moz" : false, // true: Allow Mozilla specific syntax (extends and overrides esnext features) 43 | // (ex: `for each`, multiple try/catch, function expression…) 44 | "evil" : false, // true: Tolerate use of `eval` and `new Function()` 45 | "expr" : true, // true: Tolerate `ExpressionStatement` as Programs 46 | "funcscope" : false, // true: Tolerate defining variables inside control statements" 47 | "globalstrict" : true, // true: Allow global "use strict" (also enables 'strict') 48 | "iterator" : false, // true: Tolerate using the `__iterator__` property 49 | "lastsemic" : false, // true: Tolerate omitting a semicolon for the last statement of a 1-line block 50 | "laxbreak" : false, // true: Tolerate possibly unsafe line breakings 51 | "laxcomma" : false, // true: Tolerate comma-first style coding 52 | "loopfunc" : false, // true: Tolerate functions being defined in loops 53 | "multistr" : false, // true: Tolerate multi-line strings 54 | "proto" : false, // true: Tolerate using the `__proto__` property 55 | "scripturl" : false, // true: Tolerate script-targeted URLs 56 | "smarttabs" : false, // true: Tolerate mixed tabs/spaces when used for alignment 57 | "shadow" : false, // true: Allows re-define variables later in code e.g. `var x=1; x=2;` 58 | "sub" : true, // true: Tolerate using `[]` notation when it can still be expressed in dot notation 59 | "supernew" : false, // true: Tolerate `new function () { ... };` and `new Object;` 60 | "validthis" : false, // true: Tolerate using this in a non-constructor function 61 | 62 | // Environments 63 | "browser" : true, // Web Browser (window, document, etc) 64 | "couch" : false, // CouchDB 65 | "devel" : true, // Development/debugging (alert, confirm, etc) 66 | "dojo" : false, // Dojo Toolkit 67 | "jquery" : true, // jQuery 68 | "mootools" : false, // MooTools 69 | "node" : false, // Node.js 70 | "nonstandard" : false, // Widely adopted globals (escape, unescape, etc) 71 | "prototypejs" : false, // Prototype and Scriptaculous 72 | "rhino" : false, // Rhino 73 | "worker" : true, // Web Workers 74 | "wsh" : false, // Windows Scripting Host 75 | "yui" : false, // Yahoo User Interface 76 | 77 | // Legacy 78 | "nomen" : true, // true: Prohibit dangling `_` in variables 79 | "onevar" : false, // true: Allow only one `var` statement per function 80 | "passfail" : false, // true: Stop on first error 81 | "white" : false, // true: Check against strict whitespace and indentation rules 82 | 83 | // Custom Globals 84 | "globals" : { 85 | 86 | // Glopart 87 | "DEBUG": true, 88 | "module": true, 89 | "require": true, 90 | 91 | // Angular 92 | "angular": true, 93 | 94 | // mocha 95 | "describe": true, 96 | "beforeEach": true, 97 | "afterEach": true, 98 | "before": true, 99 | "after": true, 100 | "it": true, 101 | 102 | //chai 103 | "expect": true 104 | } 105 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.11" 4 | - "0.10" -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Galkin Rostislav 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 | CSS-URL [![Build Status](https://travis-ci.org/galkinrost/gulp-rev-css-url.svg?branch=master)](https://travis-ci.org/galkinrost/gulp-rev-css-url) 2 | ========= 3 | 4 | The lightweight plugin to override urls in css files to hashed after gulp-rev 5 | 6 | What is the result? 7 | -- 8 | See here 9 | 10 | Install 11 | -- 12 | ```sh 13 | npm install gulp-rev-css-url 14 | ``` 15 | 16 | Usage 17 | -- 18 | 19 | ```javascript 20 | var gulp=require('gulp'); 21 | var rev=require('gulp-rev'); 22 | var override=require('gulp-rev-css-url'); 23 | 24 | gulp.task('rev',function(){ 25 | return gulp.src('./app/**/*') 26 | .pipe(rev()) 27 | .pipe(override()) 28 | .pipe(gulp.dest('./build/')) 29 | .pipe(rev.manifest()) 30 | .pipe(gulp.dest('./build/')); 31 | }); 32 | 33 | ``` 34 | AND 35 | ```sh 36 | gulp rev 37 | ``` 38 | 39 | Tests 40 | -- 41 | ```sh 42 | npm test 43 | ``` 44 | 45 | License 46 | ---- 47 | 48 | MIT 49 | -------------------------------------------------------------------------------- /expected/montserrat-light-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/galkinrost/gulp-rev-css-url/ee88aaa8044670396e83976d36030e1a11783a5d/expected/montserrat-light-webfont.woff -------------------------------------------------------------------------------- /expected/montserrat-light-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/galkinrost/gulp-rev-css-url/ee88aaa8044670396e83976d36030e1a11783a5d/expected/montserrat-light-webfont.woff2 -------------------------------------------------------------------------------- /expected/rev-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "images/dummy.jpg": "images/dummy-0849cad9cc.jpg", 3 | "scripts/application.js": "scripts/application-5c2dec9780.js", 4 | "scripts/script.js": "scripts/script-382f58fea6.js", 5 | "styles/styles.css": "styles/styles-d329971534.css", 6 | "fonts/montserrat-light-webfont.woff": "fonts/montserrat-light-webfont-b2f7c06e09.woff", 7 | "fonts/montserrat-light-webfont.woff2": "fonts/montserrat-light-webfont-86efde6016.woff2" 8 | } 9 | -------------------------------------------------------------------------------- /expected/script.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var img = new Image(); 3 | img.src = '/images/dummy-0849cad9cc.jpg'; -------------------------------------------------------------------------------- /expected/styles.css: -------------------------------------------------------------------------------- 1 | .background-image { 2 | background-image: url('../images/dummy-0849cad9cc.jpg'); 3 | } 4 | 5 | .background-image-2 { 6 | background-image: url('../images/dummy-0849cad9cc.jpg'); 7 | } 8 | 9 | @font-face { 10 | font-family: "font"; 11 | font-style: normal; 12 | font-weight: 300; 13 | src: url('../fonts/montserrat-light-webfont-b2f7c06e09.woff') format('woff'), 14 | url('../fonts/montserrat-light-webfont-86efde6016.woff2') format('woff2'); 15 | } 16 | -------------------------------------------------------------------------------- /fixtures/fonts/montserrat-light-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/galkinrost/gulp-rev-css-url/ee88aaa8044670396e83976d36030e1a11783a5d/fixtures/fonts/montserrat-light-webfont.woff -------------------------------------------------------------------------------- /fixtures/fonts/montserrat-light-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/galkinrost/gulp-rev-css-url/ee88aaa8044670396e83976d36030e1a11783a5d/fixtures/fonts/montserrat-light-webfont.woff2 -------------------------------------------------------------------------------- /fixtures/images/dummy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/galkinrost/gulp-rev-css-url/ee88aaa8044670396e83976d36030e1a11783a5d/fixtures/images/dummy.jpg -------------------------------------------------------------------------------- /fixtures/scripts/application.js: -------------------------------------------------------------------------------- 1 | var someFunction = function() { 2 | $.ajax({ 3 | url: 'someurl', 4 | type:"POST", 5 | data:data, 6 | contentType:"application/json; charset=utf-8", 7 | dataType:"json", 8 | success: function(){} 9 | }) 10 | } 11 | -------------------------------------------------------------------------------- /fixtures/scripts/script.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var img = new Image(); 3 | img.src = '/images/dummy.jpg'; -------------------------------------------------------------------------------- /fixtures/styles/styles.css: -------------------------------------------------------------------------------- 1 | .background-image { 2 | background-image: url('../images/dummy.jpg'); 3 | } 4 | 5 | .background-image-2 { 6 | background-image: url('../images/dummy.jpg'); 7 | } 8 | 9 | @font-face { 10 | font-family: "font"; 11 | font-style: normal; 12 | font-weight: 300; 13 | src: url('../fonts/montserrat-light-webfont.woff') format('woff'), 14 | url('../fonts/montserrat-light-webfont.woff2') format('woff2'); 15 | } 16 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var jshint = require('gulp-jshint'); 3 | 4 | var src = ['index.js', 'test.js', 'gulpfile.js']; 5 | 6 | gulp.task('lint', function () { 7 | return gulp.src(src) 8 | .pipe(jshint()) 9 | .pipe(jshint.reporter('default')); 10 | }); 11 | 12 | gulp.task('watch', function () { 13 | gulp.watch(src, ['lint']); 14 | }); -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var through = require('through2'); 2 | var crypto = require('crypto'); 3 | var gutil = require('gulp-util'); 4 | var path = require('path'); 5 | 6 | module.exports = function override() { 7 | var allowedPathRegExp = /\.(css|js)$/; 8 | 9 | function md5(str) { 10 | return crypto.createHash('md5').update(str, 'utf8').digest('hex'); 11 | } 12 | 13 | function relPath(base, filePath) { 14 | if (filePath.indexOf(base) !== 0) { 15 | return filePath; 16 | } 17 | var newPath = filePath.substr(base.length); 18 | if (newPath[0] === path.sep) { 19 | return newPath.substr(1); 20 | } else { 21 | return newPath; 22 | } 23 | } 24 | 25 | var f = []; 26 | 27 | return through.obj(function (file, enc, cb) { 28 | var firstFile = null; 29 | 30 | if (file.path && file.revOrigPath) { 31 | firstFile = firstFile || file; 32 | var _relPath = relPath(path.resolve(firstFile.revOrigBase), file.revOrigPath); 33 | 34 | f.push({ 35 | origPath: _relPath, 36 | hashedPath: relPath(path.resolve(firstFile.base), file.path), 37 | file: file 38 | }); 39 | } 40 | cb(); 41 | }, function (cb) { 42 | var self = this; 43 | 44 | // sort by filename length to not replace the common part(s) of several filenames 45 | var longestFirst = f.slice().sort(function (a, b) { 46 | if(a.origPath.length > b.origPath.length) return -1; 47 | if(a.origPath.length < b.origPath.length) return 1; 48 | return 0; 49 | }); 50 | 51 | f.forEach(function (_f) { 52 | var file = _f.file; 53 | 54 | if ((allowedPathRegExp.test(file.revOrigPath) ) && file.contents) { 55 | var contents = file.contents.toString(); 56 | longestFirst.forEach(function (__f) { 57 | var origPath = __f.origPath.replace(new RegExp('\\' + path.sep, 'g'), '/').replace(/\./g, '\\.'); 58 | var hashedPath = __f.hashedPath.replace(new RegExp('\\' + path.sep, 'g'), '/'); 59 | contents = contents.replace( 60 | new RegExp(origPath, 'g'), 61 | hashedPath); 62 | }); 63 | 64 | file.contents = new Buffer(contents); 65 | 66 | // update file's hash as it does in gulp-rev plugin 67 | var hash = file.revHash = md5(contents).slice(0, 10); 68 | var ext = path.extname(file.path); 69 | var filename = path.basename(file.revOrigPath, ext) + '-' + hash + ext; 70 | file.path = path.join(path.dirname(file.path), filename); 71 | 72 | } 73 | self.push(file); 74 | }); 75 | cb(); 76 | }); 77 | }; 78 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gulp-rev-css-url", 3 | "version": "0.1.0", 4 | "description": "The lightweight plugin to override urls in css files to hashed after gulp-rev", 5 | "repository": { 6 | "type": "git", 7 | "url": "git://github.com/galkinrost/gulp-rev-css-url" 8 | }, 9 | "author": { 10 | "name": "Galkin Rostislav", 11 | "email": "galkinrost@gmail.com", 12 | "url": "http://github.com/galkinrost" 13 | }, 14 | "keywords": [ 15 | "gulpplugin", 16 | "rev", 17 | "revision", 18 | "hash", 19 | "optimize", 20 | "version", 21 | "versioning", 22 | "cache", 23 | "expire", 24 | "static", 25 | "asset", 26 | "assets", 27 | "gulp-rev" 28 | ], 29 | "devDependencies": { 30 | "gulp": "~3.6.2", 31 | "gulp-rev": "~5.0.0", 32 | "mocha": "~1.20.0", 33 | "gulp-jshint": "~1.6.1", 34 | "fs-extra": "~0.9.1", 35 | "chai": "~1.9.1" 36 | }, 37 | "dependencies": { 38 | "crypto": "0.0.3", 39 | "gulp-util": "~2.2.14", 40 | "through2": "~0.4.2" 41 | }, 42 | "scripts": { 43 | "test": "mocha test.js -R list" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | var rev = require('gulp-rev'); 2 | var fs = require('fs'); 3 | var gulp = require('gulp'); 4 | var fse = require('fs-extra'); 5 | var override = require('./index'); 6 | var expect = require('chai').expect; 7 | var through = require('through2'); 8 | 9 | describe('gulp-rev-css-url', function () { 10 | beforeEach(function (done) { 11 | fse.remove('./results', done); 12 | }) 13 | 14 | it('Should override urls in css and js', function (done) { 15 | var expectedCSS = fs.readFileSync('./expected/styles.css', 'utf-8'), 16 | expectedJs = fs.readFileSync('./expected/script.js', 'utf-8'), 17 | expectedFont1 = fs.readFileSync('./expected/montserrat-light-webfont.woff', 'utf-8'), 18 | expectedFont2 = fs.readFileSync('./expected/montserrat-light-webfont.woff2', 'utf-8'), 19 | expectedManifest = require('./expected/rev-manifest.json', 'utf-8'); 20 | gulp.src('./fixtures/**/*') 21 | .pipe(rev()) 22 | .pipe(override()) 23 | .pipe(gulp.dest('./results/')) 24 | .pipe(rev.manifest()) 25 | .pipe(gulp.dest('./results/')) 26 | .on('end', function () { 27 | // load results 28 | var css = fs.readFileSync('./results/styles/styles-d329971534.css', 'utf-8'), 29 | js = fs.readFileSync('./results/scripts/script-382f58fea6.js', 'utf-8'), 30 | font1 = fs.readFileSync('./results/fonts/montserrat-light-webfont-b2f7c06e09.woff', 'utf-8'), 31 | font2 = fs.readFileSync('./results/fonts/montserrat-light-webfont-86efde6016.woff2', 'utf-8'), 32 | manifest = require('./results/rev-manifest.json', 'utf-8'); 33 | 34 | // check files' content 35 | expect(css).to.equal(expectedCSS); 36 | expect(js).to.equal(expectedJs); 37 | expect(expectedFont1).to.equal(font1); 38 | expect(expectedFont2).to.equal(font2); 39 | 40 | // check manifest 41 | expect(manifest).to.deep.equal(expectedManifest); 42 | 43 | done(); 44 | }); 45 | }); 46 | 47 | it('Should not replace application/json with application.js', function(done) { 48 | gulp.src('./fixtures/scripts/application.js') 49 | .pipe(rev()) 50 | .pipe(override()) 51 | .pipe(gulp.dest('./results/')) 52 | .on('end', function () { 53 | var js = fs.readFileSync( 54 | './results/application-5c2dec9780.js', 55 | 'utf-8'); 56 | expect(js).to.contain('application/json'); 57 | done(); 58 | }); 59 | }); 60 | 61 | it('Should not reorder the pipeline', function (done) { 62 | var outputOrder = []; 63 | gulp.src(['./fixtures/scripts/script.js', './fixtures/scripts/application.js']) 64 | .pipe(rev()) 65 | .pipe(override()) 66 | .pipe(through.obj( 67 | function (file, enc, cb) { 68 | outputOrder.push(file.revOrigPath.replace(file.revOrigBase, '')); 69 | cb(null, file); 70 | }, 71 | function (cb) { 72 | expect(outputOrder).to.deep.equal(['script.js', 'application.js']); 73 | done(); 74 | } 75 | )); 76 | }); 77 | 78 | }); 79 | 80 | --------------------------------------------------------------------------------