UNPKG

5.71 kBJavaScriptView Raw
1// builtin
2var fs = require('fs');
3var path = require('path');
4
5// vendor
6var resv = require('resolve');
7
8// given a path, create an array of node_module paths for it
9// borrowed from substack/resolve
10function 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(
18 path.join.apply(path, parts.slice(0, i + 1)),
19 'node_modules'
20 );
21 if (!parts[0].match(/([A-Za-z]:)/)) {
22 dir = '/' + dir;
23 }
24 dirs.push(dir);
25 }
26 return dirs;
27}
28
29// paths is mutated
30// load shims from first package.json file found
31function load_shims(paths, cb) {
32 // identify if our file should be replaced per the browser field
33 // original filename|id -> replacement
34 var shims = {};
35
36 (function next() {
37 var cur_path = paths.shift();
38 if (!cur_path) {
39 return cb(null, shims);
40 }
41
42 var pkg_path = path.join(cur_path, 'package.json');
43
44 fs.readFile(pkg_path, 'utf8', function(err, data) {
45 if (err) {
46 // ignore paths we can't open
47 // avoids an exists check
48 if (err.code === 'ENOENT') {
49 return next();
50 }
51
52 return cb(err);
53 }
54
55 try {
56 var info = JSON.parse(data);
57 }
58 catch (err) {
59 err.message = pkg_path + ' : ' + err.message
60 return cb(err);
61 }
62
63 // support legacy browserify field for easier migration from legacy
64 // many packages used this field historically
65 if (typeof info.browserify === 'string' && !info.browser) {
66 info.browser = info.browserify;
67 }
68
69 // no replacements, skip shims
70 if (!info.browser) {
71 return cb(null, shims);
72 }
73
74 // if browser field is a string
75 // then it just replaces the main entry point
76 if (typeof info.browser === 'string') {
77 var key = path.resolve(cur_path, info.main || 'index.js');
78 shims[key] = path.resolve(cur_path, info.browser);
79 return cb(null, shims);
80 }
81
82 // http://nodejs.org/api/modules.html#modules_loading_from_node_modules_folders
83 Object.keys(info.browser).forEach(function(key) {
84 if (info.browser[key] === false) {
85 return shims[key] = __dirname + '/empty.js';
86 }
87
88 var val = info.browser[key];
89
90 // if target is a relative path, then resolve
91 // otherwise we assume target is a module
92 if (val[0] === '.') {
93 val = path.resolve(cur_path, val);
94 }
95
96 // if does not begin with / ../ or ./ then it is a module
97 if (key[0] !== '/' && key[0] !== '.') {
98 return shims[key] = val;
99 }
100
101 var key = path.resolve(cur_path, key);
102 shims[key] = val;
103 });
104 return cb(null, shims);
105 });
106 })();
107};
108
109function resolve(id, opts, cb) {
110
111 // opts.filename
112 // opts.paths
113 // opts.modules
114 // opts.packageFilter
115
116 opts = opts || {};
117
118 var base = path.dirname(opts.filename);
119 var paths = nodeModulesPaths(base);
120
121 if (opts.paths) {
122 paths.push.apply(paths, opts.paths);
123 }
124
125 paths = paths.map(function(p) {
126 return path.dirname(p);
127 });
128
129 // we must always load shims because the browser field could shim out a module
130 load_shims(paths, function(err, shims) {
131 if (err) {
132 return cb(err);
133 }
134
135 if (shims[id]) {
136 // if the shim was is an absolute path, it was fully resolved
137 if (shims[id][0] === '/') {
138 return cb(null, shims[id], opts.package);
139 }
140
141 // module -> alt-module shims
142 id = shims[id];
143 }
144
145 var modules = opts.modules || {};
146 var shim_path = modules[id];
147 if (shim_path) {
148 return cb(null, shim_path);
149 }
150
151 // our browser field resolver
152 // if browser field is an object tho?
153 var full = resv(id, {
154 paths: opts.paths,
155 extensions: opts.extensions,
156 basedir: base,
157 package: opts.package,
158 packageFilter: function(info) {
159 if (opts.packageFilter) info = opts.packageFilter(info);
160
161 // support legacy browserify field
162 if (typeof info.browserify === 'string' && !info.browser) {
163 info.browser = info.browserify;
164 }
165
166 // no browser field, keep info unchanged
167 if (!info.browser) {
168 return info;
169 }
170
171 // replace main
172 if (typeof info.browser === 'string') {
173 info.main = info.browser;
174 return info;
175 }
176
177 var replace_main = info.browser[info.main || './index.js'];
178 info.main = replace_main || info.main;
179 return info;
180 }
181 }, function(err, full, pkg) {
182 if (err) {
183 return cb(err);
184 }
185
186 var resolved = (shims) ? shims[full] || full : full;
187 cb(null, resolved, pkg);
188 });
189 });
190};
191
192module.exports = resolve;
193