'use strict'; const debug = require('debug')('egg-core:extend'); const deprecate = require('depd')('egg'); const path = require('path'); const originalPrototypes = { request: require('koa/lib/request'), response: require('koa/lib/response'), context: require('koa/lib/context'), application: require('koa/lib/application'), }; module.exports = { /** * mixin Agent.prototype * @function EggLoader#loadAgentExtend * @since 1.0.0 */ loadAgentExtend() { this.loadExtend('agent', this.app); }, /** * mixin Application.prototype * @function EggLoader#loadApplicationExtend * @since 1.0.0 */ loadApplicationExtend() { this.loadExtend('application', this.app); }, /** * mixin Request.prototype * @function EggLoader#loadRequestExtend * @since 1.0.0 */ loadRequestExtend() { this.loadExtend('request', this.app.request); }, /** * mixin Response.prototype * @function EggLoader#loadResponseExtend * @since 1.0.0 */ loadResponseExtend() { this.loadExtend('response', this.app.response); }, /** * mixin Context.prototype * @function EggLoader#loadContextExtend * @since 1.0.0 */ loadContextExtend() { this.loadExtend('context', this.app.context); }, /** * mixin app.Helper.prototype * @function EggLoader#loadHelperExtend * @since 1.0.0 */ loadHelperExtend() { if (this.app && this.app.Helper) { this.loadExtend('helper', this.app.Helper.prototype); } }, /** * Find all extend file paths by name * can be override in top level framework to support load `app/extends/{name}.js` * * @param {String} name - filename which may be `app/extend/{name}.js` * @return {Array} filepaths extend file paths * @private */ getExtendFilePaths(name) { return this.getLoadUnits().map(unit => path.join(unit.path, 'app/extend', name)); }, /** * Loader app/extend/xx.js to `prototype`, * @function loadExtend * @param {String} name - filename which may be `app/extend/{name}.js` * @param {Object} proto - prototype that mixed * @since 1.0.0 */ loadExtend(name, proto) { this.timing.start(`Load extend/${name}.js`); // All extend files const filepaths = this.getExtendFilePaths(name); // if use mm.env and serverEnv is not unittest const isAddUnittest = 'EGG_MOCK_SERVER_ENV' in process.env && this.serverEnv !== 'unittest'; for (let i = 0, l = filepaths.length; i < l; i++) { const filepath = filepaths[i]; filepaths.push(filepath + `.${this.serverEnv}`); if (isAddUnittest) filepaths.push(filepath + '.unittest'); } const mergeRecord = new Map(); for (let filepath of filepaths) { filepath = this.resolveModule(filepath); if (!filepath) { continue; } else if (filepath.endsWith('/index.js')) { // TODO: remove support at next version deprecate(`app/extend/${name}/index.js is deprecated, use app/extend/${name}.js instead`); } const ext = this.requireFile(filepath); const properties = Object.getOwnPropertyNames(ext) .concat(Object.getOwnPropertySymbols(ext)); for (const property of properties) { if (mergeRecord.has(property)) { debug('Property: "%s" already exists in "%s",it will be redefined by "%s"', property, mergeRecord.get(property), filepath); } // Copy descriptor let descriptor = Object.getOwnPropertyDescriptor(ext, property); let originalDescriptor = Object.getOwnPropertyDescriptor(proto, property); if (!originalDescriptor) { // try to get descriptor from originalPrototypes const originalProto = originalPrototypes[name]; if (originalProto) { originalDescriptor = Object.getOwnPropertyDescriptor(originalProto, property); } } if (originalDescriptor) { // don't override descriptor descriptor = Object.assign({}, descriptor); if (!descriptor.set && originalDescriptor.set) { descriptor.set = originalDescriptor.set; } if (!descriptor.get && originalDescriptor.get) { descriptor.get = originalDescriptor.get; } } Object.defineProperty(proto, property, descriptor); mergeRecord.set(property, filepath); } debug('merge %j to %s from %s', Object.keys(ext), name, filepath); } this.timing.end(`Load extend/${name}.js`); }, };