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