1 | const builtins = require('./builtins');
|
2 | const nodeBuiltins = require('node-libs-browser');
|
3 | const path = require('path');
|
4 | const {isGlob} = require('./utils/glob');
|
5 | const fs = require('@parcel/fs');
|
6 | const micromatch = require('micromatch');
|
7 | const getModuleParts = require('./utils/getModuleParts');
|
8 |
|
9 | const EMPTY_SHIM = require.resolve('./builtins/_empty');
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 | class Resolver {
|
25 | constructor(options = {}) {
|
26 | this.options = options;
|
27 | this.cache = new Map();
|
28 | this.packageCache = new Map();
|
29 | this.rootPackage = null;
|
30 | }
|
31 |
|
32 | async resolve(input, parent) {
|
33 | let filename = input;
|
34 |
|
35 |
|
36 | let key = this.getCacheKey(filename, parent);
|
37 | if (this.cache.has(key)) {
|
38 | return this.cache.get(key);
|
39 | }
|
40 |
|
41 |
|
42 | if (isGlob(filename)) {
|
43 | return {path: path.resolve(path.dirname(parent), filename)};
|
44 | }
|
45 |
|
46 |
|
47 | let extensions = Array.isArray(this.options.extensions)
|
48 | ? this.options.extensions.slice()
|
49 | : Object.keys(this.options.extensions);
|
50 |
|
51 | if (parent) {
|
52 |
|
53 | const parentExt = path.extname(parent);
|
54 | extensions = [parentExt, ...extensions.filter(ext => ext !== parentExt)];
|
55 | }
|
56 |
|
57 | extensions.unshift('');
|
58 |
|
59 |
|
60 | let module = await this.resolveModule(filename, parent);
|
61 | let resolved;
|
62 |
|
63 | if (module.moduleDir) {
|
64 | resolved = await this.loadNodeModules(module, extensions);
|
65 | } else if (module.filePath) {
|
66 | resolved = await this.loadRelative(module.filePath, extensions);
|
67 | }
|
68 |
|
69 | if (!resolved) {
|
70 | let dir = parent ? path.dirname(parent) : process.cwd();
|
71 | let err = new Error(`Cannot find module '${input}' from '${dir}'`);
|
72 | err.code = 'MODULE_NOT_FOUND';
|
73 | throw err;
|
74 | }
|
75 |
|
76 | this.cache.set(key, resolved);
|
77 | return resolved;
|
78 | }
|
79 |
|
80 | async resolveModule(filename, parent) {
|
81 | let dir = parent ? path.dirname(parent) : process.cwd();
|
82 |
|
83 |
|
84 | if (parent) {
|
85 | filename = this.resolveFilename(filename, dir);
|
86 | }
|
87 |
|
88 |
|
89 | filename = await this.loadAlias(filename, dir);
|
90 |
|
91 |
|
92 | if (path.isAbsolute(filename)) {
|
93 | return {
|
94 | filePath: filename
|
95 | };
|
96 | }
|
97 |
|
98 |
|
99 | let resolved;
|
100 | try {
|
101 | resolved = await this.findNodeModulePath(filename, dir);
|
102 | } catch (err) {
|
103 |
|
104 | }
|
105 |
|
106 |
|
107 | if (!resolved) {
|
108 | let parts = getModuleParts(filename);
|
109 | resolved = {
|
110 | moduleName: parts[0],
|
111 | subPath: parts[1]
|
112 | };
|
113 | }
|
114 |
|
115 | return resolved;
|
116 | }
|
117 |
|
118 | getCacheKey(filename, parent) {
|
119 | return (parent ? path.dirname(parent) : '') + ':' + filename;
|
120 | }
|
121 |
|
122 | resolveFilename(filename, dir) {
|
123 | switch (filename[0]) {
|
124 | case '/':
|
125 |
|
126 | return path.resolve(this.options.rootDir, filename.slice(1));
|
127 |
|
128 | case '~':
|
129 |
|
130 |
|
131 | while (
|
132 | dir !== this.options.rootDir &&
|
133 | path.basename(path.dirname(dir)) !== 'node_modules'
|
134 | ) {
|
135 | dir = path.dirname(dir);
|
136 |
|
137 | if (dir === path.dirname(dir)) {
|
138 | dir = this.options.rootDir;
|
139 | break;
|
140 | }
|
141 | }
|
142 |
|
143 | return path.join(dir, filename.slice(1));
|
144 |
|
145 | case '.':
|
146 |
|
147 | return path.resolve(dir, filename);
|
148 |
|
149 | default:
|
150 |
|
151 | return filename;
|
152 | }
|
153 | }
|
154 |
|
155 | async loadRelative(filename, extensions) {
|
156 |
|
157 | let pkg = await this.findPackage(path.dirname(filename));
|
158 |
|
159 |
|
160 | return (
|
161 | (await this.loadAsFile(filename, extensions, pkg)) ||
|
162 | (await this.loadDirectory(filename, extensions, pkg))
|
163 | );
|
164 | }
|
165 |
|
166 | async findNodeModulePath(filename, dir) {
|
167 | if (builtins[filename]) {
|
168 | if (this.options.target === 'node' && filename in nodeBuiltins) {
|
169 | throw new Error('Cannot resolve builtin module for node target');
|
170 | }
|
171 |
|
172 | return {filePath: builtins[filename]};
|
173 | }
|
174 |
|
175 | let parts = getModuleParts(filename);
|
176 | let root = path.parse(dir).root;
|
177 |
|
178 | while (dir !== root) {
|
179 |
|
180 | if (path.basename(dir) === 'node_modules') {
|
181 | dir = path.dirname(dir);
|
182 | }
|
183 |
|
184 | try {
|
185 |
|
186 | let moduleDir = path.join(dir, 'node_modules', parts[0]);
|
187 | let stats = await fs.stat(moduleDir);
|
188 | if (stats.isDirectory()) {
|
189 | return {
|
190 | moduleName: parts[0],
|
191 | subPath: parts[1],
|
192 | moduleDir: moduleDir,
|
193 | filePath: path.join(dir, 'node_modules', filename)
|
194 | };
|
195 | }
|
196 | } catch (err) {
|
197 |
|
198 | }
|
199 |
|
200 |
|
201 | dir = path.dirname(dir);
|
202 | }
|
203 | }
|
204 |
|
205 | async loadNodeModules(module, extensions) {
|
206 | try {
|
207 |
|
208 |
|
209 | if (module.subPath) {
|
210 | let pkg = await this.readPackage(module.moduleDir);
|
211 | let res = await this.loadAsFile(module.filePath, extensions, pkg);
|
212 | if (res) {
|
213 | return res;
|
214 | }
|
215 | }
|
216 |
|
217 |
|
218 | return await this.loadDirectory(module.filePath, extensions);
|
219 | } catch (e) {
|
220 |
|
221 | }
|
222 | }
|
223 |
|
224 | async isFile(file) {
|
225 | try {
|
226 | let stat = await fs.stat(file);
|
227 | return stat.isFile() || stat.isFIFO();
|
228 | } catch (err) {
|
229 | return false;
|
230 | }
|
231 | }
|
232 |
|
233 | async loadDirectory(dir, extensions, pkg) {
|
234 | try {
|
235 | pkg = await this.readPackage(dir);
|
236 |
|
237 |
|
238 | let entries = this.getPackageEntries(pkg);
|
239 |
|
240 | for (let file of entries) {
|
241 |
|
242 | const res =
|
243 | (await this.loadAsFile(file, extensions, pkg)) ||
|
244 | (await this.loadDirectory(file, extensions, pkg));
|
245 | if (res) {
|
246 | return res;
|
247 | }
|
248 | }
|
249 | } catch (err) {
|
250 |
|
251 | }
|
252 |
|
253 |
|
254 | return this.loadAsFile(path.join(dir, 'index'), extensions, pkg);
|
255 | }
|
256 |
|
257 | async readPackage(dir) {
|
258 | let file = path.join(dir, 'package.json');
|
259 | if (this.packageCache.has(file)) {
|
260 | return this.packageCache.get(file);
|
261 | }
|
262 |
|
263 | let json = await fs.readFile(file, 'utf8');
|
264 | let pkg = JSON.parse(json);
|
265 |
|
266 | pkg.pkgfile = file;
|
267 | pkg.pkgdir = dir;
|
268 |
|
269 |
|
270 |
|
271 | if (pkg.source) {
|
272 | let realpath = await fs.realpath(file);
|
273 | if (realpath === file) {
|
274 | delete pkg.source;
|
275 | }
|
276 | }
|
277 |
|
278 | this.packageCache.set(file, pkg);
|
279 | return pkg;
|
280 | }
|
281 |
|
282 | getBrowserField(pkg) {
|
283 | let target = this.options.target || 'browser';
|
284 | return target === 'browser' ? pkg.browser : null;
|
285 | }
|
286 |
|
287 | getPackageEntries(pkg) {
|
288 | let browser = this.getBrowserField(pkg);
|
289 | if (browser && typeof browser === 'object' && browser[pkg.name]) {
|
290 | browser = browser[pkg.name];
|
291 | }
|
292 |
|
293 |
|
294 |
|
295 |
|
296 | return [pkg.source, browser, pkg.module, pkg.main]
|
297 | .filter(entry => typeof entry === 'string')
|
298 | .map(main => {
|
299 |
|
300 | if (!main || main === '.' || main === './') {
|
301 | main = 'index';
|
302 | }
|
303 |
|
304 | return path.resolve(pkg.pkgdir, main);
|
305 | });
|
306 | }
|
307 |
|
308 | async loadAsFile(file, extensions, pkg) {
|
309 |
|
310 | for (let f of this.expandFile(file, extensions, pkg)) {
|
311 | if (await this.isFile(f)) {
|
312 | return {path: f, pkg};
|
313 | }
|
314 | }
|
315 | }
|
316 |
|
317 | expandFile(file, extensions, pkg, expandAliases = true) {
|
318 |
|
319 | let res = [];
|
320 | for (let ext of extensions) {
|
321 | let f = file + ext;
|
322 |
|
323 | if (expandAliases) {
|
324 | let alias = this.resolveAliases(file + ext, pkg);
|
325 | if (alias !== f) {
|
326 | res = res.concat(this.expandFile(alias, extensions, pkg, false));
|
327 | }
|
328 | }
|
329 |
|
330 | res.push(f);
|
331 | }
|
332 |
|
333 | return res;
|
334 | }
|
335 |
|
336 | resolveAliases(filename, pkg) {
|
337 |
|
338 | return this.resolvePackageAliases(
|
339 | this.resolvePackageAliases(filename, pkg),
|
340 | this.rootPackage
|
341 | );
|
342 | }
|
343 |
|
344 | resolvePackageAliases(filename, pkg) {
|
345 | if (!pkg) {
|
346 | return filename;
|
347 | }
|
348 |
|
349 |
|
350 | return (
|
351 | this.getAlias(filename, pkg.pkgdir, pkg.source) ||
|
352 | this.getAlias(filename, pkg.pkgdir, pkg.alias) ||
|
353 | this.getAlias(filename, pkg.pkgdir, this.getBrowserField(pkg)) ||
|
354 | filename
|
355 | );
|
356 | }
|
357 |
|
358 | getAlias(filename, dir, aliases) {
|
359 | if (!filename || !aliases || typeof aliases !== 'object') {
|
360 | return null;
|
361 | }
|
362 |
|
363 | let alias;
|
364 |
|
365 |
|
366 | if (path.isAbsolute(filename)) {
|
367 | filename = path.relative(dir, filename);
|
368 | if (filename[0] !== '.') {
|
369 | filename = './' + filename;
|
370 | }
|
371 |
|
372 | alias = this.lookupAlias(aliases, filename, dir);
|
373 | } else {
|
374 |
|
375 | alias = this.lookupAlias(aliases, filename, dir);
|
376 | if (alias == null) {
|
377 |
|
378 | let parts = getModuleParts(filename);
|
379 | alias = this.lookupAlias(aliases, parts[0], dir);
|
380 | if (typeof alias === 'string') {
|
381 |
|
382 | alias = path.join(alias, ...parts.slice(1));
|
383 | }
|
384 | }
|
385 | }
|
386 |
|
387 |
|
388 | if (alias === false) {
|
389 | return EMPTY_SHIM;
|
390 | }
|
391 |
|
392 | return alias;
|
393 | }
|
394 |
|
395 | lookupAlias(aliases, filename, dir) {
|
396 |
|
397 | let alias = aliases[filename];
|
398 | if (alias == null) {
|
399 |
|
400 | for (let key in aliases) {
|
401 | if (isGlob(key)) {
|
402 | let re = micromatch.makeRe(key, {capture: true});
|
403 | if (re.test(filename)) {
|
404 | alias = filename.replace(re, aliases[key]);
|
405 | break;
|
406 | }
|
407 | }
|
408 | }
|
409 |
|
410 | if (alias == null && ~filename.indexOf('\\')) {
|
411 | alias = aliases[filename.replace(/\\/g, '/')];
|
412 | }
|
413 | }
|
414 |
|
415 | if (typeof alias === 'string') {
|
416 | return this.resolveFilename(alias, dir);
|
417 | }
|
418 |
|
419 | return alias;
|
420 | }
|
421 |
|
422 | async findPackage(dir) {
|
423 |
|
424 | let root = path.parse(dir).root;
|
425 | while (dir !== root && path.basename(dir) !== 'node_modules') {
|
426 | try {
|
427 | return await this.readPackage(dir);
|
428 | } catch (err) {
|
429 |
|
430 | }
|
431 |
|
432 | dir = path.dirname(dir);
|
433 | }
|
434 | }
|
435 |
|
436 | async loadAlias(filename, dir) {
|
437 |
|
438 | if (!this.rootPackage) {
|
439 | this.rootPackage = await this.findPackage(this.options.rootDir);
|
440 | }
|
441 |
|
442 |
|
443 | let pkg = await this.findPackage(dir);
|
444 | return this.resolveAliases(filename, pkg);
|
445 | }
|
446 | }
|
447 |
|
448 | module.exports = Resolver;
|