UNPKG

16.6 kBJavaScriptView Raw
1'use strict';
2
3Object.defineProperty(exports, '__esModule', { value: true });
4
5var path = require('path');
6var builtinList = require('builtin-modules');
7var deepMerge = require('deepmerge');
8var isModule = require('is-module');
9var fs = require('fs');
10var util = require('util');
11var pluginutils = require('@rollup/pluginutils');
12var resolveModule = require('resolve');
13
14function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
15
16var builtinList__default = /*#__PURE__*/_interopDefaultLegacy(builtinList);
17var deepMerge__default = /*#__PURE__*/_interopDefaultLegacy(deepMerge);
18var isModule__default = /*#__PURE__*/_interopDefaultLegacy(isModule);
19var fs__default = /*#__PURE__*/_interopDefaultLegacy(fs);
20var resolveModule__default = /*#__PURE__*/_interopDefaultLegacy(resolveModule);
21
22const exists = util.promisify(fs__default['default'].exists);
23const readFile = util.promisify(fs__default['default'].readFile);
24const realpath = util.promisify(fs__default['default'].realpath);
25const stat = util.promisify(fs__default['default'].stat);
26
27const onError = (error) => {
28 if (error.code === 'ENOENT') {
29 return false;
30 }
31 throw error;
32};
33
34const makeCache = (fn) => {
35 const cache = new Map();
36 const wrapped = async (param, done) => {
37 if (cache.has(param) === false) {
38 cache.set(
39 param,
40 fn(param).catch((err) => {
41 cache.delete(param);
42 throw err;
43 })
44 );
45 }
46
47 try {
48 const result = cache.get(param);
49 const value = await result;
50 return done(null, value);
51 } catch (error) {
52 return done(error);
53 }
54 };
55
56 wrapped.clear = () => cache.clear();
57
58 return wrapped;
59};
60
61const isDirCached = makeCache(async (file) => {
62 try {
63 const stats = await stat(file);
64 return stats.isDirectory();
65 } catch (error) {
66 return onError(error);
67 }
68});
69
70const isFileCached = makeCache(async (file) => {
71 try {
72 const stats = await stat(file);
73 return stats.isFile();
74 } catch (error) {
75 return onError(error);
76 }
77});
78
79const readCachedFile = makeCache(readFile);
80
81const resolveId = util.promisify(resolveModule__default['default']);
82
83// returns the imported package name for bare module imports
84function getPackageName(id) {
85 if (id.startsWith('.') || id.startsWith('/')) {
86 return null;
87 }
88
89 const split = id.split('/');
90
91 // @my-scope/my-package/foo.js -> @my-scope/my-package
92 // @my-scope/my-package -> @my-scope/my-package
93 if (split[0][0] === '@') {
94 return `${split[0]}/${split[1]}`;
95 }
96
97 // my-package/foo.js -> my-package
98 // my-package -> my-package
99 return split[0];
100}
101
102function getMainFields(options) {
103 let mainFields;
104 if (options.mainFields) {
105 ({ mainFields } = options);
106 } else {
107 mainFields = ['module', 'main'];
108 }
109 if (options.browser && mainFields.indexOf('browser') === -1) {
110 return ['browser'].concat(mainFields);
111 }
112 if (!mainFields.length) {
113 throw new Error('Please ensure at least one `mainFields` value is specified');
114 }
115 return mainFields;
116}
117
118function getPackageInfo(options) {
119 const { cache, extensions, pkg, mainFields, preserveSymlinks, useBrowserOverrides } = options;
120 let { pkgPath } = options;
121
122 if (cache.has(pkgPath)) {
123 return cache.get(pkgPath);
124 }
125
126 // browserify/resolve doesn't realpath paths returned in its packageFilter callback
127 if (!preserveSymlinks) {
128 pkgPath = fs.realpathSync(pkgPath);
129 }
130
131 const pkgRoot = path.dirname(pkgPath);
132
133 const packageInfo = {
134 // copy as we are about to munge the `main` field of `pkg`.
135 packageJson: Object.assign({}, pkg),
136
137 // path to package.json file
138 packageJsonPath: pkgPath,
139
140 // directory containing the package.json
141 root: pkgRoot,
142
143 // which main field was used during resolution of this module (main, module, or browser)
144 resolvedMainField: 'main',
145
146 // whether the browser map was used to resolve the entry point to this module
147 browserMappedMain: false,
148
149 // the entry point of the module with respect to the selected main field and any
150 // relevant browser mappings.
151 resolvedEntryPoint: ''
152 };
153
154 let overriddenMain = false;
155 for (let i = 0; i < mainFields.length; i++) {
156 const field = mainFields[i];
157 if (typeof pkg[field] === 'string') {
158 pkg.main = pkg[field];
159 packageInfo.resolvedMainField = field;
160 overriddenMain = true;
161 break;
162 }
163 }
164
165 const internalPackageInfo = {
166 cachedPkg: pkg,
167 hasModuleSideEffects: () => null,
168 hasPackageEntry: overriddenMain !== false || mainFields.indexOf('main') !== -1,
169 packageBrowserField:
170 useBrowserOverrides &&
171 typeof pkg.browser === 'object' &&
172 Object.keys(pkg.browser).reduce((browser, key) => {
173 let resolved = pkg.browser[key];
174 if (resolved && resolved[0] === '.') {
175 resolved = path.resolve(pkgRoot, resolved);
176 }
177 /* eslint-disable no-param-reassign */
178 browser[key] = resolved;
179 if (key[0] === '.') {
180 const absoluteKey = path.resolve(pkgRoot, key);
181 browser[absoluteKey] = resolved;
182 if (!path.extname(key)) {
183 extensions.reduce((subBrowser, ext) => {
184 subBrowser[absoluteKey + ext] = subBrowser[key];
185 return subBrowser;
186 }, browser);
187 }
188 }
189 return browser;
190 }, {}),
191 packageInfo
192 };
193
194 const browserMap = internalPackageInfo.packageBrowserField;
195 if (
196 useBrowserOverrides &&
197 typeof pkg.browser === 'object' &&
198 // eslint-disable-next-line no-prototype-builtins
199 browserMap.hasOwnProperty(pkg.main)
200 ) {
201 packageInfo.resolvedEntryPoint = browserMap[pkg.main];
202 packageInfo.browserMappedMain = true;
203 } else {
204 // index.node is technically a valid default entrypoint as well...
205 packageInfo.resolvedEntryPoint = path.resolve(pkgRoot, pkg.main || 'index.js');
206 packageInfo.browserMappedMain = false;
207 }
208
209 const packageSideEffects = pkg.sideEffects;
210 if (typeof packageSideEffects === 'boolean') {
211 internalPackageInfo.hasModuleSideEffects = () => packageSideEffects;
212 } else if (Array.isArray(packageSideEffects)) {
213 internalPackageInfo.hasModuleSideEffects = pluginutils.createFilter(packageSideEffects, null, {
214 resolve: pkgRoot
215 });
216 }
217
218 cache.set(pkgPath, internalPackageInfo);
219 return internalPackageInfo;
220}
221
222function normalizeInput(input) {
223 if (Array.isArray(input)) {
224 return input;
225 } else if (typeof input === 'object') {
226 return Object.values(input);
227 }
228
229 // otherwise it's a string
230 return [input];
231}
232
233// Resolve module specifiers in order. Promise resolves to the first module that resolves
234// successfully, or the error that resulted from the last attempted module resolution.
235function resolveImportSpecifiers(importSpecifierList, resolveOptions) {
236 let promise = Promise.resolve();
237
238 for (let i = 0; i < importSpecifierList.length; i++) {
239 // eslint-disable-next-line no-loop-func
240 promise = promise.then(async (value) => {
241 // if we've already resolved to something, just return it.
242 if (value) {
243 return value;
244 }
245
246 let result = await resolveId(importSpecifierList[i], resolveOptions);
247 if (!resolveOptions.preserveSymlinks) {
248 if (await exists(result)) {
249 result = await realpath(result);
250 }
251 }
252 return result;
253 });
254
255 // swallow MODULE_NOT_FOUND errors
256 promise = promise.catch((error) => {
257 if (error.code !== 'MODULE_NOT_FOUND') {
258 throw error;
259 }
260 });
261 }
262
263 return promise;
264}
265
266/* eslint-disable no-param-reassign, no-shadow, no-undefined */
267
268const builtins = new Set(builtinList__default['default']);
269const ES6_BROWSER_EMPTY = '\0node-resolve:empty.js';
270const nullFn = () => null;
271const deepFreeze = (object) => {
272 Object.freeze(object);
273
274 for (const value of Object.values(object)) {
275 if (typeof value === 'object' && !Object.isFrozen(value)) {
276 deepFreeze(value);
277 }
278 }
279
280 return object;
281};
282const defaults = {
283 customResolveOptions: {},
284 dedupe: [],
285 // It's important that .mjs is listed before .js so that Rollup will interpret npm modules
286 // which deploy both ESM .mjs and CommonJS .js files as ESM.
287 extensions: ['.mjs', '.js', '.json', '.node'],
288 resolveOnly: []
289};
290const DEFAULTS = deepFreeze(deepMerge__default['default']({}, defaults));
291
292function nodeResolve(opts = {}) {
293 const options = Object.assign({}, defaults, opts);
294 const { customResolveOptions, extensions, jail } = options;
295 const warnings = [];
296 const packageInfoCache = new Map();
297 const idToPackageInfo = new Map();
298 const mainFields = getMainFields(options);
299 const useBrowserOverrides = mainFields.indexOf('browser') !== -1;
300 const isPreferBuiltinsSet = options.preferBuiltins === true || options.preferBuiltins === false;
301 const preferBuiltins = isPreferBuiltinsSet ? options.preferBuiltins : true;
302 const rootDir = options.rootDir || process.cwd();
303 let { dedupe } = options;
304 let rollupOptions;
305
306 if (options.only) {
307 warnings.push('node-resolve: The `only` options is deprecated, please use `resolveOnly`');
308 options.resolveOnly = options.only;
309 }
310
311 if (typeof dedupe !== 'function') {
312 dedupe = (importee) =>
313 options.dedupe.includes(importee) || options.dedupe.includes(getPackageName(importee));
314 }
315
316 const resolveOnly = options.resolveOnly.map((pattern) => {
317 if (pattern instanceof RegExp) {
318 return pattern;
319 }
320 const normalized = pattern.replace(/[\\^$*+?.()|[\]{}]/g, '\\$&');
321 return new RegExp(`^${normalized}$`);
322 });
323
324 const browserMapCache = new Map();
325 let preserveSymlinks;
326
327 return {
328 name: 'node-resolve',
329
330 buildStart(options) {
331 rollupOptions = options;
332
333 for (const warning of warnings) {
334 this.warn(warning);
335 }
336
337 ({ preserveSymlinks } = options);
338 },
339
340 generateBundle() {
341 readCachedFile.clear();
342 isFileCached.clear();
343 isDirCached.clear();
344 },
345
346 async resolveId(importee, importer) {
347 if (importee === ES6_BROWSER_EMPTY) {
348 return importee;
349 }
350 // ignore IDs with null character, these belong to other plugins
351 if (/\0/.test(importee)) return null;
352
353 if (/\0/.test(importer)) {
354 importer = undefined;
355 }
356
357 // strip query params from import
358 const [importPath, params] = importee.split('?');
359 const importSuffix = `${params ? `?${params}` : ''}`;
360 importee = importPath;
361
362 const basedir = !importer || dedupe(importee) ? rootDir : path.dirname(importer);
363
364 // https://github.com/defunctzombie/package-browser-field-spec
365 const browser = browserMapCache.get(importer);
366 if (useBrowserOverrides && browser) {
367 const resolvedImportee = path.resolve(basedir, importee);
368 if (browser[importee] === false || browser[resolvedImportee] === false) {
369 return ES6_BROWSER_EMPTY;
370 }
371 const browserImportee =
372 browser[importee] ||
373 browser[resolvedImportee] ||
374 browser[`${resolvedImportee}.js`] ||
375 browser[`${resolvedImportee}.json`];
376 if (browserImportee) {
377 importee = browserImportee;
378 }
379 }
380
381 const parts = importee.split(/[/\\]/);
382 let id = parts.shift();
383 let isRelativeImport = false;
384
385 if (id[0] === '@' && parts.length > 0) {
386 // scoped packages
387 id += `/${parts.shift()}`;
388 } else if (id[0] === '.') {
389 // an import relative to the parent dir of the importer
390 id = path.resolve(basedir, importee);
391 isRelativeImport = true;
392 }
393
394 if (
395 !isRelativeImport &&
396 resolveOnly.length &&
397 !resolveOnly.some((pattern) => pattern.test(id))
398 ) {
399 if (normalizeInput(rollupOptions.input).includes(importee)) {
400 return null;
401 }
402 return false;
403 }
404
405 let hasModuleSideEffects = nullFn;
406 let hasPackageEntry = true;
407 let packageBrowserField = false;
408 let packageInfo;
409
410 const filter = (pkg, pkgPath) => {
411 const info = getPackageInfo({
412 cache: packageInfoCache,
413 extensions,
414 pkg,
415 pkgPath,
416 mainFields,
417 preserveSymlinks,
418 useBrowserOverrides
419 });
420
421 ({ packageInfo, hasModuleSideEffects, hasPackageEntry, packageBrowserField } = info);
422
423 return info.cachedPkg;
424 };
425
426 let resolveOptions = {
427 basedir,
428 packageFilter: filter,
429 readFile: readCachedFile,
430 isFile: isFileCached,
431 isDirectory: isDirCached,
432 extensions
433 };
434
435 if (preserveSymlinks !== undefined) {
436 resolveOptions.preserveSymlinks = preserveSymlinks;
437 }
438
439 const importSpecifierList = [];
440
441 if (importer === undefined && !importee[0].match(/^\.?\.?\//)) {
442 // For module graph roots (i.e. when importer is undefined), we
443 // need to handle 'path fragments` like `foo/bar` that are commonly
444 // found in rollup config files. If importee doesn't look like a
445 // relative or absolute path, we make it relative and attempt to
446 // resolve it. If we don't find anything, we try resolving it as we
447 // got it.
448 importSpecifierList.push(`./${importee}`);
449 }
450
451 const importeeIsBuiltin = builtins.has(importee);
452
453 if (importeeIsBuiltin && (!preferBuiltins || !isPreferBuiltinsSet)) {
454 // The `resolve` library will not resolve packages with the same
455 // name as a node built-in module. If we're resolving something
456 // that's a builtin, and we don't prefer to find built-ins, we
457 // first try to look up a local module with that name. If we don't
458 // find anything, we resolve the builtin which just returns back
459 // the built-in's name.
460 importSpecifierList.push(`${importee}/`);
461 }
462
463 // TypeScript files may import '.js' to refer to either '.ts' or '.tsx'
464 if (importer && importee.endsWith('.js')) {
465 for (const ext of ['.ts', '.tsx']) {
466 if (importer.endsWith(ext) && extensions.includes(ext)) {
467 importSpecifierList.push(importee.replace(/.js$/, ext));
468 }
469 }
470 }
471
472 importSpecifierList.push(importee);
473 resolveOptions = Object.assign(resolveOptions, customResolveOptions);
474
475 let resolved = await resolveImportSpecifiers(importSpecifierList, resolveOptions);
476 if (!resolved) {
477 return null;
478 }
479
480 if (packageBrowserField) {
481 if (Object.prototype.hasOwnProperty.call(packageBrowserField, resolved)) {
482 if (!packageBrowserField[resolved]) {
483 browserMapCache.set(resolved, packageBrowserField);
484 return ES6_BROWSER_EMPTY;
485 }
486 resolved = packageBrowserField[resolved];
487 }
488 browserMapCache.set(resolved, packageBrowserField);
489 }
490
491 if (hasPackageEntry && !preserveSymlinks) {
492 const fileExists = await exists(resolved);
493 if (fileExists) {
494 resolved = await realpath(resolved);
495 }
496 }
497
498 idToPackageInfo.set(resolved, packageInfo);
499
500 if (hasPackageEntry) {
501 if (builtins.has(resolved) && preferBuiltins && isPreferBuiltinsSet) {
502 return null;
503 } else if (importeeIsBuiltin && preferBuiltins) {
504 if (!isPreferBuiltinsSet) {
505 this.warn(
506 `preferring built-in module '${importee}' over local alternative at '${resolved}', pass 'preferBuiltins: false' to disable this behavior or 'preferBuiltins: true' to disable this warning`
507 );
508 }
509 return null;
510 } else if (jail && resolved.indexOf(path.normalize(jail.trim(path.sep))) !== 0) {
511 return null;
512 }
513 }
514
515 if (options.modulesOnly && (await exists(resolved))) {
516 const code = await readFile(resolved, 'utf-8');
517 if (isModule__default['default'](code)) {
518 return {
519 id: `${resolved}${importSuffix}`,
520 moduleSideEffects: hasModuleSideEffects(resolved)
521 };
522 }
523 return null;
524 }
525 const result = {
526 id: `${resolved}${importSuffix}`,
527 moduleSideEffects: hasModuleSideEffects(resolved)
528 };
529 return result;
530 },
531
532 load(importee) {
533 if (importee === ES6_BROWSER_EMPTY) {
534 return 'export default {};';
535 }
536 return null;
537 },
538
539 getPackageInfoForId(id) {
540 return idToPackageInfo.get(id);
541 }
542 };
543}
544
545exports.DEFAULTS = DEFAULTS;
546exports.default = nodeResolve;
547exports.nodeResolve = nodeResolve;