├── .gitignore ├── .npmignore ├── .travis.yml ├── Jakefile ├── Readme.md ├── lib ├── index.js └── minifier.js ├── package.json └── test ├── build └── scaffold │ └── index.html ├── bundles.js ├── fixtures ├── backbone app │ ├── entry.js │ └── view.js ├── brfs app │ └── entry.js ├── coffee app │ └── entry.coffee ├── complex file with exclude filter │ ├── entry.js │ ├── jsonthing.json │ ├── submodule.js │ └── subsubmodule.js ├── complex file with filters │ ├── entry.js │ ├── jsonthing.json │ ├── submodule.js │ └── subsubmodule.js ├── complex file with include filter │ ├── entry.js │ ├── jsonthing.json │ ├── submodule.js │ └── subsubmodule.js ├── complex file │ ├── entry.js │ ├── jsonthing.json │ ├── submodule.js │ └── subsubmodule.js ├── custom base path │ ├── dirA │ │ └── submodule.js │ ├── dirB │ │ └── subsubmodule.js │ ├── entry.js │ └── jsonthing.json ├── default base path │ ├── dirA │ │ └── submodule.js │ ├── dirB │ │ └── subsubmodule.js │ ├── entry.js │ └── jsonthing.json ├── index.js ├── libraries │ ├── Backbone.js │ ├── Backbone.min.js │ ├── Backbone.min.map │ ├── HandlebarsRuntime.js │ ├── Jquery.js │ ├── Lodash.js │ └── Underscore.js ├── native libs │ ├── entry.js │ └── submodule.js ├── simple file │ └── entry.js └── transformed app │ ├── entry.js │ └── template.hbs ├── plugin-api.js └── user-errors.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .project 3 | node_modules 4 | test/build 5 | secrest.json 6 | *.log 7 | pkg 8 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | *.sublime-project 2 | *.sublime-workspace 3 | 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | sudo: false 4 | 5 | node_js: 6 | - "6" 7 | - "5" 8 | - "4" 9 | 10 | -------------------------------------------------------------------------------- /Jakefile: -------------------------------------------------------------------------------- 1 | /* globals jake, npmPublishTask */ 2 | 3 | new jake.TestTask('minifyify', function () { 4 | this.testFiles.include('test/plugin-api.js'); 5 | this.testFiles.include('test/user-errors.js'); 6 | this.testFiles.include('test/bundles.js'); 7 | }); 8 | 9 | npmPublishTask('minifyify', function () { 10 | this.packageFiles.include([ 11 | 'Jakefile' 12 | , 'lib/**' 13 | , 'bin/*' 14 | , 'package.json' 15 | , 'Readme.md' 16 | ]); 17 | this.packageFiles.exclude([ 18 | 'test' 19 | ]); 20 | }); 21 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | Minifyify 2 | ========= 3 | #### Tiny, Debuggable Browserify Bundles 4 | 5 | [![Build Status](https://travis-ci.org/ben-ng/minifyify.png?branch=master)](https://travis-ci.org/ben-ng/minifyify) [![Downloads](https://img.shields.io/npm/dm/minifyify.svg)](https://npmjs.com/package/minifyify) [![Dependency Status](https://david-dm.org/ben-ng/minifyify.svg)](https://david-dm.org/ben-ng/minifyify) 6 | 7 | ## WARNING: Unmaintained! 8 | 9 | I have stopped writing Javascript full-time, and therefore do not use Minifyify anymore. While I will continue to accept pull requests and keep the test suite running, you should consider better-maintained alternatives, such as [uglifyify](https://github.com/hughsk/uglifyify). Thank you for your support! 10 | 11 | ## Why use Minifyify? 12 | 13 | Before, browserify made you choose between sane debugging and sane load times. Now, you can have both. 14 | 15 | Minifyify is a browserify plugin that minifies your code. The magic? The sourcemap points back to the original, separate source files. 16 | 17 | Now you can deploy a minified bundle in production, and still have meaningful stack traces when things inevitably break! 18 | 19 | ## Advantages 20 | 21 | There are advantages to using minifyify over your current minification solution: 22 | 23 | 24 | * Reliability 25 | 26 | If you are currently using uglifyify and realized that your sourcemap is behaving strangely, you're not alone. Minifyify builds its own sourcemap instead of depending on uglify-js's `insourcemap` option, and has proven to be more reliable in practice. 27 | 28 | * Smaller Bundles 29 | 30 | If you are currently running uglify-js on the bundle browserify outputs, minifyify can give you a smaller bundle because it removes dead code before browserify processes requires in it. 31 | 32 | For example: 33 | ```javascript 34 | if(process.env.browser) { 35 | var realtime = require('socket-io.client') 36 | } 37 | else { 38 | var realtime = require('socket-io') 39 | } 40 | ``` 41 | 42 | Only one of the required modules will be in your output bundle, because minifyify runs uglify on each individual file before browserify does its bundling. 43 | 44 | * A Neater Web Inspector 45 | 46 | Minifyify allows you to transform those obnoxious absolute paths in your web inspector with `compressPath`. 47 | 48 | * CoffeeScript Support 49 | 50 | ~~Minifyify is tested against CoffeeScript, and can map minified code all the way back to the original `.coffee` files.~~ 51 | 52 | CoffeeScript support is janky because of [this issue](https://github.com/jashkenas/coffeescript/issues/3672). The sourcemap that `coffee-script` produces is wrong, so I had to skip over minifyify's CoffeeScript test. minifyify won't crash, but the test suite validates sourcemaps for correctness. Use at your own risk! 53 | 54 | ## Usage 55 | 56 | ### Programmatic API 57 | ```js 58 | var browserify = require('browserify') 59 | // As of browserify 5, you must enable debug mode in the constructor to use minifyify 60 | , bundler = new browserify({debug: true}); 61 | 62 | bundler.add('entry.js'); 63 | 64 | bundler.plugin('minifyify', {map: 'bundle.js.map'}); 65 | 66 | bundler.bundle(function (err, src, map) { 67 | // Your code here 68 | }); 69 | ``` 70 | 71 | The map option should be the location of the sourcemap on your server, and is used to insert the `sourceMappingURL` comment in `src`. 72 | 73 | ### Command Line 74 | ```sh 75 | $ browserify entry.js -d -p [minifyify --map bundle.js.map --output bundle.js.map] > bundle.js 76 | ``` 77 | 78 | The `--output` option is a required option on the command line interface and specifies where minifyify should write the sourcemap to on disk. 79 | 80 | Passing options to uglify-js is as easy as passing extra parameters in as an `uglify` object. 81 | 82 | ```sh 83 | $ browserify entry.js -d -p [minifyify --map bundle.js.map --output bundle.js.map --uglify [ --compress [ --dead_code--comparisons 0 ] ] ] > bundle.js 84 | ``` 85 | 86 | In the example above, if you want to invoke minifyify to only minify 87 | without generating any source maps or references to it (which is done 88 | by setting `[options.map]` to `false` programatically), you can pass 89 | `--no-map` instead of `--map` and `--output`, like this: 90 | 91 | ```sh 92 | $ browserify entry.js -d -p [minifyify --no-map] > bundle.js 93 | ``` 94 | 95 | 96 | ## Options 97 | 98 | ### [options.compressPath] 99 | 100 | Shorten the paths you see in the web inspector by defining a compression function. 101 | 102 | ``` 103 | // A typical compressPath function 104 | compressPath: function (p) { 105 | return path.relative('my-app-root', p); 106 | } 107 | ``` 108 | 109 | If a string is provided, it will be used instead of `my-app-root` in the function above. This is useful if you are working from the command line and cannot define a function. 110 | 111 | Defaults to a no-op (absolute paths to all source files). 112 | 113 | ### [options.map] 114 | 115 | This is added to the bottom of the minified source file, and should point to where the map will be accessible from on your server. [More details here](http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-howwork). 116 | 117 | Example: If your bundle is at `mysite.com/bundle.js` and the map is at `mysite.com/map.js`, set `options.map = '/map.js'` 118 | 119 | Set to `false` to minify, but not produce a source map or append the source map URL comment. 120 | 121 | ### [options.minify] 122 | 123 | Set to false to disable minification and source map transforms. This essentially turns minifyify into a pass-thru stream. 124 | 125 | If you set it to an object, it will be passed as the options argument to `uglify.minify`. 126 | 127 | ### [options.output] 128 | 129 | Specify a path to write the sourcemap to. Required when using the CLI, optional when working programmatically. 130 | 131 | ### [options.uglify] 132 | 133 | Will be passed to `uglify.minify` 134 | 135 | ### [options.include] 136 | 137 | Pattern(s) matching the files you want included. Defaults to '**/*' (include everything). 138 | `null`/`undefined`, a single string, and an array of strings are all acceptable values. 139 | You have the full range of [glob](https://github.com/isaacs/node-glob#glob-primer) 140 | patterns available to you, so you can do `app/{moduleA,moduleB}/*.js`, etc. 141 | 142 | ### [options.exclude] 143 | 144 | Pattern(s) matching the files you want excluded. By default _nothing_ is excluded. 145 | Like `include`; null, a string, and an array of strings are all acceptable. 146 | Exclude always wins over include. 147 | If a file matches both the `include` and `exclude` pattern arrays, it will be excluded. 148 | 149 | ### [options.base] 150 | 151 | By default all glob strings are matched against relative paths from `process.cwd()` (your projects base directory). 152 | This option allows you to changed that. `base:'subDirA'` means evaluate globs relative from that sub directory. 153 | `base:'/'` means test your glob pattern against absolute file paths. 154 | 155 | ## FAQ 156 | 157 | * PARSE ERROR! 158 | 159 | Are you using `brfs`? Pin it to version `1.0.2`. See issue #44 for details. 160 | 161 | * Wait.. Why did the total size (source code + map) get BIGGER?? 162 | 163 | It's not immediately obvious, but the more you minify code, the bigger the sourcemap gets. Browserify can get away with merely mapping lines to lines because it is going from uncompressed code to uncompressed code. Minifyify squishes multiple lines together, so the sourcemap has to carry more information. 164 | 165 | This is OK because the sourcemap is in a separate file, which means your app will be snappy for your users as their browsers won't download the sourcemap. 166 | 167 | * How does this work? 168 | 169 | Minifyify runs UglifyJS on each file in your bundle, and transforms browserify's sourcemap to map to the original files. 170 | 171 | * Why does the sourcemap cause my debugger to behave erratically? 172 | 173 | Some of the optimizations UglifyJS performs will result in sourcemaps that appear to broken. For example, when UglifyJS uses the comma operator to shorten statements on different lines, a single debugger "step" in minified code may execute multiple lines of the original source. 174 | 175 | Another common example of erratic behavior is when code like this is compressed: 176 | 177 | ``` 178 | var myThing = myFunc('a') 179 | , cantGetHere = myFunc('b'); 180 | ``` 181 | 182 | If you set a breakpoint on the second line, your debugger might not pause execution there. I've found that setting the breakpoint on the first line and stepping onto the second line is more reliable. 183 | 184 | ## Other Modules 185 | 186 | minifyify not working for you? try [gulp-sourcemaps](https://github.com/floridoo/gulp-sourcemaps). 187 | 188 | ## License 189 | 190 | The MIT License (MIT) 191 | 192 | Copyright (c) 2013-2014 Ben Ng 193 | 194 | Permission is hereby granted, free of charge, to any person obtaining a copy 195 | of this software and associated documentation files (the "Software"), to deal 196 | in the Software without restriction, including without limitation the rights 197 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 198 | copies of the Software, and to permit persons to whom the Software is 199 | furnished to do so, subject to the following conditions: 200 | 201 | The above copyright notice and this permission notice shall be included in 202 | all copies or substantial portions of the Software. 203 | 204 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 205 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 206 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 207 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 208 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 209 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 210 | THE SOFTWARE. 211 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | var plugin 2 | , fs = require('fs') 3 | , ReadableStream = require('stream').Readable 4 | , util = require('util') 5 | , mkdirp = require('mkdirp') 6 | , path = require('path'); 7 | 8 | function MinifiedStream (opts) { 9 | this._minOptsRef = opts 10 | ReadableStream.call(this, opts); 11 | } 12 | 13 | util.inherits(MinifiedStream, ReadableStream) 14 | 15 | MinifiedStream.prototype._read = function noop () {} 16 | 17 | plugin = function (bundle, minifyifyOpts) { 18 | minifyifyOpts = minifyifyOpts || {}; 19 | 20 | var minifyify = require('./minifier') 21 | , minifier = new minifyify(minifyifyOpts) 22 | , oldBundle = bundle.bundle 23 | , bundleStarted = false; 24 | 25 | // Hook up the transform so we know what sources were used 26 | bundle.transform({global: true}, minifier.transformer); 27 | 28 | // Proxy the bundle's bundle function so we can capture its output 29 | bundle.bundle = function (bundleOpts, bundleCb) { 30 | var bundleStream 31 | , minifiedStream = new MinifiedStream(); 32 | 33 | // Normalize options 34 | if(typeof bundleOpts == 'function') { 35 | bundleCb = bundleOpts; 36 | bundleOpts = {}; 37 | } 38 | else { 39 | bundleOpts = bundleOpts || {}; 40 | } 41 | 42 | // Force debug mode for browserify < 5 43 | bundleOpts.debug = true; 44 | 45 | // For browserify 5, the bundle must be constructed with debug: true 46 | if(oldBundle.length === 1) { 47 | bundleOpts = undefined 48 | } 49 | 50 | /* 51 | * If no callback was given, require that the user 52 | * specified a path to write the sourcemap out to 53 | */ 54 | if(!bundleStarted && !bundleCb && !minifyifyOpts.output && minifyifyOpts.map) { 55 | throw new Error('Minifyify: opts.output is required since no callback was given'); 56 | } 57 | 58 | // Call browserify's bundle function and capture the output stream 59 | bundleStream = oldBundle.call(bundle); 60 | 61 | /* 62 | * Browserify has this mechanism that delays bundling until all deps 63 | * are ready, and that means bundle gets called twice. The extra time, 64 | * it should just pass thru the data instead of trying to consume it. 65 | */ 66 | if(bundleStarted) { 67 | return bundleStream; 68 | } 69 | 70 | if(!bundleStarted) { 71 | bundleStarted = true; 72 | } 73 | 74 | /* 75 | * Pipe browserify's output into the minifier's consumer 76 | * which has the ability to transform the sourcemap 77 | */ 78 | bundleStream.pipe(minifier.consumer(function (err, src, map) { 79 | var finish; 80 | 81 | // A callback we'll need later 82 | finish = function () { 83 | // Otherwise, throw if anything bad happened 84 | if(err) { 85 | bundleStarted = false; 86 | throw err; 87 | } 88 | 89 | // Push the minified src to our proxied stream 90 | minifiedStream.push(src); 91 | minifiedStream.push(null); 92 | 93 | bundleStarted = false; 94 | if(typeof bundleCb == 'function') { 95 | return bundleCb(err, new Buffer(src), map); 96 | } 97 | }; 98 | 99 | 100 | if (!map && bundle._options.debug) { 101 | // we don't have any sourcemap data but we browserify is in debug mode 102 | // so, just pass src through 103 | finish(); 104 | } else if(minifyifyOpts.minify && minifyifyOpts.output) { 105 | // Write the sourcemap to the specified output location 106 | 107 | // This is so CLI users get a helpful error when their stuff breaks 108 | if(!map) { 109 | throw new Error('Run browserify in debug mode to use minifyify') 110 | } 111 | 112 | var dir = path.dirname(minifyifyOpts.output); 113 | 114 | // create target directory if it doesn't exist 115 | if (dir) mkdirp.sync(dir); 116 | 117 | var writeStream = fs.createWriteStream(minifyifyOpts.output); 118 | 119 | // Delay completion until the map is written 120 | writeStream.on('close', finish); 121 | 122 | writeStream.write(map); 123 | writeStream.end(); 124 | } 125 | else { 126 | finish(); 127 | } 128 | })); 129 | 130 | // Forward on the error event 131 | bundleStream.on('error', function (err) { 132 | bundleStarted = false; 133 | minifiedStream.emit('error', err) 134 | }) 135 | 136 | // The bundle function should return our proxied stream 137 | return minifiedStream; 138 | }; 139 | }; 140 | 141 | module.exports = plugin; 142 | -------------------------------------------------------------------------------- /lib/minifier.js: -------------------------------------------------------------------------------- 1 | var Minifier 2 | , _ = { 3 | each: require('lodash.foreach') 4 | , bind: require('lodash.bind') 5 | , assign: require('lodash.assign') 6 | , defaults: require('lodash.defaults') 7 | } 8 | , fs = require('fs') 9 | , path = require('path') 10 | , tmp = require('tmp') 11 | , concat = require('concat-stream') 12 | , through = require('through') 13 | , uglify = require('uglify-js') 14 | , SM = require('source-map') 15 | , convertSM = require('convert-source-map') 16 | , filter = require('transform-filter') 17 | , SMConsumer = SM.SourceMapConsumer 18 | , SMGenerator = SM.SourceMapGenerator; 19 | 20 | Minifier = function (opts) { 21 | /* 22 | * Handle options/defaults 23 | */ 24 | 25 | opts = opts || {}; 26 | 27 | var defaults = { 28 | minify: true 29 | , source: 'bundle.js' 30 | , map: 'bundle.map' 31 | , sourcesContent: true 32 | , compressPath: function (filePath) { 33 | // noop 34 | return filePath; 35 | } 36 | } 37 | , compressTarget; 38 | 39 | this.opts = _.defaults(opts, defaults); 40 | 41 | /* Turn a string compressPath into a function */ 42 | if(typeof this.opts.compressPath == 'string') { 43 | compressTarget = this.opts.compressPath; 44 | 45 | this.opts.compressPath = function (p) { 46 | return path.relative(compressTarget, p); 47 | } 48 | } 49 | 50 | /* 51 | * Instance variables 52 | */ 53 | this.registry = {}; // Keep source maps and code by file 54 | 55 | /** 56 | * Browserify runs transforms with a different context 57 | * but we always want to refer to ourselves 58 | */ 59 | this.transformer = _.bind(this.transformer, this); 60 | 61 | // Apply glob string include/exclude filters 62 | this.transformer = filter(this.transformer, { 63 | include : opts.include, 64 | exclude : opts.exclude, 65 | base : opts.base 66 | }); 67 | 68 | return this; 69 | }; 70 | 71 | 72 | /* 73 | * Registers maps and code by file 74 | */ 75 | Minifier.prototype.registerMap = function (file, code, map) { 76 | this.registry[file] = {code:code, map:map}; 77 | }; 78 | 79 | /* 80 | * Gets map by file 81 | */ 82 | Minifier.prototype.mapForFile = function (file) { 83 | if(!this.fileExists(file)) { 84 | throw new Error('ENOFILE'); 85 | } 86 | 87 | return this.registry[file].map; 88 | }; 89 | 90 | /* 91 | * Gets code by file 92 | */ 93 | Minifier.prototype.codeForFile = function (file) { 94 | if(!this.fileExists(file)) { 95 | throw new Error('ENOFILE'); 96 | } 97 | 98 | return this.registry[file].code; 99 | }; 100 | 101 | Minifier.prototype.fileExists = function (file) { 102 | return (this.registry[file] != null); 103 | } 104 | 105 | /* 106 | * Compresses code before Browserify touches it 107 | * Does nothing if minify is false 108 | */ 109 | Minifier.prototype.transformer = function (file) { 110 | var self = this 111 | , basedir = this.opts.basedir || process.cwd() 112 | , buffs = [] 113 | , write 114 | , end 115 | , throughStream; 116 | 117 | //Normalize the path separators to match what Browserify stores in the original Source Map 118 | file = this.normalizePath(file); 119 | 120 | write = function (data) { 121 | if(self.opts.minify) { 122 | buffs.push(data); 123 | } 124 | else { 125 | this.queue(data); 126 | } 127 | } 128 | 129 | end = function () { 130 | if(self.opts.minify === false) { 131 | this.queue(null) 132 | return 133 | } 134 | 135 | var thisStream = this 136 | , unminCode = buffs.join('') 137 | , originalCode = false 138 | , existingMap = convertSM.fromSource(unminCode) 139 | , finish; 140 | 141 | existingMap = existingMap ? existingMap.toObject() : false; 142 | 143 | if(existingMap && existingMap.sourcesContent && existingMap.sourcesContent.length) { 144 | originalCode = convertSM.removeComments(existingMap.sourcesContent[0]); 145 | existingMap = JSON.stringify(existingMap); 146 | } 147 | // Only accept existing maps with sourcesContent 148 | else { 149 | existingMap = false; 150 | } 151 | 152 | finish = function (tempExistingMapFile, cleanupCallback) { 153 | // Don't minify JSON! 154 | if(file.match(/\.json$/)) { 155 | try { 156 | thisStream.queue(JSON.stringify(JSON.parse(unminCode))) 157 | } 158 | catch(e) { 159 | console.error('failed to parse JSON in ' + file) 160 | thisStream.queue(unminCode) 161 | } 162 | } 163 | else if (file.match(/\.css$/)) { 164 | thisStream.queue(unminCode) 165 | } 166 | else { 167 | var opts = { 168 | fromString: true 169 | , outSourceMap: (self.opts.map ? self.opts.map : undefined) 170 | , inSourceMap: (self.opts.map ? tempExistingMapFile : undefined) 171 | }; 172 | 173 | if (typeof self.opts.uglify === 'object') { 174 | self.sanitizeObject(self.opts.uglify); 175 | _.assign(opts, self.opts.uglify); 176 | } 177 | 178 | try { 179 | var min = uglify.minify(unminCode, opts); 180 | 181 | thisStream.queue(convertSM.removeMapFileComments(min.code).trim()); 182 | 183 | if(self.opts.map) { 184 | self.registerMap(file, originalCode || unminCode, new SMConsumer(min.map)); 185 | } 186 | } 187 | catch(e) { 188 | console.error('uglify-js failed on '+file+' : ' + e.toString()); 189 | thisStream.queue(unminCode); 190 | } 191 | finally { 192 | if (typeof cleanupCallback === 'function') { 193 | cleanupCallback(); 194 | } 195 | } 196 | } 197 | 198 | // Otherwise we'll never finish 199 | thisStream.queue(null); 200 | } 201 | 202 | if(existingMap) { 203 | tmp.file(function (err, path, fd, cleanupCallback) { 204 | if(err) { 205 | cleanupCallback(); 206 | throw err; 207 | } 208 | 209 | fs.writeFile(path, existingMap, function (err) { 210 | if(err) { 211 | cleanupCallback(); 212 | throw err; 213 | } 214 | finish(path, cleanupCallback); 215 | }); 216 | }); 217 | } 218 | else { 219 | finish(); 220 | } 221 | } 222 | 223 | throughStream = through(write, end); 224 | 225 | throughStream.call = function () { 226 | throw new Error('Transformer is a transform. Correct usage: `bundle.transform(minifier.transformer)`.') 227 | } 228 | 229 | return throughStream; 230 | }; 231 | 232 | /* 233 | * Consumes the output stream from Browserify 234 | */ 235 | Minifier.prototype.consumer = function (cb) { 236 | var self = this; 237 | 238 | return concat(function(data) { 239 | if(!self.opts.minify) { 240 | // Keep browserify's sourcemap 241 | return cb(null, data.toString(), null); 242 | } 243 | else if (!self.opts.map) { 244 | // Remove browserify's inline sourcemap 245 | return cb(null, convertSM.removeComments(data.toString()), null); 246 | } 247 | else { 248 | var bundle = self.decoupleBundle(data); 249 | 250 | if(bundle === false) { 251 | if(self.opts.minify) 252 | throw new Error('Browserify must be in debug mode for minifyify to consume sourcemaps') 253 | 254 | return cb(null, convertSM.removeComments(data.toString())); 255 | } 256 | 257 | // Re-maps the browserify sourcemap 258 | // to the original source using the 259 | // uglify sourcemap 260 | bundle.map = self.transformMap(bundle.map); 261 | 262 | bundle.code = bundle.code + '\n//# sourceMappingURL=' + self.opts.map 263 | 264 | cb(null, bundle.code, bundle.map); 265 | } 266 | }); 267 | }; 268 | 269 | Minifier.prototype.sanitizeObject = function (opts) { 270 | if(opts._ !== undefined) delete opts._; 271 | for (var key in opts) { 272 | if(typeof opts[key] === 'object' && opts[key] != null) { 273 | this.sanitizeObject(opts[key]); 274 | } 275 | } 276 | return opts; 277 | }; 278 | 279 | /* 280 | * Given a SourceMapConsumer from a bundle's map, 281 | * transform it so that it maps to the unminified 282 | * source 283 | */ 284 | Minifier.prototype.transformMap = function (bundleMap) { 285 | var self = this 286 | , generator = new SMGenerator({ 287 | file: self.opts.source 288 | }) 289 | // Map File -> The lowest numbered line in the bundle (offset) 290 | , bundleToMinMap = {} 291 | 292 | /* 293 | * Helper function that maps minified source to a line in the browserify bundle 294 | */ 295 | , mapSourceToLine = function (source, line) { 296 | var target = bundleToMinMap[source]; 297 | 298 | if(!target || target > line) { 299 | bundleToMinMap[source] = line; 300 | } 301 | } 302 | 303 | , hasNoMappings = function (file) { 304 | return bundleToMinMap[file] == null; 305 | } 306 | 307 | /* 308 | * Helper function that gets the line 309 | */ 310 | , lineForSource = function (source) { 311 | if(hasNoMappings(source)) { 312 | throw new Error('ENOFILE: ' + source); 313 | } 314 | 315 | var target = bundleToMinMap[source]; 316 | 317 | return target; 318 | } 319 | , missingSources = {}; 320 | 321 | // Figure out where my minified files went in the bundle 322 | bundleMap.eachMapping(function (mapping) { 323 | var source = self.normalizePath(mapping.source); 324 | if(self.fileExists(source)) { 325 | mapSourceToLine(source, mapping.generatedLine); 326 | } 327 | // Not a known source, pass thru the mapping 328 | else { 329 | generator.addMapping({ 330 | generated: { 331 | line: mapping.generatedLine 332 | , column: mapping.generatedColumn 333 | } 334 | , original: { 335 | line: mapping.originalLine 336 | , column: mapping.originalColumn 337 | } 338 | , source: self.opts.compressPath(mapping.source) 339 | , name: mapping.name 340 | }); 341 | 342 | missingSources[mapping.source] = true; 343 | } 344 | }); 345 | 346 | if(process.env.debug) { 347 | console.log(' [DEBUG] Here is where Browserify put your modules:'); 348 | _.each(bundleToMinMap, function (line, file) { 349 | console.log(' [DEBUG] line ' + line + ' "' + self.opts.compressPath(file) + '"'); 350 | }); 351 | } 352 | 353 | // Add sourceContent for missing sources 354 | if (self.opts.sourcesContent) { 355 | _.each(missingSources, function (v, source) { 356 | generator.setSourceContent(self.opts.compressPath(source), bundleMap.sourceContentFor(source)); 357 | }); 358 | } 359 | 360 | // Map from the hi-res sourcemaps to the browserify bundle 361 | if(process.env.debug) { 362 | console.log(' [DEBUG] Here is how I\'m mapping your code:'); 363 | } 364 | 365 | self.eachSource(function (file, code) { 366 | // Ignore files with no mappings 367 | if(!self.fileExists(file) || hasNoMappings(file)) { 368 | if(process.env.debug) { 369 | throw new Error('File with no mappings: ' + file) 370 | } 371 | return; 372 | } 373 | 374 | var offset = lineForSource(file) - 1 375 | , fileMap = self.mapForFile(file) 376 | , transformedFileName = self.opts.compressPath(file); 377 | 378 | if(process.env.debug) { 379 | console.log(' [DEBUG] Now mapping "' + transformedFileName + '"'); 380 | } 381 | 382 | fileMap.eachMapping(function (mapping) { 383 | var transformedMapping = self.transformMapping(transformedFileName, mapping, offset); 384 | 385 | if(process.env.debug) { 386 | console.log(' [DEBUG] Generated [' + transformedMapping.generated.line + 387 | ':' + transformedMapping.generated.column + '] > [' + 388 | mapping.originalLine + ':' + mapping.originalColumn + '] Original'); 389 | } 390 | 391 | generator.addMapping( transformedMapping ); 392 | }); 393 | 394 | if (self.opts.sourcesContent) { 395 | generator.setSourceContent(transformedFileName, code); 396 | } 397 | }); 398 | 399 | return generator.toString(); 400 | }; 401 | 402 | /* 403 | * Given a mapping (from SMConsumer.eachMapping) 404 | * return a new mapping (for SMGenerator.addMapping) 405 | * resolved to the original source 406 | */ 407 | Minifier.prototype.transformMapping = function (file, mapping, offset) { 408 | return { 409 | generated: { 410 | line: mapping.generatedLine + offset 411 | , column: mapping.generatedColumn 412 | } 413 | , original: { 414 | line: mapping.originalLine 415 | , column: mapping.originalColumn 416 | } 417 | , source: file 418 | , name: mapping.name 419 | } 420 | }; 421 | 422 | /* 423 | * Iterates over each code file, executes a function 424 | */ 425 | Minifier.prototype.eachSource = function (cb) { 426 | var self = this; 427 | 428 | _.each(Object.keys(this.registry).sort(), function(file) { 429 | cb(file, self.codeForFile(file), self.mapForFile(file)); 430 | }); 431 | }; 432 | 433 | /* 434 | * Given source with embedded sourcemap, seperate the two 435 | * Returns the code and SourcemapConsumer object seperately 436 | */ 437 | Minifier.prototype.decoupleBundle = function (src) { 438 | if(typeof src != 'string') 439 | src = src.toString(); 440 | 441 | var map = convertSM.fromSource(src); 442 | // The source didn't have a sourcemap in it 443 | if(!map) { 444 | return false; 445 | } 446 | 447 | return { 448 | code: convertSM.removeComments(src) 449 | , map: new SMConsumer( map.toObject() ) 450 | }; 451 | }; 452 | 453 | Minifier.prototype.normalizePath = function (file) { 454 | // Is file a relative path? 455 | if (!/^\w:|^\//.test(file)) { 456 | return file.replace(/\\/g, '/'); 457 | } 458 | // Resolve absolute paths relative to basedir 459 | // Force '/' as path separator 460 | var basedir = this.opts.basedir || process.cwd(); 461 | return path.relative(basedir, file).replace(/\\/g, '/'); 462 | } 463 | 464 | module.exports = Minifier; 465 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Ben Ng (http://benng.me)", 3 | "contributors": [ 4 | { 5 | "name": "Jesús Leganés Combarro 'piranna'", 6 | "email": "piranna@gmail.com", 7 | "url": "http://pirannafs.blogspot.com.es" 8 | } 9 | ], 10 | "name": "minifyify", 11 | "description": "Minify your browserify bundles without losing the sourcemap", 12 | "keywords": [ 13 | "browserify-plugin", 14 | "browserify", 15 | "uglify", 16 | "transform", 17 | "minify", 18 | "uglifyify", 19 | "compress" 20 | ], 21 | "version": "7.3.4", 22 | "repository": { 23 | "type": "git", 24 | "url": "git://github.com/ben-ng/minifyify.git" 25 | }, 26 | "main": "./lib", 27 | "dependencies": { 28 | "concat-stream": "^1.4.7", 29 | "convert-source-map": "^1.0.0", 30 | "lodash.bind": "^4.0.0", 31 | "lodash.assign": "^4.0.0", 32 | "lodash.defaults": "^4.0.0", 33 | "lodash.foreach": "^4.0.0", 34 | "mkdirp": "^0.5.0", 35 | "source-map": "^0.5.3", 36 | "through": "^2.3.6", 37 | "tmp": "0.0.28", 38 | "transform-filter": "^0.1.1", 39 | "uglify-js": "^2.6.1" 40 | }, 41 | "scripts": { 42 | "test": "test-peer-range browserify", 43 | "test-main": "jake test --trace" 44 | }, 45 | "devDependencies": { 46 | "backbone": "latest", 47 | "brfs": "latest", 48 | "browserify": "latest", 49 | "coffeeify": "latest", 50 | "envify": "latest", 51 | "handlebars": "latest", 52 | "handlebars-runtime": "latest", 53 | "hbsfy": "latest", 54 | "jake": "latest", 55 | "jquery": "latest", 56 | "jquery-browserify": "latest", 57 | "jsesc": "latest", 58 | "lodash.template": "latest", 59 | "sourcemap-validator": "latest", 60 | "test-peer-range": "^1.0.1", 61 | "utilities": "latest" 62 | }, 63 | "peerDependencies": { 64 | "browserify": ">= 5" 65 | }, 66 | "optionalDependencies": {}, 67 | "engines": { 68 | "node": ">=0.10.0" 69 | }, 70 | "license": "MIT" 71 | } 72 | -------------------------------------------------------------------------------- /test/build/scaffold/index.html: -------------------------------------------------------------------------------- 1 | 2 | Test 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /test/bundles.js: -------------------------------------------------------------------------------- 1 | var fixtures = require('./fixtures') 2 | , utils = require('utilities') 3 | , fs = require('fs') 4 | , path = require('path') 5 | , assert = require('assert') 6 | , browserify = require('browserify') 7 | , validate = require('sourcemap-validator') 8 | , Minifyify = require('../lib/minifier') 9 | 10 | // Helpers 11 | , compileApp 12 | , testApp 13 | , clean = function () { 14 | utils.file.rmRf( path.join(fixtures.buildDir, 'apps'), {silent: true}); 15 | utils.file.mkdirP( path.join(fixtures.buildDir, 'apps'), {silent: true}); 16 | } 17 | 18 | // Tests 19 | , tests = { 20 | "before": clean 21 | }; 22 | 23 | compileApp = function (appname, map, next) { 24 | var opts = { 25 | compressPath: function (p) { 26 | return path.relative(path.join(__dirname, 'fixtures', appname), p); 27 | } 28 | }; 29 | 30 | if(typeof map == 'function') { 31 | next = map; 32 | map = 'bundle.map.json'; 33 | opts.map = map; 34 | } 35 | 36 | if (typeof map == 'object') { 37 | opts = utils.object.merge(opts, map) 38 | } 39 | 40 | var bundle = new browserify({debug: true}) 41 | , minifier = new Minifyify(opts); 42 | 43 | bundle.add(fixtures.entryScript(appname)); 44 | 45 | bundle = bundle 46 | .transform(require('coffeeify')) 47 | .transform(require('hbsfy')) 48 | .transform(require('brfs')) 49 | .transform(minifier.transformer) 50 | .bundle() 51 | 52 | bundle.pipe(minifier.consumer(function (err, src, map) { 53 | if(err) { 54 | throw err; 55 | } 56 | 57 | next(src, map) 58 | })); 59 | }; 60 | 61 | /** 62 | * Builds, uploads, and validates an app 63 | */ 64 | testApp = function(appname, cb, opts) { 65 | var filename = fixtures.bundledFile(appname) 66 | , mapname = fixtures.bundledMap(appname) 67 | , destdir = fixtures.bundledDir(appname); 68 | 69 | function validateApp(min, map) { 70 | // Write to the build dir 71 | var appdir = path.join(fixtures.buildDir, 'apps', appname); 72 | 73 | utils.file.mkdirP( appdir, {silent: true}); 74 | 75 | utils.file.cpR(fixtures.scaffoldDir 76 | , path.join(fixtures.buildDir, 'apps'), {rename:appname, silent:true}); 77 | utils.file.cpR(path.dirname(fixtures.entryScript(appname)) 78 | , path.join(fixtures.buildDir, 'apps'), {rename:appname, silent:true}); 79 | fs.writeFileSync( path.join(destdir, path.basename(filename)), min ); 80 | fs.writeFileSync( path.join(destdir, path.basename(mapname)), map ); 81 | 82 | assert.doesNotThrow(function () { 83 | validate(min, map); 84 | }, appname + ' should not throw'); 85 | 86 | cb(); 87 | } 88 | 89 | // Compile lib 90 | opts ? compileApp(appname, opts, validateApp) : compileApp(appname, validateApp); 91 | }; 92 | 93 | /* 94 | * 1. Builds the app and validates the source map. 95 | * 2. Greps the bundle for a NOT_MINIFIED comment, or an special unminified json line. 96 | * 3. Builds an array of files that were not minified. 97 | * 3. compares `expected_unminified` to the array of unminified files found 98 | */ 99 | function testFilter(appname, opts, expected_unminified, cb){ 100 | 101 | expected_unminified = expected_unminified.slice().sort(); 102 | 103 | var actual_unminified = []; 104 | 105 | // scans the created bundle for unminified contents 106 | function scanUnmodified(pattern){ 107 | var contents = fs.readFileSync(fixtures.bundledFile(appname)); 108 | var match; 109 | var regex = new RegExp(pattern, 'g'); 110 | while (match = regex.exec(contents)) { 111 | actual_unminified.push(match[1]); // The first capture group should contain the file name 112 | } 113 | } 114 | 115 | function assertUnminified(){ 116 | // find unminified javascript files 117 | scanUnmodified("\\/\\/ ([a-zA-Z\\.]+) NOT_MINIFIED"); 118 | 119 | // find unminified json files 120 | scanUnmodified('"unminified":\\s+"([a-zA-Z\\.]+)"'); 121 | 122 | actual_unminified.sort(); 123 | 124 | assert.deepEqual(actual_unminified,expected_unminified); 125 | 126 | cb(); 127 | } 128 | 129 | testApp(appname, assertUnminified, opts); 130 | } 131 | 132 | tests['simple file'] = function (next) { 133 | testApp('simple file', next); 134 | }; 135 | 136 | tests['complex file'] = function (next) { 137 | testApp('complex file', next); 138 | }; 139 | 140 | tests['complex file with include filter'] = function (next) { 141 | testFilter( 142 | 'complex file with include filter', 143 | {include:'**/sub*.js'}, 144 | ['entry.js', 'jsonthing.json'], 145 | next 146 | ); 147 | }; 148 | 149 | tests['complex file with exclude filter'] = function (next) { 150 | testFilter( 151 | 'complex file with exclude filter', 152 | {exclude:'**/sub*.js'}, 153 | ['submodule.js', 'subsubmodule.js'], 154 | next 155 | ); 156 | }; 157 | 158 | tests['complex file with filters'] = function (next) { 159 | testFilter( 160 | 'complex file with filters', 161 | {include:['**/*.js'], exclude:['**/subsub*.js', '**/entry.js']}, 162 | ['subsubmodule.js', 'entry.js', 'jsonthing.json'], 163 | next 164 | ); 165 | }; 166 | 167 | tests['default base path'] = function (next) { 168 | console.log(process.cwd()) 169 | testFilter( 170 | 'default base path', 171 | {include:['**/*.js'], exclude:['test/fixtures/default base path/dirA/*.js', 'dirB/*.js']}, 172 | ['submodule.js', 'jsonthing.json'], // doesn't exclude dirB 173 | next 174 | ); 175 | }; 176 | 177 | tests['custom base path'] = function (next) { 178 | testFilter( 179 | 'custom base path', 180 | {include:['**/*.js','*.js'], exclude:['dirA/*.js', 'dirB/*.js'], base:'test/fixtures/custom base path/'}, 181 | ['submodule.js','subsubmodule.js', 'jsonthing.json'], 182 | next 183 | ); 184 | }; 185 | 186 | tests['native libs'] = function (next) { 187 | testApp('native libs', next); 188 | }; 189 | 190 | tests['brfs app'] = function (next) { 191 | testApp('brfs app', next); 192 | }; 193 | 194 | /* Broken because of coffeescript 1.8.0.. 195 | * See: https://github.com/jashkenas/coffeescript/issues/3681 196 | * See: https://github.com/jashkenas/coffeescript/issues/3672 197 | 198 | tests['coffee app'] = function (next) { 199 | testApp('coffee app', next); 200 | }; 201 | 202 | */ 203 | 204 | tests['backbone app'] = function (next) { 205 | testApp('backbone app', next); 206 | }; 207 | 208 | tests['transformed app'] = function (next) { 209 | testApp('transformed app', next); 210 | }; 211 | 212 | tests['opts.map = false should not produce a sourcemap'] = function (next) { 213 | compileApp('simple file', { map : false }, function (min, map) { 214 | assert.ok(min); 215 | assert.ok(map == null); 216 | next(); 217 | }); 218 | }; 219 | 220 | tests['opts.map = true should produce a sourcemap'] = function (next) { 221 | compileApp('simple file', { map : true }, function (min, map) { 222 | assert.ok(min); 223 | assert.ok(map); 224 | next(); 225 | }); 226 | }; 227 | 228 | tests['opts.sourcesContent = false should produce a map without sourcesContent'] = function (next) { 229 | compileApp('simple file', { sourcesContent : false }, function (min, map) { 230 | map = JSON.parse(map, null, 4); 231 | assert.ok(min); 232 | assert.ok(map); 233 | assert.ok(map.sourcesContent == null); 234 | next(); 235 | }); 236 | }; 237 | 238 | tests['opts.uglify.compress = false should not compress'] = function (next) { 239 | compileApp('simple file', { uglify: { compress: false, output: { beautify: true } } }, function (min, map) { 240 | assert.ok(min); 241 | assert.ok(map); 242 | 243 | // Check that its not compressed 244 | assert.ok(min.indexOf('header.innerHTML = anotherString;\n\ndocument.body.appendChild(header);') > -1); 245 | 246 | next(); 247 | }); 248 | }; 249 | 250 | module.exports = tests; -------------------------------------------------------------------------------- /test/fixtures/backbone app/entry.js: -------------------------------------------------------------------------------- 1 | var View = require('./view') 2 | , viewInstance = new View({el: document.body}); 3 | 4 | viewInstance.render(); 5 | -------------------------------------------------------------------------------- /test/fixtures/backbone app/view.js: -------------------------------------------------------------------------------- 1 | var Backbone = require('backbone') 2 | , $ = require('jquery-browserify') 3 | , _ = {template: require('lodash.template')} 4 | , View; 5 | 6 | Backbone.$ = $; 7 | 8 | View = Backbone.View.extend({ 9 | template: _.template('<%= message %>') 10 | , render: function () { 11 | this.el.innerHTML = (this.template({message: 'Hello From View Line 11'})); 12 | 13 | return this; 14 | } 15 | }); 16 | 17 | module.exports = View; 18 | -------------------------------------------------------------------------------- /test/fixtures/brfs app/entry.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | 3 | console.log(fs.readFileSync(__dirname + '/entry.js', {encoding: 'utf8'})); 4 | -------------------------------------------------------------------------------- /test/fixtures/coffee app/entry.coffee: -------------------------------------------------------------------------------- 1 | adjectives = ['such', 'much'] 2 | 3 | for something in adjectives 4 | console.log something + ' coffeescript' 5 | -------------------------------------------------------------------------------- /test/fixtures/complex file with exclude filter/entry.js: -------------------------------------------------------------------------------- 1 | // entry.js NOT_MINIFIED 2 | 3 | var submodule = require('./submodule') 4 | , jsonthing = require('./jsonthing.json') 5 | , myString 6 | , actual 7 | , expected; 8 | 9 | myString = submodule.createString(function () { 10 | var mathy = 1 + 1 + 2 + 3 + 5 + 8; 11 | 12 | mathy *= 1337; 13 | 14 | return 'potato #' + mathy + jsonthing.turkey; 15 | }); 16 | 17 | actual = document.createElement('pre'); 18 | expected = document.createElement('pre'); 19 | 20 | actual.innerHTML = 'Actual: ' + myString; 21 | expected.innerHTML = 'Expected: Wed Dec 31 1969 22:30:23 GMT-0800 (PST) friedpotato #26740salmonbakedpotato #26740salmonsliced potato #26740salmon'; 22 | 23 | document.body.appendChild(actual); 24 | document.body.appendChild(expected); 25 | -------------------------------------------------------------------------------- /test/fixtures/complex file with exclude filter/jsonthing.json: -------------------------------------------------------------------------------- 1 | { 2 | "unminified": "jsonthing.json", 3 | "turkey": "salmon" 4 | } -------------------------------------------------------------------------------- /test/fixtures/complex file with exclude filter/submodule.js: -------------------------------------------------------------------------------- 1 | // submodule.js NOT_MINIFIED 2 | 3 | var subsubmodule = require('./subsubmodule'); 4 | 5 | module.exports = { 6 | createString: function (mathFunction) { 7 | var date 8 | , someNumber; 9 | 10 | date = subsubmodule.getTheDate(function () { 11 | var potatoes; 12 | 13 | potatoes = ['fried', 'baked', 'sliced']; 14 | 15 | return potatoes.join(mathFunction()); 16 | }); 17 | 18 | someNumber = mathFunction(); 19 | 20 | return date + ' ' + someNumber; 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /test/fixtures/complex file with exclude filter/subsubmodule.js: -------------------------------------------------------------------------------- 1 | // subsubmodule.js NOT_MINIFIED 2 | 3 | var getTheDate = function (someFunction) { 4 | var someValue = someFunction(); 5 | 6 | return'Wed Dec 31 1969 22:30:23 GMT-0800 (PST) ' + someValue; 7 | }; 8 | 9 | module.exports = { 10 | getTheDate: getTheDate 11 | }; 12 | -------------------------------------------------------------------------------- /test/fixtures/complex file with filters/entry.js: -------------------------------------------------------------------------------- 1 | // entry.js NOT_MINIFIED 2 | 3 | var submodule = require('./submodule') 4 | , jsonthing = require('./jsonthing.json') 5 | , myString 6 | , actual 7 | , expected; 8 | 9 | myString = submodule.createString(function () { 10 | var mathy = 1 + 1 + 2 + 3 + 5 + 8; 11 | 12 | mathy *= 1337; 13 | 14 | return 'potato #' + mathy + jsonthing.turkey; 15 | }); 16 | 17 | actual = document.createElement('pre'); 18 | expected = document.createElement('pre'); 19 | 20 | actual.innerHTML = 'Actual: ' + myString; 21 | expected.innerHTML = 'Expected: Wed Dec 31 1969 22:30:23 GMT-0800 (PST) friedpotato #26740salmonbakedpotato #26740salmonsliced potato #26740salmon'; 22 | 23 | document.body.appendChild(actual); 24 | document.body.appendChild(expected) 25 | -------------------------------------------------------------------------------- /test/fixtures/complex file with filters/jsonthing.json: -------------------------------------------------------------------------------- 1 | { 2 | "unminified": "jsonthing.json", 3 | "turkey": "salmon" 4 | } -------------------------------------------------------------------------------- /test/fixtures/complex file with filters/submodule.js: -------------------------------------------------------------------------------- 1 | // submodule.js NOT_MINIFIED 2 | 3 | var subsubmodule = require('./subsubmodule'); 4 | 5 | module.exports = { 6 | createString: function (mathFunction) { 7 | var date 8 | , someNumber; 9 | 10 | date = subsubmodule.getTheDate(function () { 11 | var potatoes; 12 | 13 | potatoes = ['fried', 'baked', 'sliced']; 14 | 15 | return potatoes.join(mathFunction()); 16 | }); 17 | 18 | someNumber = mathFunction(); 19 | 20 | return date + ' ' + someNumber; 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /test/fixtures/complex file with filters/subsubmodule.js: -------------------------------------------------------------------------------- 1 | // subsubmodule.js NOT_MINIFIED 2 | 3 | var getTheDate = function (someFunction) { 4 | var someValue = someFunction(); 5 | 6 | return'Wed Dec 31 1969 22:30:23 GMT-0800 (PST) ' + someValue; 7 | }; 8 | 9 | module.exports = { 10 | getTheDate: getTheDate 11 | }; 12 | -------------------------------------------------------------------------------- /test/fixtures/complex file with include filter/entry.js: -------------------------------------------------------------------------------- 1 | // entry.js NOT_MINIFIED 2 | 3 | var submodule = require('./submodule') 4 | , jsonthing = require('./jsonthing.json') 5 | , myString 6 | , actual 7 | , expected; 8 | 9 | myString = submodule.createString(function () { 10 | var mathy = 1 + 1 + 2 + 3 + 5 + 8; 11 | 12 | mathy *= 1337; 13 | 14 | return 'potato #' + mathy + jsonthing.turkey; 15 | }); 16 | 17 | actual = document.createElement('pre'); 18 | expected = document.createElement('pre'); 19 | 20 | actual.innerHTML = 'Actual: ' + myString; 21 | expected.innerHTML = 'Expected: Wed Dec 31 1969 22:30:23 GMT-0800 (PST) friedpotato #26740salmonbakedpotato #26740salmonsliced potato #26740salmon'; 22 | 23 | document.body.appendChild(actual); 24 | document.body.appendChild(expected) 25 | -------------------------------------------------------------------------------- /test/fixtures/complex file with include filter/jsonthing.json: -------------------------------------------------------------------------------- 1 | { 2 | "unminified": "jsonthing.json", 3 | "turkey": "salmon" 4 | } -------------------------------------------------------------------------------- /test/fixtures/complex file with include filter/submodule.js: -------------------------------------------------------------------------------- 1 | // submodule.js NOT_MINIFIED 2 | 3 | var subsubmodule = require('./subsubmodule'); 4 | 5 | module.exports = { 6 | createString: function (mathFunction) { 7 | var date 8 | , someNumber; 9 | 10 | date = subsubmodule.getTheDate(function () { 11 | var potatoes; 12 | 13 | potatoes = ['fried', 'baked', 'sliced']; 14 | 15 | return potatoes.join(mathFunction()); 16 | }); 17 | 18 | someNumber = mathFunction(); 19 | 20 | return date + ' ' + someNumber; 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /test/fixtures/complex file with include filter/subsubmodule.js: -------------------------------------------------------------------------------- 1 | // subsubmodule.js NOT_MINIFIED 2 | 3 | var getTheDate = function (someFunction) { 4 | var someValue = someFunction(); 5 | 6 | return'Wed Dec 31 1969 22:30:23 GMT-0800 (PST) ' + someValue; 7 | }; 8 | 9 | module.exports = { 10 | getTheDate: getTheDate 11 | }; 12 | -------------------------------------------------------------------------------- /test/fixtures/complex file/entry.js: -------------------------------------------------------------------------------- 1 | var submodule = require('./submodule') 2 | , jsonthing = require('./jsonthing.json') 3 | , myString 4 | , actual 5 | , expected; 6 | 7 | myString = submodule.createString(function () { 8 | var mathy = 1 + 1 + 2 + 3 + 5 + 8; 9 | 10 | mathy *= 1337; 11 | 12 | return 'potato #' + mathy + jsonthing.turkey; 13 | }); 14 | 15 | actual = document.createElement('pre'); 16 | expected = document.createElement('pre'); 17 | 18 | actual.innerHTML = 'Actual: ' + myString; 19 | expected.innerHTML = 'Expected: Wed Dec 31 1969 22:30:23 GMT-0800 (PST) friedpotato #26740salmonbakedpotato #26740salmonsliced potato #26740salmon'; 20 | 21 | document.body.appendChild(actual); 22 | document.body.appendChild(expected) 23 | -------------------------------------------------------------------------------- /test/fixtures/complex file/jsonthing.json: -------------------------------------------------------------------------------- 1 | { 2 | "turkey": "salmon" 3 | } -------------------------------------------------------------------------------- /test/fixtures/complex file/submodule.js: -------------------------------------------------------------------------------- 1 | var subsubmodule = require('./subsubmodule'); 2 | 3 | module.exports = { 4 | createString: function (mathFunction) { 5 | var date 6 | , someNumber; 7 | 8 | date = subsubmodule.getTheDate(function () { 9 | var potatoes; 10 | 11 | potatoes = ['fried', 'baked', 'sliced']; 12 | 13 | return potatoes.join(mathFunction()); 14 | }); 15 | 16 | someNumber = mathFunction(); 17 | 18 | return date + ' ' + someNumber; 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /test/fixtures/complex file/subsubmodule.js: -------------------------------------------------------------------------------- 1 | var getTheDate = function (someFunction) { 2 | var someValue = someFunction(); 3 | 4 | return'Wed Dec 31 1969 22:30:23 GMT-0800 (PST) ' + someValue; 5 | }; 6 | 7 | module.exports = { 8 | getTheDate: getTheDate 9 | }; 10 | -------------------------------------------------------------------------------- /test/fixtures/custom base path/dirA/submodule.js: -------------------------------------------------------------------------------- 1 | // submodule.js NOT_MINIFIED 2 | 3 | var subsubmodule = require('../dirB/subsubmodule'); 4 | 5 | module.exports = { 6 | createString: function (mathFunction) { 7 | var date 8 | , someNumber; 9 | 10 | date = subsubmodule.getTheDate(function () { 11 | var potatoes; 12 | 13 | potatoes = ['fried', 'baked', 'sliced']; 14 | 15 | return potatoes.join(mathFunction()); 16 | }); 17 | 18 | someNumber = mathFunction(); 19 | 20 | return date + ' ' + someNumber; 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /test/fixtures/custom base path/dirB/subsubmodule.js: -------------------------------------------------------------------------------- 1 | // subsubmodule.js NOT_MINIFIED 2 | 3 | var getTheDate = function (someFunction) { 4 | var someValue = someFunction(); 5 | 6 | return'Wed Dec 31 1969 22:30:23 GMT-0800 (PST) ' + someValue; 7 | }; 8 | 9 | module.exports = { 10 | getTheDate: getTheDate 11 | }; 12 | -------------------------------------------------------------------------------- /test/fixtures/custom base path/entry.js: -------------------------------------------------------------------------------- 1 | // entry.js NOT_MINIFIED 2 | 3 | var submodule = require('./dirA/submodule') 4 | , jsonthing = require('./jsonthing.json') 5 | , myString 6 | , actual 7 | , expected; 8 | 9 | myString = submodule.createString(function () { 10 | var mathy = 1 + 1 + 2 + 3 + 5 + 8; 11 | 12 | mathy *= 1337; 13 | 14 | return 'potato #' + mathy + jsonthing.turkey; 15 | }); 16 | 17 | actual = document.createElement('pre'); 18 | expected = document.createElement('pre'); 19 | 20 | actual.innerHTML = 'Actual: ' + myString; 21 | expected.innerHTML = 'Expected: Wed Dec 31 1969 22:30:23 GMT-0800 (PST) friedpotato #26740salmonbakedpotato #26740salmonsliced potato #26740salmon'; 22 | 23 | document.body.appendChild(actual); 24 | document.body.appendChild(expected) 25 | -------------------------------------------------------------------------------- /test/fixtures/custom base path/jsonthing.json: -------------------------------------------------------------------------------- 1 | { 2 | "unminified": "jsonthing.json", 3 | "turkey": "salmon" 4 | } -------------------------------------------------------------------------------- /test/fixtures/default base path/dirA/submodule.js: -------------------------------------------------------------------------------- 1 | // submodule.js NOT_MINIFIED 2 | 3 | var subsubmodule = require('../dirB/subsubmodule'); 4 | 5 | module.exports = { 6 | createString: function (mathFunction) { 7 | var date 8 | , someNumber; 9 | 10 | date = subsubmodule.getTheDate(function () { 11 | var potatoes; 12 | 13 | potatoes = ['fried', 'baked', 'sliced']; 14 | 15 | return potatoes.join(mathFunction()); 16 | }); 17 | 18 | someNumber = mathFunction(); 19 | 20 | return date + ' ' + someNumber; 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /test/fixtures/default base path/dirB/subsubmodule.js: -------------------------------------------------------------------------------- 1 | // subsubmodule.js NOT_MINIFIED 2 | 3 | var getTheDate = function (someFunction) { 4 | var someValue = someFunction(); 5 | 6 | return'Wed Dec 31 1969 22:30:23 GMT-0800 (PST) ' + someValue; 7 | }; 8 | 9 | module.exports = { 10 | getTheDate: getTheDate 11 | }; 12 | -------------------------------------------------------------------------------- /test/fixtures/default base path/entry.js: -------------------------------------------------------------------------------- 1 | // entry.js NOT_MINIFIED 2 | 3 | var submodule = require('./dirA/submodule') 4 | , jsonthing = require('./jsonthing.json') 5 | , myString 6 | , actual 7 | , expected; 8 | 9 | myString = submodule.createString(function () { 10 | var mathy = 1 + 1 + 2 + 3 + 5 + 8; 11 | 12 | mathy *= 1337; 13 | 14 | return 'potato #' + mathy + jsonthing.turkey; 15 | }); 16 | 17 | actual = document.createElement('pre'); 18 | expected = document.createElement('pre'); 19 | 20 | actual.innerHTML = 'Actual: ' + myString; 21 | expected.innerHTML = 'Expected: Wed Dec 31 1969 22:30:23 GMT-0800 (PST) friedpotato #26740salmonbakedpotato #26740salmonsliced potato #26740salmon'; 22 | 23 | document.body.appendChild(actual); 24 | document.body.appendChild(expected) 25 | -------------------------------------------------------------------------------- /test/fixtures/default base path/jsonthing.json: -------------------------------------------------------------------------------- 1 | { 2 | "unminified": "jsonthing.json", 3 | "turkey": "salmon" 4 | } -------------------------------------------------------------------------------- /test/fixtures/index.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | , FIXTURES_DIR = path.join(__dirname) 3 | , BUILD_DIR = path.join(__dirname, '..', 'build') 4 | , SCAFFOLD_DIR = path.join(__dirname, '..', 'build', 'scaffold') 5 | , entryScript = function (name) { 6 | var ext; 7 | 8 | if(name.indexOf('coffee') >= 0) 9 | ext = 'coffee'; 10 | else 11 | ext = 'js'; 12 | 13 | return path.join(FIXTURES_DIR, name, 'entry.' + ext); 14 | } 15 | , bundledDir = function (name) { 16 | return path.join(BUILD_DIR, 'apps', name); 17 | } 18 | , bundledFile = function (name) { 19 | return path.join(BUILD_DIR, 'apps', name, 'bundle.min.js'); 20 | } 21 | , bundledMap = function (name) { 22 | return path.join(BUILD_DIR, name, 'bundle.map.json'); 23 | } 24 | , simplifyPath = function (filePath) { 25 | return path.relative(FIXTURES_DIR, filePath); 26 | }; 27 | 28 | module.exports = { 29 | entryScript: entryScript 30 | , simplifyPath: simplifyPath 31 | , bundledFile: bundledFile 32 | , bundledDir: bundledDir 33 | , bundledMap: bundledMap 34 | , dir: FIXTURES_DIR 35 | , buildDir: BUILD_DIR 36 | , scaffoldDir:SCAFFOLD_DIR 37 | }; 38 | -------------------------------------------------------------------------------- /test/fixtures/libraries/Backbone.min.js: -------------------------------------------------------------------------------- 1 | (function(){var t=this;var e=t.Backbone;var i=[];var r=i.push;var s=i.slice;var n=i.splice;var a;if(typeof exports!=="undefined"){a=exports}else{a=t.Backbone={}}a.VERSION="1.0.0";var h=t._;if(!h&&typeof require!=="undefined")h=require("underscore");a.$=t.jQuery||t.Zepto||t.ender||t.$;a.noConflict=function(){t.Backbone=e;return this};a.emulateHTTP=false;a.emulateJSON=false;var o=a.Events={on:function(t,e,i){if(!l(this,"on",t,[e,i])||!e)return this;this._events||(this._events={});var r=this._events[t]||(this._events[t]=[]);r.push({callback:e,context:i,ctx:i||this});return this},once:function(t,e,i){if(!l(this,"once",t,[e,i])||!e)return this;var r=this;var s=h.once(function(){r.off(t,s);e.apply(this,arguments)});s._callback=e;return this.on(t,s,i)},off:function(t,e,i){var r,s,n,a,o,u,c,f;if(!this._events||!l(this,"off",t,[e,i]))return this;if(!t&&!e&&!i){this._events={};return this}a=t?[t]:h.keys(this._events);for(o=0,u=a.length;o").attr(t);this.setElement(e,false)}else{this.setElement(h.result(this,"el"),false)}}});a.sync=function(t,e,i){var r=k[t];h.defaults(i||(i={}),{emulateHTTP:a.emulateHTTP,emulateJSON:a.emulateJSON});var s={type:r,dataType:"json"};if(!i.url){s.url=h.result(e,"url")||U()}if(i.data==null&&e&&(t==="create"||t==="update"||t==="patch")){s.contentType="application/json";s.data=JSON.stringify(i.attrs||e.toJSON(i))}if(i.emulateJSON){s.contentType="application/x-www-form-urlencoded";s.data=s.data?{model:s.data}:{}}if(i.emulateHTTP&&(r==="PUT"||r==="DELETE"||r==="PATCH")){s.type="POST";if(i.emulateJSON)s.data._method=r;var n=i.beforeSend;i.beforeSend=function(t){t.setRequestHeader("X-HTTP-Method-Override",r);if(n)return n.apply(this,arguments)}}if(s.type!=="GET"&&!i.emulateJSON){s.processData=false}if(s.type==="PATCH"&&window.ActiveXObject&&!(window.external&&window.external.msActiveXFilteringEnabled)){s.xhr=function(){return new ActiveXObject("Microsoft.XMLHTTP")}}var o=i.xhr=a.ajax(h.extend(s,i));e.trigger("request",e,o,i);return o};var k={create:"POST",update:"PUT",patch:"PATCH","delete":"DELETE",read:"GET"};a.ajax=function(){return a.$.ajax.apply(a.$,arguments)};var S=a.Router=function(t){t||(t={});if(t.routes)this.routes=t.routes;this._bindRoutes();this.initialize.apply(this,arguments)};var $=/\((.*?)\)/g;var T=/(\(\?)?:\w+/g;var H=/\*\w+/g;var A=/[\-{}\[\]+?.,\\\^$|#\s]/g;h.extend(S.prototype,o,{initialize:function(){},route:function(t,e,i){if(!h.isRegExp(t))t=this._routeToRegExp(t);if(h.isFunction(e)){i=e;e=""}if(!i)i=this[e];var r=this;a.history.route(t,function(s){var n=r._extractParameters(t,s);i&&i.apply(r,n);r.trigger.apply(r,["route:"+e].concat(n));r.trigger("route",e,n);a.history.trigger("route",r,e,n)});return this},navigate:function(t,e){a.history.navigate(t,e);return this},_bindRoutes:function(){if(!this.routes)return;this.routes=h.result(this,"routes");var t,e=h.keys(this.routes);while((t=e.pop())!=null){this.route(t,this.routes[t])}},_routeToRegExp:function(t){t=t.replace(A,"\\$&").replace($,"(?:$1)?").replace(T,function(t,e){return e?t:"([^/]+)"}).replace(H,"(.*?)");return new RegExp("^"+t+"$")},_extractParameters:function(t,e){var i=t.exec(e).slice(1);return h.map(i,function(t){return t?decodeURIComponent(t):null})}});var I=a.History=function(){this.handlers=[];h.bindAll(this,"checkUrl");if(typeof window!=="undefined"){this.location=window.location;this.history=window.history}};var N=/^[#\/]|\s+$/g;var P=/^\/+|\/+$/g;var O=/msie [\w.]+/;var C=/\/$/;I.started=false;h.extend(I.prototype,o,{interval:50,getHash:function(t){var e=(t||this).location.href.match(/#(.*)$/);return e?e[1]:""},getFragment:function(t,e){if(t==null){if(this._hasPushState||!this._wantsHashChange||e){t=this.location.pathname;var i=this.root.replace(C,"");if(!t.indexOf(i))t=t.substr(i.length)}else{t=this.getHash()}}return t.replace(N,"")},start:function(t){if(I.started)throw new Error("Backbone.history has already been started");I.started=true;this.options=h.extend({},{root:"/"},this.options,t);this.root=this.options.root;this._wantsHashChange=this.options.hashChange!==false;this._wantsPushState=!!this.options.pushState;this._hasPushState=!!(this.options.pushState&&this.history&&this.history.pushState);var e=this.getFragment();var i=document.documentMode;var r=O.exec(navigator.userAgent.toLowerCase())&&(!i||i<=7);this.root=("/"+this.root+"/").replace(P,"/");if(r&&this._wantsHashChange){this.iframe=a.$('