Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: loader support custom extension #156

Merged
merged 20 commits into from
Mar 23, 2018
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
10 changes: 5 additions & 5 deletions lib/loader/egg_loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,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 @@ -391,12 +391,12 @@ 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;
}
}
Expand Down
25 changes: 23 additions & 2 deletions lib/loader/file_loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const deprecate = require('depd')('egg');
const utils = require('../utils');
const FULLPATH = Symbol('EGG_LOADER_ITEM_FULLPATH');
const EXPORTS = Symbol('EGG_LOADER_ITEM_EXPORTS');
const MATCH = Symbol('EGG_LOADER_ITEM_MATCH');

const defaults = {
directory: null,
Expand Down Expand Up @@ -57,6 +58,22 @@ class FileLoader {
}
}

/**
* default glob used to match files, creating by require.extensions.
* @return {Array} match
*/
get defaultMatch() {
if (!this[MATCH]) {
// cache the default match
const extensions = Object.keys(require.extensions).map(ext => ext.substring(1));
this[MATCH] = [ '**/*.(' + extensions.join('|') + ')' ];
if (extensions.includes('ts')) {
this[MATCH].push('!**/*.d.ts');
}
}
return this[MATCH];
}

/**
* attach items to target object. Mapping the directory to properties.
* `app/controller/group/repository.js` => `target.group.repository`
Expand Down Expand Up @@ -120,8 +137,12 @@ 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) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里不能用原来的逻辑?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

如果用了 defaultMatch,就不需要再做 isArray 的判断了吧,因为就已经是 Array 了

files = this.defaultMatch;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

如果这样的话,是不是干脆配置下默认的 match 就好了,不用 extensions?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

大概想扩展吧,比如 jsx 啥的

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

那也是放到默认的 match 吧,现在这样子不就是根据 extensions 指定生成 match 么
那还不如直接让开发者写 match 覆盖即可

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

不过这里,要看怎么根据上层传递的值来开启 TS,否则我担心会 break,现在我们打包的时候好像 TS 和 JS 都打包进去了?

@whxaxes 把你 require.extensions 贴贴 @popomore

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

我想了又想,也觉得不该用 require.extensions,毕竟是已经弃用了。。。

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

可以的,改成 typescript

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

在 application 中提供一个 extensions 可供配置允许加载的后缀名会不会更好一些?方便拓展 jsx、mjs 等

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

要么就根据配置的后缀加载所有文件,然后根据是否扩展 require.extension 判断是否使用这个文件,比如 ts 通过 egg-cluster 开起 ts extension,这时就会加载 ts 了,而运行时如果只使用 js 就去除 扩展 extension

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

你看他上面那个链接就是 require.extensions, 这个方式还行,我只是担心这个 api 已经被标注为废弃

Copy link
Member Author

@whxaxes whxaxes Mar 21, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

如果可以用 require.extensions,我就回滚到此前的 commit.(7c202b2)

} else {
files = Array.isArray(files) ? files : [ files ];
}

let ignore = this.options.ignore;
if (ignore) {
Expand Down
13 changes: 7 additions & 6 deletions lib/loader/mixin/config.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
'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');
const utils = require('../../utils');

const SET_CONFIG_META = Symbol('Loader#setConfigMeta');

Expand Down Expand Up @@ -55,8 +55,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 +70,13 @@ module.exports = {
const isPlugin = type === 'plugin';
const isApp = type === 'app';

let filepath = path.join(dirpath, 'config', filename);
let filepath = utils.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 = utils.resolveModule(path.join(dirpath, 'config/config'));
}
const config = this.loadFile(filepath, this.appInfo, extraInject);

if (!config) return null;

if (isPlugin || isApp) {
Expand Down
5 changes: 3 additions & 2 deletions lib/loader/mixin/custom.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use strict';

const path = require('path');
const utils = require('../../utils');

module.exports = {

Expand All @@ -22,15 +23,15 @@ module.exports = {
*/
loadCustomApp() {
this.getLoadUnits()
.forEach(unit => this.loadFile(path.join(unit.path, 'app.js')));
.forEach(unit => this.loadFile(utils.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(utils.resolveModule(path.join(unit.path, 'agent'))));
},

};
4 changes: 2 additions & 2 deletions lib/loader/mixin/extend.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,8 @@ 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();
Expand Down
19 changes: 11 additions & 8 deletions lib/loader/mixin/plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const path = require('path');
const debug = require('debug')('egg-core:plugin');
const sequencify = require('../../utils/sequencify');
const loadFile = require('../../utils').loadFile;
const utils = require('../../utils');

module.exports = {

Expand Down Expand Up @@ -56,11 +57,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 +160,22 @@ module.exports = {
}

const plugins = {};
for (let configPath of newConfigPaths) {
for (const configPath of newConfigPaths) {
let filepath = utils.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 = utils.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
3 changes: 2 additions & 1 deletion lib/loader/mixin/router.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use strict';

const path = require('path');
const utils = require('../../utils');

module.exports = {

Expand All @@ -12,6 +13,6 @@ module.exports = {
*/
loadRouter() {
// 加载 router.js
this.loadFile(path.join(this.options.baseDir, 'app/router.js'));
this.loadFile(utils.resolveModule(path.join(this.options.baseDir, 'app/router')));
},
};
2 changes: 1 addition & 1 deletion 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 Down
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"devDependencies": {
"autod": "^3.0.1",
"coffee": "^4.1.0",
"egg": "^2.5.0",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

实现在载 ts 就好,不要依赖 egg

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个是写 fixtures 的时候引入了 egg 的 types...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

我看一下怎么去掉先

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

如果去掉的话,就只能按照 js 的方式来写 ts 了...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

测试的时候自己加一个 ts 的 extension 好了,断言转后的是否符合预期。

这里只要测 loader 就好了,另外可以在 egg 里加个用例

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK,我改一下

"egg-bin": "^4.3.7",
"egg-ci": "^1.8.0",
"eslint": "^4.18.2",
Expand All @@ -45,7 +46,9 @@
"pedding": "^1.1.0",
"rimraf": "^2.6.2",
"spy": "^1.0.0",
"supertest": "^3.0.0"
"supertest": "^3.0.0",
"ts-node": "^5.0.1",
"typescript": "^2.7.2"
},
"dependencies": {
"co": "^4.6.0",
Expand Down
57 changes: 57 additions & 0 deletions test/egg-ts.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
'use strict';

const request = require('supertest');
const assert = require('assert');
const utils = require('./utils');
const tsNode = require('ts-node');

describe('test/egg-ts.test.js', () => {
let app;

it('should support ts-node', async () => {
tsNode.register({
typeCheck: true,
compilerOptions: {
target: 'es2017',
module: 'commonjs',
moduleResolution: 'node',
},
});

app = utils.createApp('egg-ts');
app.Helper = class Helper {};
app.loader.loadPlugin();
app.loader.loadConfig();
app.loader.loadApplicationExtend();
app.loader.loadAgentExtend();
app.loader.loadRequestExtend();
app.loader.loadResponseExtend();
app.loader.loadContextExtend();
app.loader.loadHelperExtend();
app.loader.loadService();
app.loader.loadController();
app.loader.loadRouter();
app.loader.loadPlugin();
app.loader.loadMiddleware();
app.loader.loadCustomApp();
app.loader.loadCustomAgent();

await request(app.callback())
.get('/')
.expect(res => {
assert(res.text.includes('from extend context'));
assert(res.text.includes('from extend application'));
assert(res.text.includes('from extend request'));
assert(res.text.includes('from extend agent'));
assert(res.text.includes('from extend helper'));
assert(res.text.includes('from extend response'));
assert(res.text.includes('from custom app'));
assert(res.text.includes('from custom agent'));
assert(res.text.includes('from plugins'));
assert(res.text.includes('from config.default'));
assert(res.text.includes('from middleware'));
assert(res.text.includes('from service'));
})
.expect(200);
});
});
11 changes: 11 additions & 0 deletions test/fixtures/egg-ts/agent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Application } from 'egg';

export default (app: Application) => {
app.fromCustomAgent = 'from custom agent';
};

declare module 'egg' {
interface Application {
fromCustomAgent: string;
}
}
12 changes: 12 additions & 0 deletions test/fixtures/egg-ts/app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Application } from 'egg';

export default (app: Application) => {
app.fromCustomApp = 'from custom app';
};

declare module 'egg' {
interface Application {
Helper: any;
fromCustomApp: string;
}
}
24 changes: 24 additions & 0 deletions test/fixtures/egg-ts/app/controller/home.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Controller } from 'egg';

export default class HomeController extends Controller {
public async index() {
const { ctx, app } = this;
const serviceText = ctx.service.test.getTest();
const helper = new ctx.app.Helper();

ctx.body = [
ctx.contextShow(),
ctx.app.applicationShow(),
ctx.request.requestShow(),
ctx.response.responseShow(),
ctx.app.agentShow(),
helper.helperShow(),
app.fromCustomApp,
app.fromCustomAgent,
app.config.test,
app.config.testFromA,
ctx.mid,
serviceText
].join(',');
}
}
7 changes: 7 additions & 0 deletions test/fixtures/egg-ts/app/controller/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import Home from './home';

declare module 'egg' {
interface IController {
home: Home;
}
}
5 changes: 5 additions & 0 deletions test/fixtures/egg-ts/app/extend/agent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export default {
agentShow() {
return 'from extend agent';
}
}
5 changes: 5 additions & 0 deletions test/fixtures/egg-ts/app/extend/application.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export default {
applicationShow() {
return 'from extend application';
}
}
5 changes: 5 additions & 0 deletions test/fixtures/egg-ts/app/extend/context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export default {
contextShow() {
return 'from extend context';
}
}
5 changes: 5 additions & 0 deletions test/fixtures/egg-ts/app/extend/helper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export default {
helperShow() {
return 'from extend helper';
}
}
Loading