UNPKG

13 kBJavaScriptView Raw
1'use strict';
2
3function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; }
4
5const builtins = require('./builtins');
6const path = require('path');
7const glob = require('glob');
8const fs = require('./utils/fs');
9
10const EMPTY_SHIM = require.resolve('./builtins/_empty');
11
12/**
13 * This resolver implements a modified version of the node_modules resolution algorithm:
14 * https://nodejs.org/api/modules.html#modules_all_together
15 *
16 * In addition to the standard algorithm, Parcel supports:
17 * - All file extensions supported by Parcel.
18 * - Glob file paths
19 * - Absolute paths (e.g. /foo) resolved relative to the project root.
20 * - Tilde paths (e.g. ~/foo) resolved relative to the nearest module root in node_modules.
21 * - The package.json module, jsnext:main, and browser field as replacements for package.main.
22 * - The package.json browser and alias fields as an alias map within a local module.
23 * - The package.json alias field in the root package for global aliases across all modules.
24 */
25class Resolver {
26 constructor(options = {}) {
27 this.options = options;
28 this.cache = new Map();
29 this.packageCache = new Map();
30 this.rootPackage = null;
31 }
32
33 resolve(input, parent) {
34 var _this = this;
35
36 return _asyncToGenerator(function* () {
37 let filename = input;
38
39 // Check the cache first
40 let key = _this.getCacheKey(filename, parent);
41 if (_this.cache.has(key)) {
42 return _this.cache.get(key);
43 }
44
45 // Check if this is a glob
46 if (/[*+{}]/.test(filename) && glob.hasMagic(filename)) {
47 return { path: path.resolve(path.dirname(parent), filename) };
48 }
49
50 // Get file extensions to search
51 let extensions = Array.isArray(_this.options.extensions) ? _this.options.extensions.slice() : Object.keys(_this.options.extensions);
52
53 if (parent) {
54 // parent's extension given high priority
55 const parentExt = path.extname(parent);
56 extensions = [parentExt, ...extensions.filter(function (ext) {
57 return ext !== parentExt;
58 })];
59 }
60
61 extensions.unshift('');
62
63 let dir = parent ? path.dirname(parent) : process.cwd();
64
65 // If this isn't the entrypoint, resolve the input file to an absolute path
66 if (parent) {
67 filename = _this.resolveFilename(filename, dir);
68 }
69
70 // Resolve aliases in the parent module for this file.
71 filename = yield _this.loadAlias(filename, dir);
72
73 let resolved;
74 if (path.isAbsolute(filename)) {
75 // load as file
76 resolved = yield _this.loadRelative(filename, extensions);
77 } else {
78 // load node_modules
79 resolved = yield _this.loadNodeModules(filename, dir, extensions);
80 }
81
82 if (!resolved) {
83 let err = new Error("Cannot find module '" + input + "' from '" + dir + "'");
84 err.code = 'MODULE_NOT_FOUND';
85 throw err;
86 }
87
88 _this.cache.set(key, resolved);
89 return resolved;
90 })();
91 }
92
93 getCacheKey(filename, parent) {
94 return (parent ? path.dirname(parent) : '') + ':' + filename;
95 }
96
97 resolveFilename(filename, dir) {
98 switch (filename[0]) {
99 case '/':
100 // Absolute path. Resolve relative to project root.
101 return path.resolve(this.options.rootDir, filename.slice(1));
102
103 case '~':
104 // Tilde path. Resolve relative to nearest node_modules directory,
105 // or the project root - whichever comes first.
106 while (dir !== this.options.rootDir && path.basename(path.dirname(dir)) !== 'node_modules') {
107 dir = path.dirname(dir);
108 }
109
110 return path.join(dir, filename.slice(1));
111
112 case '.':
113 // Relative path.
114 return path.resolve(dir, filename);
115
116 default:
117 // Module
118 return path.normalize(filename);
119 }
120 }
121
122 loadRelative(filename, extensions) {
123 var _this2 = this;
124
125 return _asyncToGenerator(function* () {
126 // Find a package.json file in the current package.
127 let pkg = yield _this2.findPackage(path.dirname(filename));
128
129 // First try as a file, then as a directory.
130 return (yield _this2.loadAsFile(filename, extensions, pkg)) || (yield _this2.loadDirectory(filename, extensions, pkg));
131 })();
132 }
133
134 loadNodeModules(filename, dir, extensions) {
135 var _this3 = this;
136
137 return _asyncToGenerator(function* () {
138 // Check if this is a builtin module
139 if (builtins[filename]) {
140 return { path: builtins[filename] };
141 }
142
143 let parts = _this3.getModuleParts(filename);
144 let root = path.parse(dir).root;
145
146 while (dir !== root) {
147 // Skip node_modules directories
148 if (path.basename(dir) === 'node_modules') {
149 dir = path.dirname(dir);
150 }
151
152 try {
153 // First, check if the module directory exists. This prevents a lot of unnecessary checks later.
154 let moduleDir = path.join(dir, 'node_modules', parts[0]);
155 let stats = yield fs.stat(moduleDir);
156 if (stats.isDirectory()) {
157 let f = path.join(dir, 'node_modules', filename);
158
159 // If a module was specified as a module sub-path (e.g. some-module/some/path),
160 // it is likely a file. Try loading it as a file first.
161 if (parts.length > 1) {
162 let pkg = yield _this3.readPackage(moduleDir);
163 let res = yield _this3.loadAsFile(f, extensions, pkg);
164 if (res) {
165 return res;
166 }
167 }
168
169 // Otherwise, load as a directory.
170 return yield _this3.loadDirectory(f, extensions);
171 }
172 } catch (err) {}
173 // ignore
174
175
176 // Move up a directory
177 dir = path.dirname(dir);
178 }
179 })();
180 }
181
182 isFile(file) {
183 return _asyncToGenerator(function* () {
184 try {
185 let stat = yield fs.stat(file);
186 return stat.isFile() || stat.isFIFO();
187 } catch (err) {
188 return false;
189 }
190 })();
191 }
192
193 loadDirectory(dir, extensions, pkg) {
194 var _this4 = this;
195
196 return _asyncToGenerator(function* () {
197 try {
198 pkg = yield _this4.readPackage(dir);
199
200 // First try loading package.main as a file, then try as a directory.
201 let main = _this4.getPackageMain(pkg);
202 let res = (yield _this4.loadAsFile(main, extensions, pkg)) || (yield _this4.loadDirectory(main, extensions, pkg));
203
204 if (res) {
205 return res;
206 }
207 } catch (err) {}
208 // ignore
209
210
211 // Fall back to an index file inside the directory.
212 return yield _this4.loadAsFile(path.join(dir, 'index'), extensions, pkg);
213 })();
214 }
215
216 readPackage(dir) {
217 var _this5 = this;
218
219 return _asyncToGenerator(function* () {
220 let file = path.join(dir, 'package.json');
221 if (_this5.packageCache.has(file)) {
222 return _this5.packageCache.get(file);
223 }
224
225 let json = yield fs.readFile(file, 'utf8');
226 let pkg = JSON.parse(json);
227
228 pkg.pkgfile = file;
229 pkg.pkgdir = dir;
230
231 _this5.packageCache.set(file, pkg);
232 return pkg;
233 })();
234 }
235
236 getPackageMain(pkg) {
237 // libraries like d3.js specifies node.js specific files in the "main" which breaks the build
238 // we use the "module" or "jsnext:main" field to get the full dependency tree if available
239 let main = [pkg.module, pkg['jsnext:main'], pkg.browser, pkg.main].find(entry => typeof entry === 'string');
240
241 // Default to index file if no main field find
242 if (!main || main === '.' || main === './') {
243 main = 'index';
244 }
245
246 return path.resolve(pkg.pkgdir, main);
247 }
248
249 loadAsFile(file, extensions, pkg) {
250 var _this6 = this;
251
252 return _asyncToGenerator(function* () {
253 // Try all supported extensions
254 var _iteratorNormalCompletion = true;
255 var _didIteratorError = false;
256 var _iteratorError = undefined;
257
258 try {
259 for (var _iterator = _this6.expandFile(file, extensions, pkg)[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
260 let f = _step.value;
261
262 if (yield _this6.isFile(f)) {
263 return { path: f, pkg };
264 }
265 }
266 } catch (err) {
267 _didIteratorError = true;
268 _iteratorError = err;
269 } finally {
270 try {
271 if (!_iteratorNormalCompletion && _iterator.return) {
272 _iterator.return();
273 }
274 } finally {
275 if (_didIteratorError) {
276 throw _iteratorError;
277 }
278 }
279 }
280 })();
281 }
282
283 expandFile(file, extensions, pkg, expandAliases = true) {
284 // Expand extensions and aliases
285 let res = [];
286 var _iteratorNormalCompletion2 = true;
287 var _didIteratorError2 = false;
288 var _iteratorError2 = undefined;
289
290 try {
291 for (var _iterator2 = extensions[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
292 let ext = _step2.value;
293
294 let f = file + ext;
295
296 if (expandAliases) {
297 let alias = this.resolveAliases(file + ext, pkg);
298 if (alias !== f) {
299 res = res.concat(this.expandFile(alias, extensions, pkg, false));
300 }
301 }
302
303 res.push(f);
304 }
305 } catch (err) {
306 _didIteratorError2 = true;
307 _iteratorError2 = err;
308 } finally {
309 try {
310 if (!_iteratorNormalCompletion2 && _iterator2.return) {
311 _iterator2.return();
312 }
313 } finally {
314 if (_didIteratorError2) {
315 throw _iteratorError2;
316 }
317 }
318 }
319
320 return res;
321 }
322
323 resolveAliases(filename, pkg) {
324 // First resolve local package aliases, then project global ones.
325 return this.resolvePackageAliases(this.resolvePackageAliases(filename, pkg), this.rootPackage);
326 }
327
328 resolvePackageAliases(filename, pkg) {
329 // Resolve aliases in the package.alias and package.browser fields.
330 if (pkg) {
331 return this.getAlias(filename, pkg.pkgdir, pkg.alias) || this.getAlias(filename, pkg.pkgdir, pkg.browser) || filename;
332 }
333
334 return filename;
335 }
336
337 getAlias(filename, dir, aliases) {
338 if (!filename || !aliases || typeof aliases !== 'object') {
339 return null;
340 }
341
342 let alias;
343
344 // If filename is an absolute path, get one relative to the package.json directory.
345 if (path.isAbsolute(filename)) {
346 filename = path.relative(dir, filename);
347 if (filename[0] !== '.') {
348 filename = './' + filename;
349 }
350
351 alias = aliases[filename];
352 } else {
353 // It is a node_module. First try the entire filename as a key.
354 alias = aliases[filename];
355 if (alias == null) {
356 // If it didn't match, try only the module name.
357 let parts = this.getModuleParts(filename);
358 alias = aliases[parts[0]];
359 if (typeof alias === 'string') {
360 // Append the filename back onto the aliased module.
361 alias = path.join(alias, ...parts.slice(1));
362 }
363 }
364 }
365
366 // If the alias is set to `false`, return an empty file.
367 if (alias === false) {
368 return EMPTY_SHIM;
369 }
370
371 // If the alias is a relative path, then resolve
372 // relative to the package.json directory.
373 if (alias && alias[0] === '.') {
374 return path.resolve(dir, alias);
375 }
376
377 // Otherwise, assume the alias is a module
378 return alias;
379 }
380
381 findPackage(dir) {
382 var _this7 = this;
383
384 return _asyncToGenerator(function* () {
385 // Find the nearest package.json file within the current node_modules folder
386 let root = path.parse(dir).root;
387 while (dir !== root && path.basename(dir) !== 'node_modules') {
388 try {
389 return yield _this7.readPackage(dir);
390 } catch (err) {
391 // ignore
392 }
393
394 dir = path.dirname(dir);
395 }
396 })();
397 }
398
399 loadAlias(filename, dir) {
400 var _this8 = this;
401
402 return _asyncToGenerator(function* () {
403 // Load the root project's package.json file if we haven't already
404 if (!_this8.rootPackage) {
405 _this8.rootPackage = yield _this8.findPackage(_this8.options.rootDir);
406 }
407
408 // Load the local package, and resolve aliases
409 let pkg = yield _this8.findPackage(dir);
410 return _this8.resolveAliases(filename, pkg);
411 })();
412 }
413
414 getModuleParts(name) {
415 let parts = path.normalize(name).split(path.sep);
416 if (parts[0].charAt(0) === '@') {
417 // Scoped module (e.g. @scope/module). Merge the first two parts back together.
418 parts.splice(0, 2, `${parts[0]}/${parts[1]}`);
419 }
420
421 return parts;
422 }
423}
424
425module.exports = Resolver;
\No newline at end of file