Comparing version 0.1.1 to 0.2.0
303
lib/index.js
var Minifier | ||
, _ = require('lodash') | ||
, concat = require('concat-stream') | ||
, through = require('through') | ||
, uglify = require('uglify-js') | ||
, atob = require('atob') | ||
, path = require('path') | ||
, SM = require('source-map') | ||
, SMConsumer = SM.SourceMapConsumer | ||
, SMGenerator = SM.SourceMapGenerator; | ||
, optimize = require('./optimize'); | ||
Minifier = function (opts) { | ||
/* | ||
* Handle options/defaults | ||
*/ | ||
opts = opts || {}; | ||
Minifier = function (cb, opts) { | ||
return concat(function(data) { | ||
var optimized = optimize(data, opts); | ||
var self = this | ||
, defaults = { | ||
minify: true | ||
, source: 'bundle.js' | ||
, map: 'bundle.map' | ||
, transformPaths: function (filePath) { | ||
// noop | ||
return filePath; | ||
} | ||
}; | ||
_.defaults(opts, defaults); | ||
/* | ||
* Instance variables | ||
*/ | ||
self.registry = {}; // Keep source maps and code by file | ||
/* | ||
* Registers maps and code by file | ||
*/ | ||
self.registerMap = function (file, code, map) { | ||
self.registry[file] = {code:code, map:map}; | ||
}; | ||
/* | ||
* Gets map by file | ||
*/ | ||
self.mapForFile = function (file) { | ||
if(!self.fileExists(file)) { | ||
throw new Error('ENOFILE'); | ||
} | ||
return self.registry[file].map; | ||
}; | ||
/* | ||
* Gets code by file | ||
*/ | ||
self.codeForFile = function (file) { | ||
if(!self.fileExists(file)) { | ||
throw new Error('ENOFILE'); | ||
} | ||
return self.registry[file].code; | ||
}; | ||
self.fileExists = function (file) { | ||
return (self.registry[file] != null); | ||
} | ||
/* | ||
* Compresses code before Browserify touches it | ||
* Does nothing if minify is false | ||
*/ | ||
self.transformer = function (file) { | ||
var buffs = []; | ||
return through(write, end); | ||
function write(data) { | ||
if(opts.minify) { | ||
buffs.push(data); | ||
} | ||
else { | ||
this.queue(data); | ||
} | ||
} | ||
function end(data) { | ||
var unminCode = buffs.join(); | ||
if(opts.minify) { | ||
var min = uglify.minify(unminCode, { | ||
fromString: true | ||
, outSourceMap: opts.map | ||
}); | ||
this.queue(min.code); | ||
self.registerMap(file, unminCode, new SMConsumer(min.map)); | ||
} | ||
this.queue(null); | ||
} | ||
}; | ||
/* | ||
* Consumes the output stream from Browserify | ||
*/ | ||
self.consumer = function (cb) { | ||
return concat(function(data) { | ||
if(!opts.minify) { | ||
return cb(data, null); | ||
} | ||
else { | ||
var bundle; | ||
try { | ||
bundle = self.decoupleBundle(data); | ||
} | ||
catch(e) { | ||
if(e.toString() == 'Error: ENOURL') { | ||
throw new Error('Cannot consume when browserify is not in debug mode'); | ||
} | ||
else { | ||
throw e; | ||
} | ||
} | ||
// Re-maps the browserify sourcemap | ||
// to the original source using the | ||
// uglify sourcemap | ||
bundle.map = self.transformMap(bundle.map); | ||
cb(bundle.code, bundle.map); | ||
} | ||
}); | ||
}; | ||
/* | ||
* Given a SourceMapConsumer from a bundle's map, | ||
* transform it so that it maps to the unminified | ||
* source | ||
*/ | ||
self.transformMap = function (bundleMap) { | ||
var generator = new SMGenerator({ | ||
file: 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; | ||
} | ||
} | ||
/* | ||
* Helper function that gets the line | ||
*/ | ||
, lineForSource = function (source) { | ||
var target = bundleToMinMap[source]; | ||
if(!target) { | ||
throw new Error('ENOFILE'); | ||
} | ||
return target; | ||
} | ||
, missingSources = {}; | ||
// Figure out where my minified files went in the bundle | ||
bundleMap.eachMapping(function (mapping) { | ||
// Is this a known source? | ||
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: opts.transformPaths(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 + ' "' + opts.transformPaths(file) + '"'); | ||
}); | ||
} | ||
// Add sourceContent for missing sources | ||
_.each(missingSources, function (v, source) { | ||
generator.setSourceContent(opts.transformPaths(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) { | ||
var offset = lineForSource(file) - 1 | ||
, fileMap = self.mapForFile(file) | ||
, transformedFileName = opts.transformPaths(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 | ||
*/ | ||
self.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 | ||
*/ | ||
self.eachSource = function (cb) { | ||
_.each(self.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 | ||
*/ | ||
self.decoupleBundle = function (src) { | ||
var marker = '//@ sourceMappingURL=data:application/json;base64,' | ||
, offset = src.indexOf(marker) | ||
, map; | ||
if(offset<0) { | ||
throw new Error('ENOURL'); | ||
} | ||
map = atob(src.substring(offset + marker.length)); | ||
try { | ||
map = new SMConsumer( JSON.parse(map) ); | ||
} | ||
catch(e) { | ||
throw e; | ||
} | ||
return { | ||
code: src.substring(0, offset) + ';;;\n//@ sourceMappingURL=' + opts.map + '\n' | ||
, map: map | ||
}; | ||
}; | ||
return this; | ||
cb(optimized.code, optimized.map.toString()); | ||
}); | ||
}; | ||
module.exports = Minifier; |
@@ -14,3 +14,3 @@ { | ||
], | ||
"version": "0.1.1", | ||
"version": "0.2.0", | ||
"repository": { | ||
@@ -17,0 +17,0 @@ "type": "git", |
@@ -5,10 +5,6 @@ Minifyify | ||
Before, you had to choose between sane debugging and sane load times. Now, you can have both. | ||
Before, browserify made you choose between sane debugging and sane load times. Now, you can have both. | ||
Browserify in debug mode tacks on a massive sourceMappingURL with your uncompressed source code, on top of the already uncompressed bundle, resulting in a single massive Javascript file **more than twice as large as your original source code.** | ||
Minifyify minifies your bundle and pulls the source map out into a separate file. Now you can **deploy a minified bundle in production, and still have a sourcemap handy for when things inevitably break**. | ||
**Bonus:** Since Minifyify is a transform, dead code paths are removed before Browserify processes `require()`s. You only get the modules you actually use in the final bundle. Works great with [envify](https://npmjs.org/package/envify)! | ||
## Usage | ||
@@ -19,27 +15,11 @@ | ||
, minifyify = require('minifyify') | ||
, bundle = new browserify() | ||
, minifier; | ||
, bundle = new browserify(); | ||
// Create a new minifier object for each bundle | ||
// (All options are optional, but highly recommended) | ||
minifier = new minifyify({ | ||
source: 'bundle.js' // Where you intend to place the generated bundle | ||
, map: 'bundle.map' // Where you intend to place the generated sourcemap | ||
, transformPaths: // Great for shortening your source paths | ||
function (filePath) { | ||
// This will make all paths relative to 'project_dir' | ||
return path.relative('project_dir', filePath); | ||
} | ||
}); | ||
bundle.add('entryScript.js'); | ||
// Note: Pass browserify the transformer, not the minifier object | ||
bundle.transform(minifier.transformer); | ||
// You *must* run in debug mode! | ||
bundle.bundle({debug: true}) | ||
// Pipe to the consumer to receive your minified code and accompanying sourcemap | ||
.pipe(minifier.consumer(function(code, map) { | ||
// Pipe output to minifyify | ||
.pipe(minifyify(function(code, map) { | ||
fs.writeFileSync('www/bundle.js', code); | ||
@@ -46,0 +26,0 @@ fs.writeFileSync('www/bundle.map', map); |
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
22
550
1
17463
30
1