-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
xuqingshan
committed
Sep 1, 2020
1 parent
0b66e25
commit ece96ee
Showing
24 changed files
with
1,262 additions
and
1,147 deletions.
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,177 @@ | ||
```js | ||
loadController(opt) { | ||
this.timing.start('Load Controller'); | ||
opt = Object.assign({ | ||
caseStyle: 'lower', | ||
directory: path.join(this.options.baseDir, 'app/controller'), | ||
initializer: (obj, opt) => { | ||
if (is.function(obj) && !is.generatorFunction(obj) && !is.class(obj) && !is.asyncFunction(obj)) { | ||
obj = obj(this.app); | ||
} | ||
if (is.class(obj)) { | ||
obj.prototype.pathName = opt.pathName; | ||
obj.prototype.fullPath = opt.path; | ||
return wrapClass(obj); | ||
} | ||
if (is.object(obj)) { | ||
return wrapObject(obj, opt.path); | ||
} | ||
// support generatorFunction for forward compatbility | ||
if (is.generatorFunction(obj) || is.asyncFunction(obj)) { | ||
return wrapObject({ 'module.exports': obj }, opt.path)['module.exports']; | ||
} | ||
return obj; | ||
}, | ||
}, opt); | ||
const controllerBase = opt.directory; | ||
|
||
this.loadToApp(controllerBase, 'controller', opt); | ||
this.options.logger.info('[egg:loader] Controller loaded: %s', controllerBase); | ||
this.timing.end('Load Controller'); | ||
}, | ||
``` | ||
|
||
1. opt: | ||
```js | ||
opt = Object.assign({ | ||
caseStyle: 'lower', | ||
directory: path.join(this.options.baseDir, 'app/controller'), | ||
initializer: (obj, opt) => { | ||
if (is.function(obj) && !is.generatorFunction(obj) && !is.class(obj) && !is.asyncFunction(obj)) { | ||
obj = obj(this.app); | ||
} | ||
if (is.class(obj)) { | ||
obj.prototype.pathName = opt.pathName; | ||
obj.prototype.fullPath = opt.path; | ||
return wrapClass(obj); | ||
} | ||
if (is.object(obj)) { | ||
return wrapObject(obj, opt.path); | ||
} | ||
if (is.generatorFunction(obj) || is.asyncFunction(obj)) { | ||
return wrapObject({ 'module.exports': obj }, opt.path)['module.exports']; | ||
} | ||
return obj; | ||
}, | ||
}, opt); | ||
// opt 是 undefined,这个opt操作初始化了一个opt对象,供后续的fileloader调用 | ||
``` | ||
2. new FileLoader(opt)的过程; | ||
将opt复制一份挂载到 this.options上; | ||
|
||
3. load过程: | ||
先parse: | ||
files = ['**/*.js']; | ||
directories = ['app/controller']; | ||
遍历app/controller下的所有文件 | ||
取出每个文件的: | ||
properties, // app/service/foo/bar.js => [ 'foo', 'bar' ] | ||
将路径解析成字符串; | ||
pathName,// 文件路径; | ||
|
||
exports = getExports(fullpath, this.options, pathName); | ||
获取controller内容,执行initializer; | ||
obj.prototype.pathName = opt.pathName; | ||
obj.prototype.fullPath = opt.path; | ||
return wrapClass(obj); | ||
|
||
```js | ||
// Get exports from filepath | ||
// If exports is null/undefined, it will be ignored | ||
function getExports(fullpath, { initializer, call, inject }, pathName) { | ||
let exports = utils.loadFile(fullpath); | ||
if (initializer) { | ||
// { index1: classControllerMiddleware, | ||
// index2: classControllerMiddleware | ||
// } | ||
exports = initializer(exports, { path: fullpath, pathName }); | ||
} | ||
if (is.class(exports) || is.generatorFunction(exports) || is.asyncFunction(exports)) { | ||
return exports; | ||
} | ||
if (call && is.function(exports)) { | ||
exports = exports(inject); | ||
if (exports != null) { | ||
return exports; | ||
} | ||
} | ||
return exports; | ||
} | ||
``` | ||
|
||
在wrapClass内部: | ||
|
||
```js | ||
// wrap the class, yield a object with middlewares | ||
function wrapClass(Controller) { | ||
let proto = Controller.prototype; | ||
const ret = {}; | ||
// tracing the prototype chain | ||
while (proto !== Object.prototype) { | ||
const keys = Object.getOwnPropertyNames(proto); | ||
for (const key of keys) { | ||
// getOwnPropertyNames will return constructor | ||
// that should be ignored | ||
if (key === 'constructor') { | ||
continue; | ||
} | ||
// skip getter, setter & non-function properties | ||
const d = Object.getOwnPropertyDescriptor(proto, key); | ||
// prevent to override sub method | ||
if (is.function(d.value) && !ret.hasOwnProperty(key)) { | ||
ret[key] = methodToMiddleware(Controller, key); | ||
ret[key][FULLPATH] = Controller.prototype.fullPath + '#' + Controller.name + '.' + key + '()'; | ||
} | ||
} | ||
proto = Object.getPrototypeOf(proto); | ||
} | ||
return ret; | ||
|
||
function methodToMiddleware(Controller, key) { | ||
return function classControllerMiddleware(...args) { | ||
const controller = new Controller(this); | ||
if (!this.app.config.controller || !this.app.config.controller.supportParams) { | ||
args = [ this ]; | ||
} | ||
// fn.call(ctx, ...args); | ||
return utils.callFn(controller[key], args, controller); | ||
}; | ||
} | ||
} | ||
|
||
// 关键是这一句: | ||
// ret[key] = methodToMiddleware(Controller, key); | ||
// 将home.js里的所有方法放到ret[key]上。 | ||
``` | ||
|
||
|
||
挂载的关键是 | ||
```js | ||
new FileLoader(opt).load(); | ||
``` | ||
整个过程是: | ||
|
||
1. this.loadController(); | ||
|
||
2. this.loadToApp(controllerBase, 'controller', opt); | ||
|
||
3. new FileLoader(opt).load(); | ||
|
||
4. this.parse(); | ||
|
||
5. this.load(); // 将controller里的各个方法挂到 appWorkerLoader.app.controller上 | ||
|
||
parse() 过程: | ||
|
||
directories = this.options.directory; // controller 目录 | ||
找到该目录下的所有文件: | ||
遍历每个文件取得 | ||
properties = getProperties(filepath, this.options); | ||
pathName | ||
exports = getExports(fullpath, this.options, pathName); // 取得controller文件比如home.js里的所有方法, | ||
item.push({fullpath, properties, exports}); | ||
return item; | ||
|
||
load() 过程: | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
this.loadFile(path.join(this.options.baseDir, 'app/router')); | ||
|
||
router的初始化,发生在解构赋值的时候 | ||
|
||
```js app/router.js | ||
const { router, controller } = app; // TODO: app的router属性什么时候加上的。 | ||
``` | ||
|
||
```js | ||
class EggCore extends KoaApplication { | ||
constructor(options = {}) {/**/} | ||
/** | ||
* get router | ||
* @member {Router} EggCore#router | ||
* @since 1.0.0 | ||
*/ | ||
get router() { | ||
console.log('enter router') | ||
if (this[ROUTER]) { | ||
return this[ROUTER]; | ||
} | ||
const router = this[ROUTER] = new Router({ sensitive: true }, this); | ||
// register router middleware | ||
this.beforeStart(() => { | ||
this.use(router.middleware()); | ||
}); | ||
return router; | ||
} | ||
} | ||
``` | ||
|
||
路由注册的时候,又引进来一个新的类Layer | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
class FileLoader { | ||
|
||
/** | ||
* @class | ||
* @param {Object} options - options | ||
* @param {String|Array} options.directory - directories to be loaded | ||
* @param {Object} options.target - attach the target object from loaded files | ||
* @param {String} options.match - match the files when load, support glob, default to all js files | ||
* @param {String} options.ignore - ignore the files when load, support glob | ||
* @param {Function} options.initializer - custom file exports, receive two parameters, first is the inject object(if not js file, will be content buffer), second is an `options` object that contain `path` | ||
* @param {Boolean} options.call - determine whether invoke when exports is function | ||
* @param {Boolean} options.override - determine whether override the property when get the same name | ||
* @param {Object} options.inject - an object that be the argument when invoke the function | ||
* @param {Function} options.filter - a function that filter the exports which can be loaded | ||
* @param {String|Function} options.caseStyle - set property's case when converting a filepath to property list. | ||
*/ | ||
constructor(options) { | ||
assert(options.directory, 'options.directory is required'); | ||
assert(options.target, 'options.target is required'); | ||
this.options = Object.assign({}, defaults, options); | ||
|
||
// compatible old options _lowercaseFirst_ | ||
if (this.options.lowercaseFirst === true) { | ||
deprecate('lowercaseFirst is deprecated, use caseStyle instead'); | ||
this.options.caseStyle = 'lower'; | ||
} | ||
} | ||
|
||
/** | ||
* attach items to target object. Mapping the directory to properties. | ||
* `app/controller/group/repository.js` => `target.group.repository` | ||
* @return {Object} target | ||
* @since 1.0.0 | ||
*/ | ||
load() { | ||
const items = this.parse(); | ||
const target = this.options.target; | ||
// debugger; | ||
for (const item of items) { | ||
debug('loading item %j', item); | ||
// item { properties: [ 'a', 'b', 'c'], exports } | ||
// => target.a.b.c = exports | ||
item.properties.reduce((target, property, index) => { | ||
let obj; | ||
const properties = item.properties.slice(0, index + 1).join('.'); | ||
if (index === item.properties.length - 1) { | ||
if (property in target) { | ||
if (!this.options.override) throw new Error(`can't overwrite property '${properties}' from ${target[property][FULLPATH]} by ${item.fullpath}`); | ||
} | ||
obj = item.exports; | ||
if (obj && !is.primitive(obj)) { | ||
obj[FULLPATH] = item.fullpath; | ||
obj[EXPORTS] = true; | ||
} | ||
} else { | ||
obj = target[property] || {}; | ||
} | ||
target[property] = obj; | ||
debug('loaded %s', properties); | ||
return obj; | ||
}, target); | ||
} | ||
return target; | ||
} | ||
|
||
/** | ||
* Parse files from given directories, then return an items list, each item contains properties and exports. | ||
* | ||
* For example, parse `app/controller/group/repository.js` | ||
* | ||
* ``` | ||
* module.exports = app => { | ||
* return class RepositoryController extends app.Controller {}; | ||
* } | ||
* ``` | ||
* | ||
* It returns a item | ||
* | ||
* ``` | ||
* { | ||
* properties: [ 'group', 'repository' ], | ||
* exports: app => { ... }, | ||
* } | ||
* ``` | ||
* | ||
* `Properties` is an array that contains the directory of a filepath. | ||
* | ||
* `Exports` depends on type, if exports is a function, it will be called. if initializer is specified, it will be called with exports for customizing. | ||
* @return {Array} items | ||
* @since 1.0.0 | ||
* 文件加载的这个class 明天看吧。 | ||
*/ | ||
parse() { | ||
let files = this.options.match; | ||
if (!files) { | ||
files = (process.env.EGG_TYPESCRIPT === 'true' && utils.extensions['.ts']) | ||
? [ '**/*.(js|ts)', '!**/*.d.ts' ] | ||
: [ '**/*.js' ]; | ||
} else { | ||
files = Array.isArray(files) ? files : [ files ]; | ||
} | ||
|
||
let ignore = this.options.ignore; | ||
if (ignore) { | ||
ignore = Array.isArray(ignore) ? ignore : [ ignore ]; | ||
ignore = ignore.filter(f => !!f).map(f => '!' + f); | ||
files = files.concat(ignore); | ||
} | ||
|
||
let directories = this.options.directory; | ||
if (!Array.isArray(directories)) { | ||
directories = [ directories ]; | ||
} | ||
|
||
const filter = is.function(this.options.filter) ? this.options.filter : null; | ||
const items = []; | ||
debug('parsing %j', directories); | ||
for (const directory of directories) { | ||
// debugger globby 之后跳到了路由文件,怎么做到的呢。TODO: 也算进步 | ||
const filepaths = globby.sync(files, { cwd: directory }); | ||
for (const filepath of filepaths) { | ||
const fullpath = path.join(directory, filepath); | ||
if (!fs.statSync(fullpath).isFile()) continue; | ||
// get properties | ||
// app/service/foo/bar.js => [ 'foo', 'bar' ] | ||
const properties = getProperties(filepath, this.options); | ||
// app/service/foo/bar.js => service.foo.bar | ||
const pathName = directory.split(/[/\\]/).slice(-1) + '.' + properties.join('.'); | ||
// get exports from the file | ||
const exports = getExports(fullpath, this.options, pathName); | ||
|
||
// ignore exports when it's null or false returned by filter function | ||
if (exports == null || (filter && filter(exports) === false)) continue; | ||
|
||
// set properties of class | ||
if (is.class(exports)) { | ||
exports.prototype.pathName = pathName; | ||
exports.prototype.fullPath = fullpath; | ||
} | ||
|
||
items.push({ fullpath, properties, exports }); | ||
debug('parse %s, properties %j, export %j', fullpath, properties, exports); | ||
} | ||
} | ||
|
||
return items; | ||
} | ||
|
||
} | ||
|
||
/** | ||
* 两个方法:load 和 parse。 | ||
* | ||
* load时,将 | ||
* | ||
* parse时,将 controller/home.js | ||
* 挂载到appWorkerLoader.app.controller上 | ||
*/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.