Skip to content

Commit

Permalink
feat: loader support custom extension (#156)
Browse files Browse the repository at this point in the history
* feat: loader support custom extension

* feat: remove require.extensions

* refactor: refactor to require.extensions

* test: add unittest for ts

* chore: remove tsconfig.json

* test: add d.ts

* test: test loadCustomApp and loadCustomAgent

* test: add more test for custom extend

* fix: spelling mistake

* feat: add typescript options

* docs: update typescript opt to docs

* chore: update comment

* test: add more unittest for ts

* refactor: code optimization

* chore: update error msg

* test: change beforeEach/afterEach to before/after

* feat: add ts check in loadFile

* feat: move resolveModule to egg_loader

* fix: lint fix

* refactor: code optimization
  • Loading branch information
whxaxes authored and popomore committed Mar 23, 2018
1 parent 9370f86 commit 2c6fbbf
Show file tree
Hide file tree
Showing 36 changed files with 278 additions and 40 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ coverage
.logs
npm-debug.log
.vscode
.DS_Store
.DS_Store
yarn.lock
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ EggLoader can easily load files or directories in your [egg] project. In additio
- {String} baseDir - current directory of application
- {Object} app - instance of egg application
- {Object} plugins - merge plugins for test
- {Boolean} typescript - whether support typescript
- {Logger} logger - logger instance,default is console

### High Level APIs
Expand Down Expand Up @@ -226,7 +227,8 @@ Param | Type | Description
-------------- | -------------- | ------------------------
directory | `String/Array` | directories to be loaded
target | `Object` | attach the target object from loaded files
match | `String/Array` | match the files when load, default to `**/*.js`
match | `String/Array` | match the files when load, default to `**/*.js`(if typescript was true, default to `[ '**/*.(js|ts)', '!**/*.d.ts' ]`)
typescript | `Boolean` | whether support typescript
ignore | `String/Array` | ignore the files when load
initializer | `Function` | 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`
caseStyle | `String/Function` | set property's case when converting a filepath to property list.
Expand Down
2 changes: 2 additions & 0 deletions lib/egg.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class EggCore extends KoaApplication {
* @param {Object} options - options
* @param {String} [options.baseDir=process.cwd()] - the directory of application
* @param {String} [options.type=application|agent] - whether it's running in app worker or agent worker
* @param {Boolean} [options.typescript] - whether support typescript
* @param {Object} [options.plugins] - custom plugins
* @since 1.0.0
*/
Expand Down Expand Up @@ -119,6 +120,7 @@ class EggCore extends KoaApplication {
baseDir: options.baseDir,
app: this,
plugins: options.plugins,
typescript: options.typescript,
logger: this.console,
serverScope: options.serverScope,
});
Expand Down
28 changes: 23 additions & 5 deletions lib/loader/egg_loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ class EggLoader {
* @constructor
* @param {Object} options - options
* @param {String} options.baseDir - the directory of application
* @param {Boolean} options.typescript - whether support typescript
* @param {EggCore} options.app - Application instance
* @param {Logger} options.logger - logger
* @param {Object} [options.plugins] - custom plugins
Expand Down Expand Up @@ -282,7 +283,7 @@ class EggLoader {
* @since 1.0.0
*/
loadFile(filepath, ...inject) {
if (!fs.existsSync(filepath)) {
if (!filepath || !fs.existsSync(filepath)) {
return null;
}

Expand Down Expand Up @@ -354,6 +355,7 @@ class EggLoader {
directory,
target,
inject: this.app,
typescript: this.options.typescript,
}, opt);
new FileLoader(opt).load();
}
Expand All @@ -370,6 +372,7 @@ class EggLoader {
directory,
property,
inject: this.app,
typescript: this.options.typescript,
}, opt);
new ContextLoader(opt).load();
}
Expand All @@ -391,14 +394,29 @@ class EggLoader {
}

getTypeFiles(filename) {
const files = [ `${filename}.default.js` ];
if (this.serverScope) files.push(`${filename}.${this.serverScope}.js`);
const files = [ `${filename}.default` ];
if (this.serverScope) files.push(`${filename}.${this.serverScope}`);
if (this.serverEnv === 'default') return files;

files.push(`${filename}.${this.serverEnv}.js`);
if (this.serverScope) files.push(`${filename}.${this.serverScope}_${this.serverEnv}.js`);
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 (!this.options.typescript && fullPath.endsWith('.ts')) {
return undefined;
}

return fullPath;
}
}

/**
Expand Down
15 changes: 13 additions & 2 deletions lib/loader/file_loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const defaults = {
directory: null,
target: null,
match: undefined,
typescript: false,
ignore: undefined,
lowercaseFirst: false,
caseStyle: 'camel',
Expand All @@ -37,6 +38,7 @@ class FileLoader {
* @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 {Boolean} options.typescript - whether support typescript, default to false
* @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
Expand All @@ -48,6 +50,9 @@ class FileLoader {
constructor(options) {
assert(options.directory, 'options.directory is required');
assert(options.target, 'options.target is required');
if (options.typescript) {
assert(require.extensions['.ts'], '`require.extensions` should contains `.ts` while `options.typescript` was true');
}
this.options = Object.assign({}, defaults, options);

// compatible old options _lowercaseFirst_
Expand Down Expand Up @@ -120,8 +125,14 @@ class FileLoader {
* @since 1.0.0
*/
parse() {
let files = this.options.match || [ '**/*.js' ];
files = Array.isArray(files) ? files : [ files ];
let files = this.options.match;
if (!files) {
files = this.options.typescript
? [ '**/*.(js|ts)', '!**/*.d.ts' ]
: [ '**/*.js' ];
} else {
files = Array.isArray(files) ? files : [ files ];
}

let ignore = this.options.ignore;
if (ignore) {
Expand Down
12 changes: 6 additions & 6 deletions lib/loader/mixin/config.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
'use strict';

const debug = require('debug')('egg-core:config');
const fs = require('fs');
const path = require('path');
const extend = require('extend2');
const assert = require('assert');
Expand Down Expand Up @@ -55,8 +54,8 @@ module.exports = {

_preloadAppConfig() {
const names = [
'config.default.js',
`config.${this.serverEnv}.js`,
'config.default',
`config.${this.serverEnv}`,
];
const target = {};
for (const filename of names) {
Expand All @@ -70,12 +69,13 @@ module.exports = {
const isPlugin = type === 'plugin';
const isApp = type === 'app';

let filepath = path.join(dirpath, 'config', filename);
let filepath = this.resolveModule(path.join(dirpath, 'config', filename));
// let config.js compatible
if (filename === 'config.default.js' && !fs.existsSync(filepath)) {
filepath = path.join(dirpath, 'config/config.js');
if (filename === 'config.default' && !filepath) {
filepath = this.resolveModule(path.join(dirpath, 'config/config'));
}
const config = this.loadFile(filepath, this.appInfo, extraInject);

if (!config) return null;

if (isPlugin || isApp) {
Expand Down
4 changes: 2 additions & 2 deletions lib/loader/mixin/custom.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,15 @@ module.exports = {
*/
loadCustomApp() {
this.getLoadUnits()
.forEach(unit => this.loadFile(path.join(unit.path, 'app.js')));
.forEach(unit => this.loadFile(this.resolveModule(path.join(unit.path, 'app'))));
},

/**
* Load agent.js, same as {@link EggLoader#loadCustomApp}
*/
loadCustomAgent() {
this.getLoadUnits()
.forEach(unit => this.loadFile(path.join(unit.path, 'agent.js')));
.forEach(unit => this.loadFile(this.resolveModule(path.join(unit.path, 'agent'))));
},

};
9 changes: 4 additions & 5 deletions lib/loader/mixin/extend.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
const debug = require('debug')('egg-core:extend');
const deprecate = require('depd')('egg');
const path = require('path');
const utils = require('../../utils');

const originalPrototypes = {
request: require('koa/lib/request'),
Expand Down Expand Up @@ -96,21 +95,21 @@ module.exports = {
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}.js`);
if (isAddUnittest) filepaths.push(filepath + '.unittest.js');
filepaths.push(filepath + `.${this.serverEnv}`);
if (isAddUnittest) filepaths.push(filepath + '.unittest');
}

const mergeRecord = new Map();
for (let filepath of filepaths) {
filepath = utils.resolveModule(filepath);
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 = utils.loadFile(filepath);
const ext = this.loadFile(filepath);

const properties = Object.getOwnPropertyNames(ext)
.concat(Object.getOwnPropertySymbols(ext));
Expand Down
18 changes: 10 additions & 8 deletions lib/loader/mixin/plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,11 @@ module.exports = {
*/
loadPlugin() {
// loader plugins from application
const appPlugins = this.readPluginConfigs(path.join(this.options.baseDir, 'config/plugin.default.js'));
const appPlugins = this.readPluginConfigs(path.join(this.options.baseDir, 'config/plugin.default'));
debug('Loaded app plugins: %j', Object.keys(appPlugins));

// loader plugins from framework
const eggPluginConfigPaths = this.eggPaths.map(eggPath => path.join(eggPath, 'config/plugin.default.js'));
const eggPluginConfigPaths = this.eggPaths.map(eggPath => path.join(eggPath, 'config/plugin.default'));
const eggPlugins = this.readPluginConfigs(eggPluginConfigPaths);
debug('Loaded egg plugins: %j', Object.keys(eggPlugins));

Expand Down Expand Up @@ -159,20 +159,22 @@ module.exports = {
}

const plugins = {};
for (let configPath of newConfigPaths) {
for (const configPath of newConfigPaths) {
let filepath = this.resolveModule(configPath);

// let plugin.js compatible
if (configPath.endsWith('plugin.default.js') && !fs.existsSync(configPath)) {
configPath = configPath.replace(/plugin\.default\.js$/, 'plugin.js');
if (configPath.endsWith('plugin.default') && !filepath) {
filepath = this.resolveModule(configPath.replace(/plugin\.default$/, 'plugin'));
}

if (!fs.existsSync(configPath)) {
if (!filepath) {
continue;
}

const config = loadFile(configPath);
const config = loadFile(filepath);

for (const name in config) {
this.normalizePluginConfig(config, name, configPath);
this.normalizePluginConfig(config, name, filepath);
}

this._extendPlugins(plugins, config);
Expand Down
2 changes: 1 addition & 1 deletion lib/loader/mixin/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@ module.exports = {
*/
loadRouter() {
// 加载 router.js
this.loadFile(path.join(this.options.baseDir, 'app/router.js'));
this.loadFile(this.resolveModule(path.join(this.options.baseDir, 'app/router')));
},
};
10 changes: 1 addition & 9 deletions lib/utils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ module.exports = {
try {
// if not js module, just return content buffer
const extname = path.extname(filepath);
if (![ '.js', '.node', '.json', '' ].includes(extname)) {
if (extname && !require.extensions[extname]) {
return fs.readFileSync(filepath);
}
// require js module
Expand All @@ -27,14 +27,6 @@ module.exports = {
}
},

resolveModule(filepath) {
try {
return require.resolve(filepath);
} catch (e) {
return undefined;
}
},

methods: [ 'head', 'options', 'get', 'put', 'patch', 'post', 'delete' ],

async callFn(fn, args, ctx) {
Expand Down
Loading

0 comments on commit 2c6fbbf

Please sign in to comment.