UNPKG

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