198 lines
6.6 KiB
JavaScript
198 lines
6.6 KiB
JavaScript
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] });
|
|
|
|
// we double-wrap the source in IIFEs to prevent code like
|
|
// (function(Buffer){ const Buffer = null }())
|
|
// which causes a parse error.
|
|
var wrappedSource = '(function (){\n' + src + '\n}).call(this)';
|
|
if (keys.length <= 3) {
|
|
wrappedSource = '(function (' + keys.join(',') + '){'
|
|
+ wrappedSource + '}).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(',') + '){'
|
|
+ wrappedSource + '}).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;
|
|
}
|