├── .editorconfig ├── .gitignore ├── .jshintrc ├── .travis.yml ├── Gruntfile.js ├── LICENSE-MIT ├── README.md ├── changelog.md ├── config ├── complex.js ├── createCopies.js ├── deleteOriginals.js ├── hash.js ├── ignorePattern.js ├── imgsrcset.js ├── jsonOutput.js ├── outputDir.js ├── queryString.js ├── realpath.js ├── relativepath.js ├── relativepath2.js ├── scripts.js ├── sourceBusted.js ├── sourceBusted2.js ├── sourceBusted3.js ├── standard.js ├── stylesheets.js ├── templates.js ├── urlPrefixes.js └── xml.js ├── license ├── package-lock.json ├── package.json ├── tasks └── cachebust.js └── tests ├── absolutepath ├── absolutepath.html ├── absolutepath_test.js └── assets │ ├── css │ └── application.css │ └── images │ └── testbg.png ├── complex ├── complex.html ├── complex_test.js ├── css │ └── complex.css └── imgs │ └── complex-background.jpg ├── createCopies ├── assets │ ├── .htaccess │ └── dontCopy.png └── createCopies.html ├── deleteOriginals ├── assets │ ├── delete.css │ ├── delete.js │ └── delete.png ├── deleteOriginalSameAssets.html └── deleteOriginals.html ├── hash ├── assets │ ├── hash.css │ ├── hash.jpg │ └── hash.js ├── hash.html └── hash_test.js ├── ignorePattern ├── assets │ ├── toBeIgnoredCSS.css │ ├── toBeIgnoredJPG.jpg │ └── toBeIgnoredJS.js ├── ignorePattern.html └── ignorePattern_test.js ├── imgsrcset ├── assets │ ├── imgsrcset-big.jpg │ ├── imgsrcset-small.jpg │ └── imgsrcset.jpg ├── imgsrcset.html └── imgsrcset_test.js ├── jsonOutput ├── assets │ └── jsonOutput.js ├── jsonOutput.html └── jsonOutput_test.js ├── outputDir ├── assets │ ├── outputDir.css │ ├── outputDir.js │ └── outputDir.png ├── outputDir.html └── outputDir_test.js ├── queryString ├── css │ └── test.css ├── imgs │ └── background.jpg ├── queryString.html └── queryString_test.js ├── realpath ├── css │ └── app.css ├── lib │ └── css │ │ └── app.css ├── realpath.html └── realpath_test.js ├── relativepath ├── assets │ ├── css │ │ └── application.css │ └── images │ │ └── testbg.png ├── relativepath.html └── relativepath_test.js ├── relativepath2 ├── assets │ ├── css │ │ └── application.css │ └── images │ │ └── testbg.png ├── relativepath2.html └── relativepath2_test.js ├── scripts ├── assets │ └── script.js ├── scripts.js └── scripts_test.js ├── sourceBusted ├── assets │ ├── css │ │ └── application.css │ └── images │ │ └── testbg.png ├── sourceBusted.html └── sourceBusted_test.js ├── sourceBusted2 ├── assets │ ├── css │ │ └── application.css │ └── images │ │ └── testbg.png ├── sourceBusted2.html └── sourceBusted2_test.js ├── sourceBusted3 ├── assets │ ├── css │ │ └── application.css │ └── images │ │ └── testbg.png ├── sourceBusted3.html └── sourceBusted3_test.js ├── standard ├── assets │ ├── standard-apple-touch-icon.jpg │ ├── standard.css │ ├── standard.jpg │ └── standard.js ├── standard.html ├── standard_test.js └── subfolder │ └── relative-standard.html ├── stylesheets ├── assets │ ├── css-image-large.jpg │ ├── css-image-quotes.jpg │ ├── css-image.jpg │ ├── fonts │ │ ├── icons.eot │ │ ├── icons.ttf │ │ ├── icons.woff │ │ └── icons.woff2 │ ├── image1.jpg │ ├── image2.jpg │ ├── relative-css-image-large.jpg │ ├── relative-css-image-quotes.jpg │ ├── relative-css-image.jpg │ ├── relative-image1.jpg │ ├── stylesheet1.css │ ├── stylesheet2.css │ ├── stylesheet3.css │ └── stylesheet4.css ├── css │ └── replaceCssInSubdir.css ├── stylesheet.css └── stylesheet_test.js ├── templates ├── base.html ├── templates_test.js └── views │ ├── view-a.html │ └── view-b.html ├── urlPrefixes ├── assets │ ├── imgsrcset-big.jpg │ ├── imgsrcset-small.jpg │ └── imgsrcset.jpg ├── css │ └── app.css ├── urlPrefixes.html └── urlPrefixes_test.js └── xml ├── assets ├── mstile-150x150.png ├── mstile-310x150.png ├── mstile-310x310.png └── mstile-70x70.png ├── xml.xml └── xml_test.js /.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 | word_wrap = true 12 | wrap_width = 120 13 | 14 | [*.md] 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | tmp 4 | .DS_STORE 5 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "curly": true, 3 | "eqeqeq": true, 4 | "immed": true, 5 | "latedef": false, 6 | "newcap": true, 7 | "noarg": true, 8 | "sub": true, 9 | "undef": true, 10 | "boss": true, 11 | "eqnull": true, 12 | "node": true 13 | } 14 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | sudo: false 3 | node_js: 4 | - "10" 5 | - "8" 6 | - "6" 7 | before_script: 8 | - npm install -g grunt-cli 9 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | 'use strict'; 3 | 4 | var configFiles = grunt.file.expand('./config/*.js'); 5 | 6 | // Default cacheBust options 7 | var cacheBustObj = { 8 | options: { 9 | encoding: 'utf8', 10 | length: 16, 11 | algorithm: 'md5' 12 | } 13 | }; 14 | 15 | // Load each cacheBust config 16 | configFiles.forEach(function(filename) { 17 | var taskName = filename.replace('./config/', '').replace('.js', ''); 18 | cacheBustObj[taskName] = require(filename); 19 | }); 20 | 21 | grunt.initConfig({ 22 | 23 | clean: { 24 | tmp: 'tmp' 25 | }, 26 | 27 | jshint: { 28 | all: [ 29 | 'Gruntfile.js', 30 | 'tasks/**/*.js', 31 | '<%= nodeunit.tests %>' 32 | ], 33 | options: { 34 | jshintrc: '.jshintrc' 35 | } 36 | }, 37 | 38 | copy: { 39 | main: { 40 | files: [{ 41 | expand: true, 42 | dot: true, 43 | cwd: 'tests/', 44 | src: ['**', '!**/*_test.js'], 45 | dest: 'tmp/' 46 | }] 47 | } 48 | }, 49 | 50 | cacheBust: cacheBustObj, 51 | 52 | nodeunit: { 53 | tests: ['tests/**/*_test.js'] 54 | }, 55 | 56 | watch: { 57 | task: { 58 | files: ['tasks/**/*.js', 'tests/**/*', 'config/*.js'], 59 | tasks: 'test' 60 | } 61 | } 62 | 63 | }); 64 | 65 | // Load this plugins tasks 66 | grunt.loadTasks('tasks'); 67 | 68 | grunt.loadNpmTasks('grunt-contrib-jshint'); 69 | grunt.loadNpmTasks('grunt-contrib-copy'); 70 | grunt.loadNpmTasks('grunt-contrib-clean'); 71 | grunt.loadNpmTasks('grunt-contrib-nodeunit'); 72 | grunt.loadNpmTasks('grunt-contrib-watch'); 73 | 74 | grunt.registerTask('default', 'bust'); 75 | grunt.registerTask('test', ['bust', 'nodeunit']); 76 | grunt.registerTask('bust', ['jshint', 'clean', 'copy', 'cacheBust']); 77 | }; 78 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Ben Holland 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # grunt-cache-bust 2 | 3 | [![npm version](https://badge.fury.io/js/grunt-cache-bust.svg)](http://badge.fury.io/js/grunt-cache-bust) 4 | [![Build Status](https://travis-ci.org/hollandben/grunt-cache-bust.png?branch=master)](https://travis-ci.org/hollandben/grunt-cache-bust) 5 | [![Dependency Status](https://david-dm.org/hollandben/grunt-cache-bust.svg)](https://david-dm.org/hollandben/grunt-cache-bust) 6 | [![Join the chat at https://gitter.im/hollandben/grunt-cache-bust](https://badges.gitter.im/hollandben/grunt-cache-bust.svg)](https://gitter.im/hollandben/grunt-cache-bust?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 7 | 8 | > Bust static assets from the cache using content hashing 9 | 10 | * [Getting Started](#getting-started) 11 | * [Introduction](#the-cachebust-task) 12 | * [How it works](#how-it-works) 13 | * [Options](#options) 14 | * [Usage Examples](#usage-examples) 15 | * [CDNs](#cdns) 16 | * [Change Log](#change-log) 17 | 18 | ## PLEASE READ 19 | This plugin recently upgraded to `v1.0.0`!! There was a big change in the way the plugin works. You can read me about the changes in issue [#147](https://github.com/hollandben/grunt-cache-bust/issues/147). 20 | 21 | ## Getting Started 22 | _If you haven't used [grunt][] before, be sure to check out the [Getting Started][] guide._ 23 | 24 | From the same directory as your project's [Gruntfile][Getting Started] and [package.json][], install this plugin with the following command: 25 | 26 | ```bash 27 | npm install grunt-cache-bust --save-dev 28 | ``` 29 | 30 | Once the plugin has been installed, enabled it inside your Gruntfile. 31 | 32 | ```bash 33 | grunt.loadNpmTasks('grunt-cache-bust'); 34 | ``` 35 | 36 | [grunt]: http://gruntjs.com/ 37 | [Getting Started]: https://github.com/gruntjs/grunt/blob/devel/docs/getting_started.md 38 | [package.json]: https://npmjs.org/doc/json.html 39 | 40 | ## The "cacheBust" task 41 | 42 | Use the `cacheBust` task for cache busting static files in your application. This allows the assets to have a large expiry time in the browsers cache and will only be forced to use an updated file when the contents of it changes. This is a good practice. 43 | 44 | Tell the `cacheBust` task where your static assets are and the files that reference them and let it work it's magic. 45 | 46 | ### Supported file types 47 | All of them!! 48 | 49 | ### How it works 50 | In your project's Gruntfile, add a new task called `cacheBust`. 51 | 52 | This is the most basic configuration you can have: 53 | 54 | ```js 55 | cacheBust: { 56 | taskName: { 57 | options: { 58 | assets: ['assets/**'] 59 | }, 60 | src: ['index.html'] 61 | } 62 | } 63 | ``` 64 | 65 | These are the two mandatory fields you need to supply: 66 | 67 | The `assets` option that is passed to the plugin tells it what types of files you want to hash, i.e. `css` and `js` files. You must also provide the location for these files. In the example above, they live in the `assets` folder. 68 | 69 | The `src` part of the configuration you should have seen before as it's used by pretty much every Grunt plugin. We use this to tell the plugin which files contain references to assets we're going to be adding a hash to. You can [use the `expand` configuration option here as well](http://gruntjs.com/configuring-tasks#building-the-files-object-dynamically) 70 | 71 | **To summarise, the above configuration will hash all the files in the `assets` directory and replace any references to those files in the `index.html` file.** 72 | 73 | ### Options 74 | 75 | #### Summary 76 | 77 | ``` 78 | // Here is a short summary of the options and some of their 79 | defaults. Extra details are below. 80 | { 81 | algorithm: 'md5', // Algorithm used for hashing files 82 | assets: ['css/*', 'js/*'] // File patterns for the assets you wish to hash 83 | baseDir: './', // The base directory for all assets 84 | createCopies: true, // Create hashed copies of files 85 | deleteOriginals: false, // Delete the original file after hashing 86 | encoding: 'utf8', // The encoding used when reading/writing files 87 | hash: '9ef00db36970718e', // A user defined hash for every file. Not recommended. 88 | jsonOutput: false, // Output the original => new URLs to a JSON file 89 | jsonOutputFilename: 'grunt-cache-bust.json', // The file path and name of the exported JSON. Is relative to baseDir 90 | length: 16, // The length of the hash value 91 | separator: '.', // The separator between the original file name and hash 92 | queryString: false // Use a query string for cache busting instead of rewriting files 93 | outputDir: '' // Directory where all hashed assets will be copied. Is relative to baseDir 94 | clearOutputDir: false, // Clear output directory. If outputDir was not set clear will not work 95 | urlPrefixes: ['http://owncdn.test.com/path'] // Array of Url + Path of own CDNs where hashed files are uploaded to. 96 | } 97 | ``` 98 | 99 | #### options.algorithm 100 | Type: `String` 101 | Default value: `'md5'` 102 | 103 | `algorithm` is dependent on the available algorithms supported by the version of OpenSSL on the platform. Examples are `'sha1'`, `'md5'`, `'sha256'`, `'sha512'` 104 | 105 | #### options.assets 106 | Type: `Array` 107 | 108 | `assets` contains the file patterns for where all your assets live. This should point towards all the assets you wish to have busted. It uses the same glob pattern for matching files as Grunt. 109 | 110 | 111 | #### options.baseDir 112 | Type: `String` 113 | Default value: `false` 114 | 115 | When set, `cachebust` will try to find the assets using the baseDir as base path. 116 | 117 | ```js 118 | assets: { 119 | options: { 120 | baseDir: 'public/', 121 | }, 122 | files: [{ 123 | expand: true, 124 | cwd: 'public/', 125 | src: ['modules/**/*.html'] 126 | }] 127 | } 128 | ``` 129 | #### options.urlPrefixes 130 | Type: `Array of Strings` 131 | Default value: `` 132 | 133 | When set, `cachebust` will try to find the assets with this prefixes, useful for: 134 | - Asset files uploaded to a separate cdn 135 | - Asset files which are served out of a deeper path within your deployment 136 | 137 | For example: 138 | 139 | ```js 140 | cacheBust: { 141 | options: { 142 | urlPrefixes: ['/dashboard/app/app.min.js'] 143 | } 144 | } 145 | ``` 146 | 147 | will convert: 148 | 149 | ```html 150 | 151 | ``` 152 | 153 | into 154 | 155 | ```html 156 | 157 | ``` 158 | 159 | #### options.createCopies 160 | Type: `Boolean` 161 | Default value: `true` 162 | 163 | When set to `false`, `cachebust` will not create hashed copies of the files. Useful if you use server rewrites to serve your files. 164 | 165 | #### options.deleteOriginals 166 | Type: `Boolean` 167 | Default value: `false` 168 | 169 | When set, `cachebust` will delete the original versions of the files that have been hashed. For example, `style.css` will be deleted after being copied to `style.dcf1d324cb50a1f9.css`. 170 | 171 | #### options.encoding 172 | Type: `String` 173 | Default value: `'utf8'` 174 | 175 | The encoding of the file contents. 176 | 177 | #### options.hash 178 | Type: `String` 179 | 180 | A user defined value to be used as the hash value for all files. For a more beneficial caching strategy, it's advised not to supply a hash value for all files. 181 | 182 | #### options.jsonOutput 183 | Type: `Boolean` 184 | Default value: `false` 185 | 186 | When set as `true`, `cachbust` will create a json file with an object inside that contains key value pairs of the original file name, and the renamed md5 hash name for each file. 187 | 188 | Output format looks like this: 189 | ``` 190 | { 191 | '/scripts/app.js' : '/scripts/app.23e6f7ac5623e96f.js', 192 | '/scripts/vendor.js': '/scripts/vendor.h421fwaj124bfaf5.js' 193 | } 194 | ``` 195 | 196 | #### options.jsonOutputFilename 197 | Type: `String` 198 | Default value: `grunt-cache-bust.json` 199 | 200 | The file path and name of the exported JSON. It is exported relative to `baseDir`. 201 | 202 | #### options.length 203 | Type: `Number` 204 | Default value: `16` 205 | 206 | The number of characters of the file content hash to prefix the file name with. 207 | 208 | #### options.separator 209 | Type: `String` 210 | Default value: `.` 211 | 212 | The separator between the original file name and hash. 213 | 214 | #### options.queryString 215 | Type: `Boolean` 216 | Default value: `false` 217 | 218 | Use a query string for cache busting instead of rewriting files. 219 | 220 | ### Usage Examples 221 | 222 | #### The most basic setup 223 | ```js 224 | cacheBust: { 225 | taskName: { 226 | options: { 227 | assets: ['assets/**'] 228 | }, 229 | src: ['index.html'] 230 | } 231 | } 232 | ``` 233 | 234 | #### Bust using a query string instead of rewriting files 235 | ```js 236 | cacheBust: { 237 | taskName: { 238 | options: { 239 | assets: ['assets/**'], 240 | queryString: true 241 | }, 242 | src: ['index.html'] 243 | } 244 | } 245 | ``` 246 | 247 | #### Bust all assets and update references in all templates and assets 248 | ```js 249 | cacheBust: { 250 | options: { 251 | assets: ['assets/**/*'], 252 | baseDir: './public/' 253 | }, 254 | taskName: { 255 | files: [{ 256 | expand: true, 257 | cwd: 'public/', 258 | src: ['templates/**/*.html', 'assets/**/*'] 259 | }] 260 | } 261 | } 262 | ``` 263 | 264 | #### Inherited options for multiple tasks 265 | ```js 266 | cacheBust: { 267 | options: { 268 | assets: ['assets/**'], 269 | baseDir: './public/' 270 | }, 271 | staging: { 272 | options: { 273 | jsonOutput: true 274 | }, 275 | src: ['index.html'] 276 | }, 277 | production: { 278 | options: { 279 | jsonOutput: false 280 | }, 281 | src: ['index.html'] 282 | } 283 | } 284 | ``` 285 | 286 | # License 287 | 288 | MIT © [Ben Holland](https://benholland.me) 289 | -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | Change Log 2 | ========== 3 | 4 | **v1.7.0** 5 | * Add urlPrefixes option (#234) 6 | 7 | **v1.6.0** 8 | * Log out busted files in verbose mode only (#228) 9 | * Updating documentation to show the loadNpmTasks business (#231) 10 | * Fix for clearOutputDir option (#230) 11 | * Fix multiple cache bust query params being added (#227) 12 | 13 | **v1.5.1** 14 | * Fix "Warning: Cannot use 'in' operator to search" error when queryString option is passed 15 | * Fix several paths issues 16 | * Enable Busting of img srcset 17 | 18 | **v1.5.0** 19 | * Fix paths in hashed files (#211) 20 | * Files in outputDir should have files replaced (#210) 21 | 22 | **v1.4.1** 23 | * Move `fs-extra` from dev dependency to dependency 24 | 25 | **v1.4.0** 26 | * Added option `outputDir`, a directory where all hashed assets will be copied 27 | 28 | **v1.3.0** 29 | * Upgraded to `grunt@1.x.x` 30 | 31 | **v1.2.0** 32 | * Added option `queryString` to bust using a query string and keep original files intact 33 | 34 | **v1.1.0** 35 | * Added option `createCopies` to disable creating hashed copies of the files 36 | 37 | **v1.0.0** 38 | * Fundamental breaking changes - see issue [#147](https://github.com/hollandben/grunt-cache-bust/issues/147) for more details 39 | * Re-wrote the way the plugin functions. Instead of finding assets in files, the plugin now goes through a given assets folder and builds an object based on the original and hashed file name. Read more about the changes in [#147](https://github.com/hollandben/grunt-cache-bust/issues/147) 40 | * Remove string option for `jsonOutput`, enforcing the use of `jsonOutputFilename` 41 | * Sorting and reversing to collection of assets - fixes #176 42 | * Updated documentation 43 | 44 | **v0.6.1** 45 | * Support cache busting for meta tags 46 | * Support cache busting for all favicons 47 | 48 | **v0.6.0** 49 | * Support cache busting for video tag 50 | * Fix CSS processing for media queries with comments 51 | * Use passed in grunt when registering 52 | 53 | **v0.5.1** 54 | * Reading files to be hashed as a buffer rather than string 55 | 56 | **v0.5.0** - 2015-08-09 57 | * Using Node's path module to help with getting the correct paths to assets 58 | 59 | **v0.4.13** - 2015-02-27 60 | * Fixes issue with deleting the original files when referenced in more than one source file. 61 | * Fixed issue with hashe in the url of assets when referenced in CSS 62 | 63 | **v0.4.12** - 2015-02-26 64 | * Fixed tests and implementation when deleting original files. 65 | 66 | **v0.4.12** - 2015-02-25 67 | * Ignoring data-images when parsing CSS. 68 | 69 | **v0.4.12** - 2015-02-20 70 | * Added support for Windows 8.1 and IE titles browser config file. 71 | 72 | **v0.4.2** - 2015-02-19 73 | * Tidied up tests. Improved README readability. 74 | 75 | **v0.4.2** - 2015-02-18 76 | * Improved detection of remote resources 77 | 78 | **v0.4.2** - 2015-02-18 79 | * Fix for working with relative paths 80 | 81 | **v0.4.2** - 2015-02-15 82 | * Added options to remove frag hints and use a local CDN. Busting multiple values in CSS files. Bust SVG xlink:href path. Override `baseDir` on a per file basis. 83 | -------------------------------------------------------------------------------- /config/complex.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | options: { 3 | assets: ['**/*.{css,jpg}'], 4 | baseDir: 'tmp/complex', 5 | deleteOriginals: true, 6 | hash: '123456789' 7 | }, 8 | files: [{ 9 | expand: true, 10 | cwd: 'tmp/complex', 11 | src: ['css/*.css', '*.html'] 12 | }] 13 | }; 14 | -------------------------------------------------------------------------------- /config/createCopies.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | options: { 3 | baseDir: 'tmp/createCopies', 4 | createCopies: false, 5 | assets: 'assets/*' 6 | }, 7 | files: [{ 8 | expand: true, 9 | cwd: 'tmp/createCopies/', 10 | src: ['*.html'] 11 | }] 12 | }; 13 | -------------------------------------------------------------------------------- /config/deleteOriginals.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | options: { 3 | baseDir: 'tmp/deleteOriginals', 4 | deleteOriginals: true, 5 | assets: 'assets/*' 6 | }, 7 | files: [{ 8 | expand: true, 9 | cwd: 'tmp/deleteOriginals/', 10 | src: ['*.html'] 11 | }] 12 | }; 13 | -------------------------------------------------------------------------------- /config/hash.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | options: { 3 | assets: '**/*.{js,css,jpg}', 4 | baseDir: 'tmp/hash/', 5 | hash: 'abcdef123456' 6 | }, 7 | src: ['tmp/hash/**/*.html'] 8 | }; 9 | -------------------------------------------------------------------------------- /config/ignorePattern.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | options: { 3 | baseDir: 'tmp/ignorePattern', 4 | assets: ['assets/*', '!assets/*.jpg'] 5 | }, 6 | files: [{ 7 | expand: true, 8 | cwd: 'tmp/ignorePattern/', 9 | src: ['*.html'] 10 | }] 11 | }; 12 | -------------------------------------------------------------------------------- /config/imgsrcset.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | options: { 3 | assets: '**/*.jpg', 4 | baseDir: 'tmp/imgsrcset/' 5 | }, 6 | src: ['tmp/imgsrcset/**/*.html'] 7 | }; 8 | -------------------------------------------------------------------------------- /config/jsonOutput.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | options: { 3 | assets: '**/*.{js,css,jpg}', 4 | baseDir: 'tmp/jsonOutput/', 5 | jsonOutput: true, 6 | jsonOutputFilename: 'jsonOutput-map.json' 7 | }, 8 | src: ['tmp/jsonOutput/**/*.html'] 9 | }; 10 | -------------------------------------------------------------------------------- /config/outputDir.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | options: { 3 | baseDir: 'tmp/outputDir', 4 | outputDir: 'outputDir', 5 | assets: 'assets/*', 6 | hash: 'abcdef123456' 7 | }, 8 | files: [{ 9 | expand: true, 10 | cwd: 'tmp/outputDir/', 11 | src: ['outputDir.html', 'assets/outputDir.css'] 12 | }] 13 | }; 14 | -------------------------------------------------------------------------------- /config/queryString.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | options: { 3 | assets: ['**/*.{css,jpg}'], 4 | baseDir: 'tmp/queryString', 5 | queryString: true, 6 | hash: '123456789' 7 | }, 8 | files: [{ 9 | expand: true, 10 | cwd: 'tmp/queryString', 11 | src: ['css/*.css', '*.html'] 12 | }] 13 | }; 14 | -------------------------------------------------------------------------------- /config/realpath.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | options: { 3 | assets: ['css/*.{css,jpg}'], 4 | baseDir: 'tmp/realpath', 5 | deleteOriginals: true, 6 | hash: '123456789' 7 | }, 8 | files: [{ 9 | expand: true, 10 | cwd: 'tmp/realpath', 11 | src: ['css/*.css', '*.html'] 12 | }] 13 | }; 14 | -------------------------------------------------------------------------------- /config/relativepath.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | options: { 3 | assets: ['assets/**'], 4 | baseDir: 'tmp/relativepath', 5 | deleteOriginals: true, 6 | hash: '123456789', 7 | jsonOutput: true 8 | }, 9 | files: [{ 10 | expand: true, 11 | cwd: 'tmp/relativepath', 12 | src: ['assets/css/*.css', '*.html'] 13 | }] 14 | }; 15 | -------------------------------------------------------------------------------- /config/relativepath2.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | options: { 3 | assets: ['assets/**'], 4 | baseDir: './tmp/relativepath2/', 5 | deleteOriginals: true, 6 | hash: '123456789' 7 | }, 8 | files: [{ 9 | cwd: './tmp/relativepath2/', 10 | src: ['assets/css/application.css', 'relativepath2.html'] 11 | }] 12 | }; 13 | -------------------------------------------------------------------------------- /config/scripts.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | options: { 3 | baseDir: 'tmp/scripts', 4 | assets: 'assets/*' 5 | }, 6 | files: [{ 7 | expand: true, 8 | cwd: 'tmp/scripts/', 9 | src: ['*.js'] 10 | }] 11 | }; 12 | -------------------------------------------------------------------------------- /config/sourceBusted.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | options: { 3 | assets: ['assets/**'], 4 | baseDir: 'tmp/sourceBusted', 5 | deleteOriginals: true, 6 | hash: '123456789', 7 | }, 8 | files: [{ 9 | src: ['tmp/sourceBusted/assets/css/application.css', 'tmp/sourceBusted/sourceBusted.html'] 10 | }] 11 | }; 12 | -------------------------------------------------------------------------------- /config/sourceBusted2.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | options: { 3 | assets: ['assets/**'], 4 | baseDir: 'tmp/sourceBusted2', 5 | deleteOriginals: true, 6 | hash: '123456789', 7 | }, 8 | files: [{ 9 | cwd: './tmp/sourceBusted2/', 10 | src: ['assets/css/application.css', 'sourceBusted2.html'] 11 | }] 12 | }; 13 | -------------------------------------------------------------------------------- /config/sourceBusted3.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | options: { 3 | assets: ['assets/**'], 4 | baseDir: './tmp/sourceBusted3', // here ./tmp/ 5 | deleteOriginals: true, 6 | hash: '123456789', 7 | }, 8 | files: [{ 9 | src: ['tmp/sourceBusted3/assets/css/application.css', 'tmp/sourceBusted3/sourceBusted3.html'] // here tmp 10 | }] 11 | }; 12 | -------------------------------------------------------------------------------- /config/standard.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | options: { 3 | assets: '**/*.{js,css,jpg}', 4 | baseDir: 'tmp/standard/' 5 | }, 6 | src: ['tmp/standard/**/*.html'] 7 | }; 8 | -------------------------------------------------------------------------------- /config/stylesheets.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | options: { 3 | baseDir: 'tmp/stylesheets', 4 | assets: 'assets/**/*' 5 | }, 6 | files: [{ 7 | expand: true, 8 | cwd: 'tmp/stylesheets/', 9 | src: ['**/*.css'] 10 | }] 11 | }; 12 | -------------------------------------------------------------------------------- /config/templates.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | options: { 3 | assets: 'views/*.html', 4 | baseDir: 'tmp/templates/' 5 | }, 6 | src: ['tmp/templates/base.html'] 7 | }; 8 | -------------------------------------------------------------------------------- /config/urlPrefixes.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | options: { 3 | assets: ['**/*.{css,jpg}'], 4 | baseDir: 'tmp/urlPrefixes', 5 | urlPrefixes: ['http://owncdn1.test.com/path', 'http://owncdn2.test.com/path'], 6 | deleteOriginals: true, 7 | hash: '123456789' 8 | }, 9 | files: [{ 10 | expand: true, 11 | cwd: 'tmp/urlPrefixes', 12 | src: ['css/*.css', '*.html'] 13 | }] 14 | }; 15 | -------------------------------------------------------------------------------- /config/xml.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | options: { 3 | baseDir: 'tmp/xml', 4 | assets: 'assets/*' 5 | }, 6 | files: [{ 7 | expand: true, 8 | cwd: 'tmp/xml/', 9 | src: ['*.xml'] 10 | }] 11 | }; 12 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Ben Holland (benholland.me) 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "grunt-cache-bust", 3 | "description": "Bust static assets from the cache using content hashing", 4 | "version": "1.7.0", 5 | "author": "Ben Holland ", 6 | "repository": { 7 | "type": "git", 8 | "url": "git://github.com/hollandben/grunt-cache-bust.git" 9 | }, 10 | "licenses": [ 11 | { 12 | "type": "MIT", 13 | "url": "https://github.com/hollandben/grunt-cache-bust/blob/master/license" 14 | } 15 | ], 16 | "engines": { 17 | "node": ">=0.10.0" 18 | }, 19 | "scripts": { 20 | "test": "grunt test" 21 | }, 22 | "devDependencies": { 23 | "grunt": "^1.0.3", 24 | "grunt-contrib-clean": "^1.1.0", 25 | "grunt-contrib-copy": "^1.0.0", 26 | "grunt-contrib-jshint": "^1.1.0", 27 | "grunt-contrib-nodeunit": "^2.0.0", 28 | "grunt-contrib-watch": "^1.1.0" 29 | }, 30 | "peerDependencies": { 31 | "grunt": ">=0.4.0" 32 | }, 33 | "keywords": [ 34 | "grunt", 35 | "grunt plugin", 36 | "cache", 37 | "bust", 38 | "bust assets" 39 | ], 40 | "dependencies": { 41 | "fs-extra": "^6.0.1" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tasks/cachebust.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var fs = require('fs-extra'); 4 | var url = require('url'); 5 | var path = require('path'); 6 | var crypto = require('crypto'); 7 | var _ = require('grunt').util._; 8 | 9 | var DEFAULT_OPTIONS = { 10 | algorithm: 'md5', 11 | baseDir: './', 12 | createCopies: true, 13 | deleteOriginals: false, 14 | encoding: 'utf8', 15 | jsonOutput: false, 16 | jsonOutputFilename: 'grunt-cache-bust.json', 17 | length: 16, 18 | separator: '.', 19 | queryString: false, 20 | outputDir: '', 21 | clearOutputDir: false, 22 | urlPrefixes: [] 23 | }; 24 | 25 | module.exports = function(grunt) { 26 | var isUsingQueryString = function(opts) { 27 | return opts.queryString; 28 | }; 29 | grunt.registerMultiTask('cacheBust', 'Bust static assets from the cache using content hashing', function() { 30 | var opts = this.options(DEFAULT_OPTIONS); 31 | if( opts.baseDir.substr(-1) !== '/' ) { 32 | opts.baseDir += '/'; 33 | } 34 | 35 | var discoveryOpts = { 36 | cwd: path.resolve(opts.baseDir), 37 | filter: 'isFile' 38 | }; 39 | 40 | //clear output dir if it was set 41 | if (opts.clearOutputDir && opts.outputDir.length > 0) { 42 | fs.removeSync(path.resolve((discoveryOpts.cwd ? discoveryOpts.cwd + '/' +opts.outputDir : opts.outputDir))); 43 | } 44 | 45 | // Generate an asset map 46 | var assetMap = grunt.file 47 | .expand(discoveryOpts, opts.assets) 48 | .sort() 49 | .reverse() 50 | .reduce(hashFile, {}); 51 | 52 | grunt.verbose.writeln('Assets found:', JSON.stringify(assetMap, null, 2)); 53 | 54 | // Write out assetMap 55 | if (opts.jsonOutput === true) { 56 | grunt.file.write(path.resolve(opts.baseDir, opts.jsonOutputFilename), JSON.stringify(assetMap)); 57 | } 58 | 59 | // don't just split on the filename, if the filename = 'app.css' it will replace 60 | // all app.css references, even to files in other dirs 61 | // so replace this: 62 | // "{file}" 63 | // '{file}' 64 | // ({file}) (css url(...)) 65 | // /{file} (css url(...)) 66 | // ={file}> (unquoted html attribute) 67 | // ={file}\s (unquoted html attribute fonllowed by more attributes) 68 | // "{file}\s (first entry of img srcset) 69 | // \s{file}\s (other entries of img srcset) 70 | // files may contain a querystring, so all with ? as closing too 71 | var replaceEnclosedBy = [ 72 | ['"', '"'], 73 | ["'", "'"], 74 | ['(', ')'], 75 | ['=', '>'], 76 | ['=', ' '], 77 | ['"', ' '], 78 | [' ', ' '] 79 | ]; 80 | 81 | // add urlPrefixes to enclosing scenarios 82 | if (opts.urlPrefixes && Array.isArray(opts.urlPrefixes) && opts.urlPrefixes.length > 0) { 83 | opts.urlPrefixes.forEach(function(urlPrefix) { 84 | replaceEnclosedBy.push([urlPrefix, '"']); 85 | replaceEnclosedBy.push([urlPrefix, "'"]); 86 | replaceEnclosedBy.push([urlPrefix, ")"]); 87 | replaceEnclosedBy.push([urlPrefix, ">"]); 88 | replaceEnclosedBy.push([urlPrefix, " "]); 89 | }); 90 | } 91 | // don't replace references that are already cache busted 92 | if (!isUsingQueryString(opts)) { 93 | replaceEnclosedBy = replaceEnclosedBy.concat(replaceEnclosedBy.map(function(reb) { 94 | return [reb[0], '?']; 95 | })); 96 | } 97 | 98 | // Go through each source file and replace them with busted file if available 99 | var map = opts.queryString ? {} : assetMap; 100 | var files = getFilesToBeRenamed(this.files, map, opts.baseDir); 101 | files.forEach(replaceInFile); 102 | grunt.log.ok(files.length + ' file' + (files.length !== 1 ? 's ' : ' ') + 'busted.'); 103 | 104 | function replaceInFile(filepath) { 105 | var markup = grunt.file.read(filepath); 106 | var baseDir = discoveryOpts.cwd + '/'; 107 | var relativeFileDir = path.dirname(filepath).substr(baseDir.length); 108 | var fileDepth = 0; 109 | 110 | if (relativeFileDir !== '') { 111 | fileDepth = relativeFileDir.split('/').length; 112 | } 113 | 114 | var baseDirs = filepath.substr(baseDir.length).split('/'); 115 | 116 | _.each(assetMap, function(hashed, original) { 117 | var replace = [ 118 | // abs path 119 | ['/' + original, '/' + hashed], 120 | // relative 121 | [grunt.util.repeat(fileDepth, '../') + original, grunt.util.repeat(fileDepth, '../') + hashed], 122 | ]; 123 | // find relative paths for shared dirs 124 | var originalDirParts = path.dirname(original).split('/'); 125 | for (var i = 1; i <= fileDepth; i++) { 126 | var fileDir = originalDirParts.slice(0, i).join('/'); 127 | var baseDir = baseDirs.slice(0, i).join('/'); 128 | if (fileDir === baseDir) { 129 | var originalFilename = path.basename(original); 130 | var hashedFilename = path.basename(hashed); 131 | var dir = grunt.util.repeat(fileDepth - 1, '../') + originalDirParts.slice(i).join('/'); 132 | if (dir.substr(-1) !== '/') { 133 | dir += '/'; 134 | } 135 | replace.push([dir + originalFilename, dir + hashedFilename]); 136 | } 137 | } 138 | _.each(replace, function(r) { 139 | var original = r[0]; 140 | var hashed = r[1]; 141 | _.each(replaceEnclosedBy, function(reb) { 142 | markup = markup.split(reb[0] + original + reb[1]).join(reb[0] + hashed + reb[1]); 143 | }); 144 | }); 145 | }); 146 | 147 | grunt.file.write(filepath, markup); 148 | } 149 | 150 | function hashFile(obj, file) { 151 | var absPath = path.resolve(opts.baseDir, file); 152 | var hash = generateFileHash(grunt.file.read(absPath, { 153 | encoding: null 154 | })); 155 | var newFilename = addFileHash(file, hash, opts.separator); 156 | 157 | if (!opts.queryString) { 158 | if (opts.createCopies) { 159 | grunt.file.copy(absPath, path.resolve(opts.baseDir, newFilename)); 160 | } 161 | 162 | if (opts.deleteOriginals) { 163 | grunt.file.delete(absPath); 164 | } 165 | } 166 | 167 | obj[file] = newFilename; 168 | 169 | return obj; 170 | } 171 | 172 | function generateFileHash(data) { 173 | return opts.hash || crypto.createHash(opts.algorithm).update(data, opts.encoding).digest('hex').substring(0, opts.length); 174 | } 175 | 176 | function addFileHash(str, hash, separator) { 177 | if (opts.queryString) { 178 | return str + '?' + hash; 179 | } else { 180 | var parsed = url.parse(str); 181 | var pathToFile = opts.outputDir.length > 0 ? path.join(opts.outputDir, parsed.pathname.replace(/^.*[\\\/]/, '')) : parsed.pathname; 182 | var ext = path.extname(parsed.pathname); 183 | 184 | return (parsed.hostname ? parsed.protocol + parsed.hostname : '') + pathToFile.replace(ext, '') + separator + hash + ext; 185 | } 186 | } 187 | 188 | function getFilesToBeRenamed(files, assetMap, baseDir) { 189 | var originalConfig = files[0].orig; 190 | // check if fully specified filenames have been busted and replace with busted file 191 | var baseDirResolved = path.resolve(baseDir) + '/'; 192 | var cwd = process.cwd() + '/'; 193 | originalConfig.src = originalConfig.src.map(function(file) { 194 | if( assetMap ) { 195 | var files = [file]; 196 | if(path.resolve(cwd + file).substr(0, baseDirResolved.length) === baseDirResolved) { 197 | files.push(path.resolve(cwd + file).substr(baseDirResolved.length)); 198 | } 199 | var result; 200 | files.forEach(function(file2) { 201 | var fileResolved = path.resolve(baseDirResolved + file2); 202 | if (!result && fileResolved.substr(0, baseDirResolved.length) === baseDirResolved && (fileResolved.substr(baseDirResolved.length)) in assetMap) { 203 | result = assetMap[fileResolved.substr(baseDirResolved.length)]; 204 | // if original file had baseDir at the start, make sure it's there now 205 | var baseDirNormalized = path.normalize(baseDir); 206 | if(path.normalize(file).substr(0, baseDirNormalized.length) === baseDirNormalized) { 207 | result = baseDir + result; 208 | } 209 | } 210 | }); 211 | if(result) { 212 | return result; 213 | } 214 | } 215 | return file; 216 | }); 217 | 218 | return grunt.file 219 | .expand(originalConfig, originalConfig.src) 220 | .map(function(file) { 221 | 222 | // if the file is hashed, then the hashed file should be 223 | // used instead of the original for replacement. This will 224 | // only be the case if an outputDir is being used. 225 | if (!opts.queryString && opts.outputDir && _.has(assetMap, file)) { 226 | file = assetMap[file]; 227 | } 228 | grunt.verbose.writeln('Busted:', file); 229 | return path.resolve((originalConfig.cwd ? originalConfig.cwd + path.sep : '') + file); 230 | }); 231 | } 232 | 233 | }); 234 | 235 | }; 236 | -------------------------------------------------------------------------------- /tests/absolutepath/absolutepath.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Test 7 | 8 | 9 | Test 10 | 11 | 12 | -------------------------------------------------------------------------------- /tests/absolutepath/absolutepath_test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var grunt = require('grunt'); 4 | 5 | module.exports = { 6 | 7 | relativepath: function(test) { 8 | var html = grunt.file.read('tmp/relativepath/relativepath.html'); 9 | test.ok(html.match(/"\/assets\/css\/application\.123456789\.css"/), 'testing /assets/css/application.css replaced in HTML'); 10 | 11 | var css = grunt.file.read('tmp/relativepath/assets/css/application.123456789.css'); 12 | test.ok(css.match(/url\(\.\.\/images\/testbg\.123456789\.png\)/), 'testing ../images/testbg.png replaced in busted CSS'); 13 | test.ok(css.match(/url\(\.\.\/\.\.\/assets\/images\/testbg\.123456789\.png\)/), 'testing ../../assets/images/testbg.png replaced in busted CSS'); 14 | 15 | test.done(); 16 | } 17 | 18 | }; 19 | -------------------------------------------------------------------------------- /tests/absolutepath/assets/css/application.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | body { 4 | background-image: url(../images/testbg.png); 5 | } 6 | 7 | div.test { 8 | background-image: url(../../assets/images/testbg.png); 9 | } 10 | 11 | div.test2 { 12 | background-image: url(/assets/images/testbg.png); 13 | } 14 | -------------------------------------------------------------------------------- /tests/absolutepath/assets/images/testbg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benhoIIand/grunt-cache-bust/20f63f294725e5773e77c3d62f6c08180c8e6c72/tests/absolutepath/assets/images/testbg.png -------------------------------------------------------------------------------- /tests/complex/complex.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Complex example 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /tests/complex/complex_test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var grunt = require('grunt'); 4 | 5 | module.exports = { 6 | 7 | complex: function(test) { 8 | test.expect(3); 9 | 10 | var markup = grunt.file.read('tmp/complex/complex.html'); 11 | var css = grunt.file.read('tmp/complex/css/complex.123456789.css'); 12 | 13 | test.ok(markup.match(/css\/complex\.123456789\.css/), 'testing css/complex.css'); 14 | test.ok(markup.match(/imgs\/complex-background\.123456789\.jpg/), 'testing imgs/complex-background.jpg in markup'); 15 | test.ok(css.match(/imgs\/complex-background\.123456789\.jpg/), 'testing imgs/complex-background.jpg in css'); 16 | 17 | test.done(); 18 | } 19 | 20 | }; 21 | -------------------------------------------------------------------------------- /tests/complex/css/complex.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: red; 3 | background-image: url('/imgs/complex-background.jpg'); 4 | } 5 | -------------------------------------------------------------------------------- /tests/complex/imgs/complex-background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benhoIIand/grunt-cache-bust/20f63f294725e5773e77c3d62f6c08180c8e6c72/tests/complex/imgs/complex-background.jpg -------------------------------------------------------------------------------- /tests/createCopies/assets/.htaccess: -------------------------------------------------------------------------------- 1 | RewriteCond %{REQUEST_FILENAME} !-f 2 | RewriteCond %{REQUEST_FILENAME} !-d 3 | RewriteRule ^(.+)\.([\da-z]+)\.(js|css|png|jpg|gif)$ $1.$3 [L] -------------------------------------------------------------------------------- /tests/createCopies/assets/dontCopy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benhoIIand/grunt-cache-bust/20f63f294725e5773e77c3d62f6c08180c8e6c72/tests/createCopies/assets/dontCopy.png -------------------------------------------------------------------------------- /tests/createCopies/createCopies.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | This is a test page 5 | 6 | 7 | 8 |

This image should load if you use rewrites:

9 | 10 | bird 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /tests/deleteOriginals/assets/delete.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: silver; 3 | } 4 | -------------------------------------------------------------------------------- /tests/deleteOriginals/assets/delete.js: -------------------------------------------------------------------------------- 1 | alert('JS is working'); 2 | -------------------------------------------------------------------------------- /tests/deleteOriginals/assets/delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benhoIIand/grunt-cache-bust/20f63f294725e5773e77c3d62f6c08180c8e6c72/tests/deleteOriginals/assets/delete.png -------------------------------------------------------------------------------- /tests/deleteOriginals/deleteOriginalSameAssets.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | This is a test page 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /tests/deleteOriginals/deleteOriginals.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | This is a test page 5 | 6 | 7 | 8 | 9 | 10 |

Have you heard?

11 | 12 |

Heard what??

13 | 14 | bird 15 | 16 |

THE BIRD IS THE WORD!!

17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /tests/hash/assets/hash.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: silver; 3 | } 4 | -------------------------------------------------------------------------------- /tests/hash/assets/hash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benhoIIand/grunt-cache-bust/20f63f294725e5773e77c3d62f6c08180c8e6c72/tests/hash/assets/hash.jpg -------------------------------------------------------------------------------- /tests/hash/assets/hash.js: -------------------------------------------------------------------------------- 1 | alert('JS is working'); 2 | -------------------------------------------------------------------------------- /tests/hash/hash.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | This is a test page 5 | 6 | 7 | 8 | bird 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /tests/hash/hash_test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var grunt = require('grunt'); 4 | 5 | module.exports = { 6 | 7 | hash: function(test) { 8 | test.expect(3); 9 | 10 | var markup = grunt.file.read('tmp/hash/hash.html'); 11 | 12 | test.ok(markup.match(/assets\/hash\.abcdef123456\.css/), 'testing assets/hash.css'); 13 | test.ok(markup.match(/assets\/hash\.abcdef123456\.jpg/), 'testing assets/hash.jpg'); 14 | test.ok(markup.match(/assets\/hash\.abcdef123456\.js/ ), 'testing assets/hash.js'); 15 | 16 | test.done(); 17 | } 18 | 19 | }; 20 | -------------------------------------------------------------------------------- /tests/ignorePattern/assets/toBeIgnoredCSS.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: silver; 3 | } 4 | 5 | h1, h2, h3 { 6 | font-size: 100px; 7 | } 8 | -------------------------------------------------------------------------------- /tests/ignorePattern/assets/toBeIgnoredJPG.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benhoIIand/grunt-cache-bust/20f63f294725e5773e77c3d62f6c08180c8e6c72/tests/ignorePattern/assets/toBeIgnoredJPG.jpg -------------------------------------------------------------------------------- /tests/ignorePattern/assets/toBeIgnoredJS.js: -------------------------------------------------------------------------------- 1 | alert('JS is working'); 2 | alert('JS is still working'); 3 | -------------------------------------------------------------------------------- /tests/ignorePattern/ignorePattern.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | This is a test page 5 | 6 | 7 | 8 | 9 | 10 |

Have you heard?

11 | 12 |

Heard what??

13 | 14 | bird 15 | 16 |

THE BIRD IS THE WORD!!

17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /tests/ignorePattern/ignorePattern_test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var grunt = require('grunt'); 4 | 5 | module.exports = { 6 | 7 | ignore: function(test) { 8 | test.expect(3); 9 | 10 | var markup = grunt.file.read('tmp/ignorePattern/ignorePattern.html'); 11 | 12 | test.ok(markup.match(/assets\/toBeIgnoredCSS\.[a-z0-9]{16}\.css"/), 'testing toBeIgnoredCSS'); 13 | test.ok(markup.match(/assets\/toBeIgnoredJPG\.jpg"/), 'testing toBeIgnoredJPG'); 14 | test.ok(markup.match(/assets\/toBeIgnoredJS\.[a-z0-9]{16}\.js"/), 'testing toBeIgnoredJS'); 15 | 16 | test.done(); 17 | } 18 | 19 | }; 20 | -------------------------------------------------------------------------------- /tests/imgsrcset/assets/imgsrcset-big.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benhoIIand/grunt-cache-bust/20f63f294725e5773e77c3d62f6c08180c8e6c72/tests/imgsrcset/assets/imgsrcset-big.jpg -------------------------------------------------------------------------------- /tests/imgsrcset/assets/imgsrcset-small.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benhoIIand/grunt-cache-bust/20f63f294725e5773e77c3d62f6c08180c8e6c72/tests/imgsrcset/assets/imgsrcset-small.jpg -------------------------------------------------------------------------------- /tests/imgsrcset/assets/imgsrcset.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benhoIIand/grunt-cache-bust/20f63f294725e5773e77c3d62f6c08180c8e6c72/tests/imgsrcset/assets/imgsrcset.jpg -------------------------------------------------------------------------------- /tests/imgsrcset/imgsrcset.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | This is a img srcset test page 5 | 6 | 7 | bird 8 | img srcset example 9 | 10 | 11 | -------------------------------------------------------------------------------- /tests/imgsrcset/imgsrcset_test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var grunt = require('grunt'); 4 | 5 | module.exports = { 6 | 7 | imgsrcset: function(test) { 8 | test.expect(3); 9 | 10 | var markup = grunt.file.read('tmp/imgsrcset/imgsrcset.html'); 11 | 12 | test.ok(markup.match(/assets\/imgsrcset\.[a-z0-9]{16}\.jpg/), 'testing assets/imgsrcset.jpg'); 13 | test.ok(markup.match(/assets\/imgsrcset-big\.[a-z0-9]{16}\.jpg/), 'testing assets/imgsrcset-big.jpg'); 14 | test.ok(markup.match(/assets\/imgsrcset-small\.[a-z0-9]{16}\.jpg/), 'testing assets/imgsrcset-small.jpg'); 15 | 16 | test.done(); 17 | } 18 | 19 | }; 20 | -------------------------------------------------------------------------------- /tests/jsonOutput/assets/jsonOutput.js: -------------------------------------------------------------------------------- 1 | alert('JS is working'); 2 | -------------------------------------------------------------------------------- /tests/jsonOutput/jsonOutput.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/jsonOutput/jsonOutput_test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var grunt = require('grunt'); 4 | 5 | module.exports = { 6 | 7 | jsonOutput: function(test) { 8 | test.expect(1); 9 | 10 | var outputMap = grunt.file.read('tmp/jsonOutput/jsonOutput-map.json'); 11 | var expectedMap = JSON.stringify({ 12 | 'assets/jsonOutput.js': 'assets/jsonOutput.3bc124f632c7cdbc.js' 13 | }); 14 | 15 | test.equal(outputMap, expectedMap, 'testing output of json file'); 16 | 17 | test.done(); 18 | } 19 | 20 | }; 21 | -------------------------------------------------------------------------------- /tests/outputDir/assets/outputDir.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: silver; 3 | } 4 | 5 | #complex { 6 | background-color: red; 7 | background-image: url('/assets/outputDir.png'); 8 | } 9 | -------------------------------------------------------------------------------- /tests/outputDir/assets/outputDir.js: -------------------------------------------------------------------------------- 1 | alert('JS is working'); 2 | -------------------------------------------------------------------------------- /tests/outputDir/assets/outputDir.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benhoIIand/grunt-cache-bust/20f63f294725e5773e77c3d62f6c08180c8e6c72/tests/outputDir/assets/outputDir.png -------------------------------------------------------------------------------- /tests/outputDir/outputDir.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | This is a test page 5 | 6 | 7 | 8 | 9 | 10 |

Have you heard?

11 | 12 |

Heard what??

13 | 14 | bird 15 | 16 |

THE BIRD IS THE WORD!!

17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /tests/outputDir/outputDir_test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var grunt = require('grunt'); 4 | var fs = require('fs'); 5 | var _ = require('grunt').util._; 6 | 7 | module.exports = { 8 | 9 | outputDir: function(test) { 10 | test.expect(8); 11 | 12 | var markup = grunt.file.read('tmp/outputDir/outputDir.html'); 13 | var css = grunt.file.read('tmp/outputDir/outputDir/outputDir.abcdef123456.css'); 14 | var actual = fs.readdirSync('tmp/outputDir/assets').sort(); 15 | var expected = fs.readdirSync('tests/outputDir/assets').sort(); 16 | 17 | test.ok(markup.match(/outputDir\/outputDir\.abcdef123456\.css/), 'testing outputDir/outputDir.css in html'); 18 | test.ok(markup.match(/outputDir\/outputDir\.abcdef123456\.png/), 'testing outputDir/outputDir.png in html'); 19 | test.ok(markup.match(/outputDir\/outputDir\.abcdef123456\.js/ ), 'testing outputDir/outputDir.js in html'); 20 | test.ok(css.match(/outputDir\.abcdef123456\.png/ ), 'testing outputDir/outputDir.png in css'); 21 | 22 | test.deepEqual(expected, actual, 'should not have files in assets folder'); 23 | 24 | // original files should not be changed 25 | _.each(['outputDir.png', 'outputDir.css', 'outputDir.js'], function(file) { 26 | var original = grunt.file.read('tests/outputDir/assets/' + file); 27 | var copied = grunt.file.read('tmp/outputDir/assets/' + file); 28 | test.equal(original, copied, file + ' should not have changed'); 29 | }); 30 | 31 | test.done(); 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /tests/queryString/css/test.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: red; 3 | background-image: url('/imgs/background.jpg'); 4 | } 5 | -------------------------------------------------------------------------------- /tests/queryString/imgs/background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benhoIIand/grunt-cache-bust/20f63f294725e5773e77c3d62f6c08180c8e6c72/tests/queryString/imgs/background.jpg -------------------------------------------------------------------------------- /tests/queryString/queryString.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Query string example 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /tests/queryString/queryString_test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var grunt = require('grunt'); 4 | 5 | module.exports = { 6 | 7 | queryString: function(test) { 8 | test.expect(3); 9 | 10 | var markup = grunt.file.read('tmp/queryString/queryString.html'); 11 | var css = grunt.file.read('tmp/queryString/css/test.css'); 12 | 13 | test.ok(markup.match(/css\/test\.css\?123456789/), 'testing css/test.css'); 14 | test.ok(markup.match(/imgs\/background\.jpg\?123456789/), 'testing imgs/background.jpg in markup'); 15 | test.ok(css.match(/imgs\/background\.jpg\?123456789/), 'testing imgs/background.jpg in css'); 16 | 17 | test.done(); 18 | } 19 | 20 | }; 21 | -------------------------------------------------------------------------------- /tests/realpath/css/app.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: red; 3 | } 4 | -------------------------------------------------------------------------------- /tests/realpath/lib/css/app.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: green; 3 | } 4 | -------------------------------------------------------------------------------- /tests/realpath/realpath.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Realpath example 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /tests/realpath/realpath_test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var grunt = require('grunt'); 4 | 5 | module.exports = { 6 | 7 | realpath: function(test) { 8 | //test.expect(2); 9 | 10 | var markup = grunt.file.read('tmp/realpath/realpath.html'); 11 | 12 | test.ok(markup.match(/\"\/css\/app\.123456789\.css\"/), 'testing /css/app.css replaced'); 13 | test.ok(markup.match(/\"\/lib\/css\/app\.css\"/), 'testing /lib/css/app.css not replaced'); 14 | test.ok(!markup.match(/\"\/lib\/css\/app\.123456789\.css\"/), 'testing /lib/css/app.css not replaced'); 15 | 16 | test.done(); 17 | } 18 | 19 | }; 20 | -------------------------------------------------------------------------------- /tests/relativepath/assets/css/application.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | body { 4 | background-image: url(../images/testbg.png); 5 | } 6 | 7 | div.test { 8 | background-image: url(../../assets/images/testbg.png); 9 | } 10 | 11 | div.test2 { 12 | background-image: url(/assets/images/testbg.png); 13 | } 14 | -------------------------------------------------------------------------------- /tests/relativepath/assets/images/testbg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benhoIIand/grunt-cache-bust/20f63f294725e5773e77c3d62f6c08180c8e6c72/tests/relativepath/assets/images/testbg.png -------------------------------------------------------------------------------- /tests/relativepath/relativepath.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Test 7 | 8 | 9 | Test 10 | 11 | 12 | -------------------------------------------------------------------------------- /tests/relativepath/relativepath_test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var grunt = require('grunt'); 4 | 5 | module.exports = { 6 | 7 | relativepath: function(test) { 8 | var html = grunt.file.read('tmp/relativepath/relativepath.html'); 9 | test.ok(html.match(/"\/assets\/css\/application\.123456789\.css"/), 'testing /assets/css/application.css replaced in HTML'); 10 | 11 | var css = grunt.file.read('tmp/relativepath/assets/css/application.123456789.css'); 12 | test.ok(css.match(/url\(\.\.\/images\/testbg\.123456789\.png\)/), 'testing ../images/testbg.png replaced in busted CSS'); 13 | test.ok(css.match(/url\(\.\.\/\.\.\/assets\/images\/testbg\.123456789\.png\)/), 'testing ../../assets/images/testbg.png replaced in busted CSS'); 14 | 15 | test.done(); 16 | } 17 | 18 | }; 19 | -------------------------------------------------------------------------------- /tests/relativepath2/assets/css/application.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | body { 4 | background-image: url(../images/testbg.png); 5 | } 6 | 7 | div.test { 8 | background-image: url(../../assets/images/testbg.png); 9 | } 10 | 11 | div.test2 { 12 | background-image: url(/assets/images/testbg.png); 13 | } 14 | -------------------------------------------------------------------------------- /tests/relativepath2/assets/images/testbg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benhoIIand/grunt-cache-bust/20f63f294725e5773e77c3d62f6c08180c8e6c72/tests/relativepath2/assets/images/testbg.png -------------------------------------------------------------------------------- /tests/relativepath2/relativepath2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Test 7 | 8 | 9 | Test 10 | 11 | 12 | -------------------------------------------------------------------------------- /tests/relativepath2/relativepath2_test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var grunt = require('grunt'); 4 | 5 | module.exports = { 6 | 7 | relativepath: function(test) { 8 | var html = grunt.file.read('tmp/relativepath2/relativepath2.html'); 9 | test.ok(html.match(/"\/assets\/css\/application\.123456789\.css"/), 'testing /assets/css/application.css replaced in HTML'); 10 | 11 | var css = grunt.file.read('tmp/relativepath2/assets/css/application.123456789.css'); 12 | test.ok(css.match(/url\(\.\.\/images\/testbg\.123456789\.png\)/), 'testing ../images/testbg.png replaced in busted CSS'); 13 | test.ok(css.match(/url\(\.\.\/\.\.\/assets\/images\/testbg\.123456789\.png\)/), 'testing ../../assets/images/testbg.png replaced in busted CSS'); 14 | 15 | test.done(); 16 | } 17 | 18 | }; 19 | -------------------------------------------------------------------------------- /tests/scripts/assets/script.js: -------------------------------------------------------------------------------- 1 | alert('JS is working'); 2 | alert('JS is still working'); 3 | 4 | console.log('Tell me what you want, what you really really want'); 5 | -------------------------------------------------------------------------------- /tests/scripts/scripts.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | return 'assets/script.js'; 3 | })(); 4 | -------------------------------------------------------------------------------- /tests/scripts/scripts_test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var grunt = require('grunt'); 4 | 5 | module.exports = { 6 | 7 | scripts: function(test) { 8 | test.expect(1); 9 | 10 | var markup = grunt.file.read('tmp/scripts/scripts.js'); 11 | 12 | test.ok(markup.match(/script\.[a-z0-9]{16}\.js/), 'testing script'); 13 | 14 | test.done(); 15 | } 16 | 17 | }; 18 | -------------------------------------------------------------------------------- /tests/sourceBusted/assets/css/application.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | div.test2 { 4 | background-image: url(/assets/images/testbg.png); 5 | } 6 | -------------------------------------------------------------------------------- /tests/sourceBusted/assets/images/testbg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benhoIIand/grunt-cache-bust/20f63f294725e5773e77c3d62f6c08180c8e6c72/tests/sourceBusted/assets/images/testbg.png -------------------------------------------------------------------------------- /tests/sourceBusted/sourceBusted.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Test 7 | 8 | 9 | Test 10 | 11 | 12 | -------------------------------------------------------------------------------- /tests/sourceBusted/sourceBusted_test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var grunt = require('grunt'); 4 | 5 | module.exports = { 6 | 7 | // The hardcoded source file is busted to a hashed filename, make sure that file is busted instead 8 | sourceBusted: function(test) { 9 | var html = grunt.file.read('tmp/sourceBusted/sourceBusted.html'); 10 | test.ok(html.match(/\"assets\/css\/application\.123456789\.css\"/), 'testing /assets/css/application.css replaced in HTML'); 11 | 12 | var css = grunt.file.read('tmp/sourceBusted/assets/css/application.123456789.css'); 13 | test.ok(css.match(/url\(\/assets\/images\/testbg\.123456789\.png\)/), 'testing /assets/images/testbg.png replaced in busted CSS'); 14 | 15 | test.done(); 16 | } 17 | 18 | }; 19 | -------------------------------------------------------------------------------- /tests/sourceBusted2/assets/css/application.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | div.test2 { 4 | background-image: url(/assets/images/testbg.png); 5 | } 6 | -------------------------------------------------------------------------------- /tests/sourceBusted2/assets/images/testbg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benhoIIand/grunt-cache-bust/20f63f294725e5773e77c3d62f6c08180c8e6c72/tests/sourceBusted2/assets/images/testbg.png -------------------------------------------------------------------------------- /tests/sourceBusted2/sourceBusted2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Test 7 | 8 | 9 | Test 10 | 11 | 12 | -------------------------------------------------------------------------------- /tests/sourceBusted2/sourceBusted2_test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var grunt = require('grunt'); 4 | 5 | module.exports = { 6 | 7 | // The hardcoded source file is busted to a hashed filename, make sure that file is busted instead 8 | sourceBusted: function(test) { 9 | var html = grunt.file.read('tmp/sourceBusted2/sourceBusted2.html'); 10 | test.ok(html.match(/\"assets\/css\/application\.123456789\.css\"/), 'testing /assets/css/application.css replaced in HTML'); 11 | 12 | var css = grunt.file.read('tmp/sourceBusted2/assets/css/application.123456789.css'); 13 | test.ok(css.match(/url\(\/assets\/images\/testbg\.123456789\.png\)/), 'testing /assets/images/testbg.png replaced in busted CSS'); 14 | 15 | test.done(); 16 | } 17 | 18 | }; 19 | -------------------------------------------------------------------------------- /tests/sourceBusted3/assets/css/application.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | div.test2 { 4 | background-image: url(/assets/images/testbg.png); 5 | } 6 | -------------------------------------------------------------------------------- /tests/sourceBusted3/assets/images/testbg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benhoIIand/grunt-cache-bust/20f63f294725e5773e77c3d62f6c08180c8e6c72/tests/sourceBusted3/assets/images/testbg.png -------------------------------------------------------------------------------- /tests/sourceBusted3/sourceBusted3.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Test 7 | 8 | 9 | Test 10 | 11 | 12 | -------------------------------------------------------------------------------- /tests/sourceBusted3/sourceBusted3_test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var grunt = require('grunt'); 4 | 5 | module.exports = { 6 | 7 | // The hardcoded source file is busted to a hashed filename, make sure that file is busted instead 8 | sourceBusted: function(test) { 9 | var html = grunt.file.read('tmp/sourceBusted3/sourceBusted3.html'); 10 | test.ok(html.match(/\"assets\/css\/application\.123456789\.css\"/), 'testing /assets/css/application.css replaced in HTML'); 11 | 12 | var css = grunt.file.read('tmp/sourceBusted3/assets/css/application.123456789.css'); 13 | test.ok(css.match(/url\(\/assets\/images\/testbg\.123456789\.png\)/), 'testing /assets/images/testbg.png replaced in busted CSS'); 14 | 15 | test.done(); 16 | } 17 | 18 | }; 19 | -------------------------------------------------------------------------------- /tests/standard/assets/standard-apple-touch-icon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benhoIIand/grunt-cache-bust/20f63f294725e5773e77c3d62f6c08180c8e6c72/tests/standard/assets/standard-apple-touch-icon.jpg -------------------------------------------------------------------------------- /tests/standard/assets/standard.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: silver; 3 | } 4 | -------------------------------------------------------------------------------- /tests/standard/assets/standard.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benhoIIand/grunt-cache-bust/20f63f294725e5773e77c3d62f6c08180c8e6c72/tests/standard/assets/standard.jpg -------------------------------------------------------------------------------- /tests/standard/assets/standard.js: -------------------------------------------------------------------------------- 1 | alert('JS is working'); 2 | -------------------------------------------------------------------------------- /tests/standard/standard.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | This is a test page 5 | 6 | 7 | 8 | 9 | 10 | bird 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /tests/standard/standard_test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var grunt = require('grunt'); 4 | 5 | module.exports = { 6 | 7 | standard: function(test) { 8 | test.expect(4); 9 | 10 | var markup = grunt.file.read('tmp/standard/standard.html'); 11 | 12 | test.ok(markup.match(/assets\/standard\.[a-z0-9]{16}\.css/), 'testing assets/standard.css'); 13 | test.ok(markup.match(/assets\/standard\.[a-z0-9]{16}\.jpg/), 'testing assets/standard.jpg'); 14 | test.ok(markup.match(/assets\/standard\.[a-z0-9]{16}\.js/ ), 'testing assets/standard.js'); 15 | test.ok(markup.match(/assets\/standard-apple-touch-icon\.[a-z0-9]{16}\.jpg/), 'testing assets/standard-apple-touch-icon.jpg'); 16 | 17 | test.done(); 18 | }, 19 | 20 | relative: function(test) { 21 | test.expect(4); 22 | 23 | var markup = grunt.file.read('tmp/standard/subfolder/relative-standard.html'); 24 | 25 | test.ok(markup.match(/assets\/standard\.[a-z0-9]{16}\.css/), 'testing relative file path of assets/standard.css'); 26 | test.ok(markup.match(/assets\/standard\.[a-z0-9]{16}\.jpg/), 'testing relative file path of assets/standard.jpg'); 27 | test.ok(markup.match(/assets\/standard\.[a-z0-9]{16}\.js/ ), 'testing relative file path of assets/standard.js'); 28 | test.ok(markup.match(/assets\/standard-apple-touch-icon\.[a-z0-9]{16}\.jpg/ ), 'testing relative file path of assets/standard-apple-touch-icon.jpg'); 29 | 30 | test.done(); 31 | } 32 | 33 | }; 34 | -------------------------------------------------------------------------------- /tests/standard/subfolder/relative-standard.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | This is a test page 5 | 6 | 7 | 8 | 9 | 10 | bird 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /tests/stylesheets/assets/css-image-large.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benhoIIand/grunt-cache-bust/20f63f294725e5773e77c3d62f6c08180c8e6c72/tests/stylesheets/assets/css-image-large.jpg -------------------------------------------------------------------------------- /tests/stylesheets/assets/css-image-quotes.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benhoIIand/grunt-cache-bust/20f63f294725e5773e77c3d62f6c08180c8e6c72/tests/stylesheets/assets/css-image-quotes.jpg -------------------------------------------------------------------------------- /tests/stylesheets/assets/css-image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benhoIIand/grunt-cache-bust/20f63f294725e5773e77c3d62f6c08180c8e6c72/tests/stylesheets/assets/css-image.jpg -------------------------------------------------------------------------------- /tests/stylesheets/assets/fonts/icons.eot: -------------------------------------------------------------------------------- 1 | icons.eot 2 | -------------------------------------------------------------------------------- /tests/stylesheets/assets/fonts/icons.ttf: -------------------------------------------------------------------------------- 1 | icons.tff 2 | -------------------------------------------------------------------------------- /tests/stylesheets/assets/fonts/icons.woff: -------------------------------------------------------------------------------- 1 | icons.woff 2 | -------------------------------------------------------------------------------- /tests/stylesheets/assets/fonts/icons.woff2: -------------------------------------------------------------------------------- 1 | icons.woff2 2 | -------------------------------------------------------------------------------- /tests/stylesheets/assets/image1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benhoIIand/grunt-cache-bust/20f63f294725e5773e77c3d62f6c08180c8e6c72/tests/stylesheets/assets/image1.jpg -------------------------------------------------------------------------------- /tests/stylesheets/assets/image2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benhoIIand/grunt-cache-bust/20f63f294725e5773e77c3d62f6c08180c8e6c72/tests/stylesheets/assets/image2.jpg -------------------------------------------------------------------------------- /tests/stylesheets/assets/relative-css-image-large.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benhoIIand/grunt-cache-bust/20f63f294725e5773e77c3d62f6c08180c8e6c72/tests/stylesheets/assets/relative-css-image-large.jpg -------------------------------------------------------------------------------- /tests/stylesheets/assets/relative-css-image-quotes.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benhoIIand/grunt-cache-bust/20f63f294725e5773e77c3d62f6c08180c8e6c72/tests/stylesheets/assets/relative-css-image-quotes.jpg -------------------------------------------------------------------------------- /tests/stylesheets/assets/relative-css-image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benhoIIand/grunt-cache-bust/20f63f294725e5773e77c3d62f6c08180c8e6c72/tests/stylesheets/assets/relative-css-image.jpg -------------------------------------------------------------------------------- /tests/stylesheets/assets/relative-image1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benhoIIand/grunt-cache-bust/20f63f294725e5773e77c3d62f6c08180c8e6c72/tests/stylesheets/assets/relative-image1.jpg -------------------------------------------------------------------------------- /tests/stylesheets/assets/stylesheet1.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: red; 3 | } 4 | -------------------------------------------------------------------------------- /tests/stylesheets/assets/stylesheet2.css: -------------------------------------------------------------------------------- 1 | .text-info { 2 | color: #3a87ad; 3 | } 4 | 5 | .text-info:hover { 6 | color: #2d6987; 7 | } 8 | 9 | .text-left { 10 | text-align: left; 11 | } 12 | 13 | .text-right { 14 | text-align: right; 15 | } 16 | 17 | .text-center { 18 | text-align: center; 19 | } 20 | -------------------------------------------------------------------------------- /tests/stylesheets/assets/stylesheet3.css: -------------------------------------------------------------------------------- 1 | .list-unstyled { 2 | padding-left: 0; 3 | list-style: none; 4 | } 5 | 6 | .list-inline { 7 | padding-left: 0; 8 | list-style: none; 9 | } 10 | 11 | .list-inline > li { 12 | display: inline-block; 13 | padding-right: 5px; 14 | padding-left: 5px; 15 | } 16 | 17 | .list-inline > li:first-child { 18 | padding-left: 0; 19 | } 20 | 21 | dl { 22 | margin-bottom: 20px; 23 | } 24 | 25 | dt, 26 | dd { 27 | line-height: 1.428571429; 28 | } 29 | 30 | dt { 31 | font-weight: bold; 32 | } 33 | 34 | dd { 35 | margin-left: 0; 36 | } 37 | -------------------------------------------------------------------------------- /tests/stylesheets/assets/stylesheet4.css: -------------------------------------------------------------------------------- 1 | pre code { 2 | padding: 0; 3 | font-size: inherit; 4 | color: inherit; 5 | white-space: pre-wrap; 6 | background-color: transparent; 7 | border-radius: 0; 8 | } 9 | 10 | .pre-scrollable { 11 | max-height: 340px; 12 | overflow-y: scroll; 13 | } 14 | 15 | .container { 16 | padding-right: 15px; 17 | padding-left: 15px; 18 | margin-right: auto; 19 | margin-left: auto; 20 | } 21 | 22 | .container:before, 23 | .container:after { 24 | display: table; 25 | content: " "; 26 | } 27 | 28 | .container:after { 29 | clear: both; 30 | } 31 | -------------------------------------------------------------------------------- /tests/stylesheets/css/replaceCssInSubdir.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: red; 3 | } 4 | 5 | .image { 6 | width: auto; 7 | max-height: 120px; 8 | background: #FFFFFF url('../assets/relative-css-image.jpg') center center no-repeat; 9 | } 10 | 11 | .image-quotes { 12 | width: auto; 13 | max-height: 120px; 14 | background: #FFFFFF url("../assets/relative-css-image-quotes.jpg") center center no-repeat; 15 | } 16 | 17 | .large-image { 18 | width: 100%; 19 | background-image: url(../assets/relative-css-image-large.jpg); 20 | background-position: top center; 21 | background-size: contain; 22 | } 23 | 24 | @media only screen and (min-device-pixel-ratio: 2) and (min-resolution: 2dppx) { 25 | .large-image { 26 | background-image: url('../assets/relative-image1.jpg'); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/stylesheets/stylesheet.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: red; 3 | } 4 | 5 | .body:after { 6 | content: url('assets/image2.jpg'); 7 | } 8 | 9 | .image { 10 | width: auto; 11 | max-height: 120px; 12 | background: #FFFFFF url('assets/css-image.jpg') center center no-repeat; 13 | } 14 | 15 | .image-quotes { 16 | width: auto; 17 | max-height: 120px; 18 | background: #FFFFFF url("assets/css-image-quotes.jpg") center center no-repeat; 19 | } 20 | 21 | .large-image { 22 | width: 100%; 23 | background-image: url(assets/css-image-large.jpg); 24 | background-position: top center; 25 | background-size: contain; 26 | } 27 | 28 | .external-image { 29 | background-image: url('//www.external.com/external-image1.jpg'); 30 | background-image: url('http://www.external.com/external-image2.jpg'); 31 | background-image: url('https://www.external.com/external-image3.jpg'); 32 | } 33 | 34 | @font-face { 35 | src: local('Lato-Hairline'), url('https://fonts.gstatic.com/s/lato/v11/zJY4gsxBiSo5L7tNutxFNg.ttf'); 36 | } 37 | 38 | .data-url-image { 39 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAOwgAADsIBFShKgAAAABp0RVh0U29mdHdhcmUAUGFpbnQuTkVUIHYzLjUuMTAw9HKhAAAAmElEQVQoU33PQapBURjA8UtkwJuaWYGSgfQWYBMvczPmTCzAAGVuaA228BZhRCkDGSmE31FucuRfvzq3vr5zT/JSjSU7DsypEPXDkDVn2hSIytJhw4kWGaLCxgHh2gt/RBuLzNhz5caWPjnSqqw4EraFfwznf8qklWjwy4IRTerkiQoPGtPl40OehcEJvcfXl8LglLfBJLkDcMgbgHlHhK8AAAAASUVORK5CYII=); 40 | } 41 | 42 | @media only screen and (min-device-pixel-ratio: 2) and (min-resolution: 2dppx) { 43 | .large-image { 44 | background-image: url('assets/image1.jpg'); 45 | } 46 | } 47 | 48 | @font-face { 49 | font-family: icons; 50 | src: url(/assets/fonts/icons.eot); 51 | src: url(/assets/fonts/icons.eot?#iefix) format(embedded-opentype), 52 | url(/assets/fonts/icons.woff) format(woff), 53 | url(/assets/fonts/icons.woff2) format(woff2), 54 | url(/assets/fonts/icons.ttf) format(truetype); 55 | font-weight: 400; 56 | font-style: normal; 57 | } 58 | -------------------------------------------------------------------------------- /tests/stylesheets/stylesheet_test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var grunt = require('grunt'); 4 | 5 | module.exports = { 6 | 7 | stylesheet: function(test) { 8 | test.expect(8); 9 | 10 | var markup = grunt.file.read('tmp/stylesheets/stylesheet.css'); 11 | 12 | test.ok(markup.match(/css-image\.[a-z0-9]{16}\.jpg/), 'testing an image in a CSS file'); 13 | test.ok(markup.match(/css-image-quotes.[a-z0-9]{16}\.jpg/), 'testing an image in a CSS file'); 14 | test.ok(markup.match(/css-image-large\.[a-z0-9]{16}\.jpg/), 'testing an image in a CSS file'); 15 | test.ok(markup.match(/image1\.[a-z0-9]{16}\.jpg/), 'testing an image in a CSS file within a media query'); 16 | test.ok(markup.match(/image2\.[a-z0-9]{16}\.jpg/), 'testing an image in a CSS file within a media query'); 17 | test.ok(markup.match(/'\/\/www\.external\.com\/external-image1.jpg'/), 'testing an external image in a CSS file'); 18 | test.ok(markup.match(/'http:\/\/www\.external\.com\/external-image2.jpg'/), 'testing an external image in a CSS file'); 19 | test.ok(markup.match(/'https:\/\/www\.external\.com\/external-image3.jpg'/), 'testing an external image in a CSS file'); 20 | 21 | test.done(); 22 | }, 23 | 24 | replaceCssInSubdir: function(test) { 25 | test.expect(4); 26 | 27 | var markup = grunt.file.read('tmp/stylesheets/css/replaceCssInSubdir.css'); 28 | 29 | test.ok(markup.match(/assets\/relative-css-image\.[a-z0-9]{16}\.jpg/), 'testing the replacement of an image when css in a different subdir than asset'); 30 | test.ok(markup.match(/assets\/relative-css-image-quotes\.[a-z0-9]{16}\.jpg/), 'testing the replacement of an image when css in a different subdir than asset'); 31 | test.ok(markup.match(/assets\/relative-css-image-large\.[a-z0-9]{16}\.jpg/), 'testing the replacement of an image when css in a different subdir than asset'); 32 | test.ok(markup.match(/assets\/relative-image1\.[a-z0-9]{16}\.jpg/), 'testing the replacement of an image when css in a different subdir than asset'); 33 | 34 | test.done(); 35 | }, 36 | 37 | multipleUrls: function(test) { 38 | test.expect(6); 39 | 40 | var markup = grunt.file.read('tmp/stylesheets/stylesheet.css'); 41 | 42 | test.ok(markup.match(/assets\/fonts\/icons\.[a-z0-9]{16}\.eot/), 'testing the that multiple urls busted'); 43 | test.ok(markup.match(/assets\/fonts\/icons\.[a-z0-9]{16}\.eot\?#iefix/), 'testing the that multiple urls busted'); 44 | test.ok(markup.match(/assets\/fonts\/icons\.[a-z0-9]{16}\.eot/), 'testing the that multiple urls busted'); 45 | test.ok(markup.match(/assets\/fonts\/icons\.[a-z0-9]{16}\.woff/), 'testing the that multiple urls busted'); 46 | test.ok(markup.match(/assets\/fonts\/icons\.[a-z0-9]{16}\.woff2/), 'testing the that multiple urls busted'); 47 | test.ok(markup.match(/assets\/fonts\/icons\.[a-z0-9]{16}\.ttf/), 'testing the that multiple urls busted'); 48 | 49 | test.done(); 50 | }, 51 | 52 | correctlyOrdered: function(test) { 53 | test.expect(2); 54 | 55 | var markup = grunt.file.read('tmp/stylesheets/stylesheet.css'); 56 | 57 | test.ok(markup.match(/assets\/fonts\/icons.303f279e24e9a1ed.woff/), 'testing the correct order of replacements'); 58 | test.ok(markup.match(/assets\/fonts\/icons.f90c25689874683b.woff2/), 'testing the correct order of replacements'); 59 | 60 | test.done(); 61 | } 62 | 63 | }; 64 | -------------------------------------------------------------------------------- /tests/templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Template example 5 | 6 | 7 | 8 |
9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /tests/templates/templates_test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var grunt = require('grunt'); 4 | 5 | module.exports = { 6 | 7 | stylesheet: function(test) { 8 | test.expect(2); 9 | 10 | var markup = grunt.file.read('tmp/templates/base.html'); 11 | 12 | test.ok(markup.match(/views\/view-b\.[a-z0-9]{16}\.html/), 'testing view A'); 13 | test.ok(markup.match(/views\/view-a\.[a-z0-9]{16}\.html/), 'testing view B'); 14 | 15 | test.done(); 16 | } 17 | 18 | }; 19 | -------------------------------------------------------------------------------- /tests/templates/views/view-a.html: -------------------------------------------------------------------------------- 1 |

View A

2 | -------------------------------------------------------------------------------- /tests/templates/views/view-b.html: -------------------------------------------------------------------------------- 1 |

View B

2 | -------------------------------------------------------------------------------- /tests/urlPrefixes/assets/imgsrcset-big.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benhoIIand/grunt-cache-bust/20f63f294725e5773e77c3d62f6c08180c8e6c72/tests/urlPrefixes/assets/imgsrcset-big.jpg -------------------------------------------------------------------------------- /tests/urlPrefixes/assets/imgsrcset-small.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benhoIIand/grunt-cache-bust/20f63f294725e5773e77c3d62f6c08180c8e6c72/tests/urlPrefixes/assets/imgsrcset-small.jpg -------------------------------------------------------------------------------- /tests/urlPrefixes/assets/imgsrcset.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benhoIIand/grunt-cache-bust/20f63f294725e5773e77c3d62f6c08180c8e6c72/tests/urlPrefixes/assets/imgsrcset.jpg -------------------------------------------------------------------------------- /tests/urlPrefixes/css/app.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: red; 3 | } 4 | -------------------------------------------------------------------------------- /tests/urlPrefixes/urlPrefixes.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Realpath example 5 | 6 | 7 | 8 | 9 | img srcset with urlPrefix example 10 | 11 | 12 | -------------------------------------------------------------------------------- /tests/urlPrefixes/urlPrefixes_test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var grunt = require('grunt'); 4 | 5 | module.exports = { 6 | 7 | urlPrefixes: function(test) { 8 | var markup = grunt.file.read('tmp/urlPrefixes/urlPrefixes.html'); 9 | 10 | test.ok(markup.match(/\/css\/app\.123456789\.css\"/), 'testing /css/app.css replaced'); 11 | test.ok(markup.match(/http:\/\/owncdn1\.test\.com\/path\/css\/app\.123456789\.css\"/), 'testing http://owncdn1.test.com/path/css/app.css replaced'); 12 | 13 | test.ok(markup.match(/http:\/\/owncdn1\.test\.com\/path\/assets\/imgsrcset\.123456789\.jpg/), 'testing srcset assets/imgsrcset.jpg'); 14 | test.ok(markup.match(/http:\/\/owncdn1\.test\.com\/path\/assets\/imgsrcset-big\.123456789\.jpg/), 'testing srcset assets/imgsrcset-big.jpg'); 15 | test.ok(markup.match(/http:\/\/owncdn2\.test\.com\/path\/assets\/imgsrcset-small\.123456789\.jpg/), 'testing srcset assets/imgsrcset-small.jpg'); 16 | 17 | test.done(); 18 | } 19 | 20 | }; 21 | -------------------------------------------------------------------------------- /tests/xml/assets/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benhoIIand/grunt-cache-bust/20f63f294725e5773e77c3d62f6c08180c8e6c72/tests/xml/assets/mstile-150x150.png -------------------------------------------------------------------------------- /tests/xml/assets/mstile-310x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benhoIIand/grunt-cache-bust/20f63f294725e5773e77c3d62f6c08180c8e6c72/tests/xml/assets/mstile-310x150.png -------------------------------------------------------------------------------- /tests/xml/assets/mstile-310x310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benhoIIand/grunt-cache-bust/20f63f294725e5773e77c3d62f6c08180c8e6c72/tests/xml/assets/mstile-310x310.png -------------------------------------------------------------------------------- /tests/xml/assets/mstile-70x70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benhoIIand/grunt-cache-bust/20f63f294725e5773e77c3d62f6c08180c8e6c72/tests/xml/assets/mstile-70x70.png -------------------------------------------------------------------------------- /tests/xml/xml.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | #2B5797 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /tests/xml/xml_test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var grunt = require('grunt'); 4 | 5 | module.exports = { 6 | 7 | xml: function(test) { 8 | test.expect(4); 9 | 10 | var browserConfig = grunt.file.read('tmp/xml/xml.xml'); 11 | 12 | test.ok(browserConfig.match(/assets\/mstile-70x70\.[a-z0-9]{16}\.png/), 'testing busting of an xml file'); 13 | test.ok(browserConfig.match(/assets\/mstile-150x150\.[a-z0-9]{16}\.png/), 'testing busting of an xml file'); 14 | test.ok(browserConfig.match(/assets\/mstile-310x310\.[a-z0-9]{16}\.png/), 'testing busting of an xml file'); 15 | test.ok(browserConfig.match(/assets\/mstile-310x150\.[a-z0-9]{16}\.png/), 'testing busting of an xml file'); 16 | 17 | test.done(); 18 | } 19 | 20 | }; 21 | --------------------------------------------------------------------------------