UNPKG

16.4 kBJavaScriptView Raw
1"use strict";
2
3var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
5var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
6
7const builtins = require('./builtins');
8
9const nodeBuiltins = require('node-libs-browser');
10
11const path = require('path');
12
13const _require = require('./utils/glob'),
14 isGlob = _require.isGlob;
15
16const fs = require('@parcel/fs');
17
18const micromatch = require('micromatch');
19
20const getModuleParts = require('./utils/getModuleParts');
21
22const EMPTY_SHIM = require.resolve('./builtins/_empty');
23/**
24 * This resolver implements a modified version of the node_modules resolution algorithm:
25 * https://nodejs.org/api/modules.html#modules_all_together
26 *
27 * In addition to the standard algorithm, Parcel supports:
28 * - All file extensions supported by Parcel.
29 * - Glob file paths
30 * - Absolute paths (e.g. /foo) resolved relative to the project root.
31 * - Tilde paths (e.g. ~/foo) resolved relative to the nearest module root in node_modules.
32 * - The package.json module, jsnext:main, and browser field as replacements for package.main.
33 * - The package.json browser and alias fields as an alias map within a local module.
34 * - The package.json alias field in the root package for global aliases across all modules.
35 */
36
37
38class Resolver {
39 constructor(options = {}) {
40 this.options = options;
41 this.cache = new Map();
42 this.packageCache = new Map();
43 this.rootPackage = null;
44 }
45
46 resolve(input, parent) {
47 var _this = this;
48
49 return (0, _asyncToGenerator2.default)(function* () {
50 let filename = input; // Check the cache first
51
52 let key = _this.getCacheKey(filename, parent);
53
54 if (_this.cache.has(key)) {
55 return _this.cache.get(key);
56 } // Check if this is a glob
57
58
59 if (isGlob(filename)) {
60 return {
61 path: path.resolve(path.dirname(parent), filename)
62 };
63 } // Get file extensions to search
64
65
66 let extensions = Array.isArray(_this.options.extensions) ? _this.options.extensions.slice() : Object.keys(_this.options.extensions);
67
68 if (parent) {
69 // parent's extension given high priority
70 const parentExt = path.extname(parent);
71 extensions = [parentExt, ...extensions.filter(ext => ext !== parentExt)];
72 }
73
74 extensions.unshift(''); // Resolve the module directory or local file path
75
76 let module = yield _this.resolveModule(filename, parent);
77 let resolved;
78
79 if (module.moduleDir) {
80 resolved = yield _this.loadNodeModules(module, extensions);
81 } else if (module.filePath) {
82 resolved = yield _this.loadRelative(module.filePath, extensions);
83 }
84
85 if (!resolved) {
86 let dir = parent ? path.dirname(parent) : process.cwd();
87 let err = new Error(`Cannot find module '${input}' from '${dir}'`);
88 err.code = 'MODULE_NOT_FOUND';
89 throw err;
90 }
91
92 _this.cache.set(key, resolved);
93
94 return resolved;
95 })();
96 }
97
98 resolveModule(filename, parent) {
99 var _this2 = this;
100
101 return (0, _asyncToGenerator2.default)(function* () {
102 let dir = parent ? path.dirname(parent) : process.cwd(); // If this isn't the entrypoint, resolve the input file to an absolute path
103
104 if (parent) {
105 filename = _this2.resolveFilename(filename, dir);
106 } // Resolve aliases in the parent module for this file.
107
108
109 filename = yield _this2.loadAlias(filename, dir); // Return just the file path if this is a file, not in node_modules
110
111 if (path.isAbsolute(filename)) {
112 return {
113 filePath: filename
114 };
115 } // Resolve the module in node_modules
116
117
118 let resolved;
119
120 try {
121 resolved = yield _this2.findNodeModulePath(filename, dir);
122 } catch (err) {} // ignore
123 // If we couldn't resolve the node_modules path, just return the module name info
124
125
126 if (!resolved) {
127 let parts = getModuleParts(filename);
128 resolved = {
129 moduleName: parts[0],
130 subPath: parts[1]
131 };
132 }
133
134 return resolved;
135 })();
136 }
137
138 getCacheKey(filename, parent) {
139 return (parent ? path.dirname(parent) : '') + ':' + filename;
140 }
141
142 resolveFilename(filename, dir) {
143 switch (filename[0]) {
144 case '/':
145 // Absolute path. Resolve relative to project root.
146 return path.resolve(this.options.rootDir, filename.slice(1));
147
148 case '~':
149 // Tilde path. Resolve relative to nearest node_modules directory,
150 // or the project root - whichever comes first.
151 while (dir !== this.options.rootDir && path.basename(path.dirname(dir)) !== 'node_modules') {
152 dir = path.dirname(dir);
153
154 if (dir === path.dirname(dir)) {
155 dir = this.options.rootDir;
156 break;
157 }
158 }
159
160 return path.join(dir, filename.slice(1));
161
162 case '.':
163 // Relative path.
164 return path.resolve(dir, filename);
165
166 default:
167 // Module
168 return filename;
169 }
170 }
171
172 loadRelative(filename, extensions) {
173 var _this3 = this;
174
175 return (0, _asyncToGenerator2.default)(function* () {
176 // Find a package.json file in the current package.
177 let pkg = yield _this3.findPackage(path.dirname(filename)); // First try as a file, then as a directory.
178
179 return (yield _this3.loadAsFile(filename, extensions, pkg)) || (yield _this3.loadDirectory(filename, extensions, pkg)) // eslint-disable-line no-return-await
180 ;
181 })();
182 }
183
184 findNodeModulePath(filename, dir) {
185 var _this4 = this;
186
187 return (0, _asyncToGenerator2.default)(function* () {
188 if (builtins[filename]) {
189 if (_this4.options.target === 'node' && filename in nodeBuiltins) {
190 throw new Error('Cannot resolve builtin module for node target');
191 }
192
193 return {
194 filePath: builtins[filename]
195 };
196 }
197
198 let parts = getModuleParts(filename);
199 let root = path.parse(dir).root;
200
201 while (dir !== root) {
202 // Skip node_modules directories
203 if (path.basename(dir) === 'node_modules') {
204 dir = path.dirname(dir);
205 }
206
207 try {
208 // First, check if the module directory exists. This prevents a lot of unnecessary checks later.
209 let moduleDir = path.join(dir, 'node_modules', parts[0]);
210 let stats = yield fs.stat(moduleDir);
211
212 if (stats.isDirectory()) {
213 return {
214 moduleName: parts[0],
215 subPath: parts[1],
216 moduleDir: moduleDir,
217 filePath: path.join(dir, 'node_modules', filename)
218 };
219 }
220 } catch (err) {} // ignore
221 // Move up a directory
222
223
224 dir = path.dirname(dir);
225 }
226 })();
227 }
228
229 loadNodeModules(module, extensions) {
230 var _this5 = this;
231
232 return (0, _asyncToGenerator2.default)(function* () {
233 try {
234 // If a module was specified as a module sub-path (e.g. some-module/some/path),
235 // it is likely a file. Try loading it as a file first.
236 if (module.subPath) {
237 let pkg = yield _this5.readPackage(module.moduleDir);
238 let res = yield _this5.loadAsFile(module.filePath, extensions, pkg);
239
240 if (res) {
241 return res;
242 }
243 } // Otherwise, load as a directory.
244
245
246 return yield _this5.loadDirectory(module.filePath, extensions);
247 } catch (e) {// ignore
248 }
249 })();
250 }
251
252 isFile(file) {
253 return (0, _asyncToGenerator2.default)(function* () {
254 try {
255 let stat = yield fs.stat(file);
256 return stat.isFile() || stat.isFIFO();
257 } catch (err) {
258 return false;
259 }
260 })();
261 }
262
263 loadDirectory(dir, extensions, pkg) {
264 var _this6 = this;
265
266 return (0, _asyncToGenerator2.default)(function* () {
267 try {
268 pkg = yield _this6.readPackage(dir); // Get a list of possible package entry points.
269
270 let entries = _this6.getPackageEntries(pkg);
271
272 var _iteratorNormalCompletion = true;
273 var _didIteratorError = false;
274 var _iteratorError = undefined;
275
276 try {
277 for (var _iterator = entries[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
278 let file = _step.value;
279 // First try loading package.main as a file, then try as a directory.
280 const res = (yield _this6.loadAsFile(file, extensions, pkg)) || (yield _this6.loadDirectory(file, extensions, pkg));
281
282 if (res) {
283 return res;
284 }
285 }
286 } catch (err) {
287 _didIteratorError = true;
288 _iteratorError = err;
289 } finally {
290 try {
291 if (!_iteratorNormalCompletion && _iterator.return != null) {
292 _iterator.return();
293 }
294 } finally {
295 if (_didIteratorError) {
296 throw _iteratorError;
297 }
298 }
299 }
300 } catch (err) {} // ignore
301 // Fall back to an index file inside the directory.
302
303
304 return _this6.loadAsFile(path.join(dir, 'index'), extensions, pkg);
305 })();
306 }
307
308 readPackage(dir) {
309 var _this7 = this;
310
311 return (0, _asyncToGenerator2.default)(function* () {
312 let file = path.join(dir, 'package.json');
313
314 if (_this7.packageCache.has(file)) {
315 return _this7.packageCache.get(file);
316 }
317
318 let json = yield fs.readFile(file, 'utf8');
319 let pkg = JSON.parse(json);
320 pkg.pkgfile = file;
321 pkg.pkgdir = dir; // If the package has a `source` field, check if it is behind a symlink.
322 // If so, we treat the module as source code rather than a pre-compiled module.
323
324 if (pkg.source) {
325 let realpath = yield fs.realpath(file);
326
327 if (realpath === file) {
328 delete pkg.source;
329 }
330 }
331
332 _this7.packageCache.set(file, pkg);
333
334 return pkg;
335 })();
336 }
337
338 getBrowserField(pkg) {
339 let target = this.options.target || 'browser';
340 return target === 'browser' ? pkg.browser : null;
341 }
342
343 getPackageEntries(pkg) {
344 let browser = this.getBrowserField(pkg);
345
346 if (browser && typeof browser === 'object' && browser[pkg.name]) {
347 browser = browser[pkg.name];
348 } // libraries like d3.js specifies node.js specific files in the "main" which breaks the build
349 // we use the "browser" or "module" field to get the full dependency tree if available.
350 // If this is a linked module with a `source` field, use that as the entry point.
351
352
353 return [pkg.source, browser, pkg.module, pkg.main].filter(entry => typeof entry === 'string').map(main => {
354 // Default to index file if no main field find
355 if (!main || main === '.' || main === './') {
356 main = 'index';
357 }
358
359 return path.resolve(pkg.pkgdir, main);
360 });
361 }
362
363 loadAsFile(file, extensions, pkg) {
364 var _this8 = this;
365
366 return (0, _asyncToGenerator2.default)(function* () {
367 // Try all supported extensions
368 var _iteratorNormalCompletion2 = true;
369 var _didIteratorError2 = false;
370 var _iteratorError2 = undefined;
371
372 try {
373 for (var _iterator2 = _this8.expandFile(file, extensions, pkg)[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
374 let f = _step2.value;
375
376 if (yield _this8.isFile(f)) {
377 return {
378 path: f,
379 pkg
380 };
381 }
382 }
383 } catch (err) {
384 _didIteratorError2 = true;
385 _iteratorError2 = err;
386 } finally {
387 try {
388 if (!_iteratorNormalCompletion2 && _iterator2.return != null) {
389 _iterator2.return();
390 }
391 } finally {
392 if (_didIteratorError2) {
393 throw _iteratorError2;
394 }
395 }
396 }
397 })();
398 }
399
400 expandFile(file, extensions, pkg, expandAliases = true) {
401 // Expand extensions and aliases
402 let res = [];
403 var _iteratorNormalCompletion3 = true;
404 var _didIteratorError3 = false;
405 var _iteratorError3 = undefined;
406
407 try {
408 for (var _iterator3 = extensions[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) {
409 let ext = _step3.value;
410 let f = file + ext;
411
412 if (expandAliases) {
413 let alias = this.resolveAliases(file + ext, pkg);
414
415 if (alias !== f) {
416 res = res.concat(this.expandFile(alias, extensions, pkg, false));
417 }
418 }
419
420 res.push(f);
421 }
422 } catch (err) {
423 _didIteratorError3 = true;
424 _iteratorError3 = err;
425 } finally {
426 try {
427 if (!_iteratorNormalCompletion3 && _iterator3.return != null) {
428 _iterator3.return();
429 }
430 } finally {
431 if (_didIteratorError3) {
432 throw _iteratorError3;
433 }
434 }
435 }
436
437 return res;
438 }
439
440 resolveAliases(filename, pkg) {
441 // First resolve local package aliases, then project global ones.
442 return this.resolvePackageAliases(this.resolvePackageAliases(filename, pkg), this.rootPackage);
443 }
444
445 resolvePackageAliases(filename, pkg) {
446 if (!pkg) {
447 return filename;
448 } // Resolve aliases in the package.source, package.alias, and package.browser fields.
449
450
451 return this.getAlias(filename, pkg.pkgdir, pkg.source) || this.getAlias(filename, pkg.pkgdir, pkg.alias) || this.getAlias(filename, pkg.pkgdir, this.getBrowserField(pkg)) || filename;
452 }
453
454 getAlias(filename, dir, aliases) {
455 if (!filename || !aliases || typeof aliases !== 'object') {
456 return null;
457 }
458
459 let alias; // If filename is an absolute path, get one relative to the package.json directory.
460
461 if (path.isAbsolute(filename)) {
462 filename = path.relative(dir, filename);
463
464 if (filename[0] !== '.') {
465 filename = './' + filename;
466 }
467
468 alias = this.lookupAlias(aliases, filename, dir);
469 } else {
470 // It is a node_module. First try the entire filename as a key.
471 alias = this.lookupAlias(aliases, filename, dir);
472
473 if (alias == null) {
474 // If it didn't match, try only the module name.
475 let parts = getModuleParts(filename);
476 alias = this.lookupAlias(aliases, parts[0], dir);
477
478 if (typeof alias === 'string') {
479 // Append the filename back onto the aliased module.
480 alias = path.join(alias, ...parts.slice(1));
481 }
482 }
483 } // If the alias is set to `false`, return an empty file.
484
485
486 if (alias === false) {
487 return EMPTY_SHIM;
488 }
489
490 return alias;
491 }
492
493 lookupAlias(aliases, filename, dir) {
494 // First, try looking up the exact filename
495 let alias = aliases[filename];
496
497 if (alias == null) {
498 // Otherwise, try replacing glob keys
499 for (let key in aliases) {
500 if (isGlob(key)) {
501 let re = micromatch.makeRe(key, {
502 capture: true
503 });
504
505 if (re.test(filename)) {
506 alias = filename.replace(re, aliases[key]);
507 break;
508 }
509 }
510 } // Or try a lookup replacing backslash characters with forward slash
511
512
513 if (alias == null && ~filename.indexOf('\\')) {
514 alias = aliases[filename.replace(/\\/g, '/')];
515 }
516 }
517
518 if (typeof alias === 'string') {
519 return this.resolveFilename(alias, dir);
520 }
521
522 return alias;
523 }
524
525 findPackage(dir) {
526 var _this9 = this;
527
528 return (0, _asyncToGenerator2.default)(function* () {
529 // Find the nearest package.json file within the current node_modules folder
530 let root = path.parse(dir).root;
531
532 while (dir !== root && path.basename(dir) !== 'node_modules') {
533 try {
534 return yield _this9.readPackage(dir);
535 } catch (err) {// ignore
536 }
537
538 dir = path.dirname(dir);
539 }
540 })();
541 }
542
543 loadAlias(filename, dir) {
544 var _this10 = this;
545
546 return (0, _asyncToGenerator2.default)(function* () {
547 // Load the root project's package.json file if we haven't already
548 if (!_this10.rootPackage) {
549 _this10.rootPackage = yield _this10.findPackage(_this10.options.rootDir);
550 } // Load the local package, and resolve aliases
551
552
553 let pkg = yield _this10.findPackage(dir);
554 return _this10.resolveAliases(filename, pkg);
555 })();
556 }
557
558}
559
560module.exports = Resolver;
\No newline at end of file