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