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