├── .gitattributes ├── .gitignore ├── .npmignore ├── index.js ├── package.json ├── prepareManualTests.js ├── readme.md ├── src ├── css.js ├── minify.js └── utils.js └── test ├── common.js ├── integration ├── api.css.js ├── api.errors.js ├── api.js.js ├── data-js │ ├── a.js │ └── b.js └── data │ ├── a.css │ ├── a.html │ ├── a │ └── c.css │ ├── b.css │ └── b │ └── d.css ├── manual ├── css │ ├── a.css │ ├── a │ │ └── c.css │ ├── b.css │ └── b │ │ └── d.css ├── gfx │ └── img.png ├── index.html ├── processed.html └── unprocessed.html ├── mocha.opts └── unit ├── css.parser.js ├── minifier.js └── utils.js /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *min.js 3 | *min.css 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *min.js 3 | *min.css 4 | test 5 | prepareManualTests.js 6 | runAllTests.js 7 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var program = require('commander') 4 | var minifier = require('./src/minify') 5 | 6 | if(require.main === module) { 7 | var skip = [] 8 | program 9 | .version(require('./package.json').version) 10 | .option('-o, --output [file]', 'The output file') 11 | .option('-t, --template [template]', 'A template for building the output file') 12 | .option('-c, --clean', 'Deletes any files that resembles the template') 13 | .option('-C, --clean-only', 'Same as `--clean`, but without minifying the files afterwards') 14 | .option('-s, --skip ', 'Skip any files that contains this in the path') 15 | .option('--no-comments', 'Remove license-style comments') 16 | .usage('[--output file] path/to/input [...path/to/other/input]') 17 | 18 | .on('skip', function(path) { 19 | skip.push(path) 20 | }) 21 | 22 | .parse(process.argv) 23 | 24 | var inputs = program.args 25 | var input 26 | if(inputs.length == 1) { 27 | input = inputs[0] 28 | } 29 | 30 | if(inputs.length == 0) { 31 | program.parse(['bla', 'bla', '--help']) 32 | process.exit() 33 | } 34 | 35 | if(skip.length) program.skip = skip 36 | 37 | program.noComments = program.comments === false 38 | 39 | minifier.on('error', function(msg) { 40 | console.log(msg) 41 | process.exit(1) 42 | }) 43 | program.uglify = { 44 | output: { 45 | semicolons:false, 46 | }, 47 | } 48 | minifier.minify(input || inputs, program) 49 | 50 | if(program.cleanOnly) { 51 | return console.log('Minified files cleaned') 52 | } 53 | 54 | console.log('Minification complete') 55 | } 56 | 57 | module.exports = minifier 58 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Benjamin Horsleben (http://fizkerinc.dk)", 3 | "name": "minifier", 4 | "description": "A simple tool for minifying CSS/JS without a big setup", 5 | "keywords": "javascript js css minify minifier build", 6 | "version": "0.8.1", 7 | "license": "WTFPL", 8 | "main": "index.js", 9 | "repository": { 10 | "type": "git", 11 | "url": "git://github.com/fizker/minifier.git" 12 | }, 13 | "bugs": "https://github.com/fizker/minifier/issues", 14 | "dependencies": { 15 | "commander": "^2.8.1", 16 | "css-resolve-import": "^0.1.1", 17 | "fmerge": "^1.2.0", 18 | "glob": "^7.1.1", 19 | "hogan.js": "^3.0.2", 20 | "sqwish": "~0.2.2", 21 | "uglify-js": "^2.4.24" 22 | }, 23 | "bin": { 24 | "minify": "index.js" 25 | }, 26 | "scripts": { 27 | "test": "mocha" 28 | }, 29 | "devDependencies": { 30 | "chai": "^3.2.0", 31 | "finc-chai-helpers": "~0.1.0", 32 | "fzkes": "^0.14.1", 33 | "mocha": "^3.2.0" 34 | }, 35 | "optionalDependencies": {}, 36 | "engines": { 37 | "node": "^4.4.7 || >=6.3.0" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /prepareManualTests.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var path = require('path') 4 | var input = path.join(__dirname, 'test/manual/css/a.css') 5 | var output = path.join(__dirname, 'test/manual/min.css') 6 | 7 | var minifier = require('./index') 8 | minifier.minify(input, { output: output }) 9 | 10 | console.log('The manual test is complete') 11 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | This project should be considered deprecated 2 | -------------------------------------------- 3 | 4 | This should be considered deprecated. I simply have neither the time or interest 5 | in keeping it up-to-date. 6 | 7 | For alternatives, consider [less](http://lesscss.org) or [sass](http://sass-lang.com/guide) 8 | for the CSS part. I prefer less because it is more lightweight, but both are good 9 | options and serve much of the same purpose. 10 | 11 | For JS, you can either use [uglify](https://github.com/mishoo/UglifyJS2) directly 12 | (this project is already just a thin wrapper around uglify). Alternatively, look 13 | to bundlers like [webpack](https://webpack.js.org), but there are tons of 14 | different ones. 15 | 16 | --- 17 | 18 | minifier 19 | ======== 20 | 21 | A simple tool for minifying CSS/JS without a big setup. 22 | 23 | Feature highlights 24 | ------------------ 25 | 26 | - It minifies JS and CSS 27 | - URLs in CSS are updated relative to the output location 28 | - It automatically resolves `@import` statements in CSS. 29 | 30 | 31 | How to install 32 | -------------- 33 | 34 | There are two ways to install it: 35 | 36 | 1. `npm install [-g] minifier` 37 | 2. Cloning directly from [github](https://github.com/fizker/minifier). 38 | 39 | Installing through `npm` will create a binary (`minify`) in the usual 40 | locations. Cloning and installing from `github` will not, but the `index.js` 41 | file can be executed either directly or via `node index.js`; this is the file 42 | that the binary links to anyway. 43 | 44 | 45 | How to run from a command-line 46 | ------------------------------ 47 | 48 | Running it is simple: 49 | 50 | minify [--output path/to/put/file] path/to/file 51 | 52 | If the output parameter is not set, it will place a file next to the original, 53 | with the suffix `.min`. 54 | 55 | For example, `minifier script.js` will output `script.min.js`, whereas 56 | `minifier --output out.js script.js` will output `out.js`. 57 | 58 | A folder can also be targeted. When that is done, it will minify all css and js 59 | file in that folder. 60 | 61 | In that case, `--output` does not make much sense, as all files will be minified 62 | to the same. If the name still requires a specific pattern such as a timestamp, 63 | `--template` is the option for you. Note that files named after a template will 64 | be left in the same folder as the original file. 65 | 66 | There are various options available: 67 | 68 | - `{{filename}}` is the original filename. 69 | - `{{ext}}` is the extension. 70 | - `{{sha}}` is a sha-digest of the unminified file contents. 71 | - `{{md5}}` is a md5-digest of the unminified file contents. 72 | 73 | For example, `{{filename}}-{{md5}}.min.{{ext}}` will make `abc.js` into something 74 | like `abc-f90731d65c61af25b149658a120d26cf.min.js`. 75 | 76 | To avoid the minification of previously minified files, there is a `--clean` 77 | option, which will delete all files that match the output pattern. 78 | 79 | This also means that any real files that match the pattern will be removed as 80 | well, so please be careful. 81 | 82 | 83 | Running from a node-script 84 | -------------------------- 85 | 86 | It is also possible to run the minifier from within another node script: 87 | 88 | var minifier = require('minifier') 89 | var input = '/some/path' 90 | 91 | minifier.on('error', function(err) { 92 | // handle any potential error 93 | }) 94 | minifier.minify(input, options) 95 | 96 | As with the command-line variant, the input should be a path to either a 97 | javascript file, a css file or a folder. 98 | 99 | The options-dictionary takes the same parameters as the command-line variant: 100 | 101 | - output: A path-string that tells where to put the output. 102 | - template: A string template for how to build the outputted filenames. 103 | - clean: A bool for whether other files with names similar to the template 104 | should be deleted before minifying the contents of a folder. 105 | - cleanOnly: A bool for whether to run with `clean` option and then exiting 106 | before minifying any files. 107 | 108 | There are one important additional option: `uglify`. This will be passed on to 109 | uglify, so that the minification can be controlled. See the 110 | [uglify documentation](https://github.com/mishoo/UglifyJS2#the-simple-way) 111 | for more details (the `uglify.minify(path, opts)` function is used internally). 112 | 113 | ----- 114 | 115 | The method for building the output name from the template is exposed for 116 | convenience: 117 | 118 | var minifier = require('minifier') 119 | var file = 'abc.js' 120 | var template = '{{filename}}.{{md5}}.{{ext}}' 121 | var content = null; // or the content, if md5 or sha1 should be calculated 122 | var result = minifier.generateOutputName(file, { template: template, content: content }) 123 | 124 | If the input-path includes any folders, they will also be added to the output. 125 | 126 | If `content` is eschewed, the `md5` and `sha` digests cannot be calculated. 127 | 128 | But there is an option for turning them into either `RegExp` or `glob` compatible 129 | syntax: Simply add `glob: true` or `regex: true` to the options array: 130 | 131 | var result = minifier.generateOutputName(file, { template: template, glob: true }) 132 | 133 | `glob` will return a string for passing to a `glob` function, whereas `regex` 134 | will return a `RegExp` instance for manual comparison. 135 | 136 | 137 | Concatenating multiple javascript files 138 | --------------------------------------- 139 | 140 | It is possible to concatenate multiple javascript files into a single, minified file. 141 | 142 | Simply pass multiple in via the CLI interface, or pass an array to the API. 143 | 144 | They will have the same order as the input-parameter. 145 | 146 | CSS URL Rewrites 147 | ---------------- 148 | 149 | Any URLs that are found in CSS files, are automatically updated relative to the output destination. An example is shown below: 150 | 151 | ``` 152 | - styles.css 153 | - dist/ 154 | - styles.min.css 155 | - lib/ 156 | - backgrond.jpg 157 | 158 | 159 | styles.css 160 | 161 | .background { 162 | background-image: url("lib/background.jpg"); 163 | } 164 | 165 | styles.min.css 166 | 167 | .background { 168 | background-image: url("../lib/background.jpg"); 169 | } 170 | ``` 171 | 172 | Running the tests 173 | ----------------- 174 | 175 | After installing from [github](https://github.com/fizker/minifier), simply run 176 | `npm test`. 177 | 178 | There is a script called `prepareManualTests.js`, which will run the script 179 | against the css-files inside `test/manual/css/` and provides a real-world 180 | example of the CSS minification tools. 181 | 182 | The manual tests can be seen by opening `test/manual/index.html` in a browser 183 | after executing `prepareManualTests.js`. 184 | 185 | 186 | Credits 187 | ------- 188 | 189 | In no particular order: 190 | 191 | - [duckduckgo](http://duckduckgo.com) for the image used by the manual tests. 192 | - [sqwish](https://github.com/ded/sqwish) for minifying CSS files. 193 | - [uglify-js](https://github.com/mishoo/UglifyJS2) for minifying JS files. 194 | - [commander](https://github.com/visionmedia/commander.js) for command-line 195 | interaction. 196 | - [mocha](https://github.com/visionmedia/mocha) and [chai](http://chaijs.com) 197 | for testing home-brewed logic. 198 | - [glob](https://github.com/isaacs/node-glob) for trawling through the 199 | file-system when targetting a folder. 200 | - [hogan.js](http://twitter.github.com/hogan.js/) for parsing the template 201 | string. 202 | -------------------------------------------------------------------------------- /src/css.js: -------------------------------------------------------------------------------- 1 | module.exports = 2 | { parse: parse 3 | } 4 | 5 | var fs = require('fs') 6 | var path = require('path') 7 | var format = require('util').format 8 | var utils = require('./utils') 9 | var stringImportMatcher = /@import ["'](.+)["'];/g 10 | var importMatcher = /@import +(url\()?([^()]+)\)? *;/g 11 | var urlMatcher = /url\(["']?([^"'()]+)["']?\)/g 12 | var absoluteUrl = /^([a-zA-Z]:\/)?\// 13 | var dataUrl = /^data:/ 14 | 15 | function parse(file, absRoot, minifier) { 16 | if(!minifier) minifier = function(content) { return content } 17 | var root = path.dirname(file) 18 | var absRoot = absRoot || '' 19 | var relRoot = path.relative(absRoot, root) 20 | var content = minifier(utils.stripUTF8ByteOrder(fs.readFileSync(file, 'utf8'))) 21 | 22 | return content 23 | .replace(stringImportMatcher, function(match, url) { 24 | return format('@import url(%s);', url) 25 | }) 26 | .replace(urlMatcher, function(match, url) { 27 | url = url.trim() 28 | if(!url.match(dataUrl) && !url.match(absoluteUrl)) { 29 | url = path.join(relRoot, url).replace(/\\/g, '/') 30 | } 31 | return format('url(%s)', url) 32 | }) 33 | .replace(importMatcher, function(match, junk, file) { 34 | if(!file.match(absoluteUrl)) { 35 | file = path.join(absRoot, file) 36 | } 37 | var parsedFile = parse(file, absRoot) 38 | return parsedFile +'\n' 39 | }) 40 | .trim() 41 | } 42 | -------------------------------------------------------------------------------- /src/minify.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs') 2 | var path = require('path') 3 | var fmerge = require('fmerge') 4 | var format = require('util').format 5 | var sqwish = require('sqwish') 6 | var uglify = require('uglify-js') 7 | var stripUTF8ByteOrder = require('./utils').stripUTF8ByteOrder 8 | var generateOutput = require('./utils').generateOutputName 9 | var glob = require('glob') 10 | var cssParser = require('css-resolve-import') 11 | 12 | var EventEmitter = require('events').EventEmitter 13 | var obj = new EventEmitter() 14 | 15 | obj.minify = minify 16 | obj.generateOutputName = generateOutput 17 | 18 | module.exports = obj 19 | 20 | function minify(input, options) { 21 | options = fmerge({}, options) 22 | 23 | var output 24 | var template 25 | 26 | if(!input || (Array.isArray(input) && input.length == 0)) { 27 | obj.emit('error', new Error('The input is required')) 28 | } 29 | 30 | if(options.cleanOnly) { 31 | options.clean = true 32 | } 33 | output = options.output 34 | template = options.template 35 | 36 | if(output && template) { 37 | return obj.emit( 38 | 'error' 39 | , new Error('It does not make sense to provide both --output and ' 40 | + '--template options. Please choose one.') 41 | ) 42 | } 43 | 44 | if(!Array.isArray(input) && fs.statSync(input).isDirectory()) { 45 | if(output) { 46 | return obj.emit('error', 47 | new Error('You cannot use `output` option against a directory')) 48 | } 49 | if(options.clean) { 50 | clean(input, template || '{{filename}}.min.{{ext}}') 51 | } 52 | if(options.cleanOnly) { 53 | return 54 | } 55 | 56 | var files = glob.sync(path.join(input, '**/*.js')) 57 | .concat( 58 | glob.sync(path.join(input, '**/*.css')) 59 | ) 60 | if(options.skip) { 61 | files = files.filter(function(file) { 62 | return !options.skip.some(function(filter) { 63 | return ~file.indexOf(filter) 64 | }) 65 | }) 66 | } 67 | files.every(x => handleInputs([x])) 68 | 69 | return 70 | } 71 | 72 | var inputs = Array.isArray(input) ? input : [input] 73 | 74 | if(options.clean) { 75 | if(template) { 76 | clean(path.dirname(inputs[0]), template) 77 | } else if(fs.existsSync(output)) { 78 | fs.unlinkSync(output) 79 | } 80 | } 81 | 82 | if(options.cleanOnly) { 83 | return 84 | } 85 | 86 | handleInputs(inputs) 87 | 88 | function handleInputs(inputs) { 89 | var extensionRegex = /(\.js|css)$/ 90 | 91 | var usedExtensions = inputs 92 | .map(function(i) { return i.match(extensionRegex) }) 93 | .filter(function(i) { return i != null }) 94 | .map(function(i) { return i[1] }) 95 | .filter(function(ext, idx, arr) { return arr.indexOf(ext) == idx }) 96 | 97 | if(usedExtensions.length > 1) { 98 | obj.emit('error', new Error('Please only use one type of extension per run')) 99 | return false 100 | } else if(usedExtensions.length == 0 || usedExtensions[0].match(extensionRegex) == null) { 101 | obj.emit('error', new Error('Please reference files with the extension as either .js or .css')) 102 | return false 103 | } 104 | 105 | var jsFiles = inputs.filter(x => x.endsWith('.js')) 106 | var cssFiles = inputs.filter(x => x.endsWith('.css')) 107 | if(jsFiles.length > 0) { 108 | js(jsFiles) 109 | } 110 | if(cssFiles.length > 0) { 111 | css(cssFiles) 112 | } 113 | return true 114 | } 115 | 116 | function js(inputs) { 117 | var max = inputs.map(function(input) { 118 | return stripUTF8ByteOrder(fs.readFileSync(input, 'utf8')) 119 | }).join(';\n') 120 | 121 | var comment = firstComment(max) 122 | var min = uglify.minify(max, fmerge(options.uglify, { fromString: true })).code 123 | var opts = { content: min, template: template } 124 | var renderedOutput = output || generateOutput(inputs[0], opts) 125 | 126 | if(comment) { 127 | min = comment +'\n' + min 128 | } 129 | 130 | fs.writeFileSync(renderedOutput, min) 131 | } 132 | 133 | function css(inputs) { 134 | var inDir = path.dirname(inputs[0]) 135 | var outDir = path.dirname(output || inputs[0]) 136 | var root = path.join(inDir, path.relative(inDir, outDir)) 137 | var min = inputs.map(input => cssParser(input, root, function(max) { 138 | var max = stripUTF8ByteOrder(max) 139 | var comment = firstComment(max) 140 | var min = sqwish.minify(max, false) 141 | 142 | if(comment) { 143 | min = comment + '\n' + min 144 | } 145 | 146 | return min 147 | })).join('\n') 148 | var opts = { content: min, template: template } 149 | var renderedOutput = output || generateOutput(inputs[0], opts) 150 | 151 | fs.writeFileSync(renderedOutput, min) 152 | } 153 | 154 | function clean(dir, template) { 155 | template = template.replace(/{{[^}]*}}/g, '*') 156 | glob.sync(path.join(dir, '**', template)).forEach(function(file) { 157 | fs.unlinkSync(file) 158 | }) 159 | } 160 | 161 | function firstComment(content) { 162 | if(options.noComments) return null 163 | content = content.trim() 164 | if(content[0] == '/' && content[1] == '*') { 165 | return content.substring(0, content.indexOf('*/')+2) 166 | } 167 | if(content[0] == '/' && content[1] == '/') { 168 | var lines = content.split(/[\r\n]{1,2}/g) 169 | content = lines[0] 170 | for(var i = 1; i < lines.length; i++) { 171 | var line = lines[i] 172 | if(line[0] == '/' && line[1] == '/') { 173 | content += '\n' + line 174 | } 175 | } 176 | return content 177 | } 178 | return null 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | module.exports = 2 | { stripUTF8ByteOrder: stripUTF8ByteOrder 3 | , generateOutputName: generateOutputName 4 | } 5 | 6 | var format = require('util').format 7 | var hogan = require('hogan.js') 8 | var digest = require('crypto').createHash 9 | var path = require('path') 10 | 11 | function generateOutputName(input, options) { 12 | if(!options) options = {} 13 | var extractedInput = 14 | { md5: generate.bind(null, 'md5') 15 | , sha: generate.bind(null, 'sha256') 16 | } 17 | var dir = path.dirname(input) 18 | path.basename(input).replace(/^(.*)\.([^.]+)$/, function(match, file, ext) { 19 | extractedInput.ext = ext 20 | extractedInput.filename = file 21 | return '' 22 | }) 23 | 24 | var output = hogan.compile(options.template || '{{filename}}.min.{{ext}}').render(extractedInput) 25 | 26 | output = path.join(dir, output) 27 | 28 | if(options.regex) return new RegExp(output.replace(/\.([^*])/g, '\\.$1')) 29 | return output 30 | 31 | function generate(algorithm) { 32 | if(options.regex) return '.*' 33 | if(options.glob) return '*' 34 | if(!options.content) throw new Error('Content is required for producing ' + algorithm) 35 | var digester = digest(algorithm) 36 | digester.update(options.content, 'utf8') 37 | return digester.digest('hex') 38 | } 39 | } 40 | 41 | function stripUTF8ByteOrder(data) { 42 | var content = data.toString() 43 | if(content[0] === '\uFEFF') { 44 | content = content.substring(1) 45 | } 46 | return content 47 | } 48 | -------------------------------------------------------------------------------- /test/common.js: -------------------------------------------------------------------------------- 1 | global.chai = require('chai'); 2 | global.expect = chai.expect; 3 | 4 | global.fzkes = require('fzkes') 5 | chai.use(fzkes) 6 | 7 | require('finc-chai-helpers').addMethods(chai); 8 | -------------------------------------------------------------------------------- /test/integration/api.css.js: -------------------------------------------------------------------------------- 1 | describe('integration/api.css.js', function() { 2 | var minifier = require('../../index') 3 | var fs = require('fs') 4 | var path = require('path') 5 | 6 | describe('When calling the api on a folder', function() { 7 | const input = path.join(__dirname, 'data') 8 | const template = '{{filename}}.{{md5}}.out.{{ext}}' 9 | 10 | beforeEach(function() { 11 | minifier.minify(input, { template: template }) 12 | }) 13 | afterEach(function() { 14 | safeDelete(path.join(__dirname, 'data/a.5fe39ce3416b224850714849c52781ee.out.css')) 15 | safeDelete(path.join(__dirname, 'data/b.463682a6278fbd2d2c7c3691a4f5b441.out.css')) 16 | safeDelete(path.join(__dirname, 'data/a/c.ef9596f4e227fdc4d4131f2e9b278e00.out.css')) 17 | safeDelete(path.join(__dirname, 'data/b/d.a8c574a0b15ea78c32e0ea5db202ad21.out.css')) 18 | }) 19 | 20 | it('should minify all the files', function() { 21 | expect(fs.existsSync(path.join(__dirname, 'data/a.5fe39ce3416b224850714849c52781ee.out.css'))) 22 | .to.be.true 23 | expect(fs.existsSync(path.join(__dirname, 'data/b.463682a6278fbd2d2c7c3691a4f5b441.out.css'))) 24 | .to.be.true 25 | expect(fs.existsSync(path.join(__dirname, 'data/a/c.ef9596f4e227fdc4d4131f2e9b278e00.out.css'))) 26 | .to.be.true 27 | expect(fs.existsSync(path.join(__dirname, 'data/b/d.a8c574a0b15ea78c32e0ea5db202ad21.out.css'))) 28 | .to.be.true 29 | }) 30 | describe('with the `clean` option set', function() { 31 | var oldContent 32 | beforeEach(function() { 33 | oldContent = fs.readFileSync(path.join(input, 'a.css')) 34 | fs.writeFileSync(path.join(__dirname, 'data/a.css'), 'abc{}') 35 | minifier.minify(input, { template: template, clean: true }) 36 | }) 37 | afterEach(function() { 38 | fs.writeFileSync(path.join(__dirname, 'data/a.css'), oldContent) 39 | safeDelete(path.join(__dirname, 'data/a.34c67064c3f76ca1f5798ad0fd1f8f98.out.css')) 40 | }) 41 | it('should delete the old file', function() { 42 | expect(fs.existsSync(path.join(__dirname, 'data/a.5fe39ce3416b224850714849c52781ee.out.css'))) 43 | .to.be.false 44 | expect(fs.existsSync(path.join(__dirname, 'data/b.463682a6278fbd2d2c7c3691a4f5b441.out.css'))) 45 | .to.be.true 46 | expect(fs.existsSync(path.join(__dirname, 'data/a/c.ef9596f4e227fdc4d4131f2e9b278e00.out.css'))) 47 | .to.be.true 48 | expect(fs.existsSync(path.join(__dirname, 'data/b/d.a8c574a0b15ea78c32e0ea5db202ad21.out.css'))) 49 | .to.be.true 50 | }) 51 | it('should create the new file correctly', function() { 52 | expect(fs.existsSync(path.join(__dirname, 'data/a.34c67064c3f76ca1f5798ad0fd1f8f98.out.css'))) 53 | .to.be.true 54 | }) 55 | }) 56 | describe('with the `cleanOnly` option set', function() { 57 | beforeEach(function() { 58 | minifier.minify(input, { template: template, cleanOnly: true }) 59 | }) 60 | it('should clean but not create the files', function() { 61 | expect(fs.existsSync(path.join(__dirname, 'data/a.5fe39ce3416b224850714849c52781ee.out.css'))) 62 | .to.be.false 63 | expect(fs.existsSync(path.join(__dirname, 'data/b.463682a6278fbd2d2c7c3691a4f5b441.out.css'))) 64 | .to.be.false 65 | expect(fs.existsSync(path.join(__dirname, 'data/a/c.ef9596f4e227fdc4d4131f2e9b278e00.out.css'))) 66 | .to.be.false 67 | expect(fs.existsSync(path.join(__dirname, 'data/b/d.a8c574a0b15ea78c32e0ea5db202ad21.out.css'))) 68 | .to.be.false 69 | }) 70 | }) 71 | }) 72 | describe('When calling the api on a single file', function() { 73 | const input = path.join(__dirname, 'data/a.css') 74 | const output = path.join(__dirname, 'a.output.css') 75 | beforeEach(function() { 76 | minifier.minify(input, { output: output }) 77 | }) 78 | afterEach(function() { 79 | safeDelete(output) 80 | }) 81 | 82 | it('should create the minified file', function() { 83 | expect(fs.existsSync(output)).to.be.ok 84 | }) 85 | it('should reformat the url correctly', function() { 86 | var contents = fs.readFileSync(output, 'utf8') 87 | expect(contents).to.match(/\.nested-img[^}]+gfx\/img\.png/) 88 | }) 89 | it('should import all files', function() { 90 | var contents = fs.readFileSync(output, 'utf8') 91 | expect(contents) 92 | .to.contain('.a-file') 93 | .and.to.contain('.b-file') 94 | .and.to.contain('.c-file') 95 | .and.to.contain('.d-file') 96 | }) 97 | describe('with the `cleanOnly` option set', function() { 98 | beforeEach(function() { 99 | minifier.minify(input, { output: output, cleanOnly: true }) 100 | }) 101 | it('should remove the file', function() { 102 | expect(fs.existsSync(output)).to.be.false 103 | }) 104 | }) 105 | describe('with the `template` option set', function() { 106 | const template = 'template.{{md5}}.out.{{ext}}' 107 | const firstOutput = path.join(__dirname, 'data/template.5fe39ce3416b224850714849c52781ee.out.css') 108 | beforeEach(function() { 109 | minifier.minify(input, { template: template }) 110 | }) 111 | afterEach(function() { 112 | safeDelete(firstOutput) 113 | }) 114 | it('should create the file correctly', function() { 115 | expect(fs.existsSync(firstOutput)) 116 | .to.be.true 117 | }) 118 | describe('and the `clean` option', function() { 119 | var oldContent 120 | const secondOutput = path.join(__dirname, 'data/template.34c67064c3f76ca1f5798ad0fd1f8f98.out.css') 121 | beforeEach(function() { 122 | oldContent = fs.readFileSync(input) 123 | fs.writeFileSync(input, 'abc{}') 124 | minifier.minify(input, { template: template, clean: true }) 125 | }) 126 | afterEach(function() { 127 | fs.writeFileSync(input, oldContent) 128 | safeDelete(secondOutput) 129 | }) 130 | it('should delete the old file', function() { 131 | expect(fs.existsSync(firstOutput)) 132 | .to.be.false 133 | }) 134 | it('should create the new file', function() { 135 | expect(fs.existsSync(secondOutput)) 136 | .to.be.true 137 | }) 138 | }) 139 | describe('and the `cleanOnly` option', function() { 140 | beforeEach(function() { 141 | minifier.minify(input, { template: template, cleanOnly: true }) 142 | }) 143 | it('should clean, but not create the file', function() { 144 | expect(fs.existsSync(firstOutput)) 145 | .to.be.false 146 | }) 147 | }) 148 | }) 149 | }) 150 | describe('When calling the api on a list of files', function() { 151 | const inputs = ['a/c', 'b'].map(x => path.join(__dirname, `data/${x}.css`)) 152 | const output = path.join(__dirname, 'output.css') 153 | beforeEach(function() { 154 | minifier.minify(inputs, { output: output }) 155 | }) 156 | afterEach(function() { 157 | safeDelete(output) 158 | }) 159 | 160 | it('should create the minified file', function() { 161 | expect(fs.existsSync(output)).to.be.ok 162 | }) 163 | it('should reformat the url correctly', function() { 164 | var contents = fs.readFileSync(output, 'utf8') 165 | expect(contents).to.match(/\.nested-img[^}]+gfx\/img\.png/) 166 | }) 167 | it('should import all files', function() { 168 | var contents = fs.readFileSync(output, 'utf8') 169 | expect(contents) 170 | .to.contain('.b-file') 171 | .and.to.contain('.c-file') 172 | .and.to.contain('.d-file') 173 | }) 174 | describe('with the `cleanOnly` option set', function() { 175 | beforeEach(function() { 176 | minifier.minify(inputs, { output: output, cleanOnly: true }) 177 | }) 178 | it('should remove the file', function() { 179 | expect(fs.existsSync(output)).to.be.false 180 | }) 181 | }) 182 | describe('with the `template` option set', function() { 183 | const template = 'template.{{md5}}.out.{{ext}}' 184 | const firstOutput = path.join(__dirname, 'data/a/template.1909ce21348346f98abc37cbeae8e805.out.css') 185 | beforeEach(function() { 186 | minifier.minify(inputs, { template: template }) 187 | }) 188 | afterEach(function() { 189 | safeDelete(firstOutput) 190 | }) 191 | it('should create the file correctly', function() { 192 | expect(fs.existsSync(firstOutput)) 193 | .to.be.true 194 | }) 195 | describe('and the `clean` option', function() { 196 | var oldContent 197 | const secondOutput = path.join(__dirname, 'data/a/template.09c2d80354dcf526e64518d1bd60560b.out.css') 198 | beforeEach(function() { 199 | oldContent = fs.readFileSync(inputs[0]) 200 | fs.writeFileSync(inputs[0], 'abc{}') 201 | minifier.minify(inputs, { template: template, clean: true }) 202 | }) 203 | afterEach(function() { 204 | fs.writeFileSync(inputs[0], oldContent) 205 | safeDelete(secondOutput) 206 | }) 207 | it('should delete the old file', function() { 208 | expect(fs.existsSync(firstOutput)) 209 | .to.be.false 210 | }) 211 | it('should create the new file', function() { 212 | expect(fs.existsSync(secondOutput)) 213 | .to.be.true 214 | }) 215 | }) 216 | describe('and the `cleanOnly` option', function() { 217 | beforeEach(function() { 218 | minifier.minify(inputs, { template: template, cleanOnly: true }) 219 | }) 220 | it('should clean, but not create the file', function() { 221 | expect(fs.existsSync(firstOutput)) 222 | .to.be.false 223 | }) 224 | }) 225 | }) 226 | }) 227 | 228 | function safeDelete(file) { 229 | fs.existsSync(file) && fs.unlinkSync(file) 230 | } 231 | }) 232 | -------------------------------------------------------------------------------- /test/integration/api.errors.js: -------------------------------------------------------------------------------- 1 | describe('integration/api.errors.js', function() { 2 | var minifier = require('../../index') 3 | var fs = require('fs') 4 | var path = require('path') 5 | 6 | beforeEach(function() { 7 | fzkes.fake(fs, 'writeFile') 8 | fzkes.fake(fs, 'writeFileSync') 9 | }) 10 | afterEach(function() { 11 | fzkes.restore() 12 | }) 13 | 14 | describe('When giving input as a non-supported filetype', function() { 15 | var input 16 | beforeEach(function() { 17 | input = path.join(__dirname, 'data/a.html') 18 | }) 19 | it('should throw an error', function() { 20 | expect(function() { 21 | minifier.minify(input) 22 | }).to.throw() 23 | }) 24 | describe('and listening to `error` event', function() { 25 | var listener 26 | beforeEach(function() { 27 | listener = fzkes.fake('listener') 28 | minifier.on('error', listener) 29 | 30 | minifier.minify(input) 31 | }) 32 | afterEach(function() { 33 | minifier.removeListener('error', listener) 34 | }) 35 | it('should emit the event', function() { 36 | expect(listener).to.have.been.called 37 | }) 38 | it('should not create the files', function() { 39 | expect(fs.writeFile).not.to.have.been.called 40 | expect(fs.writeFileSync).not.to.have.been.called 41 | }) 42 | }) 43 | }) 44 | describe('When giving both `output` and `template` option', function() { 45 | var input 46 | var options 47 | beforeEach(function() { 48 | input = path.join(__dirname, 'data-js/a.js') 49 | options = 50 | { output: 'a.min.js' 51 | , template: 'abc' 52 | } 53 | }) 54 | it('should throw', function() { 55 | expect(function() { 56 | minifier.minify(input, options) 57 | }).to.throw() 58 | }) 59 | describe('and listening to `error` event', function() { 60 | var listener 61 | beforeEach(function() { 62 | listener = fzkes.fake('listener') 63 | minifier.on('error', listener) 64 | 65 | minifier.minify(input, options) 66 | }) 67 | afterEach(function() { 68 | minifier.removeListener('error', listener) 69 | }) 70 | it('should emit the event', function() { 71 | expect(listener).to.have.been.called 72 | }) 73 | it('should not create the files', function() { 74 | expect(fs.writeFile).not.to.have.been.called 75 | expect(fs.writeFileSync).not.to.have.been.called 76 | }) 77 | }) 78 | }) 79 | describe('When giving `output` option against a directory', function() { 80 | var input 81 | var options 82 | beforeEach(function() { 83 | input = path.join(__dirname, 'data-js') 84 | options = { output: 'a.b' } 85 | }) 86 | it('should throw', function() { 87 | expect(function() { 88 | minifier.minify(input, options) 89 | }).to.throw() 90 | }) 91 | describe('and listening to `error` event', function() { 92 | var listener 93 | beforeEach(function() { 94 | listener = fzkes.fake('listener') 95 | minifier.on('error', listener) 96 | 97 | minifier.minify(input, options) 98 | }) 99 | afterEach(function() { 100 | minifier.removeListener('error', listener) 101 | }) 102 | it('should give the error', function() { 103 | expect(listener).to.have.been.called 104 | }) 105 | it('should not create the files', function() { 106 | expect(fs.writeFile).not.to.have.been.called 107 | expect(fs.writeFileSync).not.to.have.been.called 108 | }) 109 | }) 110 | }) 111 | }) 112 | -------------------------------------------------------------------------------- /test/integration/api.js.js: -------------------------------------------------------------------------------- 1 | describe('integration/api.js.js', function() { 2 | var minifier = require('../../index') 3 | var fs = require('fs') 4 | var path = require('path') 5 | describe('When aimed at a JS file', function() { 6 | var input = path.join(__dirname, 'data-js/a.js') 7 | var output = path.join(__dirname, 'data-js/a.output.js') 8 | var options 9 | beforeEach(function() { 10 | options = { output: output } 11 | minifier.minify(input, options) 12 | }) 13 | afterEach(function() { 14 | fs.unlinkSync(output) 15 | }) 16 | it('should output a minified version', function() { 17 | expect(fs.existsSync(output)) 18 | .to.be.ok 19 | }) 20 | it('should not modify the options object', function() { 21 | expect(options).to.deep.equal({ output: output }) 22 | }) 23 | }) 24 | describe('When aimed at a folder of JS files', function() { 25 | var input = path.join(__dirname, 'data-js') 26 | var options 27 | beforeEach(function() { 28 | options = { template: 'output-{{filename}}.{{ext}}' } 29 | minifier.minify(input, options) 30 | }) 31 | afterEach(function() { 32 | options.cleanOnly = true 33 | minifier.minify(input, options) 34 | }) 35 | it('should minify both files', function() { 36 | expect(fs.existsSync(path.join(input, 'output-a.js'))) 37 | .to.be.true 38 | expect(fs.existsSync(path.join(input, 'output-b.js'))) 39 | .to.be.true 40 | }) 41 | 42 | describe('and the `clean` option is set', function() { 43 | beforeEach(function() { 44 | fs.renameSync(path.join(input, 'a.js'), path.join(input, 'aa.js')) 45 | options.clean = true 46 | minifier.minify(input, options) 47 | }) 48 | afterEach(function() { 49 | fs.renameSync(path.join(input, 'aa.js'), path.join(input, 'a.js')) 50 | }) 51 | 52 | describe('and the `skip` option is set', function() { 53 | beforeEach(function() { 54 | options.skip = [ 'b.js' ] 55 | minifier.minify(input, options) 56 | }) 57 | it('should skip the right files', function() { 58 | expect(fs.existsSync(path.join(input, 'output-b.js'))) 59 | .to.be.false 60 | }) 61 | it('should minify the other file', function() { 62 | expect(fs.existsSync(path.join(input, 'output-aa.js'))) 63 | .to.be.true 64 | }) 65 | }) 66 | 67 | it('should remove the old files', function() { 68 | expect(fs.existsSync(path.join(input, 'output-a.js'))) 69 | .to.be.false 70 | }) 71 | it('should minify both files', function() { 72 | expect(fs.existsSync(path.join(input, 'output-aa.js'))) 73 | .to.be.true 74 | expect(fs.existsSync(path.join(input, 'output-b.js'))) 75 | .to.be.true 76 | }) 77 | }) 78 | }) 79 | }) 80 | -------------------------------------------------------------------------------- /test/integration/data-js/a.js: -------------------------------------------------------------------------------- 1 | 'a'; 2 | 'b'; 3 | -------------------------------------------------------------------------------- /test/integration/data-js/b.js: -------------------------------------------------------------------------------- 1 | 'c'; 2 | 'd'; 3 | -------------------------------------------------------------------------------- /test/integration/data/a.css: -------------------------------------------------------------------------------- 1 | @import url(b.css); 2 | @import url(a/c.css); 3 | 4 | .root-img { 5 | background: url(../gfx/img.png); 6 | } 7 | 8 | .a-file { 9 | background: url(a-file); 10 | } 11 | -------------------------------------------------------------------------------- /test/integration/data/a.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Test page 4 | 5 |

This is a test page

6 | -------------------------------------------------------------------------------- /test/integration/data/a/c.css: -------------------------------------------------------------------------------- 1 | @import url(../b/d.css); 2 | 3 | .c-file { 4 | background: url(c-file); 5 | } 6 | -------------------------------------------------------------------------------- /test/integration/data/b.css: -------------------------------------------------------------------------------- 1 | div { 2 | height: 160px; 3 | width: 202px; 4 | } 5 | 6 | .b-file { 7 | background: url(b-file); 8 | } 9 | -------------------------------------------------------------------------------- /test/integration/data/b/d.css: -------------------------------------------------------------------------------- 1 | .nested-img { 2 | background: url(../../gfx/img.png); 3 | } 4 | 5 | .d-file { 6 | background: url(d-file); 7 | } 8 | -------------------------------------------------------------------------------- /test/manual/css/a.css: -------------------------------------------------------------------------------- 1 | @import url(b.css); 2 | @import url(a/c.css); 3 | 4 | .root-img { 5 | background: url(../gfx/img.png); 6 | } 7 | -------------------------------------------------------------------------------- /test/manual/css/a/c.css: -------------------------------------------------------------------------------- 1 | @import url(../b/d.css); 2 | -------------------------------------------------------------------------------- /test/manual/css/b.css: -------------------------------------------------------------------------------- 1 | div { 2 | height: 160px; 3 | width: 202px; 4 | } 5 | -------------------------------------------------------------------------------- /test/manual/css/b/d.css: -------------------------------------------------------------------------------- 1 | .nested-img { 2 | background: url(../../gfx/img.png); 3 | } 4 | -------------------------------------------------------------------------------- /test/manual/gfx/img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fizker/minifier/225f6dbe743d233a78d2ae3fb2c61feeb3a6ca5c/test/manual/gfx/img.png -------------------------------------------------------------------------------- /test/manual/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12 | 13 |
14 | Unprocessed 15 | 16 |
17 |
18 | Processed 19 | 20 |
21 | -------------------------------------------------------------------------------- /test/manual/processed.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 | manual/gfx/img.png 7 |
8 |
9 | 10 |
11 | manual/gfx/a/img.png 12 |
13 |
14 | -------------------------------------------------------------------------------- /test/manual/unprocessed.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 | manual/gfx/img.png 7 |
8 |
9 | 10 |
11 | manual/gfx/a/img.png 12 |
13 |
14 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --recursive 2 | --ui bdd 3 | --reporter dot 4 | --require test/common.js 5 | -------------------------------------------------------------------------------- /test/unit/css.parser.js: -------------------------------------------------------------------------------- 1 | describe('unit/css.parser.js', function() { 2 | var parser = require('../../src/css') 3 | var fs = require('fs') 4 | var path = require('path') 5 | 6 | beforeEach(function() { 7 | fzkes.fake(fs, 'readFileSync').calls(function(file) { 8 | return 'read ' + file 9 | }) 10 | }) 11 | afterEach(function() { 12 | fzkes.restore() 13 | }) 14 | 15 | describe('When providing a data-url', function() { 16 | it('should not replace forward slashes', function() { 17 | fs.readFileSync.withArgs('a') 18 | .returns('a{background:url(data:image/png;base64,abc//def)}') 19 | var result = parser.parse('a') 20 | expect(result).to.equal('a{background:url(data:image/png;base64,abc//def)}') 21 | }) 22 | }) 23 | 24 | describe('When providing a root path', function() { 25 | it('should fix urls according to the base path', function() { 26 | fs.readFileSync.withArgs('a/b').returns('a{background: url(c);}') 27 | var result = parser.parse('a/b', 'a') 28 | expect(result).to.equal('a{background: url(c);}') 29 | }) 30 | it('should fix imported urls according to the base path', function() { 31 | fs.readFileSync.withArgs('a/b').returns('@import url(c);') 32 | fs.readFileSync.withArgs(path.join('a/c')).returns('a{background: url(d);}') 33 | var result = parser.parse('a/b', 'a') 34 | expect(result).to.equal('a{background: url(d);}') 35 | }) 36 | it('should work with absolute base paths', function() { 37 | fs.readFileSync.withArgs('/a/file').returns('@import url(b/file);') 38 | fs.readFileSync.withArgs(path.join('/a/b/file')).returns('a{background: url(c/file);}') 39 | var result = parser.parse('/a/file', '/a') 40 | expect(result).to.equal('a{background: url(b/c/file);}') 41 | }) 42 | it('should work with deeper nested base paths', function() { 43 | fs.readFileSync.withArgs('/a/b/file').returns('@import url(c/file);') 44 | fs.readFileSync.withArgs(path.join('/a/b/c/file')).returns('a{background: url(d/file);}') 45 | var result = parser.parse('/a/b/file', '/a') 46 | expect(result).to.equal('a{background: url(b/c/d/file);}') 47 | }) 48 | it('should not change absolute import urls', function() { 49 | fs.readFileSync.withArgs('/a/b').returns('@import url(/absolute/path);') 50 | parser.parse('/a/b', '/a') 51 | expect(fs.readFileSync).to.have.been.calledWith('/absolute/path') 52 | }) 53 | it('should not change other absolute urls', function() { 54 | fs.readFileSync.withArgs('/a/b').returns('a{background:url(/absolute/path);}') 55 | var result = parser.parse('/a/b', '/a') 56 | expect(result).to.equal('a{background:url(/absolute/path);}') 57 | }) 58 | }) 59 | describe('When parsing an import', function() { 60 | it('should take the current path into consideration', function() { 61 | fs.readFileSync.withArgs('a/b').returns('@import url(c);') 62 | parser.parse('a/b') 63 | expect(fs.readFileSync).to.have.been.calledWith(path.join('a/c')) 64 | }) 65 | it('should apply the path to any other urls as well', function() { 66 | fs.readFileSync.withArgs('a/b').returns('a{background: url(c);}') 67 | var result = parser.parse('a/b') 68 | expect(result).to.equal('a{background: url(a/c);}') 69 | }) 70 | it('should apply the path through imports', function() { 71 | fs.readFileSync.withArgs('a').returns('@import url(b/c);') 72 | fs.readFileSync.withArgs(path.join('b/c')).returns('a{background:url(d);') 73 | var result = parser.parse('a') 74 | expect(result).to.equal('a{background:url(b/d);') 75 | }) 76 | }) 77 | describe('When parsing a file with different imports', function() { 78 | var result 79 | it('should work with urls containing white-space', function() { 80 | fs.readFileSync.withArgs('a.css').returns('@import url( file ) ;') 81 | result = parser.parse('a.css') 82 | expect(result).to.equal('read file') 83 | }) 84 | it('should work with strings with single quotes', function() { 85 | fs.readFileSync.withArgs('a.css').returns("@import 'file';") 86 | result = parser.parse('a.css') 87 | expect(result).to.equal('read file') 88 | }) 89 | it('should work with strings with double quotes', function() { 90 | fs.readFileSync.withArgs('a.css').returns('@import "file";') 91 | result = parser.parse('a.css') 92 | expect(result).to.equal('read file') 93 | }) 94 | it('should work with urls without quotes', function() { 95 | fs.readFileSync.withArgs('a.css').returns('@import url(file);') 96 | result = parser.parse('a.css') 97 | expect(result).to.equal('read file') 98 | }) 99 | it('should work with urls with single quotes', function() { 100 | fs.readFileSync.withArgs('a.css').returns("@import url('file');") 101 | result = parser.parse('a.css') 102 | expect(result).to.equal('read file') 103 | }) 104 | it('should work with urls with double quotes', function() { 105 | fs.readFileSync.withArgs('a.css').returns('@import url("file");') 106 | result = parser.parse('a.css') 107 | expect(result).to.equal('read file') 108 | }) 109 | }) 110 | describe('When calling parse() with a css file with no imports', function() { 111 | var result 112 | beforeEach(function() { 113 | fs.readFileSync.withArgs('a.css').returns('abc'); 114 | result = parser.parse('a.css') 115 | }) 116 | it('should ask the file-system for the file', function() { 117 | expect(fs.readFileSync).to.have.been.calledWith('a.css', 'utf8') 118 | }) 119 | it('should return the file', function() { 120 | expect(result).to.equal('abc') 121 | }) 122 | }) 123 | }) 124 | -------------------------------------------------------------------------------- /test/unit/minifier.js: -------------------------------------------------------------------------------- 1 | describe('unit/minifier.js', function() { 2 | var minifier = require('../../index') 3 | var fs = require('fs') 4 | var path = require('path') 5 | 6 | beforeEach(function() { 7 | fzkes.fake(fs, 'readFileSync').calls(function(file) { 8 | return 'read ' + file 9 | }) 10 | fzkes.fake(fs, 'statSync').returns({ isDirectory: function() { return false } }) 11 | fzkes.fake(fs, 'writeFileSync') 12 | }) 13 | afterEach(function() { 14 | fzkes.restore() 15 | }) 16 | 17 | describe('When having a license block in JS', function() { 18 | var opts 19 | beforeEach(function() { 20 | opts = { output: 'out.js' } 21 | }) 22 | describe('as block comment', function() { 23 | beforeEach(function() { 24 | fs.readFileSync.withArgs('a.js') 25 | .returns('/* test a\n * test\n */\n"a";') 26 | }) 27 | it('should retain that block', function() { 28 | minifier.minify('a.js', opts) 29 | expect(fs.writeFileSync._calls[0][1]) 30 | .to.equal('/* test a\n * test\n */\n"a";') 31 | }) 32 | }) 33 | describe('as single-line comments', function() { 34 | beforeEach(function() { 35 | fs.readFileSync.withArgs('a.js') 36 | .returns('// test a\n// test\n"a";') 37 | }) 38 | it('should retain that block', function() { 39 | minifier.minify('a.js', opts) 40 | expect(fs.writeFileSync._calls[0][1]) 41 | .to.equal('// test a\n// test\n"a";') 42 | }) 43 | }) 44 | describe('with the `noComments` flag', function() { 45 | beforeEach(function() { 46 | opts.noComments = true 47 | }) 48 | it('should remove comment blocks', function() { 49 | fs.readFileSync.withArgs('a.js') 50 | .returns('/* test a\n * test\n */\n"a";') 51 | minifier.minify('a.js', opts) 52 | expect(fs.writeFileSync._calls[0][1]) 53 | .to.equal('"a";') 54 | }) 55 | it('should remove comment lines', function() { 56 | fs.readFileSync.withArgs('a.js') 57 | .returns('// test a\n// test\n"a";') 58 | minifier.minify('a.js', opts) 59 | expect(fs.writeFileSync._calls[0][1]) 60 | .to.equal('"a";') 61 | }) 62 | }) 63 | }) 64 | 65 | describe('When having a license block in CSS', function() { 66 | var opts 67 | beforeEach(function() { 68 | opts = { output: 'out.css' } 69 | fs.readFileSync.withArgs('a.css') 70 | .returns('/* test a\n * test\n */\na{color:blue}') 71 | fs.readFileSync.withArgs('b.css') 72 | .returns('/* test b\n * test\n */\nb{color:green}') 73 | fs.readFileSync.withArgs('c.css') 74 | .returns('/* test c\n * test\n */\n@import url(a.css);\n@import url(b.css);') 75 | }) 76 | describe('in a single file', function() { 77 | it('should retain that block', function() { 78 | minifier.minify('a.css', opts) 79 | expect(fs.writeFileSync._calls[0][1]) 80 | .to.equal('/* test a\n * test\n */\na{color:blue}') 81 | }) 82 | }) 83 | describe('in referenced files', function() { 84 | it('should retain all blocks', function() { 85 | var result = minifier.minify('c.css', opts) 86 | expect(fs.writeFileSync._calls[0][1]) 87 | .to.equal('/* test c\n * test\n */\n/* test a\n * test\n */\na{color:blue}\n/* test b\n * test\n */\nb{color:green}') 88 | }) 89 | }) 90 | describe('with the `noComments` flag set', function() { 91 | beforeEach(function() { 92 | opts.noComments = true 93 | }) 94 | it('should remove the comment block', function() { 95 | minifier.minify('a.css', opts) 96 | expect(fs.writeFileSync._calls[0][1]) 97 | .to.equal('a{color:blue}') 98 | }) 99 | }) 100 | }) 101 | }) 102 | -------------------------------------------------------------------------------- /test/unit/utils.js: -------------------------------------------------------------------------------- 1 | describe('unit/utils.js', function() { 2 | var utils = require('../../src/utils') 3 | var minifier = require('../../index') 4 | it('should expose `generateOutputName`', function() { 5 | expect(minifier.generateOutputName).to.equal(utils.generateOutputName) 6 | }) 7 | describe('When calling `generateOutputName()`', function() { 8 | describe('with no template vars', function() { 9 | it('should only inject `min`', function() { 10 | expect(utils.generateOutputName('a.js')) 11 | .to.equal('a.min.js') 12 | expect(utils.generateOutputName('a.css')) 13 | .to.equal('a.min.css') 14 | }) 15 | }) 16 | describe('with template-vars', function() { 17 | it('should place the output next to the input', function() { 18 | var opts = { template: '1.{{ext}}' } 19 | expect(utils.generateOutputName('a/b.js', opts)) 20 | .to.equal('a/1.js') 21 | }) 22 | it('should replace {{filename}}', function() { 23 | var opts = { template: '1-{{filename}}-2' } 24 | expect(utils.generateOutputName('a.js', opts)) 25 | .to.equal('1-a-2') 26 | expect(utils.generateOutputName('a.css', opts)) 27 | .to.equal('1-a-2') 28 | }) 29 | it('should replace {{ext}}', function() { 30 | var opts = { template: '1.{{ext}}' } 31 | expect(utils.generateOutputName('a.js', opts)) 32 | .to.equal('1.js') 33 | expect(utils.generateOutputName('a.css', opts)) 34 | .to.equal('1.css') 35 | }) 36 | }) 37 | describe('with `{{md5}}`', function() { 38 | it('should throw an exception if no content is given', function() { 39 | var opts = 40 | { template: '{{md5}}' 41 | } 42 | expect(function() { 43 | utils.generateOutputName('filename.ext', opts) 44 | }).to.throw(/content.*required/i) 45 | }) 46 | it('should support digest content correctly', function() { 47 | var opts = 48 | { content: 'content' 49 | , template: '{{md5}}' 50 | } 51 | expect(utils.generateOutputName('filename.ext', opts)) 52 | .to.equal('9a0364b9e99bb480dd25e1f0284c8555') 53 | }) 54 | }) 55 | describe('with `{{sha}}`', function() { 56 | it('should throw an exception if no content is given', function() { 57 | var opts = 58 | { template: '{{sha}}' 59 | } 60 | expect(function() { 61 | utils.generateOutputName('filename.ext', opts) 62 | }).to.throw(/content.*required/i) 63 | }) 64 | it('should support sha-hashed content', function() { 65 | var opts = 66 | { content: 'content' 67 | , template: '{{sha}}' 68 | } 69 | expect(utils.generateOutputName('filename.ext', opts)) 70 | .to.equal('ed7002b439e9ac845f22357d822bac1444730fbdb6016d3ec9432297b9ec9f73') 71 | }) 72 | }) 73 | describe('with `regex` flag', function() { 74 | it('should return a regex', function() { 75 | var opts = { regex: true } 76 | expect(utils.generateOutputName('a.b', opts)) 77 | .to.be.a('RegExp') 78 | }) 79 | it('should escape `.` properly', function() { 80 | var opts = { regex: true, template: 'a.min.b' } 81 | expect(utils.generateOutputName('a.b', opts).toString()) 82 | .to.equal('/a\\.min\\.b/') 83 | }) 84 | it('should return `.*` for `{{sha}}`', function() { 85 | var opts = { regex: true, template: '{{filename}}.{{sha}}.{{ext}}' } 86 | expect(utils.generateOutputName('a.b', opts).toString()) 87 | .to.equal('/a\\..*\\.b/') 88 | }) 89 | it('should return `.*` for `{{md5}}`', function() { 90 | var opts = { regex: true, template: '{{filename}}.{{md5}}.{{ext}}' } 91 | expect(utils.generateOutputName('a.b', opts).toString()) 92 | .to.equal('/a\\..*\\.b/') 93 | }) 94 | }) 95 | describe('with `glob` flag', function() { 96 | it('should return a string', function() { 97 | var opts = { glob: true } 98 | expect(utils.generateOutputName('a.b', opts)) 99 | .to.be.a('string') 100 | }) 101 | it('should return `*` for `{{sha}}`', function() { 102 | var opts = { glob: true, template: '{{filename}}.{{sha}}.{{ext}}' } 103 | expect(utils.generateOutputName('a.b', opts)) 104 | .to.equal('a.*.b') 105 | }) 106 | it('should return `*` for `{{md5}}`', function() { 107 | var opts = { glob: true, template: '{{filename}}.{{md5}}.{{ext}}' } 108 | expect(utils.generateOutputName('a.b', opts)) 109 | .to.equal('a.*.b') 110 | }) 111 | }) 112 | }) 113 | }) 114 | --------------------------------------------------------------------------------