exorcist
Version:
Externalizes the source map found inside a stream to an external `.js.map` file
297 lines (240 loc) • 11 kB
JavaScript
;
/*jshint asi: true */
var test = require('tap').test
var fs = require('fs');
var through = require('through2');
var proxyquire = require('proxyquire');
var exorcist = require('../')
var fixtures = __dirname + '/fixtures';
var scriptMapfile = fixtures + '/bundle.js.map';
var styleMapfile = fixtures + '/to.css.map';
// This base path is baken into the source maps in the fixtures.
var base = '/Users/thlorenz/dev/projects/exorcist';
function cleanup() {
if (fs.existsSync(scriptMapfile)) fs.unlinkSync(scriptMapfile);
if (fs.existsSync(styleMapfile)) fs.unlinkSync(styleMapfile);
}
test('\nwhen piping a bundle generated with browserify through exorcist without adjusting properties', function (t) {
t.on('end', cleanup);
var data = ''
fs.createReadStream(fixtures + '/bundle.js')
.pipe(exorcist(scriptMapfile))
.pipe(through(onread, onflush));
function onread(d, _, cb) { data += d; cb(); }
function onflush(cb) {
var lines = data.split('\n')
lines.pop(); // Trailing newline
t.equal(lines.length, 25, 'pipes entire bundle including prelude, sources and source map url')
t.equal(lines.pop(), '//# sourceMappingURL=bundle.js.map', 'last line as source map url pointing to .js.map file')
var map = JSON.parse(fs.readFileSync(scriptMapfile, 'utf8'));
t.equal(map.file, 'generated.js', 'leaves file name unchanged')
t.equal(map.sources.length, 4, 'maps 4 source files')
t.equal(map.sources[0].indexOf(base), 0, 'uses absolute source paths')
t.equal(map.sourcesContent.length, 4, 'includes 4 source contents')
t.equal(map.mappings.length, 106, 'maintains mappings')
t.equal(map.sourceRoot, '', 'if source root missing, use empty string')
cb();
t.end()
}
})
test('\nwhen piping a bundle generated with browserify through exorcist and adjusting url', function (t) {
t.on('end', cleanup);
var data = ''
fs.createReadStream(fixtures + '/bundle.js')
.pipe(exorcist(scriptMapfile, 'http://my.awseome.site/bundle.js.map'))
.pipe(through(onread, onflush));
function onread(d, _, cb) { data += d; cb(); }
function onflush(cb) {
var lines = data.split('\n')
lines.pop(); // Trailing newline
t.equal(lines.length, 25, 'pipes entire bundle including prelude, sources and source map url')
t.equal(lines.pop(), '//# sourceMappingURL=http://my.awseome.site/bundle.js.map', 'last line as source map url pointing to .js.map file at url set to supplied url')
var map = JSON.parse(fs.readFileSync(scriptMapfile, 'utf8'));
t.equal(map.file, 'generated.js', 'leaves file name unchanged')
t.equal(map.sources.length, 4, 'maps 4 source files')
t.equal(map.sourcesContent.length, 4, 'includes 4 source contents')
t.equal(map.mappings.length, 106, 'maintains mappings')
t.equal(map.sourceRoot, '', 'if source root missing, use empty string')
cb();
t.end()
}
})
test('\nwhen piping a bundle generated with browserify through exorcist and adjusting root and url', function (t) {
t.on('end', cleanup);
var data = ''
fs.createReadStream(fixtures + '/bundle.js')
.pipe(exorcist(scriptMapfile, 'http://my.awseome.site/bundle.js.map', 'http://my.awesome.site/src'))
.pipe(through(onread, onflush));
function onread(d, _, cb) { data += d; cb(); }
function onflush(cb) {
var lines = data.split('\n')
lines.pop(); // Trailing newline
t.equal(lines.length, 25, 'pipes entire bundle including prelude, sources and source map url')
t.equal(lines.pop(), '//# sourceMappingURL=http://my.awseome.site/bundle.js.map', 'last line as source map url pointing to .js.map file at url set to supplied url')
var map = JSON.parse(fs.readFileSync(scriptMapfile, 'utf8'));
t.equal(map.file, 'generated.js', 'leaves file name unchanged')
t.equal(map.sources.length, 4, 'maps 4 source files')
t.equal(map.sourcesContent.length, 4, 'includes 4 source contents')
t.equal(map.mappings.length, 106, 'maintains mappings')
t.equal(map.sourceRoot, 'http://my.awesome.site/src', 'adapts source root')
cb();
t.end()
}
})
test('\nwhen piping a bundle generated with browserify through exorcist and adjusting root, url, and base', function (t) {
t.on('end', cleanup);
var data = ''
fs.createReadStream(fixtures + '/bundle.js')
.pipe(exorcist(scriptMapfile, 'http://my.awseome.site/bundle.js.map', 'http://my.awesome.site/src', base))
.pipe(through(onread, onflush));
function onread(d, _, cb) { data += d; cb(); }
function onflush(cb) {
var lines = data.split('\n')
lines.pop(); // Trailing newline
t.equal(lines.length, 25, 'pipes entire bundle including prelude, sources and source map url')
t.equal(lines.pop(), '//# sourceMappingURL=http://my.awseome.site/bundle.js.map', 'last line as source map url pointing to .js.map file at url set to supplied url')
var map = JSON.parse(fs.readFileSync(scriptMapfile, 'utf8'));
t.equal(map.file, 'generated.js', 'leaves file name unchanged')
t.equal(map.sources.length, 4, 'maps 4 source files')
t.equal(map.sources[0].indexOf(base), -1, 'uses relative source paths')
t.equal(map.sourcesContent.length, 4, 'includes 4 source contents')
t.equal(map.mappings.length, 106, 'maintains mappings')
t.equal(map.sourceRoot, 'http://my.awesome.site/src', 'adapts source root')
cb();
t.end()
}
})
test('\nwhen piping a bundle generated with browserify to a map file in a directory that does not exist', function (t) {
t.on('end', cleanup);
var badPathScriptMapfile = fixtures + '/noexists/bundle.js.map';
fs.createReadStream(fixtures + '/bundle.js')
.pipe(exorcist(badPathScriptMapfile))
.on('error', t.end)
.on('end', function () {
var map = JSON.parse(fs.readFileSync(badPathScriptMapfile, 'utf8'));
t.ok(map);
fs.unlinkSync(badPathScriptMapfile);
t.end();
})
})
test('\nwhen piping a bundle generated with browserify and the write fails', function (t) {
t.on('end', cleanup);
var expectedErr = new Error('File write failed')
var ex = proxyquire('../', {
fs: {
writeFile: function (file, content, enc, callback) {
callback(expectedErr)
}
}
})
fs.createReadStream(fixtures + '/bundle.js')
.pipe(ex(scriptMapfile))
.on('error', function (err) {
t.equal(err, expectedErr)
t.end();
})
})
test('\nwhen piping a bundle generated with browserify thats missing a map through exorcist and errorOnMissing is truthy' , function (t) {
t.on('end', cleanup);
var data = ''
fs.createReadStream(fixtures + '/bundle.nomap.js')
.pipe(exorcist(scriptMapfile, undefined, undefined, undefined, true))
.on('error', onerror);
function onerror(err) {
t.type(err, 'Error');
t.end();
}
})
test('\nwhen piping a bundle generated with browserify thats missing a map through exorcist and errorOnMissing is falsey' , function (t) {
t.on('end', cleanup);
var data = ''
var missingMapEmitted = false;
fs.createReadStream(fixtures + '/bundle.nomap.js')
.pipe(exorcist(scriptMapfile))
.on('missing-map', function () { missingMapEmitted = true })
.pipe(through(onread, onflush));
function onread(d, _, cb) { data += d; cb(); }
function onflush(cb) {
var lines = data.split('\n')
t.equal(lines.length, 25, 'pipes entire bundle including prelude')
t.ok(missingMapEmitted, 'emits missing-map event')
cb();
t.end()
}
})
test('\nwhen performing a stylish exorcism', function (t) {
t.on('end', cleanup);
var data = ''
fs.createReadStream(fixtures + '/to.css')
.pipe(exorcist(styleMapfile))
.pipe(through(onread, onflush));
function onread(d, _, cb) { data += d; cb(); }
function onflush(cb) {
var lines = data.split('\n')
t.equal(lines.length, 22, 'pipes entire style including prelude, sources and source map url')
t.equal(lines.pop(), '/*# sourceMappingURL=to.css.map */', 'last line as source map url pointing to .css.map file')
var map = JSON.parse(fs.readFileSync(styleMapfile, 'utf8'));
t.equal(map.file, 'to.css', 'leaves file name unchanged')
t.equal(map.sources.length, 2, 'maps 4 source files')
t.equal(map.sourcesContent.length, 2, 'includes 4 source contents')
t.equal(map.mappings.length, 214, 'maintains mappings')
t.equal(map.sourceRoot, '', 'if source root missing, use empty string')
cb();
t.end();
}
})
test('\nwhen piping a bundle generated with browserify with preexisting source root', function(t) {
t.on('end', cleanup);
fs.createReadStream(fixtures + '/bundle.withroot.js')
.pipe(exorcist(scriptMapfile))
.pipe(through(onread, onflush));
function onread(_, __, cb) { cb(); }
function onflush(cb) {
var map = JSON.parse(fs.readFileSync(scriptMapfile, 'utf8'));
t.equal(map.sourceRoot, base, 'leaves source root value in place')
cb();
t.end();
}
});
test('\nwhen piping a bundle generated with browserify through exorcist without adjusting properties and sending source map to stream', function (t) {
t.on('end', cleanup);
var data = ''
var map = ''
fs.createReadStream(fixtures + '/bundle.js')
.pipe(exorcist(through(onreadMap, onflushMap), 'bundle.js.map'))
.pipe(through(onread, onflush));
function onread(d, _, cb) { data += d; cb(); }
function onflush(cb) {
var lines = data.split('\n')
lines.pop(); // Trailing newline
t.equal(lines.length, 25, 'pipes entire bundle including prelude, sources and source map url')
t.equal(lines.pop(), '//# sourceMappingURL=bundle.js.map', 'last line as source map url pointing to .js.map file')
cb();
t.end()
}
function onreadMap(d, _, cb) { map += d; cb(); }
function onflushMap(cb) {
map = JSON.parse(map);
t.equal(map.file, 'generated.js', 'leaves file name unchanged')
t.equal(map.sources.length, 4, 'maps 4 source files')
t.equal(map.sources[0].indexOf(base), 0, 'uses absolute source paths')
t.equal(map.sourcesContent.length, 4, 'includes 4 source contents')
t.equal(map.mappings.length, 106, 'maintains mappings')
t.equal(map.sourceRoot, '', 'leaves source root an empty string')
cb();
}
})
test('\nwhen piping a browserify bundle thru exorcist sending source map to a stream with missing required url', function (t) {
t.on('end', cleanup);
var exorcistStream =
exorcist(through(onread)) // this is missing the URL as second argument
.on('error', function (err) {
t.ok(/map file URL is required/.test(err.message));
t.end();
})
.on('end', function () {
t.fail('should have emitted an error about missing url');
});
fs.createReadStream(fixtures + '/bundle.js').pipe(exorcistStream);
function onread(d, _, cb) { cb(); }
})