UNPKG

9.5 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.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
28function 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
85function 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
121function 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
148function 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
209function 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
274resolve.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
328function normalizeBrowserFieldName(browser) {
329 return browser || 'browser';
330}
331
332function 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
345module.exports = resolve;