index.js 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. /**
  2. * @param {object} exports
  3. * @param {Set<string>} keys
  4. */
  5. function loop(exports, keys) {
  6. if (typeof exports === 'string') {
  7. return exports;
  8. }
  9. if (exports) {
  10. let idx, tmp;
  11. if (Array.isArray(exports)) {
  12. for (idx=0; idx < exports.length; idx++) {
  13. if (tmp = loop(exports[idx], keys)) return tmp;
  14. }
  15. } else {
  16. for (idx in exports) {
  17. if (keys.has(idx)) {
  18. return loop(exports[idx], keys);
  19. }
  20. }
  21. }
  22. }
  23. }
  24. /**
  25. * @param {string} name The package name
  26. * @param {string} entry The target entry, eg "."
  27. * @param {number} [condition] Unmatched condition?
  28. */
  29. function bail(name, entry, condition) {
  30. throw new Error(
  31. condition
  32. ? `No known conditions for "${entry}" entry in "${name}" package`
  33. : `Missing "${entry}" export in "${name}" package`
  34. );
  35. }
  36. /**
  37. * @param {string} name the package name
  38. * @param {string} entry the target path/import
  39. */
  40. function toName(name, entry) {
  41. return entry === name ? '.'
  42. : entry[0] === '.' ? entry
  43. : entry.replace(new RegExp('^' + name + '\/'), './');
  44. }
  45. /**
  46. * @param {object} pkg package.json contents
  47. * @param {string} [entry] entry name or import path
  48. * @param {object} [options]
  49. * @param {boolean} [options.browser]
  50. * @param {boolean} [options.require]
  51. * @param {string[]} [options.conditions]
  52. * @param {boolean} [options.unsafe]
  53. */
  54. function resolve(pkg, entry='.', options={}) {
  55. let { name, exports } = pkg;
  56. if (exports) {
  57. let { browser, require, unsafe, conditions=[] } = options;
  58. let target = toName(name, entry);
  59. if (target !== '.' && !target.startsWith('./')) {
  60. target = './' + target; // ".ini" => "./.ini"
  61. }
  62. if (typeof exports === 'string') {
  63. return target === '.' ? exports : bail(name, target);
  64. }
  65. let allows = new Set(['default', ...conditions]);
  66. unsafe || allows.add(require ? 'require' : 'import');
  67. unsafe || allows.add(browser ? 'browser' : 'node');
  68. let key, m, k, kv, tmp, isSingle=false;
  69. for (key in exports) {
  70. isSingle = key[0] !== '.';
  71. break;
  72. }
  73. if (isSingle) {
  74. return target === '.'
  75. ? loop(exports, allows) || bail(name, target, 1)
  76. : bail(name, target);
  77. }
  78. if (tmp = exports[target]) {
  79. return loop(tmp, allows) || bail(name, target, 1);
  80. }
  81. if (target !== '.') {
  82. for (key in exports) {
  83. if (k && key.length < k.length) {
  84. // do not allow "./" to match if already matched "./foo*" key
  85. } else if (key[key.length - 1] === '/' && target.startsWith(key)) {
  86. kv = target.substring(key.length);
  87. k = key;
  88. } else {
  89. tmp = key.indexOf('*', 2);
  90. if (!!~tmp) {
  91. m = RegExp(
  92. '^\.\/' + key.substring(2, tmp) + '(.*)' + key.substring(1+tmp)
  93. ).exec(target);
  94. if (m && m[1]) {
  95. kv = m[1];
  96. k = key;
  97. }
  98. }
  99. }
  100. }
  101. if (k && kv) {
  102. // must have value
  103. tmp = loop(exports[k], allows);
  104. if (!tmp) return bail(name, target);
  105. return tmp.includes('*')
  106. ? tmp.replace(/[*]/g, kv)
  107. : tmp + kv;
  108. }
  109. }
  110. return bail(name, target);
  111. }
  112. }
  113. /**
  114. * @param {object} pkg
  115. * @param {object} [options]
  116. * @param {string|boolean} [options.browser]
  117. * @param {string[]} [options.fields]
  118. */
  119. function legacy(pkg, options={}) {
  120. let i=0, value,
  121. browser = options.browser,
  122. fields = options.fields || ['module', 'main'];
  123. if (browser && !fields.includes('browser')) {
  124. fields.unshift('browser');
  125. }
  126. for (; i < fields.length; i++) {
  127. if (value = pkg[fields[i]]) {
  128. if (typeof value == 'string') {
  129. //
  130. } else if (typeof value == 'object' && fields[i] == 'browser') {
  131. if (typeof browser == 'string') {
  132. value = value[browser=toName(pkg.name, browser)];
  133. if (value == null) return browser;
  134. }
  135. } else {
  136. continue;
  137. }
  138. return typeof value == 'string'
  139. ? ('./' + value.replace(/^\.?\//, ''))
  140. : value;
  141. }
  142. }
  143. }
  144. exports.legacy = legacy;
  145. exports.resolve = resolve;