346 lines
9.3 KiB
JavaScript
346 lines
9.3 KiB
JavaScript
// builtin
|
|
var fs = require('fs');
|
|
var path = require('path');
|
|
|
|
// vendor
|
|
var resv = require('resolve');
|
|
|
|
// given a path, create an array of node_module paths for it
|
|
// borrowed from substack/resolve
|
|
function nodeModulesPaths (start, cb) {
|
|
var splitRe = process.platform === 'win32' ? /[\/\\]/ : /\/+/;
|
|
var parts = start.split(splitRe);
|
|
|
|
var dirs = [];
|
|
for (var i = parts.length - 1; i >= 0; i--) {
|
|
if (parts[i] === 'node_modules') continue;
|
|
var dir = path.join.apply(
|
|
path, parts.slice(0, i + 1).concat(['node_modules'])
|
|
);
|
|
if (!parts[0].match(/([A-Za-z]:)/)) {
|
|
dir = '/' + dir;
|
|
}
|
|
dirs.push(dir);
|
|
}
|
|
return dirs;
|
|
}
|
|
|
|
function find_shims_in_package(pkgJson, cur_path, shims, browser) {
|
|
try {
|
|
var info = JSON.parse(pkgJson);
|
|
}
|
|
catch (err) {
|
|
err.message = pkgJson + ' : ' + err.message
|
|
throw err;
|
|
}
|
|
|
|
var replacements = getReplacements(info, browser);
|
|
|
|
// no replacements, skip shims
|
|
if (!replacements) {
|
|
return;
|
|
}
|
|
|
|
// if browser mapping is a string
|
|
// then it just replaces the main entry point
|
|
if (typeof replacements === 'string') {
|
|
var key = path.resolve(cur_path, info.main || 'index.js');
|
|
shims[key] = path.resolve(cur_path, replacements);
|
|
return;
|
|
}
|
|
|
|
// http://nodejs.org/api/modules.html#modules_loading_from_node_modules_folders
|
|
Object.keys(replacements).forEach(function(key) {
|
|
var val;
|
|
if (replacements[key] === false) {
|
|
val = path.normalize(__dirname + '/empty.js');
|
|
}
|
|
else {
|
|
val = replacements[key];
|
|
// if target is a relative path, then resolve
|
|
// otherwise we assume target is a module
|
|
if (val[0] === '.') {
|
|
val = path.resolve(cur_path, val);
|
|
}
|
|
}
|
|
|
|
if (key[0] === '/' || key[0] === '.') {
|
|
// if begins with / ../ or ./ then we must resolve to a full path
|
|
key = path.resolve(cur_path, key);
|
|
}
|
|
shims[key] = val;
|
|
});
|
|
|
|
[ '.js', '.json' ].forEach(function (ext) {
|
|
Object.keys(shims).forEach(function (key) {
|
|
if (!shims[key + ext]) {
|
|
shims[key + ext] = shims[key];
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
// paths is mutated
|
|
// load shims from first package.json file found
|
|
function load_shims(paths, browser, cb) {
|
|
// identify if our file should be replaced per the browser field
|
|
// original filename|id -> replacement
|
|
var shims = Object.create(null);
|
|
|
|
(function next() {
|
|
var cur_path = paths.shift();
|
|
if (!cur_path) {
|
|
return cb(null, shims);
|
|
}
|
|
|
|
var pkg_path = path.join(cur_path, 'package.json');
|
|
|
|
fs.readFile(pkg_path, 'utf8', function(err, data) {
|
|
if (err) {
|
|
// ignore paths we can't open
|
|
// avoids an exists check
|
|
if (err.code === 'ENOENT') {
|
|
return next();
|
|
}
|
|
|
|
return cb(err);
|
|
}
|
|
try {
|
|
find_shims_in_package(data, cur_path, shims, browser);
|
|
return cb(null, shims);
|
|
}
|
|
catch (err) {
|
|
return cb(err);
|
|
}
|
|
});
|
|
})();
|
|
};
|
|
|
|
// paths is mutated
|
|
// synchronously load shims from first package.json file found
|
|
function load_shims_sync(paths, browser) {
|
|
// identify if our file should be replaced per the browser field
|
|
// original filename|id -> replacement
|
|
var shims = Object.create(null);
|
|
var cur_path;
|
|
|
|
while (cur_path = paths.shift()) {
|
|
var pkg_path = path.join(cur_path, 'package.json');
|
|
|
|
try {
|
|
var data = fs.readFileSync(pkg_path, 'utf8');
|
|
find_shims_in_package(data, cur_path, shims, browser);
|
|
return shims;
|
|
}
|
|
catch (err) {
|
|
// ignore paths we can't open
|
|
// avoids an exists check
|
|
if (err.code === 'ENOENT') {
|
|
continue;
|
|
}
|
|
|
|
throw err;
|
|
}
|
|
}
|
|
return shims;
|
|
}
|
|
|
|
function build_resolve_opts(opts, base) {
|
|
var packageFilter = opts.packageFilter;
|
|
var browser = normalizeBrowserFieldName(opts.browser)
|
|
|
|
opts.basedir = base;
|
|
opts.packageFilter = function (info, pkgdir) {
|
|
if (packageFilter) info = packageFilter(info, pkgdir);
|
|
|
|
var replacements = getReplacements(info, browser);
|
|
|
|
// no browser field, keep info unchanged
|
|
if (!replacements) {
|
|
return info;
|
|
}
|
|
|
|
info[browser] = replacements;
|
|
|
|
// replace main
|
|
if (typeof replacements === 'string') {
|
|
info.main = replacements;
|
|
return info;
|
|
}
|
|
|
|
var replace_main = replacements[info.main || './index.js'] ||
|
|
replacements['./' + info.main || './index.js'];
|
|
|
|
info.main = replace_main || info.main;
|
|
return info;
|
|
};
|
|
|
|
var pathFilter = opts.pathFilter;
|
|
opts.pathFilter = function(info, resvPath, relativePath) {
|
|
if (relativePath[0] != '.') {
|
|
relativePath = './' + relativePath;
|
|
}
|
|
var mappedPath;
|
|
if (pathFilter) {
|
|
mappedPath = pathFilter.apply(this, arguments);
|
|
}
|
|
if (mappedPath) {
|
|
return mappedPath;
|
|
}
|
|
|
|
var replacements = info[browser];
|
|
if (!replacements) {
|
|
return;
|
|
}
|
|
|
|
mappedPath = replacements[relativePath];
|
|
if (!mappedPath && path.extname(relativePath) === '') {
|
|
mappedPath = replacements[relativePath + '.js'];
|
|
if (!mappedPath) {
|
|
mappedPath = replacements[relativePath + '.json'];
|
|
}
|
|
}
|
|
return mappedPath;
|
|
};
|
|
|
|
return opts;
|
|
}
|
|
|
|
function resolve(id, opts, cb) {
|
|
|
|
// opts.filename
|
|
// opts.paths
|
|
// opts.modules
|
|
// opts.packageFilter
|
|
|
|
opts = opts || {};
|
|
opts.filename = opts.filename || '';
|
|
|
|
var base = path.dirname(opts.filename);
|
|
|
|
if (opts.basedir) {
|
|
base = opts.basedir;
|
|
}
|
|
|
|
var paths = nodeModulesPaths(base);
|
|
|
|
if (opts.paths) {
|
|
paths.push.apply(paths, opts.paths);
|
|
}
|
|
|
|
paths = paths.map(function(p) {
|
|
return path.dirname(p);
|
|
});
|
|
|
|
// we must always load shims because the browser field could shim out a module
|
|
load_shims(paths, opts.browser, function(err, shims) {
|
|
if (err) {
|
|
return cb(err);
|
|
}
|
|
|
|
var resid = path.resolve(opts.basedir || path.dirname(opts.filename), id);
|
|
if (shims[id] || shims[resid]) {
|
|
var xid = shims[id] ? id : resid;
|
|
// if the shim was is an absolute path, it was fully resolved
|
|
if (shims[xid][0] === '/') {
|
|
return resv(shims[xid], build_resolve_opts(opts, base), function(err, full, pkg) {
|
|
cb(null, full, pkg);
|
|
});
|
|
}
|
|
|
|
// module -> alt-module shims
|
|
id = shims[xid];
|
|
}
|
|
|
|
var modules = opts.modules || Object.create(null);
|
|
var shim_path = modules[id];
|
|
if (shim_path) {
|
|
return cb(null, shim_path);
|
|
}
|
|
|
|
// our browser field resolver
|
|
// if browser field is an object tho?
|
|
var full = resv(id, build_resolve_opts(opts, base), function(err, full, pkg) {
|
|
if (err) {
|
|
return cb(err);
|
|
}
|
|
|
|
var resolved = (shims) ? shims[full] || full : full;
|
|
cb(null, resolved, pkg);
|
|
});
|
|
});
|
|
};
|
|
|
|
resolve.sync = function (id, opts) {
|
|
|
|
// opts.filename
|
|
// opts.paths
|
|
// opts.modules
|
|
// opts.packageFilter
|
|
|
|
opts = opts || {};
|
|
opts.filename = opts.filename || '';
|
|
|
|
var base = path.dirname(opts.filename);
|
|
|
|
if (opts.basedir) {
|
|
base = opts.basedir;
|
|
}
|
|
|
|
var paths = nodeModulesPaths(base);
|
|
|
|
if (opts.paths) {
|
|
paths.push.apply(paths, opts.paths);
|
|
}
|
|
|
|
paths = paths.map(function(p) {
|
|
return path.dirname(p);
|
|
});
|
|
|
|
// we must always load shims because the browser field could shim out a module
|
|
var shims = load_shims_sync(paths, opts.browser);
|
|
var resid = path.resolve(opts.basedir || path.dirname(opts.filename), id);
|
|
|
|
if (shims[id] || shims[resid]) {
|
|
var xid = shims[id] ? id : resid;
|
|
// if the shim was is an absolute path, it was fully resolved
|
|
if (shims[xid][0] === '/') {
|
|
return resv.sync(shims[xid], build_resolve_opts(opts, base));
|
|
}
|
|
|
|
// module -> alt-module shims
|
|
id = shims[xid];
|
|
}
|
|
|
|
var modules = opts.modules || Object.create(null);
|
|
var shim_path = modules[id];
|
|
if (shim_path) {
|
|
return shim_path;
|
|
}
|
|
|
|
// our browser field resolver
|
|
// if browser field is an object tho?
|
|
var full = resv.sync(id, build_resolve_opts(opts, base));
|
|
|
|
return (shims) ? shims[full] || full : full;
|
|
};
|
|
|
|
function normalizeBrowserFieldName(browser) {
|
|
return browser || 'browser';
|
|
}
|
|
|
|
function getReplacements(info, browser) {
|
|
browser = normalizeBrowserFieldName(browser);
|
|
var replacements = info[browser] || info.browser;
|
|
|
|
// support legacy browserify field for easier migration from legacy
|
|
// many packages used this field historically
|
|
if (typeof info.browserify === 'string' && !replacements) {
|
|
replacements = info.browserify;
|
|
}
|
|
|
|
return replacements;
|
|
}
|
|
|
|
module.exports = resolve;
|