@rollup/plugin-node-resolve 11.0.1 → 11.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # @rollup/plugin-node-resolve ChangeLog
2
2
 
3
+ ## v11.1.0
4
+
5
+ _2021-01-15_
6
+
7
+ ### Features
8
+
9
+ - feat: support pkg imports and export array (#693)
10
+
3
11
  ## v11.0.1
4
12
 
5
13
  _2020-12-14_
package/README.md CHANGED
@@ -42,6 +42,10 @@ export default {
42
42
 
43
43
  Then call `rollup` either via the [CLI](https://www.rollupjs.org/guide/en/#command-line-reference) or the [API](https://www.rollupjs.org/guide/en/#javascript-api).
44
44
 
45
+ ## Package entrypoints
46
+
47
+ This plugin supports the package entrypoints feature from node js, specified in the `exports` or `imports` field of a package. Check the [official documentation](https://nodejs.org/api/packages.html#packages_package_entry_points) for more information on how this works.
48
+
45
49
  ## Options
46
50
 
47
51
  ### `exportConditions`
@@ -62,6 +66,8 @@ Default: `false`
62
66
 
63
67
  If `true`, instructs the plugin to use the `"browser"` property in `package.json` files to specify alternative files to load for bundling. This is useful when bundling for a browser environment. Alternatively, a value of `'browser'` can be added to the `mainFields` option. If `false`, any `"browser"` properties in package files will be ignored. This option takes precedence over `mainFields`.
64
68
 
69
+ > This option does not work when a package is using [package entrypoints](https://nodejs.org/api/packages.html#packages_package_entry_points)
70
+
65
71
  ### `moduleDirectories`
66
72
 
67
73
  Type: `Array[...String]`<br>
package/dist/cjs/index.js CHANGED
@@ -8,6 +8,7 @@ var deepMerge = require('deepmerge');
8
8
  var isModule = require('is-module');
9
9
  var fs = require('fs');
10
10
  var util = require('util');
11
+ var url = require('url');
11
12
  var resolve = require('resolve');
12
13
  var pluginutils = require('@rollup/pluginutils');
13
14
 
@@ -229,168 +230,332 @@ function normalizeInput(input) {
229
230
  return [input];
230
231
  }
231
232
 
232
- const resolveImportPath = util.promisify(resolve__default['default']);
233
- const readFile$1 = util.promisify(fs__default['default'].readFile);
233
+ /* eslint-disable no-await-in-loop */
234
+
235
+ const fileExists = util.promisify(fs__default['default'].exists);
236
+
237
+ function isModuleDir(current, moduleDirs) {
238
+ return moduleDirs.some((dir) => current.endsWith(dir));
239
+ }
234
240
 
235
- const pathNotFoundError = (importPath, importer, subPath, pkgPath) =>
236
- new Error(
237
- `Could not resolve import "${importPath}" in "${importer}".` +
238
- ` Package subpath "${subPath}" is not defined by "exports" in ${pkgPath}`
239
- );
241
+ async function findPackageJson(base, moduleDirs) {
242
+ const { root } = path__default['default'].parse(base);
243
+ let current = base;
240
244
 
241
- function findExportKeyMatch(exportMap, subPath) {
242
- if (subPath in exportMap) {
243
- return subPath;
245
+ while (current !== root && !isModuleDir(current, moduleDirs)) {
246
+ const pkgJsonPath = path__default['default'].join(current, 'package.json');
247
+ if (await fileExists(pkgJsonPath)) {
248
+ const pkgJsonString = fs__default['default'].readFileSync(pkgJsonPath, 'utf-8');
249
+ return { pkgJson: JSON.parse(pkgJsonString), pkgPath: current, pkgJsonPath };
250
+ }
251
+ current = path__default['default'].resolve(current, '..');
244
252
  }
253
+ return null;
254
+ }
245
255
 
246
- const matchKeys = Object.keys(exportMap)
247
- .filter((key) => key.endsWith('/') || key.endsWith('*'))
248
- .sort((a, b) => b.length - a.length);
256
+ function isUrl(str) {
257
+ try {
258
+ return !!new URL(str);
259
+ } catch (_) {
260
+ return false;
261
+ }
262
+ }
263
+
264
+ function isConditions(exports) {
265
+ return typeof exports === 'object' && Object.keys(exports).every((k) => !k.startsWith('.'));
266
+ }
267
+
268
+ function isMappings(exports) {
269
+ return typeof exports === 'object' && !isConditions(exports);
270
+ }
271
+
272
+ function isMixedExports(exports) {
273
+ const keys = Object.keys(exports);
274
+ return keys.some((k) => k.startsWith('.')) && keys.some((k) => !k.startsWith('.'));
275
+ }
276
+
277
+ function createBaseErrorMsg(importSpecifier, importer) {
278
+ return `Could not resolve import "${importSpecifier}" in ${importer}`;
279
+ }
280
+
281
+ function createErrorMsg(context, reason, internal) {
282
+ const { importSpecifier, importer, pkgJsonPath } = context;
283
+ const base = createBaseErrorMsg(importSpecifier, importer);
284
+ const field = internal ? 'imports' : 'exports';
285
+ return `${base} using ${field} defined in ${pkgJsonPath}.${reason ? ` ${reason}` : ''}`;
286
+ }
287
+
288
+ class ResolveError extends Error {}
289
+
290
+ class InvalidConfigurationError extends ResolveError {
291
+ constructor(context, reason) {
292
+ super(createErrorMsg(context, `Invalid "exports" field. ${reason}`));
293
+ }
294
+ }
295
+
296
+ class InvalidModuleSpecifierError extends ResolveError {
297
+ constructor(context, internal) {
298
+ super(createErrorMsg(context, internal));
299
+ }
300
+ }
249
301
 
250
- for (const key of matchKeys) {
251
- if (key.endsWith('*')) {
252
- // star match: "./foo/*": "./foo/*.js"
253
- const keyWithoutStar = key.substring(0, key.length - 1);
254
- if (subPath.startsWith(keyWithoutStar)) {
255
- return key;
302
+ class InvalidPackageTargetError extends ResolveError {
303
+ constructor(context, reason) {
304
+ super(createErrorMsg(context, reason));
305
+ }
306
+ }
307
+
308
+ /* eslint-disable no-await-in-loop, no-undefined */
309
+
310
+ function includesInvalidSegments(pathSegments, moduleDirs) {
311
+ return pathSegments
312
+ .split('/')
313
+ .slice(1)
314
+ .some((t) => ['.', '..', ...moduleDirs].includes(t));
315
+ }
316
+
317
+ async function resolvePackageTarget(context, { target, subpath, pattern, internal }) {
318
+ if (typeof target === 'string') {
319
+ if (!pattern && subpath.length > 0 && !target.endsWith('/')) {
320
+ throw new InvalidModuleSpecifierError(context);
321
+ }
322
+
323
+ if (!target.startsWith('./')) {
324
+ if (internal && !['/', '../'].some((p) => target.startsWith(p)) && !isUrl(target)) {
325
+ // this is a bare package import, remap it and resolve it using regular node resolve
326
+ if (pattern) {
327
+ const result = await context.resolveId(
328
+ target.replace(/\*/g, subpath),
329
+ context.pkgURL.href
330
+ );
331
+ return result ? url.pathToFileURL(result.location) : null;
332
+ }
333
+
334
+ const result = await context.resolveId(`${target}${subpath}`, context.pkgURL.href);
335
+ return result ? url.pathToFileURL(result.location) : null;
256
336
  }
337
+ throw new InvalidPackageTargetError(context, `Invalid mapping: "${target}".`);
338
+ }
339
+
340
+ if (includesInvalidSegments(target, context.moduleDirs)) {
341
+ throw new InvalidPackageTargetError(context, `Invalid mapping: "${target}".`);
342
+ }
343
+
344
+ const resolvedTarget = new URL(target, context.pkgURL);
345
+ if (!resolvedTarget.href.startsWith(context.pkgURL.href)) {
346
+ throw new InvalidPackageTargetError(
347
+ context,
348
+ `Resolved to ${resolvedTarget.href} which is outside package ${context.pkgURL.href}`
349
+ );
257
350
  }
258
351
 
259
- if (key.endsWith('/') && subPath.startsWith(key)) {
260
- // directory match (deprecated by node): "./foo/": "./foo/.js"
261
- return key;
352
+ if (includesInvalidSegments(subpath, context.moduleDirs)) {
353
+ throw new InvalidModuleSpecifierError(context);
262
354
  }
263
355
 
264
- if (key === subPath) {
265
- // literal match
266
- return key;
356
+ if (pattern) {
357
+ return resolvedTarget.href.replace(/\*/g, subpath);
267
358
  }
359
+ return new URL(subpath, resolvedTarget).href;
268
360
  }
269
- return null;
270
- }
271
361
 
272
- function mapSubPath({ importPath, importer, pkgJsonPath, subPath, key, value }) {
273
- if (typeof value === 'string') {
274
- if (typeof key === 'string' && key.endsWith('*')) {
275
- // star match: "./foo/*": "./foo/*.js"
276
- const keyWithoutStar = key.substring(0, key.length - 1);
277
- const subPathAfterKey = subPath.substring(keyWithoutStar.length);
278
- return value.replace(/\*/g, subPathAfterKey);
362
+ if (Array.isArray(target)) {
363
+ let lastError;
364
+ for (const item of target) {
365
+ try {
366
+ const resolved = await resolvePackageTarget(context, {
367
+ target: item,
368
+ subpath,
369
+ pattern,
370
+ internal
371
+ });
372
+
373
+ // return if defined or null, but not undefined
374
+ if (resolved !== undefined) {
375
+ return resolved;
376
+ }
377
+ } catch (error) {
378
+ if (!(error instanceof InvalidPackageTargetError)) {
379
+ throw error;
380
+ } else {
381
+ lastError = error;
382
+ }
383
+ }
279
384
  }
280
385
 
281
- if (value.endsWith('/')) {
282
- // directory match (deprecated by node): "./foo/": "./foo/.js"
283
- return `${value}${subPath.substring(key.length)}`;
386
+ if (lastError) {
387
+ throw lastError;
284
388
  }
389
+ return null;
390
+ }
285
391
 
286
- // mapping is a string, for example { "./foo": "./dist/foo.js" }
287
- return value;
392
+ if (target && typeof target === 'object') {
393
+ for (const [key, value] of Object.entries(target)) {
394
+ if (key === 'default' || context.conditions.includes(key)) {
395
+ const resolved = await resolvePackageTarget(context, {
396
+ target: value,
397
+ subpath,
398
+ pattern,
399
+ internal
400
+ });
401
+
402
+ // return if defined or null, but not undefined
403
+ if (resolved !== undefined) {
404
+ return resolved;
405
+ }
406
+ }
407
+ }
408
+ return undefined;
288
409
  }
289
410
 
290
- if (Array.isArray(value)) {
291
- // mapping is an array with fallbacks, for example { "./foo": ["foo:bar", "./dist/foo.js"] }
292
- return value.find((v) => v.startsWith('./'));
411
+ if (target === null) {
412
+ return null;
293
413
  }
294
414
 
295
- throw pathNotFoundError(importPath, importer, subPath, pkgJsonPath);
415
+ throw new InvalidPackageTargetError(context, `Invalid exports field.`);
296
416
  }
297
417
 
298
- function findEntrypoint({
299
- importPath,
300
- importer,
301
- pkgJsonPath,
302
- subPath,
303
- exportMap,
304
- conditions,
305
- key
306
- }) {
307
- if (typeof exportMap !== 'object') {
308
- return mapSubPath({ importPath, importer, pkgJsonPath, subPath, key, value: exportMap });
418
+ /* eslint-disable no-await-in-loop */
419
+
420
+ async function resolvePackageImportsExports(context, { matchKey, matchObj, internal }) {
421
+ if (!matchKey.endsWith('*') && matchKey in matchObj) {
422
+ const target = matchObj[matchKey];
423
+ const resolved = await resolvePackageTarget(context, { target, subpath: '', internal });
424
+ return resolved;
309
425
  }
310
426
 
311
- // iterate conditions recursively, find the first that matches all conditions
312
- for (const [condition, subExportMap] of Object.entries(exportMap)) {
313
- if (conditions.includes(condition)) {
314
- const mappedSubPath = findEntrypoint({
315
- importPath,
316
- importer,
317
- pkgJsonPath,
318
- subPath,
319
- exportMap: subExportMap,
320
- conditions,
321
- key
427
+ const expansionKeys = Object.keys(matchObj)
428
+ .filter((k) => k.endsWith('/') || k.endsWith('*'))
429
+ .sort((a, b) => b.length - a.length);
430
+
431
+ for (const expansionKey of expansionKeys) {
432
+ const prefix = expansionKey.substring(0, expansionKey.length - 1);
433
+
434
+ if (expansionKey.endsWith('*') && matchKey.startsWith(prefix)) {
435
+ const target = matchObj[expansionKey];
436
+ const subpath = matchKey.substring(expansionKey.length - 1);
437
+ const resolved = await resolvePackageTarget(context, {
438
+ target,
439
+ subpath,
440
+ pattern: true,
441
+ internal
322
442
  });
323
- if (mappedSubPath) {
324
- return mappedSubPath;
325
- }
443
+ return resolved;
326
444
  }
327
- }
328
- throw pathNotFoundError(importer, subPath, pkgJsonPath);
329
- }
330
445
 
331
- function findEntrypointTopLevel({
332
- importPath,
333
- importer,
334
- pkgJsonPath,
335
- subPath,
336
- exportMap,
337
- conditions
338
- }) {
339
- if (typeof exportMap !== 'object') {
340
- // the export map shorthand, for example { exports: "./index.js" }
341
- if (subPath !== '.') {
342
- // shorthand only supports a main entrypoint
343
- throw pathNotFoundError(importPath, importer, subPath, pkgJsonPath);
446
+ if (matchKey.startsWith(expansionKey)) {
447
+ const target = matchObj[expansionKey];
448
+ const subpath = matchKey.substring(expansionKey.length);
449
+
450
+ const resolved = await resolvePackageTarget(context, { target, subpath, internal });
451
+ return resolved;
344
452
  }
345
- return mapSubPath({ importPath, importer, pkgJsonPath, subPath, key: null, value: exportMap });
346
453
  }
347
454
 
348
- // export map is an object, the top level can be either conditions or sub path mappings
349
- const keys = Object.keys(exportMap);
350
- const isConditions = keys.every((k) => !k.startsWith('.'));
351
- const isMappings = keys.every((k) => k.startsWith('.'));
455
+ throw new InvalidModuleSpecifierError(context, internal);
456
+ }
352
457
 
353
- if (!isConditions && !isMappings) {
354
- throw new Error(
355
- `Invalid package config ${pkgJsonPath}, "exports" cannot contain some keys starting with '.'` +
356
- ' and some not. The exports object must either be an object of package subpath keys or an object of main entry' +
357
- ' condition name keys only.'
458
+ async function resolvePackageExports(context, subpath, exports) {
459
+ if (isMixedExports(exports)) {
460
+ throw new InvalidConfigurationError(
461
+ context,
462
+ 'All keys must either start with ./, or without one.'
358
463
  );
359
464
  }
360
465
 
361
- let key = null;
362
- let exportMapForSubPath;
466
+ if (subpath === '.') {
467
+ let mainExport;
468
+ // If exports is a String or Array, or an Object containing no keys starting with ".", then
469
+ if (typeof exports === 'string' || Array.isArray(exports) || isConditions(exports)) {
470
+ mainExport = exports;
471
+ } else if (isMappings(exports)) {
472
+ mainExport = exports['.'];
473
+ }
363
474
 
364
- if (isConditions) {
365
- // top level is conditions, for example { "import": ..., "require": ..., "module": ... }
366
- if (subPath !== '.') {
367
- // package with top level conditions means it only supports a main entrypoint
368
- throw pathNotFoundError(importPath, importer, subPath, pkgJsonPath);
475
+ if (mainExport) {
476
+ const resolved = await resolvePackageTarget(context, { target: mainExport, subpath: '' });
477
+ if (resolved) {
478
+ return resolved;
479
+ }
369
480
  }
370
- exportMapForSubPath = exportMap;
371
- } else {
372
- // top level is sub path mappings, for example { ".": ..., "./foo": ..., "./bar": ... }
373
- key = findExportKeyMatch(exportMap, subPath);
374
- if (!key) {
375
- throw pathNotFoundError(importPath, importer, subPath, pkgJsonPath);
481
+ } else if (isMappings(exports)) {
482
+ const resolvedMatch = await resolvePackageImportsExports(context, {
483
+ matchKey: subpath,
484
+ matchObj: exports
485
+ });
486
+
487
+ if (resolvedMatch) {
488
+ return resolvedMatch;
376
489
  }
377
- exportMapForSubPath = exportMap[key];
378
490
  }
379
491
 
380
- return findEntrypoint({
381
- importPath,
492
+ throw new InvalidModuleSpecifierError(context);
493
+ }
494
+
495
+ async function resolvePackageImports({
496
+ importSpecifier,
497
+ importer,
498
+ moduleDirs,
499
+ conditions,
500
+ resolveId
501
+ }) {
502
+ const result = await findPackageJson(importer, moduleDirs);
503
+ if (!result) {
504
+ throw new Error(createBaseErrorMsg('. Could not find a parent package.json.'));
505
+ }
506
+
507
+ const { pkgPath, pkgJsonPath, pkgJson } = result;
508
+ const pkgURL = url.pathToFileURL(`${pkgPath}/`);
509
+ const context = {
382
510
  importer,
511
+ importSpecifier,
512
+ moduleDirs,
513
+ pkgURL,
383
514
  pkgJsonPath,
384
- subPath,
385
- exportMap: exportMapForSubPath,
386
515
  conditions,
387
- key
516
+ resolveId
517
+ };
518
+
519
+ const { imports } = pkgJson;
520
+ if (!imports) {
521
+ throw new InvalidModuleSpecifierError(context, true);
522
+ }
523
+
524
+ if (importSpecifier === '#' || importSpecifier.startsWith('#/')) {
525
+ throw new InvalidModuleSpecifierError(context, 'Invalid import specifier.');
526
+ }
527
+
528
+ return resolvePackageImportsExports(context, {
529
+ matchKey: importSpecifier,
530
+ matchObj: imports,
531
+ internal: true
388
532
  });
389
533
  }
390
534
 
535
+ const resolveImportPath = util.promisify(resolve__default['default']);
536
+ const readFile$1 = util.promisify(fs__default['default'].readFile);
537
+
538
+ async function getPackageJson(importer, pkgName, resolveOptions, moduleDirectories) {
539
+ if (importer) {
540
+ const selfPackageJsonResult = await findPackageJson(importer, moduleDirectories);
541
+ if (selfPackageJsonResult && selfPackageJsonResult.pkgJson.name === pkgName) {
542
+ // the referenced package name is the current package
543
+ return selfPackageJsonResult;
544
+ }
545
+ }
546
+
547
+ try {
548
+ const pkgJsonPath = await resolveImportPath(`${pkgName}/package.json`, resolveOptions);
549
+ const pkgJson = JSON.parse(await readFile$1(pkgJsonPath, 'utf-8'));
550
+ return { pkgJsonPath, pkgJson };
551
+ } catch (_) {
552
+ return null;
553
+ }
554
+ }
555
+
391
556
  async function resolveId({
392
557
  importer,
393
- importPath,
558
+ importSpecifier,
394
559
  exportConditions,
395
560
  warn,
396
561
  packageInfoCache,
@@ -436,32 +601,61 @@ async function resolveId({
436
601
 
437
602
  let location;
438
603
 
439
- const pkgName = getPackageName(importPath);
440
- if (pkgName) {
441
- let pkgJsonPath;
442
- let pkgJson;
443
- try {
444
- pkgJsonPath = await resolveImportPath(`${pkgName}/package.json`, resolveOptions);
445
- pkgJson = JSON.parse(await readFile$1(pkgJsonPath, 'utf-8'));
446
- } catch (_) {
447
- // if there is no package.json we defer to regular resolve behavior
448
- }
604
+ const pkgName = getPackageName(importSpecifier);
605
+ if (importSpecifier.startsWith('#')) {
606
+ // this is a package internal import, resolve using package imports field
607
+ const resolveResult = await resolvePackageImports({
608
+ importSpecifier,
609
+ importer,
610
+ moduleDirs: moduleDirectories,
611
+ conditions: exportConditions,
612
+ resolveId(id, parent) {
613
+ return resolveId({
614
+ importSpecifier: id,
615
+ importer: parent,
616
+ exportConditions,
617
+ warn,
618
+ packageInfoCache,
619
+ extensions,
620
+ mainFields,
621
+ preserveSymlinks,
622
+ useBrowserOverrides,
623
+ baseDir,
624
+ moduleDirectories
625
+ });
626
+ }
627
+ });
628
+ location = url.fileURLToPath(resolveResult);
629
+ } else if (pkgName) {
630
+ // it's a bare import, find the package.json and resolve using package exports if available
631
+ const result = await getPackageJson(importer, pkgName, resolveOptions, moduleDirectories);
449
632
 
450
- if (pkgJsonPath && pkgJson && pkgJson.exports) {
633
+ if (result && result.pkgJson.exports) {
634
+ const { pkgJson, pkgJsonPath } = result;
451
635
  try {
452
- const packageSubPath =
453
- pkgName === importPath ? '.' : `.${importPath.substring(pkgName.length)}`;
454
- const mappedSubPath = findEntrypointTopLevel({
636
+ const subpath =
637
+ pkgName === importSpecifier ? '.' : `.${importSpecifier.substring(pkgName.length)}`;
638
+ const pkgDr = pkgJsonPath.replace('package.json', '');
639
+ const pkgURL = url.pathToFileURL(pkgDr);
640
+
641
+ const context = {
455
642
  importer,
456
- importPath,
643
+ importSpecifier,
644
+ moduleDirs: moduleDirectories,
645
+ pkgURL,
457
646
  pkgJsonPath,
458
- subPath: packageSubPath,
459
- exportMap: pkgJson.exports,
460
647
  conditions: exportConditions
461
- });
462
- const pkgDir = path__default['default'].dirname(pkgJsonPath);
463
- location = path__default['default'].join(pkgDir, mappedSubPath);
648
+ };
649
+ const resolvedPackageExport = await resolvePackageExports(
650
+ context,
651
+ subpath,
652
+ pkgJson.exports
653
+ );
654
+ location = url.fileURLToPath(resolvedPackageExport);
464
655
  } catch (error) {
656
+ if (!(error instanceof ResolveError)) {
657
+ throw error;
658
+ }
465
659
  warn(error);
466
660
  return null;
467
661
  }
@@ -469,8 +663,9 @@ async function resolveId({
469
663
  }
470
664
 
471
665
  if (!location) {
666
+ // package has no imports or exports, use classic node resolve
472
667
  try {
473
- location = await resolveImportPath(importPath, resolveOptions);
668
+ location = await resolveImportPath(importSpecifier, resolveOptions);
474
669
  } catch (error) {
475
670
  if (error.code !== 'MODULE_NOT_FOUND') {
476
671
  throw error;
@@ -513,7 +708,7 @@ async function resolveImportSpecifiers({
513
708
  // eslint-disable-next-line no-await-in-loop
514
709
  const resolved = await resolveId({
515
710
  importer,
516
- importPath: importSpecifierList[i],
711
+ importSpecifier: importSpecifierList[i],
517
712
  exportConditions,
518
713
  warn,
519
714
  packageInfoCache,
package/dist/es/index.js CHANGED
@@ -4,6 +4,7 @@ import deepMerge from 'deepmerge';
4
4
  import isModule from 'is-module';
5
5
  import fs, { realpathSync } from 'fs';
6
6
  import { promisify } from 'util';
7
+ import { pathToFileURL, fileURLToPath } from 'url';
7
8
  import resolve$1 from 'resolve';
8
9
  import { createFilter } from '@rollup/pluginutils';
9
10
 
@@ -216,168 +217,332 @@ function normalizeInput(input) {
216
217
  return [input];
217
218
  }
218
219
 
219
- const resolveImportPath = promisify(resolve$1);
220
- const readFile$1 = promisify(fs.readFile);
220
+ /* eslint-disable no-await-in-loop */
221
+
222
+ const fileExists = promisify(fs.exists);
223
+
224
+ function isModuleDir(current, moduleDirs) {
225
+ return moduleDirs.some((dir) => current.endsWith(dir));
226
+ }
221
227
 
222
- const pathNotFoundError = (importPath, importer, subPath, pkgPath) =>
223
- new Error(
224
- `Could not resolve import "${importPath}" in "${importer}".` +
225
- ` Package subpath "${subPath}" is not defined by "exports" in ${pkgPath}`
226
- );
228
+ async function findPackageJson(base, moduleDirs) {
229
+ const { root } = path.parse(base);
230
+ let current = base;
227
231
 
228
- function findExportKeyMatch(exportMap, subPath) {
229
- if (subPath in exportMap) {
230
- return subPath;
232
+ while (current !== root && !isModuleDir(current, moduleDirs)) {
233
+ const pkgJsonPath = path.join(current, 'package.json');
234
+ if (await fileExists(pkgJsonPath)) {
235
+ const pkgJsonString = fs.readFileSync(pkgJsonPath, 'utf-8');
236
+ return { pkgJson: JSON.parse(pkgJsonString), pkgPath: current, pkgJsonPath };
237
+ }
238
+ current = path.resolve(current, '..');
231
239
  }
240
+ return null;
241
+ }
232
242
 
233
- const matchKeys = Object.keys(exportMap)
234
- .filter((key) => key.endsWith('/') || key.endsWith('*'))
235
- .sort((a, b) => b.length - a.length);
243
+ function isUrl(str) {
244
+ try {
245
+ return !!new URL(str);
246
+ } catch (_) {
247
+ return false;
248
+ }
249
+ }
250
+
251
+ function isConditions(exports) {
252
+ return typeof exports === 'object' && Object.keys(exports).every((k) => !k.startsWith('.'));
253
+ }
254
+
255
+ function isMappings(exports) {
256
+ return typeof exports === 'object' && !isConditions(exports);
257
+ }
258
+
259
+ function isMixedExports(exports) {
260
+ const keys = Object.keys(exports);
261
+ return keys.some((k) => k.startsWith('.')) && keys.some((k) => !k.startsWith('.'));
262
+ }
263
+
264
+ function createBaseErrorMsg(importSpecifier, importer) {
265
+ return `Could not resolve import "${importSpecifier}" in ${importer}`;
266
+ }
267
+
268
+ function createErrorMsg(context, reason, internal) {
269
+ const { importSpecifier, importer, pkgJsonPath } = context;
270
+ const base = createBaseErrorMsg(importSpecifier, importer);
271
+ const field = internal ? 'imports' : 'exports';
272
+ return `${base} using ${field} defined in ${pkgJsonPath}.${reason ? ` ${reason}` : ''}`;
273
+ }
274
+
275
+ class ResolveError extends Error {}
276
+
277
+ class InvalidConfigurationError extends ResolveError {
278
+ constructor(context, reason) {
279
+ super(createErrorMsg(context, `Invalid "exports" field. ${reason}`));
280
+ }
281
+ }
282
+
283
+ class InvalidModuleSpecifierError extends ResolveError {
284
+ constructor(context, internal) {
285
+ super(createErrorMsg(context, internal));
286
+ }
287
+ }
236
288
 
237
- for (const key of matchKeys) {
238
- if (key.endsWith('*')) {
239
- // star match: "./foo/*": "./foo/*.js"
240
- const keyWithoutStar = key.substring(0, key.length - 1);
241
- if (subPath.startsWith(keyWithoutStar)) {
242
- return key;
289
+ class InvalidPackageTargetError extends ResolveError {
290
+ constructor(context, reason) {
291
+ super(createErrorMsg(context, reason));
292
+ }
293
+ }
294
+
295
+ /* eslint-disable no-await-in-loop, no-undefined */
296
+
297
+ function includesInvalidSegments(pathSegments, moduleDirs) {
298
+ return pathSegments
299
+ .split('/')
300
+ .slice(1)
301
+ .some((t) => ['.', '..', ...moduleDirs].includes(t));
302
+ }
303
+
304
+ async function resolvePackageTarget(context, { target, subpath, pattern, internal }) {
305
+ if (typeof target === 'string') {
306
+ if (!pattern && subpath.length > 0 && !target.endsWith('/')) {
307
+ throw new InvalidModuleSpecifierError(context);
308
+ }
309
+
310
+ if (!target.startsWith('./')) {
311
+ if (internal && !['/', '../'].some((p) => target.startsWith(p)) && !isUrl(target)) {
312
+ // this is a bare package import, remap it and resolve it using regular node resolve
313
+ if (pattern) {
314
+ const result = await context.resolveId(
315
+ target.replace(/\*/g, subpath),
316
+ context.pkgURL.href
317
+ );
318
+ return result ? pathToFileURL(result.location) : null;
319
+ }
320
+
321
+ const result = await context.resolveId(`${target}${subpath}`, context.pkgURL.href);
322
+ return result ? pathToFileURL(result.location) : null;
243
323
  }
324
+ throw new InvalidPackageTargetError(context, `Invalid mapping: "${target}".`);
325
+ }
326
+
327
+ if (includesInvalidSegments(target, context.moduleDirs)) {
328
+ throw new InvalidPackageTargetError(context, `Invalid mapping: "${target}".`);
329
+ }
330
+
331
+ const resolvedTarget = new URL(target, context.pkgURL);
332
+ if (!resolvedTarget.href.startsWith(context.pkgURL.href)) {
333
+ throw new InvalidPackageTargetError(
334
+ context,
335
+ `Resolved to ${resolvedTarget.href} which is outside package ${context.pkgURL.href}`
336
+ );
244
337
  }
245
338
 
246
- if (key.endsWith('/') && subPath.startsWith(key)) {
247
- // directory match (deprecated by node): "./foo/": "./foo/.js"
248
- return key;
339
+ if (includesInvalidSegments(subpath, context.moduleDirs)) {
340
+ throw new InvalidModuleSpecifierError(context);
249
341
  }
250
342
 
251
- if (key === subPath) {
252
- // literal match
253
- return key;
343
+ if (pattern) {
344
+ return resolvedTarget.href.replace(/\*/g, subpath);
254
345
  }
346
+ return new URL(subpath, resolvedTarget).href;
255
347
  }
256
- return null;
257
- }
258
348
 
259
- function mapSubPath({ importPath, importer, pkgJsonPath, subPath, key, value }) {
260
- if (typeof value === 'string') {
261
- if (typeof key === 'string' && key.endsWith('*')) {
262
- // star match: "./foo/*": "./foo/*.js"
263
- const keyWithoutStar = key.substring(0, key.length - 1);
264
- const subPathAfterKey = subPath.substring(keyWithoutStar.length);
265
- return value.replace(/\*/g, subPathAfterKey);
349
+ if (Array.isArray(target)) {
350
+ let lastError;
351
+ for (const item of target) {
352
+ try {
353
+ const resolved = await resolvePackageTarget(context, {
354
+ target: item,
355
+ subpath,
356
+ pattern,
357
+ internal
358
+ });
359
+
360
+ // return if defined or null, but not undefined
361
+ if (resolved !== undefined) {
362
+ return resolved;
363
+ }
364
+ } catch (error) {
365
+ if (!(error instanceof InvalidPackageTargetError)) {
366
+ throw error;
367
+ } else {
368
+ lastError = error;
369
+ }
370
+ }
266
371
  }
267
372
 
268
- if (value.endsWith('/')) {
269
- // directory match (deprecated by node): "./foo/": "./foo/.js"
270
- return `${value}${subPath.substring(key.length)}`;
373
+ if (lastError) {
374
+ throw lastError;
271
375
  }
376
+ return null;
377
+ }
272
378
 
273
- // mapping is a string, for example { "./foo": "./dist/foo.js" }
274
- return value;
379
+ if (target && typeof target === 'object') {
380
+ for (const [key, value] of Object.entries(target)) {
381
+ if (key === 'default' || context.conditions.includes(key)) {
382
+ const resolved = await resolvePackageTarget(context, {
383
+ target: value,
384
+ subpath,
385
+ pattern,
386
+ internal
387
+ });
388
+
389
+ // return if defined or null, but not undefined
390
+ if (resolved !== undefined) {
391
+ return resolved;
392
+ }
393
+ }
394
+ }
395
+ return undefined;
275
396
  }
276
397
 
277
- if (Array.isArray(value)) {
278
- // mapping is an array with fallbacks, for example { "./foo": ["foo:bar", "./dist/foo.js"] }
279
- return value.find((v) => v.startsWith('./'));
398
+ if (target === null) {
399
+ return null;
280
400
  }
281
401
 
282
- throw pathNotFoundError(importPath, importer, subPath, pkgJsonPath);
402
+ throw new InvalidPackageTargetError(context, `Invalid exports field.`);
283
403
  }
284
404
 
285
- function findEntrypoint({
286
- importPath,
287
- importer,
288
- pkgJsonPath,
289
- subPath,
290
- exportMap,
291
- conditions,
292
- key
293
- }) {
294
- if (typeof exportMap !== 'object') {
295
- return mapSubPath({ importPath, importer, pkgJsonPath, subPath, key, value: exportMap });
405
+ /* eslint-disable no-await-in-loop */
406
+
407
+ async function resolvePackageImportsExports(context, { matchKey, matchObj, internal }) {
408
+ if (!matchKey.endsWith('*') && matchKey in matchObj) {
409
+ const target = matchObj[matchKey];
410
+ const resolved = await resolvePackageTarget(context, { target, subpath: '', internal });
411
+ return resolved;
296
412
  }
297
413
 
298
- // iterate conditions recursively, find the first that matches all conditions
299
- for (const [condition, subExportMap] of Object.entries(exportMap)) {
300
- if (conditions.includes(condition)) {
301
- const mappedSubPath = findEntrypoint({
302
- importPath,
303
- importer,
304
- pkgJsonPath,
305
- subPath,
306
- exportMap: subExportMap,
307
- conditions,
308
- key
414
+ const expansionKeys = Object.keys(matchObj)
415
+ .filter((k) => k.endsWith('/') || k.endsWith('*'))
416
+ .sort((a, b) => b.length - a.length);
417
+
418
+ for (const expansionKey of expansionKeys) {
419
+ const prefix = expansionKey.substring(0, expansionKey.length - 1);
420
+
421
+ if (expansionKey.endsWith('*') && matchKey.startsWith(prefix)) {
422
+ const target = matchObj[expansionKey];
423
+ const subpath = matchKey.substring(expansionKey.length - 1);
424
+ const resolved = await resolvePackageTarget(context, {
425
+ target,
426
+ subpath,
427
+ pattern: true,
428
+ internal
309
429
  });
310
- if (mappedSubPath) {
311
- return mappedSubPath;
312
- }
430
+ return resolved;
313
431
  }
314
- }
315
- throw pathNotFoundError(importer, subPath, pkgJsonPath);
316
- }
317
432
 
318
- function findEntrypointTopLevel({
319
- importPath,
320
- importer,
321
- pkgJsonPath,
322
- subPath,
323
- exportMap,
324
- conditions
325
- }) {
326
- if (typeof exportMap !== 'object') {
327
- // the export map shorthand, for example { exports: "./index.js" }
328
- if (subPath !== '.') {
329
- // shorthand only supports a main entrypoint
330
- throw pathNotFoundError(importPath, importer, subPath, pkgJsonPath);
433
+ if (matchKey.startsWith(expansionKey)) {
434
+ const target = matchObj[expansionKey];
435
+ const subpath = matchKey.substring(expansionKey.length);
436
+
437
+ const resolved = await resolvePackageTarget(context, { target, subpath, internal });
438
+ return resolved;
331
439
  }
332
- return mapSubPath({ importPath, importer, pkgJsonPath, subPath, key: null, value: exportMap });
333
440
  }
334
441
 
335
- // export map is an object, the top level can be either conditions or sub path mappings
336
- const keys = Object.keys(exportMap);
337
- const isConditions = keys.every((k) => !k.startsWith('.'));
338
- const isMappings = keys.every((k) => k.startsWith('.'));
442
+ throw new InvalidModuleSpecifierError(context, internal);
443
+ }
339
444
 
340
- if (!isConditions && !isMappings) {
341
- throw new Error(
342
- `Invalid package config ${pkgJsonPath}, "exports" cannot contain some keys starting with '.'` +
343
- ' and some not. The exports object must either be an object of package subpath keys or an object of main entry' +
344
- ' condition name keys only.'
445
+ async function resolvePackageExports(context, subpath, exports) {
446
+ if (isMixedExports(exports)) {
447
+ throw new InvalidConfigurationError(
448
+ context,
449
+ 'All keys must either start with ./, or without one.'
345
450
  );
346
451
  }
347
452
 
348
- let key = null;
349
- let exportMapForSubPath;
453
+ if (subpath === '.') {
454
+ let mainExport;
455
+ // If exports is a String or Array, or an Object containing no keys starting with ".", then
456
+ if (typeof exports === 'string' || Array.isArray(exports) || isConditions(exports)) {
457
+ mainExport = exports;
458
+ } else if (isMappings(exports)) {
459
+ mainExport = exports['.'];
460
+ }
350
461
 
351
- if (isConditions) {
352
- // top level is conditions, for example { "import": ..., "require": ..., "module": ... }
353
- if (subPath !== '.') {
354
- // package with top level conditions means it only supports a main entrypoint
355
- throw pathNotFoundError(importPath, importer, subPath, pkgJsonPath);
462
+ if (mainExport) {
463
+ const resolved = await resolvePackageTarget(context, { target: mainExport, subpath: '' });
464
+ if (resolved) {
465
+ return resolved;
466
+ }
356
467
  }
357
- exportMapForSubPath = exportMap;
358
- } else {
359
- // top level is sub path mappings, for example { ".": ..., "./foo": ..., "./bar": ... }
360
- key = findExportKeyMatch(exportMap, subPath);
361
- if (!key) {
362
- throw pathNotFoundError(importPath, importer, subPath, pkgJsonPath);
468
+ } else if (isMappings(exports)) {
469
+ const resolvedMatch = await resolvePackageImportsExports(context, {
470
+ matchKey: subpath,
471
+ matchObj: exports
472
+ });
473
+
474
+ if (resolvedMatch) {
475
+ return resolvedMatch;
363
476
  }
364
- exportMapForSubPath = exportMap[key];
365
477
  }
366
478
 
367
- return findEntrypoint({
368
- importPath,
479
+ throw new InvalidModuleSpecifierError(context);
480
+ }
481
+
482
+ async function resolvePackageImports({
483
+ importSpecifier,
484
+ importer,
485
+ moduleDirs,
486
+ conditions,
487
+ resolveId
488
+ }) {
489
+ const result = await findPackageJson(importer, moduleDirs);
490
+ if (!result) {
491
+ throw new Error(createBaseErrorMsg('. Could not find a parent package.json.'));
492
+ }
493
+
494
+ const { pkgPath, pkgJsonPath, pkgJson } = result;
495
+ const pkgURL = pathToFileURL(`${pkgPath}/`);
496
+ const context = {
369
497
  importer,
498
+ importSpecifier,
499
+ moduleDirs,
500
+ pkgURL,
370
501
  pkgJsonPath,
371
- subPath,
372
- exportMap: exportMapForSubPath,
373
502
  conditions,
374
- key
503
+ resolveId
504
+ };
505
+
506
+ const { imports } = pkgJson;
507
+ if (!imports) {
508
+ throw new InvalidModuleSpecifierError(context, true);
509
+ }
510
+
511
+ if (importSpecifier === '#' || importSpecifier.startsWith('#/')) {
512
+ throw new InvalidModuleSpecifierError(context, 'Invalid import specifier.');
513
+ }
514
+
515
+ return resolvePackageImportsExports(context, {
516
+ matchKey: importSpecifier,
517
+ matchObj: imports,
518
+ internal: true
375
519
  });
376
520
  }
377
521
 
522
+ const resolveImportPath = promisify(resolve$1);
523
+ const readFile$1 = promisify(fs.readFile);
524
+
525
+ async function getPackageJson(importer, pkgName, resolveOptions, moduleDirectories) {
526
+ if (importer) {
527
+ const selfPackageJsonResult = await findPackageJson(importer, moduleDirectories);
528
+ if (selfPackageJsonResult && selfPackageJsonResult.pkgJson.name === pkgName) {
529
+ // the referenced package name is the current package
530
+ return selfPackageJsonResult;
531
+ }
532
+ }
533
+
534
+ try {
535
+ const pkgJsonPath = await resolveImportPath(`${pkgName}/package.json`, resolveOptions);
536
+ const pkgJson = JSON.parse(await readFile$1(pkgJsonPath, 'utf-8'));
537
+ return { pkgJsonPath, pkgJson };
538
+ } catch (_) {
539
+ return null;
540
+ }
541
+ }
542
+
378
543
  async function resolveId({
379
544
  importer,
380
- importPath,
545
+ importSpecifier,
381
546
  exportConditions,
382
547
  warn,
383
548
  packageInfoCache,
@@ -423,32 +588,61 @@ async function resolveId({
423
588
 
424
589
  let location;
425
590
 
426
- const pkgName = getPackageName(importPath);
427
- if (pkgName) {
428
- let pkgJsonPath;
429
- let pkgJson;
430
- try {
431
- pkgJsonPath = await resolveImportPath(`${pkgName}/package.json`, resolveOptions);
432
- pkgJson = JSON.parse(await readFile$1(pkgJsonPath, 'utf-8'));
433
- } catch (_) {
434
- // if there is no package.json we defer to regular resolve behavior
435
- }
591
+ const pkgName = getPackageName(importSpecifier);
592
+ if (importSpecifier.startsWith('#')) {
593
+ // this is a package internal import, resolve using package imports field
594
+ const resolveResult = await resolvePackageImports({
595
+ importSpecifier,
596
+ importer,
597
+ moduleDirs: moduleDirectories,
598
+ conditions: exportConditions,
599
+ resolveId(id, parent) {
600
+ return resolveId({
601
+ importSpecifier: id,
602
+ importer: parent,
603
+ exportConditions,
604
+ warn,
605
+ packageInfoCache,
606
+ extensions,
607
+ mainFields,
608
+ preserveSymlinks,
609
+ useBrowserOverrides,
610
+ baseDir,
611
+ moduleDirectories
612
+ });
613
+ }
614
+ });
615
+ location = fileURLToPath(resolveResult);
616
+ } else if (pkgName) {
617
+ // it's a bare import, find the package.json and resolve using package exports if available
618
+ const result = await getPackageJson(importer, pkgName, resolveOptions, moduleDirectories);
436
619
 
437
- if (pkgJsonPath && pkgJson && pkgJson.exports) {
620
+ if (result && result.pkgJson.exports) {
621
+ const { pkgJson, pkgJsonPath } = result;
438
622
  try {
439
- const packageSubPath =
440
- pkgName === importPath ? '.' : `.${importPath.substring(pkgName.length)}`;
441
- const mappedSubPath = findEntrypointTopLevel({
623
+ const subpath =
624
+ pkgName === importSpecifier ? '.' : `.${importSpecifier.substring(pkgName.length)}`;
625
+ const pkgDr = pkgJsonPath.replace('package.json', '');
626
+ const pkgURL = pathToFileURL(pkgDr);
627
+
628
+ const context = {
442
629
  importer,
443
- importPath,
630
+ importSpecifier,
631
+ moduleDirs: moduleDirectories,
632
+ pkgURL,
444
633
  pkgJsonPath,
445
- subPath: packageSubPath,
446
- exportMap: pkgJson.exports,
447
634
  conditions: exportConditions
448
- });
449
- const pkgDir = path.dirname(pkgJsonPath);
450
- location = path.join(pkgDir, mappedSubPath);
635
+ };
636
+ const resolvedPackageExport = await resolvePackageExports(
637
+ context,
638
+ subpath,
639
+ pkgJson.exports
640
+ );
641
+ location = fileURLToPath(resolvedPackageExport);
451
642
  } catch (error) {
643
+ if (!(error instanceof ResolveError)) {
644
+ throw error;
645
+ }
452
646
  warn(error);
453
647
  return null;
454
648
  }
@@ -456,8 +650,9 @@ async function resolveId({
456
650
  }
457
651
 
458
652
  if (!location) {
653
+ // package has no imports or exports, use classic node resolve
459
654
  try {
460
- location = await resolveImportPath(importPath, resolveOptions);
655
+ location = await resolveImportPath(importSpecifier, resolveOptions);
461
656
  } catch (error) {
462
657
  if (error.code !== 'MODULE_NOT_FOUND') {
463
658
  throw error;
@@ -500,7 +695,7 @@ async function resolveImportSpecifiers({
500
695
  // eslint-disable-next-line no-await-in-loop
501
696
  const resolved = await resolveId({
502
697
  importer,
503
- importPath: importSpecifierList[i],
698
+ importSpecifier: importSpecifierList[i],
504
699
  exportConditions,
505
700
  warn,
506
701
  packageInfoCache,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rollup/plugin-node-resolve",
3
- "version": "11.0.1",
3
+ "version": "11.1.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
package/types/index.d.ts CHANGED
File without changes