Comparing version 3.0.0-beta11 to 3.0.0-beta12
406
lib/index.js
@@ -1,372 +0,92 @@ | ||
var Minifier | ||
, _ = { | ||
each: require('lodash.foreach') | ||
, defaults: require('lodash.defaults') | ||
, bind: require('lodash.bind') | ||
} | ||
var plugin | ||
, fs = require('fs') | ||
, tmp = require('tmp') | ||
, concat = require('concat-stream') | ||
, through = require('through') | ||
, uglify = require('uglify-js') | ||
, SM = require('source-map') | ||
, convertSM = require('convert-source-map') | ||
, SMConsumer = SM.SourceMapConsumer | ||
, SMGenerator = SM.SourceMapGenerator; | ||
, ReadableStream = require('stream').Readable; | ||
Minifier = function (opts) { | ||
/* | ||
* Handle options/defaults | ||
*/ | ||
opts = opts || {}; | ||
plugin = function (bundle, minifyifyOpts) { | ||
minifyifyOpts = minifyifyOpts || {}; | ||
var defaults = { | ||
minify: true | ||
, source: 'bundle.js' | ||
, map: 'bundle.map' | ||
, compressPath: function (filePath) { | ||
// noop | ||
return filePath; | ||
} | ||
}; | ||
var minifyify = require('./minifier') | ||
, minifier = new minifyify(minifyifyOpts) | ||
, oldBundle = bundle.bundle | ||
, bundleStarted = false; | ||
this.opts = _.defaults(opts, defaults); | ||
// Hook up the transform so we know what sources were used | ||
bundle.transform({global: true}, minifier.transformer); | ||
if(this.opts.map === false) | ||
this.opts.minify = false; | ||
// Proxy the bundle's bundle function so we can capture its output | ||
bundle.bundle = function (bundleOpts, bundleCb) { | ||
/* | ||
* Instance variables | ||
*/ | ||
this.registry = {}; // Keep source maps and code by file | ||
var bundleStream | ||
, minifiedStream = new ReadableStream(); | ||
/** | ||
* Browserify runs transforms with a different context | ||
* but we always want to refer to ourselves | ||
*/ | ||
this.transformer = _.bind(this.transformer, this); | ||
return this; | ||
}; | ||
/* | ||
* Registers maps and code by file | ||
*/ | ||
Minifier.prototype.registerMap = function (file, code, map) { | ||
this.registry[file] = {code:code, map:map}; | ||
}; | ||
/* | ||
* Gets map by file | ||
*/ | ||
Minifier.prototype.mapForFile = function (file) { | ||
if(!this.fileExists(file)) { | ||
throw new Error('ENOFILE'); | ||
} | ||
return this.registry[file].map; | ||
}; | ||
/* | ||
* Gets code by file | ||
*/ | ||
Minifier.prototype.codeForFile = function (file) { | ||
if(!this.fileExists(file)) { | ||
throw new Error('ENOFILE'); | ||
} | ||
return this.registry[file].code; | ||
}; | ||
Minifier.prototype.fileExists = function (file) { | ||
return (this.registry[file] != null); | ||
} | ||
/* | ||
* Compresses code before Browserify touches it | ||
* Does nothing if minify is false | ||
*/ | ||
Minifier.prototype.transformer = function (file) { | ||
var self = this | ||
, buffs = [] | ||
, write | ||
, end | ||
, throughStream; | ||
write = function (data) { | ||
if(self.opts.minify) { | ||
buffs.push(data); | ||
// Normalize options | ||
if(typeof bundleOpts == 'function') { | ||
bundleCb = bundleOpts; | ||
bundleOpts = {}; | ||
} | ||
else { | ||
this.queue(data); | ||
bundleOpts = bundleOpts || {}; | ||
} | ||
} | ||
end = function () { | ||
var thisStream = this | ||
, unminCode = buffs.join() | ||
, originalCode = false | ||
, existingMap = convertSM.fromSource(unminCode) | ||
, finish; | ||
// Force debug mode | ||
bundleOpts.debug = true; | ||
existingMap = existingMap ? existingMap.toObject() : false; | ||
if(existingMap && existingMap.sourcesContent && existingMap.sourcesContent.length) { | ||
originalCode = convertSM.removeComments(existingMap.sourcesContent[0]); | ||
existingMap = JSON.stringify(existingMap); | ||
/* | ||
* If no callback was given, require that the user | ||
* specified a path to write the sourcemap out to | ||
*/ | ||
if(!bundleStarted && !bundleCb && !minifyifyOpts.output) { | ||
throw new Error('Minifyify: opts.output is required since no callback was given'); | ||
} | ||
// Only accept existing maps with sourcesContent | ||
else { | ||
existingMap = false; | ||
} | ||
finish = function (tempExistingMapFile) { | ||
if(self.opts.minify) { | ||
var min = uglify.minify(unminCode, { | ||
fromString: true | ||
, outSourceMap: self.opts.map | ||
, inSourceMap: tempExistingMapFile | ||
}); | ||
// Call browserify's bundle function and capture the output stream | ||
bundleStream = oldBundle.call(bundle, bundleOpts); | ||
thisStream.queue(min.code); | ||
self.registerMap(file, originalCode || unminCode, new SMConsumer(min.map)); | ||
} | ||
// Otherwise we'll never finish | ||
thisStream.queue(null); | ||
/* | ||
* Browserify has this mechanism that delays bundling until all deps | ||
* are ready, and that means bundle gets called twice. The extra time, | ||
* it should just pass thru the data instead of trying to consume it. | ||
*/ | ||
if(bundleStarted) { | ||
return bundleStream; | ||
} | ||
if(existingMap) { | ||
tmp.file(function (err, path) { | ||
if(err) { throw err; } | ||
fs.writeFile(path, existingMap, function (err) { | ||
if(err) { throw err; } | ||
finish(path); | ||
}); | ||
}); | ||
if(!bundleStarted) { | ||
bundleStarted = true; | ||
} | ||
else { | ||
finish(); | ||
} | ||
} | ||
throughStream = through(write, end); | ||
throughStream.call = function () { | ||
throw new Error('Transformer is a transform. Correct usage: `bundle.transform(minifier.transformer)`.') | ||
} | ||
return throughStream; | ||
}; | ||
/* | ||
* Consumes the output stream from Browserify | ||
*/ | ||
Minifier.prototype.consumer = function (cb) { | ||
var self = this; | ||
return concat(function(data) { | ||
if(!self.opts.minify) { | ||
return cb(null, data, null); | ||
} | ||
else { | ||
var bundle; | ||
try { | ||
bundle = self.decoupleBundle(data); | ||
/* | ||
* Pipe browserify's output into the minifier's consumer | ||
* which has the ability to transform the sourcemap | ||
*/ | ||
bundleStream.pipe(minifier.consumer(function (err, src, map) { | ||
// If there was a callback given, we are done | ||
if(typeof bundleCb == 'function') { | ||
return bundleCb(err, src, map); | ||
} | ||
catch(e) { | ||
if(e.toString() == 'ENOURL') { | ||
return cb(new Error('Browserify must be in debug mode for minifyify to consume sourcemaps')); | ||
} | ||
else { | ||
return cb(e); | ||
} | ||
} | ||
// Re-maps the browserify sourcemap | ||
// to the original source using the | ||
// uglify sourcemap | ||
bundle.map = self.transformMap(bundle.map); | ||
// Otherwise, throw if anything bad happened | ||
if(err) { throw err; } | ||
bundle.code = bundle.code + '\n//# sourceMappingURL=' + self.opts.map | ||
// Write the sourcemap to the specified output location | ||
var writeStream = fs.createWriteStream(minifyifyOpts.output); | ||
writeStream.write(map); | ||
writeStream.end(); | ||
cb(null, bundle.code, bundle.map); | ||
} | ||
}); | ||
}; | ||
/* | ||
* Given a SourceMapConsumer from a bundle's map, | ||
* transform it so that it maps to the unminified | ||
* source | ||
*/ | ||
Minifier.prototype.transformMap = function (bundleMap) { | ||
var self = this | ||
, generator = new SMGenerator({ | ||
file: self.opts.source | ||
}) | ||
// Map File -> The lowest numbered line in the bundle (offset) | ||
, bundleToMinMap = {} | ||
/* | ||
* Helper function that maps minified source to a line in the browserify bundle | ||
*/ | ||
, mapSourceToLine = function (source, line) { | ||
var target = bundleToMinMap[source]; | ||
if(!target || target > line) { | ||
bundleToMinMap[source] = line; | ||
} | ||
// Push the minified src to our proxied stream | ||
minifiedStream._read = function () { | ||
minifiedStream.push(src); | ||
minifiedStream.push(null); | ||
} | ||
minifiedStream.resume(); | ||
minifiedStream.emit('readable'); | ||
})); | ||
, hasNoMappings = function (file) { | ||
return bundleToMinMap[file] == null; | ||
} | ||
minifiedStream.pause(); | ||
/* | ||
* Helper function that gets the line | ||
*/ | ||
, lineForSource = function (source) { | ||
if(hasNoMappings(source)) { | ||
throw new Error('ENOFILE: ' + source); | ||
} | ||
var target = bundleToMinMap[source]; | ||
return target; | ||
} | ||
, missingSources = {}; | ||
// Figure out where my minified files went in the bundle | ||
bundleMap.eachMapping(function (mapping) { | ||
if(self.fileExists(mapping.source)) { | ||
mapSourceToLine(mapping.source, mapping.generatedLine); | ||
} | ||
// Not a known source, pass thru the mapping | ||
else { | ||
generator.addMapping({ | ||
generated: { | ||
line: mapping.generatedLine | ||
, column: mapping.generatedColumn | ||
} | ||
, original: { | ||
line: mapping.originalLine | ||
, column: mapping.originalColumn | ||
} | ||
, source: self.opts.compressPath(mapping.source) | ||
, name: mapping.name | ||
}); | ||
missingSources[mapping.source] = true; | ||
} | ||
}); | ||
if(process.env.debug) { | ||
console.log(' [DEBUG] Here is where Browserify put your modules:'); | ||
_.each(bundleToMinMap, function (line, file) { | ||
console.log(' [DEBUG] line ' + line + ' "' + self.opts.compressPath(file) + '"'); | ||
}); | ||
} | ||
// Add sourceContent for missing sources | ||
_.each(missingSources, function (v, source) { | ||
generator.setSourceContent(self.opts.compressPath(source), bundleMap.sourceContentFor(source)); | ||
}); | ||
// Map from the hi-res sourcemaps to the browserify bundle | ||
if(process.env.debug) { | ||
console.log(' [DEBUG] Here is how I\'m mapping your code:'); | ||
} | ||
self.eachSource(function (file, code) { | ||
// Ignore files with no mappings | ||
if(!self.fileExists(file) || hasNoMappings(file)) { | ||
if(process.env.debug) { | ||
throw new Error('File with no mappings: ' + file) | ||
} | ||
return; | ||
} | ||
var offset = lineForSource(file) - 1 | ||
, fileMap = self.mapForFile(file) | ||
, transformedFileName = self.opts.compressPath(file); | ||
if(process.env.debug) { | ||
console.log(' [DEBUG] Now mapping "' + transformedFileName + '"'); | ||
} | ||
fileMap.eachMapping(function (mapping) { | ||
var transformedMapping = self.transformMapping(transformedFileName, mapping, offset); | ||
if(process.env.debug) { | ||
console.log(' [DEBUG] Generated [' + transformedMapping.generated.line + | ||
':' + transformedMapping.generated.column + '] > [' + | ||
mapping.originalLine + ':' + mapping.originalColumn + '] Original'); | ||
} | ||
generator.addMapping( transformedMapping ); | ||
}); | ||
generator.setSourceContent(transformedFileName, code); | ||
}); | ||
return generator.toString(); | ||
}; | ||
/* | ||
* Given a mapping (from SMConsumer.eachMapping) | ||
* return a new mapping (for SMGenerator.addMapping) | ||
* resolved to the original source | ||
*/ | ||
Minifier.prototype.transformMapping = function (file, mapping, offset) { | ||
return { | ||
generated: { | ||
line: mapping.generatedLine + offset | ||
, column: mapping.generatedColumn | ||
} | ||
, original: { | ||
line: mapping.originalLine | ||
, column: mapping.originalColumn | ||
} | ||
, source: file | ||
, name: mapping.name | ||
} | ||
}; | ||
/* | ||
* Iterates over each code file, executes a function | ||
*/ | ||
Minifier.prototype.eachSource = function (cb) { | ||
var self = this; | ||
_.each(this.registry, function(v, file) { | ||
cb(file, self.codeForFile(file), self.mapForFile(file)); | ||
}); | ||
}; | ||
/* | ||
* Given source with embedded sourcemap, seperate the two | ||
* Returns the code and SourcemapConsumer object seperately | ||
*/ | ||
Minifier.prototype.decoupleBundle = function (src) { | ||
if(typeof src != 'string') | ||
src = src.toString(); | ||
var map = convertSM.fromSource(src); | ||
// The source didn't have a sourcemap in it | ||
if(!map) { | ||
throw new Error('ENOURL'); | ||
} | ||
return { | ||
code: convertSM.removeComments(src) | ||
, map: new SMConsumer( map.toObject() ) | ||
// The bundle function should return our proxied stream | ||
return minifiedStream; | ||
}; | ||
}; | ||
module.exports = Minifier; | ||
module.exports = plugin; |
@@ -21,3 +21,3 @@ { | ||
], | ||
"version": "3.0.0-beta11", | ||
"version": "3.0.0-beta12", | ||
"repository": { | ||
@@ -24,0 +24,0 @@ "type": "git", |
@@ -11,61 +11,23 @@ Minifyify | ||
Minifyify takes your browserify bundle and minfies it. The magic: your code still maps back to the original, separate source files. | ||
Minifyify is a browserify plugin that minifies your code. The magic? The sourcemap points back to the original, separate source files. | ||
Now you can deploy a minified bundle in production, and still have a sourcemap handy for when things inevitably break! | ||
## Simple Usage | ||
## Usage | ||
```js | ||
var browserify = require('browserify') | ||
, minifyify = require('minifyify') | ||
, bundler = new browserify(); | ||
, minifier = new minifyify(); | ||
bundler.add('entry.js'); | ||
bundler.transform({global: true}, minifier.transformer); | ||
bundler | ||
.bundle({debug: true}) | ||
.pipe(minifier.consumer(function (err, src, map) { | ||
// Your code here | ||
})); | ||
bundler.plugin('minifyify', options); | ||
bundler.bundle(function (err, src, map) { | ||
// Your code here | ||
}); | ||
``` | ||
## Full Usage | ||
```js | ||
var path = require('path') | ||
, browserify = require('browserify') | ||
, minifyify = require('minifyify') | ||
, bundler | ||
, minifier | ||
, options = { | ||
compressPath: function (p) { | ||
return path.relative('my-app-root', p); | ||
} | ||
, map: '/bundle.map.json' | ||
}; | ||
bundler = new browserify(); | ||
minifier = new minifyify(options); | ||
bundler.add('entry.js') | ||
// Your other transforms | ||
bundler.transform(require('coffeeify')); | ||
bundler.transform(require('hbsfy')); | ||
// Minifies code while tracking sourcemaps | ||
// {global: true} lets us also minify browser shims | ||
bundler.transform({global: true}, minifier.transformer); | ||
bundler | ||
// Debug must be true for minifyify to work | ||
.bundle({debug: true}) | ||
// Consume pulls the source map out of src and transforms the mappings | ||
.pipe(minifier.consumer(function (err, src, map) { | ||
// src and map are strings | ||
// src has a comment pointing to map | ||
})); | ||
```sh | ||
browserify entry.js -p [minifyify --output bundle.map.json] > bundle.json | ||
``` | ||
@@ -72,0 +34,0 @@ |
@@ -8,3 +8,3 @@ var fixtures = require('./fixtures') | ||
, validate = require('sourcemap-validator') | ||
, Minifyify = require('../lib') | ||
, Minifyify = require('../lib/minifier') | ||
@@ -11,0 +11,0 @@ // Helpers |
@@ -10,3 +10,3 @@ /** | ||
, browserify = require('browserify') | ||
, minifyify = require('../lib') | ||
, minifyify = require('../lib/minifier') | ||
, fixtures = require('./fixtures') | ||
@@ -13,0 +13,0 @@ , tests = {}; |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
659691
31
15876
113
9