lazyImport.js 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. const stackTrace = require('./stackTrace');
  2. const splitPath = require('./splitPath');
  3. const startWith = require('./startWith');
  4. const endWith = require('./endWith');
  5. const defineProp = require('./defineProp');
  6. const isStr = require('./isStr');
  7. const has = require('./has');
  8. const objToStr = require('./objToStr');
  9. const unique = require('./unique');
  10. const concat = require('./concat');
  11. const keys = require('./keys');
  12. const isArr = require('./isArr');
  13. const toBool = require('./toBool');
  14. const path = require('path');
  15. exports = function(importFn, dirname) {
  16. return function(moduleId) {
  17. if (isRelative(moduleId)) {
  18. if (!dirname) {
  19. dirname = findDirName();
  20. }
  21. moduleId = path.join(dirname, moduleId);
  22. }
  23. const { cache } = importFn;
  24. if (cache) {
  25. if (cache[moduleId]) {
  26. return importFn(moduleId);
  27. }
  28. if (!endWith(moduleId, '.js') && cache[`${moduleId}.js`]) {
  29. return importFn(`${moduleId}.js`);
  30. }
  31. }
  32. return proxyExports(importFn, moduleId);
  33. };
  34. };
  35. function proxyExports(importFn, moduleId) {
  36. const fakeExports = function() {};
  37. let cache;
  38. function doImport() {
  39. if (cache) {
  40. return;
  41. }
  42. const module = importFn(moduleId);
  43. cache = Object(module);
  44. const valueOfDescriptor = createDescriptor(0, 0, 1);
  45. if (isStr(module)) {
  46. valueOfDescriptor.value = () => String(module.valueOf());
  47. } else {
  48. valueOfDescriptor.value = () => Number(module.valueOf());
  49. }
  50. defineProp(cache, 'valueOf', valueOfDescriptor);
  51. defineProp(
  52. cache,
  53. 'toString',
  54. createDescriptor(0, 0, 1, () => String(module.toString()))
  55. );
  56. if (!has(cache, Symbol.toStringTag)) {
  57. const realType = objToStr(module).slice(8, -1);
  58. Object.defineProperty(cache, Symbol.toStringTag, {
  59. configurable: true,
  60. get() {
  61. return realType;
  62. }
  63. });
  64. }
  65. }
  66. return new Proxy(fakeExports, {
  67. get(target, property) {
  68. doImport();
  69. return cache[property];
  70. },
  71. set(target, property, value) {
  72. doImport();
  73. cache[property] = value;
  74. return true;
  75. },
  76. has(target, prop) {
  77. doImport();
  78. return prop in cache;
  79. },
  80. construct(target, argumentsList) {
  81. doImport();
  82. return new cache(...argumentsList);
  83. },
  84. apply(target, thisArg, argumentsList) {
  85. doImport();
  86. return cache.apply(thisArg, argumentsList);
  87. },
  88. ownKeys() {
  89. doImport();
  90. const descriptors = Object.getOwnPropertyDescriptors(cache);
  91. delete descriptors.valueOf;
  92. delete descriptors.toString;
  93. return unique(
  94. concat(
  95. [
  96. 'arguments',
  97. 'caller',
  98. 'prototype',
  99. 'name',
  100. 'length',
  101. Symbol.toStringTag
  102. ],
  103. keys(descriptors)
  104. )
  105. );
  106. },
  107. getOwnPropertyDescriptor(target, prop) {
  108. if (has(cache, prop)) {
  109. if (isArr(cache) && prop === 'length') {
  110. return {
  111. configurable: true,
  112. enumerable: false,
  113. writable: true
  114. };
  115. } else {
  116. const descriptor = Object.getOwnPropertyDescriptor(
  117. cache,
  118. prop
  119. );
  120. if (descriptor.configurable) {
  121. return descriptor;
  122. }
  123. if (!fakeExports.prop) {
  124. defineProp(fakeExports, prop, descriptor);
  125. }
  126. return descriptor;
  127. }
  128. } else {
  129. switch (prop) {
  130. case 'arguments':
  131. return createDescriptor(0, 0, 0, null);
  132. case 'caller':
  133. return createDescriptor(0, 0, 0, null);
  134. case 'prototype':
  135. return createDescriptor(0, 0, 1, null);
  136. case 'length':
  137. return createDescriptor(1, 0, 0, 0);
  138. case 'name':
  139. return createDescriptor(1, 0, 0, '');
  140. default:
  141. return {
  142. configurable: true,
  143. enumerable: true,
  144. writable: true
  145. };
  146. }
  147. }
  148. }
  149. });
  150. }
  151. function createDescriptor(configurable, enumerable, writable, value) {
  152. return {
  153. configurable: toBool(configurable),
  154. enumerable: toBool(enumerable),
  155. writable: toBool(writable),
  156. value
  157. };
  158. }
  159. function findDirName() {
  160. const stack = stackTrace();
  161. for (const item of stack) {
  162. const fileName = item.getFileName();
  163. if (fileName !== module.filename) {
  164. return splitPath(fileName).dir;
  165. }
  166. }
  167. return '';
  168. }
  169. function isRelative(moduleId) {
  170. return startWith(moduleId, './') || startWith(moduleId, '../');
  171. }
  172. module.exports = exports;