extend.js 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. 'use strict';
  2. const debug = require('debug')('egg-core:extend');
  3. const deprecate = require('depd')('egg');
  4. const path = require('path');
  5. const originalPrototypes = {
  6. request: require('koa/lib/request'),
  7. response: require('koa/lib/response'),
  8. context: require('koa/lib/context'),
  9. application: require('koa/lib/application'),
  10. };
  11. module.exports = {
  12. /**
  13. * mixin Agent.prototype
  14. * @function EggLoader#loadAgentExtend
  15. * @since 1.0.0
  16. */
  17. loadAgentExtend() {
  18. this.loadExtend('agent', this.app);
  19. },
  20. /**
  21. * mixin Application.prototype
  22. * @function EggLoader#loadApplicationExtend
  23. * @since 1.0.0
  24. */
  25. loadApplicationExtend() {
  26. this.loadExtend('application', this.app);
  27. },
  28. /**
  29. * mixin Request.prototype
  30. * @function EggLoader#loadRequestExtend
  31. * @since 1.0.0
  32. */
  33. loadRequestExtend() {
  34. this.loadExtend('request', this.app.request);
  35. },
  36. /**
  37. * mixin Response.prototype
  38. * @function EggLoader#loadResponseExtend
  39. * @since 1.0.0
  40. */
  41. loadResponseExtend() {
  42. this.loadExtend('response', this.app.response);
  43. },
  44. /**
  45. * mixin Context.prototype
  46. * @function EggLoader#loadContextExtend
  47. * @since 1.0.0
  48. */
  49. loadContextExtend() {
  50. this.loadExtend('context', this.app.context);
  51. },
  52. /**
  53. * mixin app.Helper.prototype
  54. * @function EggLoader#loadHelperExtend
  55. * @since 1.0.0
  56. */
  57. loadHelperExtend() {
  58. if (this.app && this.app.Helper) {
  59. this.loadExtend('helper', this.app.Helper.prototype);
  60. }
  61. },
  62. /**
  63. * Find all extend file paths by name
  64. * can be override in top level framework to support load `app/extends/{name}.js`
  65. *
  66. * @param {String} name - filename which may be `app/extend/{name}.js`
  67. * @return {Array} filepaths extend file paths
  68. * @private
  69. */
  70. getExtendFilePaths(name) {
  71. return this.getLoadUnits().map(unit => path.join(unit.path, 'app/extend', name));
  72. },
  73. /**
  74. * Loader app/extend/xx.js to `prototype`,
  75. * @function loadExtend
  76. * @param {String} name - filename which may be `app/extend/{name}.js`
  77. * @param {Object} proto - prototype that mixed
  78. * @since 1.0.0
  79. */
  80. loadExtend(name, proto) {
  81. this.timing.start(`Load extend/${name}.js`);
  82. // All extend files
  83. const filepaths = this.getExtendFilePaths(name);
  84. // if use mm.env and serverEnv is not unittest
  85. const isAddUnittest = 'EGG_MOCK_SERVER_ENV' in process.env && this.serverEnv !== 'unittest';
  86. for (let i = 0, l = filepaths.length; i < l; i++) {
  87. const filepath = filepaths[i];
  88. filepaths.push(filepath + `.${this.serverEnv}`);
  89. if (isAddUnittest) filepaths.push(filepath + '.unittest');
  90. }
  91. const mergeRecord = new Map();
  92. for (let filepath of filepaths) {
  93. filepath = this.resolveModule(filepath);
  94. if (!filepath) {
  95. continue;
  96. } else if (filepath.endsWith('/index.js')) {
  97. // TODO: remove support at next version
  98. deprecate(`app/extend/${name}/index.js is deprecated, use app/extend/${name}.js instead`);
  99. }
  100. const ext = this.requireFile(filepath);
  101. const properties = Object.getOwnPropertyNames(ext)
  102. .concat(Object.getOwnPropertySymbols(ext));
  103. for (const property of properties) {
  104. if (mergeRecord.has(property)) {
  105. debug('Property: "%s" already exists in "%s",it will be redefined by "%s"',
  106. property, mergeRecord.get(property), filepath);
  107. }
  108. // Copy descriptor
  109. let descriptor = Object.getOwnPropertyDescriptor(ext, property);
  110. let originalDescriptor = Object.getOwnPropertyDescriptor(proto, property);
  111. if (!originalDescriptor) {
  112. // try to get descriptor from originalPrototypes
  113. const originalProto = originalPrototypes[name];
  114. if (originalProto) {
  115. originalDescriptor = Object.getOwnPropertyDescriptor(originalProto, property);
  116. }
  117. }
  118. if (originalDescriptor) {
  119. // don't override descriptor
  120. descriptor = Object.assign({}, descriptor);
  121. if (!descriptor.set && originalDescriptor.set) {
  122. descriptor.set = originalDescriptor.set;
  123. }
  124. if (!descriptor.get && originalDescriptor.get) {
  125. descriptor.get = originalDescriptor.get;
  126. }
  127. }
  128. Object.defineProperty(proto, property, descriptor);
  129. mergeRecord.set(property, filepath);
  130. }
  131. debug('merge %j to %s from %s', Object.keys(ext), name, filepath);
  132. }
  133. this.timing.end(`Load extend/${name}.js`);
  134. },
  135. };