index.js 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  1. // builtin
  2. var fs = require('fs');
  3. var path = require('path');
  4. // vendor
  5. var resv = require('resolve');
  6. // given a path, create an array of node_module paths for it
  7. // borrowed from substack/resolve
  8. function nodeModulesPaths (start, cb) {
  9. var splitRe = process.platform === 'win32' ? /[\/\\]/ : /\/+/;
  10. var parts = start.split(splitRe);
  11. var dirs = [];
  12. for (var i = parts.length - 1; i >= 0; i--) {
  13. if (parts[i] === 'node_modules') continue;
  14. var dir = path.join.apply(
  15. path, parts.slice(0, i + 1).concat(['node_modules'])
  16. );
  17. if (!parts[0].match(/([A-Za-z]:)/)) {
  18. dir = '/' + dir;
  19. }
  20. dirs.push(dir);
  21. }
  22. return dirs;
  23. }
  24. function find_shims_in_package(pkgJson, cur_path, shims, browser) {
  25. try {
  26. var info = JSON.parse(pkgJson);
  27. }
  28. catch (err) {
  29. err.message = pkgJson + ' : ' + err.message
  30. throw err;
  31. }
  32. var replacements = getReplacements(info, browser);
  33. // no replacements, skip shims
  34. if (!replacements) {
  35. return;
  36. }
  37. // if browser mapping is a string
  38. // then it just replaces the main entry point
  39. if (typeof replacements === 'string') {
  40. var key = path.resolve(cur_path, info.main || 'index.js');
  41. shims[key] = path.resolve(cur_path, replacements);
  42. return;
  43. }
  44. // http://nodejs.org/api/modules.html#modules_loading_from_node_modules_folders
  45. Object.keys(replacements).forEach(function(key) {
  46. var val;
  47. if (replacements[key] === false) {
  48. val = path.normalize(__dirname + '/empty.js');
  49. }
  50. else {
  51. val = replacements[key];
  52. // if target is a relative path, then resolve
  53. // otherwise we assume target is a module
  54. if (val[0] === '.') {
  55. val = path.resolve(cur_path, val);
  56. }
  57. }
  58. if (key[0] === '/' || key[0] === '.') {
  59. // if begins with / ../ or ./ then we must resolve to a full path
  60. key = path.resolve(cur_path, key);
  61. }
  62. shims[key] = val;
  63. });
  64. [ '.js', '.json' ].forEach(function (ext) {
  65. Object.keys(shims).forEach(function (key) {
  66. if (!shims[key + ext]) {
  67. shims[key + ext] = shims[key];
  68. }
  69. });
  70. });
  71. }
  72. // paths is mutated
  73. // load shims from first package.json file found
  74. function load_shims(paths, browser, cb) {
  75. // identify if our file should be replaced per the browser field
  76. // original filename|id -> replacement
  77. var shims = Object.create(null);
  78. (function next() {
  79. var cur_path = paths.shift();
  80. if (!cur_path) {
  81. return cb(null, shims);
  82. }
  83. var pkg_path = path.join(cur_path, 'package.json');
  84. fs.readFile(pkg_path, 'utf8', function(err, data) {
  85. if (err) {
  86. // ignore paths we can't open
  87. // avoids an exists check
  88. if (err.code === 'ENOENT') {
  89. return next();
  90. }
  91. return cb(err);
  92. }
  93. try {
  94. find_shims_in_package(data, cur_path, shims, browser);
  95. return cb(null, shims);
  96. }
  97. catch (err) {
  98. return cb(err);
  99. }
  100. });
  101. })();
  102. };
  103. // paths is mutated
  104. // synchronously load shims from first package.json file found
  105. function load_shims_sync(paths, browser) {
  106. // identify if our file should be replaced per the browser field
  107. // original filename|id -> replacement
  108. var shims = Object.create(null);
  109. var cur_path;
  110. while (cur_path = paths.shift()) {
  111. var pkg_path = path.join(cur_path, 'package.json');
  112. try {
  113. var data = fs.readFileSync(pkg_path, 'utf8');
  114. find_shims_in_package(data, cur_path, shims, browser);
  115. return shims;
  116. }
  117. catch (err) {
  118. // ignore paths we can't open
  119. // avoids an exists check
  120. if (err.code === 'ENOENT') {
  121. continue;
  122. }
  123. throw err;
  124. }
  125. }
  126. return shims;
  127. }
  128. function build_resolve_opts(opts, base) {
  129. var packageFilter = opts.packageFilter;
  130. var browser = normalizeBrowserFieldName(opts.browser)
  131. opts.basedir = base;
  132. opts.packageFilter = function (info, pkgdir) {
  133. if (packageFilter) info = packageFilter(info, pkgdir);
  134. var replacements = getReplacements(info, browser);
  135. // no browser field, keep info unchanged
  136. if (!replacements) {
  137. return info;
  138. }
  139. info[browser] = replacements;
  140. // replace main
  141. if (typeof replacements === 'string') {
  142. info.main = replacements;
  143. return info;
  144. }
  145. var replace_main = replacements[info.main || './index.js'] ||
  146. replacements['./' + info.main || './index.js'];
  147. info.main = replace_main || info.main;
  148. return info;
  149. };
  150. var pathFilter = opts.pathFilter;
  151. opts.pathFilter = function(info, resvPath, relativePath) {
  152. if (relativePath[0] != '.') {
  153. relativePath = './' + relativePath;
  154. }
  155. var mappedPath;
  156. if (pathFilter) {
  157. mappedPath = pathFilter.apply(this, arguments);
  158. }
  159. if (mappedPath) {
  160. return mappedPath;
  161. }
  162. var replacements = info[browser];
  163. if (!replacements) {
  164. return;
  165. }
  166. mappedPath = replacements[relativePath];
  167. if (!mappedPath && path.extname(relativePath) === '') {
  168. mappedPath = replacements[relativePath + '.js'];
  169. if (!mappedPath) {
  170. mappedPath = replacements[relativePath + '.json'];
  171. }
  172. }
  173. return mappedPath;
  174. };
  175. return opts;
  176. }
  177. function resolve(id, opts, cb) {
  178. // opts.filename
  179. // opts.paths
  180. // opts.modules
  181. // opts.packageFilter
  182. opts = opts || {};
  183. opts.filename = opts.filename || '';
  184. var base = path.dirname(opts.filename);
  185. if (opts.basedir) {
  186. base = opts.basedir;
  187. }
  188. var paths = nodeModulesPaths(base);
  189. if (opts.paths) {
  190. paths.push.apply(paths, opts.paths);
  191. }
  192. paths = paths.map(function(p) {
  193. return path.dirname(p);
  194. });
  195. // we must always load shims because the browser field could shim out a module
  196. load_shims(paths, opts.browser, function(err, shims) {
  197. if (err) {
  198. return cb(err);
  199. }
  200. var resid = path.resolve(opts.basedir || path.dirname(opts.filename), id);
  201. if (shims[id] || shims[resid]) {
  202. var xid = shims[id] ? id : resid;
  203. // if the shim was is an absolute path, it was fully resolved
  204. if (shims[xid][0] === '/') {
  205. return resv(shims[xid], build_resolve_opts(opts, base), function(err, full, pkg) {
  206. cb(null, full, pkg);
  207. });
  208. }
  209. // module -> alt-module shims
  210. id = shims[xid];
  211. }
  212. var modules = opts.modules || Object.create(null);
  213. var shim_path = modules[id];
  214. if (shim_path) {
  215. return cb(null, shim_path);
  216. }
  217. // our browser field resolver
  218. // if browser field is an object tho?
  219. var full = resv(id, build_resolve_opts(opts, base), function(err, full, pkg) {
  220. if (err) {
  221. return cb(err);
  222. }
  223. var resolved = (shims) ? shims[full] || full : full;
  224. cb(null, resolved, pkg);
  225. });
  226. });
  227. };
  228. resolve.sync = function (id, opts) {
  229. // opts.filename
  230. // opts.paths
  231. // opts.modules
  232. // opts.packageFilter
  233. opts = opts || {};
  234. opts.filename = opts.filename || '';
  235. var base = path.dirname(opts.filename);
  236. if (opts.basedir) {
  237. base = opts.basedir;
  238. }
  239. var paths = nodeModulesPaths(base);
  240. if (opts.paths) {
  241. paths.push.apply(paths, opts.paths);
  242. }
  243. paths = paths.map(function(p) {
  244. return path.dirname(p);
  245. });
  246. // we must always load shims because the browser field could shim out a module
  247. var shims = load_shims_sync(paths, opts.browser);
  248. var resid = path.resolve(opts.basedir || path.dirname(opts.filename), id);
  249. if (shims[id] || shims[resid]) {
  250. var xid = shims[id] ? id : resid;
  251. // if the shim was is an absolute path, it was fully resolved
  252. if (shims[xid][0] === '/') {
  253. return resv.sync(shims[xid], build_resolve_opts(opts, base));
  254. }
  255. // module -> alt-module shims
  256. id = shims[xid];
  257. }
  258. var modules = opts.modules || Object.create(null);
  259. var shim_path = modules[id];
  260. if (shim_path) {
  261. return shim_path;
  262. }
  263. // our browser field resolver
  264. // if browser field is an object tho?
  265. var full = resv.sync(id, build_resolve_opts(opts, base));
  266. return (shims) ? shims[full] || full : full;
  267. };
  268. function normalizeBrowserFieldName(browser) {
  269. return browser || 'browser';
  270. }
  271. function getReplacements(info, browser) {
  272. browser = normalizeBrowserFieldName(browser);
  273. var replacements = info[browser] || info.browser;
  274. // support legacy browserify field for easier migration from legacy
  275. // many packages used this field historically
  276. if (typeof info.browserify === 'string' && !replacements) {
  277. replacements = info.browserify;
  278. }
  279. return replacements;
  280. }
  281. module.exports = resolve;