| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475 | 
							- 'use strict';
 
- const fs = require('fs');
 
- const path = require('path');
 
- const assert = require('assert');
 
- const is = require('is-type-of');
 
- const debug = require('debug')('egg-core');
 
- const homedir = require('node-homedir');
 
- const FileLoader = require('./file_loader');
 
- const ContextLoader = require('./context_loader');
 
- const utility = require('utility');
 
- const utils = require('../utils');
 
- const Timing = require('../utils/timing');
 
- const REQUIRE_COUNT = Symbol('EggLoader#requireCount');
 
- class EggLoader {
 
-   /**
 
-    * @class
 
-    * @param {Object} options - options
 
-    * @param {String} options.baseDir - the directory of application
 
-    * @param {EggCore} options.app - Application instance
 
-    * @param {Logger} options.logger - logger
 
-    * @param {Object} [options.plugins] - custom plugins
 
-    * @since 1.0.0
 
-    */
 
-   constructor(options) {
 
-     this.options = options;
 
-     assert(fs.existsSync(this.options.baseDir), `${this.options.baseDir} not exists`);
 
-     assert(this.options.app, 'options.app is required');
 
-     assert(this.options.logger, 'options.logger is required');
 
-     this.app = this.options.app;
 
-     this.lifecycle = this.app.lifecycle;
 
-     this.timing = this.app.timing || new Timing();
 
-     this[REQUIRE_COUNT] = 0;
 
-     /**
 
-      * @member {Object} EggLoader#pkg
 
-      * @see {@link AppInfo#pkg}
 
-      * @since 1.0.0
 
-      */
 
-     this.pkg = utility.readJSONSync(path.join(this.options.baseDir, 'package.json'));
 
-     /**
 
-      * All framework directories.
 
-      *
 
-      * You can extend Application of egg, the entry point is options.app,
 
-      *
 
-      * loader will find all directories from the prototype of Application,
 
-      * you should define `Symbol.for('egg#eggPath')` property.
 
-      *
 
-      * ```
 
-      * // lib/example.js
 
-      * const egg = require('egg');
 
-      * class ExampleApplication extends egg.Application {
 
-      *   constructor(options) {
 
-      *     super(options);
 
-      *   }
 
-      *
 
-      *   get [Symbol.for('egg#eggPath')]() {
 
-      *     return path.join(__dirname, '..');
 
-      *   }
 
-      * }
 
-      * ```
 
-      * @member {Array} EggLoader#eggPaths
 
-      * @see EggLoader#getEggPaths
 
-      * @since 1.0.0
 
-      */
 
-     this.eggPaths = this.getEggPaths();
 
-     debug('Loaded eggPaths %j', this.eggPaths);
 
-     /**
 
-      * @member {String} EggLoader#serverEnv
 
-      * @see AppInfo#env
 
-      * @since 1.0.0
 
-      */
 
-     this.serverEnv = this.getServerEnv();
 
-     debug('Loaded serverEnv %j', this.serverEnv);
 
-     /**
 
-      * @member {AppInfo} EggLoader#appInfo
 
-      * @since 1.0.0
 
-      */
 
-     this.appInfo = this.getAppInfo();
 
-     /**
 
-      * @member {String} EggLoader#serverScope
 
-      * @see AppInfo#serverScope
 
-      */
 
-     this.serverScope = options.serverScope !== undefined
 
-       ? options.serverScope
 
-       : this.getServerScope();
 
-   }
 
-   /**
 
-    * Get {@link AppInfo#env}
 
-    * @return {String} env
 
-    * @see AppInfo#env
 
-    * @private
 
-    * @since 1.0.0
 
-    */
 
-   getServerEnv() {
 
-     let serverEnv = this.options.env;
 
-     const envPath = path.join(this.options.baseDir, 'config/env');
 
-     if (!serverEnv && fs.existsSync(envPath)) {
 
-       serverEnv = fs.readFileSync(envPath, 'utf8').trim();
 
-     }
 
-     if (!serverEnv) {
 
-       serverEnv = process.env.EGG_SERVER_ENV;
 
-     }
 
-     if (!serverEnv) {
 
-       if (process.env.NODE_ENV === 'test') {
 
-         serverEnv = 'unittest';
 
-       } else if (process.env.NODE_ENV === 'production') {
 
-         serverEnv = 'prod';
 
-       } else {
 
-         serverEnv = 'local';
 
-       }
 
-     } else {
 
-       serverEnv = serverEnv.trim();
 
-     }
 
-     return serverEnv;
 
-   }
 
-   /**
 
-    * Get {@link AppInfo#scope}
 
-    * @return {String} serverScope
 
-    * @private
 
-    */
 
-   getServerScope() {
 
-     return process.env.EGG_SERVER_SCOPE || '';
 
-   }
 
-   /**
 
-    * Get {@link AppInfo#name}
 
-    * @return {String} appname
 
-    * @private
 
-    * @since 1.0.0
 
-    */
 
-   getAppname() {
 
-     if (this.pkg.name) {
 
-       debug('Loaded appname(%s) from package.json', this.pkg.name);
 
-       return this.pkg.name;
 
-     }
 
-     const pkg = path.join(this.options.baseDir, 'package.json');
 
-     throw new Error(`name is required from ${pkg}`);
 
-   }
 
-   /**
 
-    * Get home directory
 
-    * @return {String} home directory
 
-    * @since 3.4.0
 
-    */
 
-   getHomedir() {
 
-     // EGG_HOME for test
 
-     return process.env.EGG_HOME || homedir() || '/home/admin';
 
-   }
 
-   /**
 
-    * Get app info
 
-    * @return {AppInfo} appInfo
 
-    * @since 1.0.0
 
-    */
 
-   getAppInfo() {
 
-     const env = this.serverEnv;
 
-     const scope = this.serverScope;
 
-     const home = this.getHomedir();
 
-     const baseDir = this.options.baseDir;
 
-     /**
 
-      * Meta information of the application
 
-      * @class AppInfo
 
-      */
 
-     return {
 
-       /**
 
-        * The name of the application, retrieve from the name property in `package.json`.
 
-        * @member {String} AppInfo#name
 
-        */
 
-       name: this.getAppname(),
 
-       /**
 
-        * The current directory, where the application code is.
 
-        * @member {String} AppInfo#baseDir
 
-        */
 
-       baseDir,
 
-       /**
 
-        * The environment of the application, **it's not NODE_ENV**
 
-        *
 
-        * 1. from `$baseDir/config/env`
 
-        * 2. from EGG_SERVER_ENV
 
-        * 3. from NODE_ENV
 
-        *
 
-        * env | description
 
-        * ---       | ---
 
-        * test      | system integration testing
 
-        * prod      | production
 
-        * local     | local on your own computer
 
-        * unittest  | unit test
 
-        *
 
-        * @member {String} AppInfo#env
 
-        * @see https://eggjs.org/zh-cn/basics/env.html
 
-        */
 
-       env,
 
-       /**
 
-        * @member {String} AppInfo#scope
 
-        */
 
-       scope,
 
-       /**
 
-        * The use directory, same as `process.env.HOME`
 
-        * @member {String} AppInfo#HOME
 
-        */
 
-       HOME: home,
 
-       /**
 
-        * parsed from `package.json`
 
-        * @member {Object} AppInfo#pkg
 
-        */
 
-       pkg: this.pkg,
 
-       /**
 
-        * The directory whether is baseDir or HOME depend on env.
 
-        * it's good for test when you want to write some file to HOME,
 
-        * but don't want to write to the real directory,
 
-        * so use root to write file to baseDir instead of HOME when unittest.
 
-        * keep root directory in baseDir when local and unittest
 
-        * @member {String} AppInfo#root
 
-        */
 
-       root: env === 'local' || env === 'unittest' ? baseDir : home,
 
-     };
 
-   }
 
-   /**
 
-    * Get {@link EggLoader#eggPaths}
 
-    * @return {Array} framework directories
 
-    * @see {@link EggLoader#eggPaths}
 
-    * @private
 
-    * @since 1.0.0
 
-    */
 
-   getEggPaths() {
 
-     // avoid require recursively
 
-     const EggCore = require('../egg');
 
-     const eggPaths = [];
 
-     let proto = this.app;
 
-     // Loop for the prototype chain
 
-     while (proto) {
 
-       proto = Object.getPrototypeOf(proto);
 
-       // stop the loop if
 
-       // - object extends Object
 
-       // - object extends EggCore
 
-       if (proto === Object.prototype || proto === EggCore.prototype) {
 
-         break;
 
-       }
 
-       assert(proto.hasOwnProperty(Symbol.for('egg#eggPath')), 'Symbol.for(\'egg#eggPath\') is required on Application');
 
-       const eggPath = proto[Symbol.for('egg#eggPath')];
 
-       assert(eggPath && typeof eggPath === 'string', 'Symbol.for(\'egg#eggPath\') should be string');
 
-       assert(fs.existsSync(eggPath), `${eggPath} not exists`);
 
-       const realpath = fs.realpathSync(eggPath);
 
-       if (!eggPaths.includes(realpath)) {
 
-         eggPaths.unshift(realpath);
 
-       }
 
-     }
 
-     return eggPaths;
 
-   }
 
-   // Low Level API
 
-   /**
 
-    * Load single file, will invoke when export is function
 
-    *
 
-    * @param {String} filepath - fullpath
 
-    * @param {Array} inject - pass rest arguments into the function when invoke
 
-    * @return {Object} exports
 
-    * @example
 
-    * ```js
 
-    * app.loader.loadFile(path.join(app.options.baseDir, 'config/router.js'));
 
-    * ```
 
-    * @since 1.0.0
 
-    */
 
-   loadFile(filepath, ...inject) {
 
-     filepath = filepath && this.resolveModule(filepath);
 
-     if (!filepath) {
 
-       return null;
 
-     }
 
-     // function(arg1, args, ...) {}
 
-     if (inject.length === 0) inject = [ this.app ];
 
-     let ret = this.requireFile(filepath);
 
-     if (is.function(ret) && !is.class(ret)) {
 
-       ret = ret(...inject);
 
-     }
 
-     return ret;
 
-   }
 
-   /**
 
-    * @param {String} filepath - fullpath
 
-    * @return {Object} exports
 
-    * @private
 
-    */
 
-   requireFile(filepath) {
 
-     const timingKey = `Require(${this[REQUIRE_COUNT]++}) ${utils.getResolvedFilename(filepath, this.options.baseDir)}`;
 
-     this.timing.start(timingKey);
 
-     const ret = utils.loadFile(filepath);
 
-     this.timing.end(timingKey);
 
-     return ret;
 
-   }
 
-   /**
 
-    * Get all loadUnit
 
-    *
 
-    * loadUnit is a directory that can be loaded by EggLoader, it has the same structure.
 
-    * loadUnit has a path and a type(app, framework, plugin).
 
-    *
 
-    * The order of the loadUnits:
 
-    *
 
-    * 1. plugin
 
-    * 2. framework
 
-    * 3. app
 
-    *
 
-    * @return {Array} loadUnits
 
-    * @since 1.0.0
 
-    */
 
-   getLoadUnits() {
 
-     if (this.dirs) {
 
-       return this.dirs;
 
-     }
 
-     const dirs = this.dirs = [];
 
-     if (this.orderPlugins) {
 
-       for (const plugin of this.orderPlugins) {
 
-         dirs.push({
 
-           path: plugin.path,
 
-           type: 'plugin',
 
-         });
 
-       }
 
-     }
 
-     // framework or egg path
 
-     for (const eggPath of this.eggPaths) {
 
-       dirs.push({
 
-         path: eggPath,
 
-         type: 'framework',
 
-       });
 
-     }
 
-     // application
 
-     dirs.push({
 
-       path: this.options.baseDir,
 
-       type: 'app',
 
-     });
 
-     debug('Loaded dirs %j', dirs);
 
-     return dirs;
 
-   }
 
-   /**
 
-    * Load files using {@link FileLoader}, inject to {@link Application}
 
-    * @param {String|Array} directory - see {@link FileLoader}
 
-    * @param {String} property - see {@link FileLoader}
 
-    * @param {Object} opt - see {@link FileLoader}
 
-    * @since 1.0.0
 
-    */
 
-   loadToApp(directory, property, opt) {
 
-     const target = this.app[property] = {};
 
-     opt = Object.assign({}, {
 
-       directory,
 
-       target,
 
-       inject: this.app,
 
-     }, opt);
 
-     const timingKey = `Load "${String(property)}" to Application`;
 
-     this.timing.start(timingKey);
 
-     new FileLoader(opt).load();
 
-     this.timing.end(timingKey);
 
-   }
 
-   /**
 
-    * Load files using {@link ContextLoader}
 
-    * @param {String|Array} directory - see {@link ContextLoader}
 
-    * @param {String} property - see {@link ContextLoader}
 
-    * @param {Object} opt - see {@link ContextLoader}
 
-    * @since 1.0.0
 
-    */
 
-   loadToContext(directory, property, opt) {
 
-     opt = Object.assign({}, {
 
-       directory,
 
-       property,
 
-       inject: this.app,
 
-     }, opt);
 
-     const timingKey = `Load "${String(property)}" to Context`;
 
-     this.timing.start(timingKey);
 
-     new ContextLoader(opt).load();
 
-     this.timing.end(timingKey);
 
-   }
 
-   /**
 
-    * @member {FileLoader} EggLoader#FileLoader
 
-    * @since 1.0.0
 
-    */
 
-   get FileLoader() {
 
-     return FileLoader;
 
-   }
 
-   /**
 
-    * @member {ContextLoader} EggLoader#ContextLoader
 
-    * @since 1.0.0
 
-    */
 
-   get ContextLoader() {
 
-     return ContextLoader;
 
-   }
 
-   getTypeFiles(filename) {
 
-     const files = [ `${filename}.default` ];
 
-     if (this.serverScope) files.push(`${filename}.${this.serverScope}`);
 
-     if (this.serverEnv === 'default') return files;
 
-     files.push(`${filename}.${this.serverEnv}`);
 
-     if (this.serverScope) files.push(`${filename}.${this.serverScope}_${this.serverEnv}`);
 
-     return files;
 
-   }
 
-   resolveModule(filepath) {
 
-     let fullPath;
 
-     try {
 
-       fullPath = require.resolve(filepath);
 
-     } catch (e) {
 
-       return undefined;
 
-     }
 
-     if (process.env.EGG_TYPESCRIPT !== 'true' && fullPath.endsWith('.ts')) {
 
-       return undefined;
 
-     }
 
-     return fullPath;
 
-   }
 
- }
 
- /**
 
-  * Mixin methods to EggLoader
 
-  * // ES6 Multiple Inheritance
 
-  * https://medium.com/@leocavalcante/es6-multiple-inheritance-73a3c66d2b6b
 
-  */
 
- const loaders = [
 
-   require('./mixin/plugin'),
 
-   require('./mixin/config'),
 
-   require('./mixin/extend'),
 
-   require('./mixin/custom'),
 
-   require('./mixin/service'),
 
-   require('./mixin/middleware'),
 
-   require('./mixin/controller'),
 
-   require('./mixin/router'),
 
-   require('./mixin/custom_loader'),
 
- ];
 
- for (const loader of loaders) {
 
-   Object.assign(EggLoader.prototype, loader);
 
- }
 
- module.exports = EggLoader;
 
 
  |