Skip to content

Commit

Permalink
refactor: implement Loader instead of loading (#4)
Browse files Browse the repository at this point in the history
  • Loading branch information
popomore authored and fengmk2 committed Aug 2, 2016
1 parent 9ddef9d commit c95e85d
Show file tree
Hide file tree
Showing 44 changed files with 573 additions and 20 deletions.
22 changes: 17 additions & 5 deletions lib/base_loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ const fs = require('fs');
const path = require('path');
const assert = require('assert');
const isFunction = require('is-type-of').function;
const loading = require('loading');
const interopRequire = require('interop-require');
const debug = require('debug')('egg:loader');
const Loader = require('./loader');

class EggLoader {

Expand Down Expand Up @@ -129,10 +129,8 @@ class EggLoader {
opt = Object.assign({ lowercaseFirst: true }, opt);
const controllerBase = path.join(this.options.baseDir, 'app/controller');

delete app.controller;
app.controller = {};

loading(controllerBase, opt).into(app, 'controller');
this.loadToApp(controllerBase, 'controller', opt);
app.controllers = app.controller;
app.coreLogger.info('[egg:loader] Controller loaded: %s', controllerBase);
}

Expand Down Expand Up @@ -285,6 +283,20 @@ class EggLoader {
throw new Error('Can not get appname from package.json');
}

loadTo(directory, target, opt) {
opt = Object.assign({}, {
directory,
target,
inject: this.app,
}, opt);
new Loader(opt).load();
}

loadToApp(directory, field, opt) {
const target = this.app[field] = {};
this.loadTo(directory, target, opt);
}

}

/**
Expand Down
135 changes: 135 additions & 0 deletions lib/loader.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
'use strict';

const assert = require('assert');
const fs = require('fs');
const debug = require('debug')('egg-loader:loader');
const path = require('path');
const globby = require('globby');
const interopRequire = require('interop-require');
const is = require('is-type-of');
const FULLPATH = Symbol('EGG_LOADER_ITEM_FULLPATH');

const defaults = {
directory: null,
target: null,
ignore: undefined,
lowercaseFirst: false,
initializer: null,
call: true,
override: false,
inject: undefined,
};

class Loader {

constructor(options) {
assert(options.directory, 'options.directory is required');
assert(options.target, 'options.target is required');
this.options = Object.assign({}, defaults, options);
}

load() {
const items = this.parse();
const target = this.options.target;
for (const item of items) {
debug('loading item %j', item);
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) obj[FULLPATH] = item.fullpath;
} else {
obj = target[property] || {};
}
target[property] = obj;
debug('loaded %s', properties);
return obj;
}, target);
}
return target;
}

parse() {
const files = [ '**/*.js' ];
if (typeof this.options.ignore === 'string') {
files.push('!' + this.options.ignore);
}

let directories = this.options.directory;
if (!Array.isArray(directories)) {
directories = [ directories ];
}

const items = [];
debug('parsing %j', directories);
for (const directory of directories) {
const filepaths = globby.sync(files, { cwd: directory });
for (const filepath of filepaths) {
const fullpath = path.join(directory, filepath);
if (!fs.statSync(fullpath).isFile()) {
continue;
}
const properties = getProperties(filepath, this.options.lowercaseFirst);
const exports = getExports(fullpath, this.options.initializer, this.options.call, this.options.inject);
if (exports == null) continue;
items.push({ fullpath, properties, exports });
debug('parse %s, properties %j, export %j', fullpath, properties, exports);
}
}

return items;
}

}

module.exports = Loader;

// a/b/c.js => ['a', 'b', 'c']
function getProperties(filepath, lowercaseFirst) {
return filepath
.replace('.js', '')
.split('/')
.map(function(property) {
if (!/^[a-z][a-z0-9_-]*$/i.test(property)) {
throw new Error(`${property} is not match 'a-z0-9_-' in ${filepath}`);
}
let result = property.replace(/[_-][a-z]/ig, function(s) {
return s.substring(1).toUpperCase();
});
if (lowercaseFirst) {
result = result[0].toLowerCase() + result.substring(1);
}
return result;
});
}

function getExports(fullpath, initializer, isCall, inject) {
let exports;
try {
exports = interopRequire(fullpath);
} catch (err) {
err.message = 'load file: ' + fullpath + ', error: ' + err.message;
throw err;
}

if (initializer) {
exports = initializer(exports);
}

if (is.class(exports) || is.generatorFunction(exports)) {
return exports;
}

if (isCall && is.function(exports)) {
exports = exports(inject);
if (exports != null) {
return exports;
}
}

return exports;
}
4 changes: 1 addition & 3 deletions lib/middleware_loader.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
'use strict';

const join = require('path').join;
const loading = require('loading');
const is = require('is-type-of');
const debug = require('debug')('egg:loader:middleware');
const inspect = require('util').inspect;
Expand Down Expand Up @@ -36,8 +35,7 @@ module.exports = {
}, opt);
const middlewarePaths = this.loadDirs().map(dir => join(dir, 'app/middleware'));

delete app.middlewares;
loading(middlewarePaths, opt).into(app, 'middlewares');
this.loadToApp(middlewarePaths, 'middlewares', opt);

app.coreLogger.info('Use coreMiddleware order: %j', this.config.coreMiddleware);
app.coreLogger.info('Use appMiddleware order: %j', this.config.appMiddleware);
Expand Down
4 changes: 1 addition & 3 deletions lib/proxy_loader.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
'use strict';

const join = require('path').join;
const loading = require('loading');
const classLoader = Symbol('classLoader');
const utils = require('./utils');

Expand All @@ -21,8 +20,7 @@ module.exports = {
opt = Object.assign({ call: true, lowercaseFirst: true }, opt);
const arr = this.loadDirs().map(dir => join(dir, 'app/proxy'));
// load proxy classes to app.proxyClasses
delete app.proxyClasses;
loading(arr, opt).into(app, 'proxyClasses');
this.loadToApp(arr, 'proxyClasses', opt);

// this.proxy.demoQuery.getUser(uid)
Object.defineProperty(app.context, 'proxy', {
Expand Down
6 changes: 2 additions & 4 deletions lib/service_loader.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
'use strict';

const path = require('path');
const loading = require('loading');
const utils = require('./utils');
const classLoader = Symbol('classLoader');

Expand All @@ -18,15 +17,14 @@ module.exports = {
*/
loadService(opt) {
const app = this.app;
opt = Object.assign({ call: false, lowercaseFirst: true }, opt);
const servicePaths = this.loadDirs().map(dir => {
const servicePath = path.join(dir, 'app/service');
return servicePath;
});

// 载入到 app.serviceClasses
delete app.serviceClasses;
loading(servicePaths, opt).into(app, 'serviceClasses');
opt = Object.assign({ call: false, lowercaseFirst: true }, opt);
this.loadToApp(servicePaths, 'serviceClasses', opt);

/**
* 可以访问到当前应用配置的所有 service,
Expand Down
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,16 @@
"koa": "1",
"koa-router": "4",
"mm": "1",
"pedding": "^1.0.0",
"should": "9",
"supertest": "1"
},
"dependencies": {
"debug": "^2.2.0",
"depd": "^1.1.0",
"extend": "^3.0.0",
"globby": "^6.0.0",
"interop-require": "^1.0.0",
"is-type-of": "^1.0.0",
"loading": "^1.12.0"
"is-type-of": "^1.0.0"
}
}
}
32 changes: 32 additions & 0 deletions test/fixtures/load_dirs/babel/UserProxy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
'use strict';

var _temporalUndefined = {};

var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();

var UserProxy = _temporalUndefined;

function _temporalAssertDefined(val, name, undef) { if (val === undef) { throw new ReferenceError(name + ' is not defined - temporal dead zone'); } return true; }

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }

UserProxy = (function () {
function UserProxy() {
_classCallCheck(this, _temporalAssertDefined(UserProxy, 'UserProxy', _temporalUndefined) && UserProxy);

this.user = {
name: 'xiaochen.gaoxc'
};
}

_createClass(_temporalAssertDefined(UserProxy, 'UserProxy', _temporalUndefined) && UserProxy, [{
key: 'getUser',
value: function getUser() {
return this.user;
}
}]);

return _temporalAssertDefined(UserProxy, 'UserProxy', _temporalUndefined) && UserProxy;
})();

module.exports = _temporalAssertDefined(UserProxy, 'UserProxy', _temporalUndefined) && UserProxy;
15 changes: 15 additions & 0 deletions test/fixtures/load_dirs/class/UserProxy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
'use strict';

class UserProxy {
constructor() {
this.user = {
name: 'xiaochen.gaoxc',
};
}

getUser() {
return this.user;
}
}

module.exports = UserProxy;
9 changes: 9 additions & 0 deletions test/fixtures/load_dirs/dao/TestClass.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
'use strict';

module.exports = class TestClass {
constructor() {
this.user = {
name: 'kai.fangk',
};
}
}
9 changes: 9 additions & 0 deletions test/fixtures/load_dirs/dao/testFunction.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
'use strict';

module.exports = function(obj) {
return {
user: {
name: 'kai.fangk',
},
};
};
11 changes: 11 additions & 0 deletions test/fixtures/load_dirs/dao/testReturnFunction.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
'use strict';

module.exports = function(obj) {
return function() {
return {
user: {
name: 'kai.fangk',
},
}
};
};
Empty file.
Empty file.
Empty file.
Empty file.
6 changes: 6 additions & 0 deletions test/fixtures/load_dirs/es6_module/mod.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
'use strict';

Object.defineProperty(exports, "__esModule", { value: true });
exports.default = function() {
return { a: 1 };
};
3 changes: 3 additions & 0 deletions test/fixtures/load_dirs/ignore/a.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = function() {
return { a: 1 };
};
4 changes: 4 additions & 0 deletions test/fixtures/load_dirs/ignore/util/a.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module.exports = {
method1: function() {
}
};
1 change: 1 addition & 0 deletions test/fixtures/load_dirs/ignore/util/b/b.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = { b: 2 };
5 changes: 5 additions & 0 deletions test/fixtures/load_dirs/lowercase/SomeClass.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
exports.getByName = function (name, callback) {
setTimeout(function () {
callback(null, {name: name});
}, 1);
};
5 changes: 5 additions & 0 deletions test/fixtures/load_dirs/lowercase/SomeDir/SomeSubClass.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
exports.getByName = function (name, callback) {
setTimeout(function () {
callback(null, {name: name});
}, 1);
};
1 change: 1 addition & 0 deletions test/fixtures/load_dirs/middlewares/app/m1.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = function () {};
1 change: 1 addition & 0 deletions test/fixtures/load_dirs/middlewares/app/m2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = function () {};
Empty file.
Empty file.
1 change: 1 addition & 0 deletions test/fixtures/load_dirs/middlewares/default/dm1.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = function () {};
1 change: 1 addition & 0 deletions test/fixtures/load_dirs/middlewares/default/dm2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = function () {};
1 change: 1 addition & 0 deletions test/fixtures/load_dirs/middlewares/default/session.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = function () {};
Loading

0 comments on commit c95e85d

Please sign in to comment.