blob: c697437e0c940b99f785ebdb8ea94a9fe708f8d2 [file] [log] [blame]
var undeclaredIdentifiers = require('undeclared-identifiers');
var through = require('through2');
var merge = require('xtend');
var parse = require('acorn-node').parse;
var path = require('path');
var isAbsolute = path.isAbsolute || require('path-is-absolute');
var processPath = require.resolve('process/browser.js');
var isbufferPath = require.resolve('is-buffer')
var combineSourceMap = require('combine-source-map');
function getRelativeRequirePath(fullPath, fromPath) {
var relpath = path.relative(path.dirname(fromPath), fullPath);
// If fullPath is in the same directory or a subdirectory of fromPath,
// relpath will result in something like "index.js", "src/abc.js".
// require() needs "./" prepended to these paths.
if (!/^\./.test(relpath) && !isAbsolute(relpath)) {
relpath = "./" + relpath;
}
// On Windows: Convert path separators to what require() expects
if (path.sep === '\\') {
relpath = relpath.replace(/\\/g, '/');
}
return relpath;
}
var defaultVars = {
process: function (file) {
var relpath = getRelativeRequirePath(processPath, file);
return 'require(' + JSON.stringify(relpath) + ')';
},
global: function () {
return 'typeof global !== "undefined" ? global : '
+ 'typeof self !== "undefined" ? self : '
+ 'typeof window !== "undefined" ? window : {}'
;
},
'Buffer.isBuffer': function (file) {
var relpath = getRelativeRequirePath(isbufferPath, file);
return 'require(' + JSON.stringify(relpath) + ')';
},
Buffer: function () {
return 'require("buffer").Buffer';
},
setImmediate: function () {
return 'require("timers").setImmediate';
},
clearImmediate: function () {
return 'require("timers").clearImmediate';
},
__filename: function (file, basedir) {
var relpath = path.relative(basedir, file);
// standardize path separators, use slash in Windows too
if ( path.sep === '\\' ) {
relpath = relpath.replace(/\\/g, '/');
}
var filename = '/' + relpath;
return JSON.stringify(filename);
},
__dirname: function (file, basedir) {
var relpath = path.relative(basedir, file);
// standardize path separators, use slash in Windows too
if ( path.sep === '\\' ) {
relpath = relpath.replace(/\\/g, '/');
}
var dir = path.dirname('/' + relpath );
return JSON.stringify(dir);
}
};
module.exports = function (file, opts) {
if (/\.json$/i.test(file)) return through();
if (!opts) opts = {};
var basedir = opts.basedir || '/';
var vars = merge(defaultVars, opts.vars);
var varNames = Object.keys(vars).filter(function(name) {
return typeof vars[name] === 'function';
});
var quick = RegExp(varNames.map(function (name) {
return '\\b' + name + '\\b';
}).join('|'));
var chunks = [];
return through(write, end);
function write (chunk, enc, next) { chunks.push(chunk); next() }
function end () {
var self = this;
var source = Buffer.isBuffer(chunks[0])
? Buffer.concat(chunks).toString('utf8')
: chunks.join('')
;
source = source
.replace(/^\ufeff/, '')
.replace(/^#![^\n]*\n/, '\n');
if (opts.always !== true && !quick.test(source)) {
this.push(source);
this.push(null);
return;
}
try {
var undeclared = opts.always
? { identifiers: varNames, properties: [] }
: undeclaredIdentifiers(parse(source), { wildcard: true })
;
}
catch (err) {
var e = new SyntaxError(
(err.message || err) + ' while parsing ' + file
);
e.type = 'syntax';
e.filename = file;
return this.emit('error', e);
}
var globals = {};
varNames.forEach(function (name) {
if (!/\./.test(name)) return;
var parts = name.split('.')
var prop = undeclared.properties.indexOf(name)
if (prop === -1 || countprops(undeclared.properties, parts[0]) > 1) return;
var value = vars[name](file, basedir);
if (!value) return;
globals[parts[0]] = '{'
+ JSON.stringify(parts[1]) + ':' + value + '}';
self.emit('global', name);
});
varNames.forEach(function (name) {
if (/\./.test(name)) return;
if (globals[name]) return;
if (undeclared.identifiers.indexOf(name) < 0) return;
var value = vars[name](file, basedir);
if (!value) return;
globals[name] = value;
self.emit('global', name);
});
this.push(closeOver(globals, source, file, opts));
this.push(null);
}
};
module.exports.vars = defaultVars;
function closeOver (globals, src, file, opts) {
var keys = Object.keys(globals);
if (keys.length === 0) return src;
var values = keys.map(function (key) { return globals[key] });
var wrappedSource;
if (keys.length <= 3) {
wrappedSource = '(function (' + keys.join(',') + '){\n'
+ src + '\n}).call(this,' + values.join(',') + ')'
;
}
else {
// necessary to make arguments[3..6] still work for workerify etc
// a,b,c,arguments[3..6],d,e,f...
var extra = [ '__argument0', '__argument1', '__argument2', '__argument3' ];
var names = keys.slice(0,3).concat(extra).concat(keys.slice(3));
values.splice(3, 0,
'arguments[3]','arguments[4]',
'arguments[5]','arguments[6]'
);
wrappedSource = '(function (' + names.join(',') + '){\n'
+ src + '\n}).call(this,' + values.join(',') + ')';
}
// Generate source maps if wanted. Including the right offset for
// the wrapped source.
if (!opts.debug) {
return wrappedSource;
}
var sourceFile = path.relative(opts.basedir, file)
.replace(/\\/g, '/');
var sourceMap = combineSourceMap.create().addFile(
{ sourceFile: sourceFile, source: src},
{ line: 1 });
return combineSourceMap.removeComments(wrappedSource) + "\n"
+ sourceMap.comment();
}
function countprops (props, name) {
return props.filter(function (prop) {
return prop.slice(0, name.length + 1) === name + '.';
}).length;
}