| // 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; |