ee-core 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +2 -0
- package/bin/tools.js +22 -0
- package/config/config.default.js +179 -0
- package/core/index.js +13 -0
- package/core/lib/ee.js +220 -0
- package/core/lib/loader/context_loader.js +105 -0
- package/core/lib/loader/ee_loader.js +427 -0
- package/core/lib/loader/file_loader.js +262 -0
- package/core/lib/loader/mixin/config.js +138 -0
- package/core/lib/loader/mixin/controller.js +123 -0
- package/core/lib/loader/mixin/service.js +29 -0
- package/core/lib/utils/base_context_class.js +34 -0
- package/core/lib/utils/index.js +100 -0
- package/core/lib/utils/sequencify.js +59 -0
- package/core/lib/utils/timing.js +77 -0
- package/index.js +50 -0
- package/lib/appLoader.js +45 -0
- package/lib/application.js +69 -0
- package/lib/baseApp.js +155 -0
- package/lib/constant.js +30 -0
- package/lib/eeApp.js +306 -0
- package/lib/helper.js +52 -0
- package/lib/httpclient.js +136 -0
- package/lib/logger.js +47 -0
- package/lib/socket/io.js +22 -0
- package/lib/socket/ipcServer.js +112 -0
- package/lib/socket/socketClient.js +51 -0
- package/lib/socket/socketServer.js +70 -0
- package/lib/socket/start.js +18 -0
- package/lib/storage/appStorage.js +14 -0
- package/lib/storage/index.js +22 -0
- package/lib/storage/lowdbStorage.js +144 -0
- package/package.json +39 -0
- package/resource/images/loding.gif +0 -0
- package/resource/images/tray_logo.png +0 -0
- package/resource/loading.html +22 -0
- package/resource/view_example.html +22 -0
- package/tools/codeCompress.js +202 -0
- package/tools/replaceDist.js +71 -0
- package/utils/index.js +141 -0
- package/utils/wrap.js +38 -0
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const assert = require('assert');
|
|
6
|
+
const is = require('is-type-of');
|
|
7
|
+
const debug = require('debug')('ee-core:EeLoader');
|
|
8
|
+
const FileLoader = require('./file_loader');
|
|
9
|
+
const ContextLoader = require('./context_loader');
|
|
10
|
+
const utility = require('utility');
|
|
11
|
+
const utils = require('../utils');
|
|
12
|
+
const Timing = require('../utils/timing');
|
|
13
|
+
|
|
14
|
+
const REQUIRE_COUNT = Symbol('EeLoader#requireCount');
|
|
15
|
+
|
|
16
|
+
class EeLoader {
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @class
|
|
20
|
+
* @param {Object} options - options
|
|
21
|
+
* @param {String} options.baseDir - the directory of application
|
|
22
|
+
* @param {EeCore} options.app - Application instance
|
|
23
|
+
* @param {Logger} options.logger - logger
|
|
24
|
+
* @since 1.0.0
|
|
25
|
+
*/
|
|
26
|
+
constructor(options) {
|
|
27
|
+
this.options = options;
|
|
28
|
+
assert(fs.existsSync(this.options.baseDir), `${this.options.baseDir} not exists`);
|
|
29
|
+
assert(this.options.app, 'options.app is required');
|
|
30
|
+
assert(this.options.logger, 'options.logger is required');
|
|
31
|
+
|
|
32
|
+
this.app = this.options.app;
|
|
33
|
+
this.timing = this.app.timing || new Timing();
|
|
34
|
+
this[REQUIRE_COUNT] = 0;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @member {Object} EeLoader#pkg
|
|
38
|
+
* @see {@link AppInfo#pkg}
|
|
39
|
+
* @since 1.0.0
|
|
40
|
+
*/
|
|
41
|
+
this.pkg = utility.readJSONSync(path.join(this.options.homeDir, 'package.json'));
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Framework directories
|
|
45
|
+
*
|
|
46
|
+
* @member {Array} EeLoader#EePaths
|
|
47
|
+
* @see EeLoader#getEePaths
|
|
48
|
+
* @since 1.0.0
|
|
49
|
+
*/
|
|
50
|
+
this.EePaths = this.getEePaths();
|
|
51
|
+
debug('Loaded EePaths %j', this.EePaths);
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @member {String} EeLoader#serverEnv
|
|
55
|
+
* @see AppInfo#env
|
|
56
|
+
* @since 1.0.0
|
|
57
|
+
*/
|
|
58
|
+
this.serverEnv = this.getServerEnv();
|
|
59
|
+
debug('Loaded serverEnv %j', this.serverEnv);
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* @member {AppInfo} EeLoader#appInfo
|
|
63
|
+
* @since 1.0.0
|
|
64
|
+
*/
|
|
65
|
+
this.appInfo = this.getAppInfo();
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* @member {String} EeLoader#serverScope
|
|
69
|
+
* @see AppInfo#serverScope
|
|
70
|
+
*/
|
|
71
|
+
this.serverScope = options.serverScope !== undefined
|
|
72
|
+
? options.serverScope
|
|
73
|
+
: this.getServerScope();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Get {@link AppInfo#env}
|
|
78
|
+
* @return {String} env
|
|
79
|
+
* @see AppInfo#env
|
|
80
|
+
* @private
|
|
81
|
+
* @since 1.0.0
|
|
82
|
+
*/
|
|
83
|
+
getServerEnv() {
|
|
84
|
+
let serverEnv = this.options.env;
|
|
85
|
+
|
|
86
|
+
const envPath = path.join(this.options.baseDir, 'config/env');
|
|
87
|
+
if (!serverEnv && fs.existsSync(envPath)) {
|
|
88
|
+
serverEnv = fs.readFileSync(envPath, 'utf8').trim();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (!serverEnv) {
|
|
92
|
+
serverEnv = process.env.EE_SERVER_ENV;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (!serverEnv) {
|
|
96
|
+
if (process.env.NODE_ENV === 'test') {
|
|
97
|
+
serverEnv = 'unittest';
|
|
98
|
+
} else if (process.env.NODE_ENV === 'production') {
|
|
99
|
+
serverEnv = 'prod';
|
|
100
|
+
} else {
|
|
101
|
+
serverEnv = 'local';
|
|
102
|
+
}
|
|
103
|
+
} else {
|
|
104
|
+
serverEnv = serverEnv.trim();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return serverEnv;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Get {@link AppInfo#scope}
|
|
112
|
+
* @return {String} serverScope
|
|
113
|
+
* @private
|
|
114
|
+
*/
|
|
115
|
+
getServerScope() {
|
|
116
|
+
return process.env.EE_SERVER_SCOPE || '';
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Get {@link AppInfo#name}
|
|
121
|
+
* @return {String} appname
|
|
122
|
+
* @private
|
|
123
|
+
* @since 1.0.0
|
|
124
|
+
*/
|
|
125
|
+
getAppname() {
|
|
126
|
+
if (this.pkg.name) {
|
|
127
|
+
debug('Loaded appname(%s) from package.json', this.pkg.name);
|
|
128
|
+
return this.pkg.name;
|
|
129
|
+
}
|
|
130
|
+
const pkg = path.join(this.options.baseDir, 'package.json');
|
|
131
|
+
throw new Error(`name is required from ${pkg}`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Get home directory
|
|
136
|
+
* @return {String} home directory
|
|
137
|
+
* @since 3.4.0
|
|
138
|
+
*/
|
|
139
|
+
getHomedir() {
|
|
140
|
+
return this.options.homeDir;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Get app info
|
|
145
|
+
* @return {AppInfo} appInfo
|
|
146
|
+
* @since 1.0.0
|
|
147
|
+
*/
|
|
148
|
+
getAppInfo() {
|
|
149
|
+
const env = this.serverEnv;
|
|
150
|
+
const scope = this.serverScope;
|
|
151
|
+
const home = this.getHomedir();
|
|
152
|
+
const baseDir = this.options.baseDir;
|
|
153
|
+
const appUserDataDir = this.options.appUserData;
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Meta information of the application
|
|
157
|
+
* @class AppInfo
|
|
158
|
+
*/
|
|
159
|
+
return {
|
|
160
|
+
/**
|
|
161
|
+
* The name of the application, retrieve from the name property in `package.json`.
|
|
162
|
+
* @member {String} AppInfo#name
|
|
163
|
+
*/
|
|
164
|
+
name: this.getAppname(),
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* The current directory, where the application code is.
|
|
168
|
+
* @member {String} AppInfo#baseDir
|
|
169
|
+
*/
|
|
170
|
+
baseDir,
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* The environment of the application, **it's not NODE_ENV**
|
|
174
|
+
*
|
|
175
|
+
* 1. from `$baseDir/config/env`
|
|
176
|
+
* 2. from EE_SERVER_ENV
|
|
177
|
+
* 3. from NODE_ENV
|
|
178
|
+
*
|
|
179
|
+
* env | description
|
|
180
|
+
* --- | ---
|
|
181
|
+
* test | system integration testing
|
|
182
|
+
* prod | production
|
|
183
|
+
* local | local on your own computer
|
|
184
|
+
* unittest | unit test
|
|
185
|
+
*
|
|
186
|
+
* @member {String} AppInfo#env
|
|
187
|
+
* @see https://Eejs.org/zh-cn/basics/env.html
|
|
188
|
+
*/
|
|
189
|
+
env,
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* @member {String} AppInfo#scope
|
|
193
|
+
*/
|
|
194
|
+
scope,
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* The use directory, same as `process.env.HOME`
|
|
198
|
+
* @member {String} AppInfo#HOME
|
|
199
|
+
*/
|
|
200
|
+
home: home,
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* parsed from `package.json`
|
|
204
|
+
* @member {Object} AppInfo#pkg
|
|
205
|
+
*/
|
|
206
|
+
pkg: this.pkg,
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* The directory whether is baseDir or HOME depend on env.
|
|
210
|
+
* it's good for test when you want to write some file to HOME,
|
|
211
|
+
* but don't want to write to the real directory,
|
|
212
|
+
* so use root to write file to baseDir instead of HOME when unittest.
|
|
213
|
+
* keep root directory in baseDir when local and unittest
|
|
214
|
+
* @member {String} AppInfo#root
|
|
215
|
+
*/
|
|
216
|
+
root: env === 'local' || env === 'unittest' ? home : appUserDataDir,
|
|
217
|
+
|
|
218
|
+
appUserDataDir: appUserDataDir
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Get {@link EeLoader#EePaths}
|
|
224
|
+
* @return {Array} framework directories
|
|
225
|
+
* @see {@link EeLoader#EePaths}
|
|
226
|
+
* @private
|
|
227
|
+
* @since 1.0.0
|
|
228
|
+
*/
|
|
229
|
+
getEePaths() {
|
|
230
|
+
// avoid require recursively
|
|
231
|
+
const EePaths = [];
|
|
232
|
+
EePaths.push(this.app[Symbol.for('ee#eePath')]);
|
|
233
|
+
|
|
234
|
+
return EePaths;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Low Level API
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Load single file, will invoke when export is function
|
|
241
|
+
*
|
|
242
|
+
* @param {String} filepath - fullpath
|
|
243
|
+
* @param {Array} inject - pass rest arguments into the function when invoke
|
|
244
|
+
* @return {Object} exports
|
|
245
|
+
* @example
|
|
246
|
+
* ```js
|
|
247
|
+
* app.loader.loadFile(path.join(app.options.baseDir, 'config/router.js'));
|
|
248
|
+
* ```
|
|
249
|
+
* @since 1.0.0
|
|
250
|
+
*/
|
|
251
|
+
loadFile(filepath, ...inject) {
|
|
252
|
+
filepath = filepath && this.resolveModule(filepath);
|
|
253
|
+
if (!filepath) {
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// function(arg1, args, ...) {}
|
|
258
|
+
if (inject.length === 0) inject = [ this.app ];
|
|
259
|
+
|
|
260
|
+
let ret = this.requireFile(filepath);
|
|
261
|
+
if (is.function(ret) && !is.class(ret)) {
|
|
262
|
+
ret = ret(...inject);
|
|
263
|
+
}
|
|
264
|
+
return ret;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* @param {String} filepath - fullpath
|
|
269
|
+
* @return {Object} exports
|
|
270
|
+
* @private
|
|
271
|
+
*/
|
|
272
|
+
requireFile(filepath) {
|
|
273
|
+
const timingKey = `Require(${this[REQUIRE_COUNT]++}) ${utils.getResolvedFilename(filepath, this.options.baseDir)}`;
|
|
274
|
+
this.timing.start(timingKey);
|
|
275
|
+
const ret = utils.loadFile(filepath);
|
|
276
|
+
this.timing.end(timingKey);
|
|
277
|
+
return ret;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Get all loadUnit
|
|
282
|
+
*
|
|
283
|
+
* loadUnit is a directory that can be loaded by EeLoader, it has the same structure.
|
|
284
|
+
* loadUnit has a path and a type(app, framework, plugin).
|
|
285
|
+
*
|
|
286
|
+
* The order of the loadUnits:
|
|
287
|
+
*
|
|
288
|
+
* 1. plugin
|
|
289
|
+
* 2. framework
|
|
290
|
+
* 3. app
|
|
291
|
+
*
|
|
292
|
+
* @return {Array} loadUnits
|
|
293
|
+
* @since 1.0.0
|
|
294
|
+
*/
|
|
295
|
+
getLoadUnits() {
|
|
296
|
+
if (this.dirs) {
|
|
297
|
+
return this.dirs;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const dirs = this.dirs = [];
|
|
301
|
+
|
|
302
|
+
if (this.orderPlugins) {
|
|
303
|
+
for (const plugin of this.orderPlugins) {
|
|
304
|
+
dirs.push({
|
|
305
|
+
path: plugin.path,
|
|
306
|
+
type: 'plugin',
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// framework or Ee path
|
|
312
|
+
for (const EePath of this.EePaths) {
|
|
313
|
+
dirs.push({
|
|
314
|
+
path: EePath,
|
|
315
|
+
type: 'framework',
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// application
|
|
320
|
+
dirs.push({
|
|
321
|
+
path: this.options.baseDir,
|
|
322
|
+
type: 'app',
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
debug('Loaded dirs %j', dirs);
|
|
326
|
+
return dirs;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Load files using {@link FileLoader}, inject to {@link Application}
|
|
331
|
+
* @param {String|Array} directory - see {@link FileLoader}
|
|
332
|
+
* @param {String} property - see {@link FileLoader}
|
|
333
|
+
* @param {Object} opt - see {@link FileLoader}
|
|
334
|
+
* @since 1.0.0
|
|
335
|
+
*/
|
|
336
|
+
loadToApp(directory, property, opt) {
|
|
337
|
+
const target = this.app[property] = {};
|
|
338
|
+
opt = Object.assign({}, {
|
|
339
|
+
directory,
|
|
340
|
+
target,
|
|
341
|
+
inject: this.app,
|
|
342
|
+
}, opt);
|
|
343
|
+
|
|
344
|
+
const timingKey = `Load "${String(property)}" to Application`;
|
|
345
|
+
this.timing.start(timingKey);
|
|
346
|
+
new FileLoader(opt).load();
|
|
347
|
+
//console.log('app property:', this.app[property]);
|
|
348
|
+
this.timing.end(timingKey);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Load files using {@link ContextLoader}
|
|
353
|
+
* @param {String|Array} directory - see {@link ContextLoader}
|
|
354
|
+
* @param {String} property - see {@link ContextLoader}
|
|
355
|
+
* @param {Object} opt - see {@link ContextLoader}
|
|
356
|
+
* @since 1.0.0
|
|
357
|
+
*/
|
|
358
|
+
loadToContext(directory, property, opt) {
|
|
359
|
+
opt = Object.assign({}, {
|
|
360
|
+
directory,
|
|
361
|
+
property,
|
|
362
|
+
inject: this.app,
|
|
363
|
+
}, opt);
|
|
364
|
+
const timingKey = `Load "${String(property)}" to Context`;
|
|
365
|
+
this.timing.start(timingKey);
|
|
366
|
+
new ContextLoader(opt).load();
|
|
367
|
+
this.timing.end(timingKey);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* @member {FileLoader} EeLoader#FileLoader
|
|
372
|
+
* @since 1.0.0
|
|
373
|
+
*/
|
|
374
|
+
get FileLoader() {
|
|
375
|
+
return FileLoader;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* @member {ContextLoader} EeLoader#ContextLoader
|
|
380
|
+
* @since 1.0.0
|
|
381
|
+
*/
|
|
382
|
+
get ContextLoader() {
|
|
383
|
+
return ContextLoader;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
getTypeFiles(filename) {
|
|
387
|
+
const files = [ `${filename}.default` ];
|
|
388
|
+
if (this.serverScope) files.push(`${filename}.${this.serverScope}`);
|
|
389
|
+
if (this.serverEnv === 'default') return files;
|
|
390
|
+
|
|
391
|
+
files.push(`${filename}.${this.serverEnv}`);
|
|
392
|
+
if (this.serverScope) files.push(`${filename}.${this.serverScope}_${this.serverEnv}`);
|
|
393
|
+
return files;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
resolveModule(filepath) {
|
|
397
|
+
let fullPath;
|
|
398
|
+
try {
|
|
399
|
+
fullPath = require.resolve(filepath);
|
|
400
|
+
} catch (e) {
|
|
401
|
+
return undefined;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
if (process.env.Ee_TYPESCRIPT !== 'true' && fullPath.endsWith('.ts')) {
|
|
405
|
+
return undefined;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
return fullPath;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Mixin methods to EeLoader
|
|
414
|
+
* // ES6 Multiple Inheritance
|
|
415
|
+
* https://medium.com/@leocavalcante/es6-multiple-inheritance-73a3c66d2b6b
|
|
416
|
+
*/
|
|
417
|
+
const loaders = [
|
|
418
|
+
require('./mixin/config'),
|
|
419
|
+
require('./mixin/service'),
|
|
420
|
+
require('./mixin/controller'),
|
|
421
|
+
];
|
|
422
|
+
|
|
423
|
+
for (const loader of loaders) {
|
|
424
|
+
Object.assign(EeLoader.prototype, loader);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
module.exports = EeLoader;
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const assert = require('assert');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const debug = require('debug')('ee-core:fileLoader');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const globby = require('globby');
|
|
8
|
+
const is = require('is-type-of');
|
|
9
|
+
const deprecate = require('depd')('ee');
|
|
10
|
+
const utils = require('../utils');
|
|
11
|
+
const FULLPATH = Symbol('EE_LOADER_ITEM_FULLPATH');
|
|
12
|
+
const EXPORTS = Symbol('EE_LOADER_ITEM_EXPORTS');
|
|
13
|
+
|
|
14
|
+
const defaults = {
|
|
15
|
+
directory: null,
|
|
16
|
+
target: null,
|
|
17
|
+
match: undefined,
|
|
18
|
+
ignore: undefined,
|
|
19
|
+
lowercaseFirst: false,
|
|
20
|
+
caseStyle: 'camel',
|
|
21
|
+
initializer: null,
|
|
22
|
+
call: true,
|
|
23
|
+
override: false,
|
|
24
|
+
inject: undefined,
|
|
25
|
+
filter: null,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Load files from directory to target object.
|
|
30
|
+
* @since 1.0.0
|
|
31
|
+
*/
|
|
32
|
+
class FileLoader {
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @class
|
|
36
|
+
* @param {Object} options - options
|
|
37
|
+
* @param {String|Array} options.directory - directories to be loaded
|
|
38
|
+
* @param {Object} options.target - attach the target object from loaded files
|
|
39
|
+
* @param {String} options.match - match the files when load, support glob, default to all js files
|
|
40
|
+
* @param {String} options.ignore - ignore the files when load, support glob
|
|
41
|
+
* @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`
|
|
42
|
+
* @param {Boolean} options.call - determine whether invoke when exports is function
|
|
43
|
+
* @param {Boolean} options.override - determine whether override the property when get the same name
|
|
44
|
+
* @param {Object} options.inject - an object that be the argument when invoke the function
|
|
45
|
+
* @param {Function} options.filter - a function that filter the exports which can be loaded
|
|
46
|
+
* @param {String|Function} options.caseStyle - set property's case when converting a filepath to property list.
|
|
47
|
+
*/
|
|
48
|
+
constructor(options) {
|
|
49
|
+
assert(options.directory, 'options.directory is required');
|
|
50
|
+
assert(options.target, 'options.target is required');
|
|
51
|
+
this.options = Object.assign({}, defaults, options);
|
|
52
|
+
|
|
53
|
+
// compatible old options _lowercaseFirst_
|
|
54
|
+
if (this.options.lowercaseFirst === true) {
|
|
55
|
+
deprecate('lowercaseFirst is deprecated, use caseStyle instead');
|
|
56
|
+
this.options.caseStyle = 'lower';
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* attach items to target object. Mapping the directory to properties.
|
|
62
|
+
* `app/controller/group/repository.js` => `target.group.repository`
|
|
63
|
+
* @return {Object} target
|
|
64
|
+
* @since 1.0.0
|
|
65
|
+
*/
|
|
66
|
+
load() {
|
|
67
|
+
const items = this.parse();
|
|
68
|
+
//console.log('FileLoader load items:', items);
|
|
69
|
+
const target = this.options.target;
|
|
70
|
+
for (const item of items) {
|
|
71
|
+
// item { properties: [ 'a', 'b', 'c'], exports }
|
|
72
|
+
// => target.a.b.c = exports
|
|
73
|
+
item.properties.reduce((target, property, index) => {
|
|
74
|
+
let obj;
|
|
75
|
+
const properties = item.properties.slice(0, index + 1).join('.');
|
|
76
|
+
if (index === item.properties.length - 1) {
|
|
77
|
+
if (property in target) {
|
|
78
|
+
if (!this.options.override) throw new Error(`can't overwrite property '${properties}' from ${target[property][FULLPATH]} by ${item.fullpath}`);
|
|
79
|
+
}
|
|
80
|
+
obj = item.exports;
|
|
81
|
+
if (obj && !is.primitive(obj)) {
|
|
82
|
+
obj[FULLPATH] = item.fullpath;
|
|
83
|
+
obj[EXPORTS] = true;
|
|
84
|
+
}
|
|
85
|
+
} else {
|
|
86
|
+
obj = target[property] || {};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
target[property] = obj;
|
|
90
|
+
debug('loaded %s', properties);
|
|
91
|
+
return obj;
|
|
92
|
+
}, target);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return target;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Parse files from given directories, then return an items list, each item contains properties and exports.
|
|
100
|
+
*
|
|
101
|
+
* For example, parse `app/controller/group/repository.js`
|
|
102
|
+
*
|
|
103
|
+
* ```
|
|
104
|
+
* module.exports = app => {
|
|
105
|
+
* return class RepositoryController extends app.Controller {};
|
|
106
|
+
* }
|
|
107
|
+
* ```
|
|
108
|
+
*
|
|
109
|
+
* It returns a item
|
|
110
|
+
*
|
|
111
|
+
* ```
|
|
112
|
+
* {
|
|
113
|
+
* properties: [ 'group', 'repository' ],
|
|
114
|
+
* exports: app => { ... },
|
|
115
|
+
* }
|
|
116
|
+
* ```
|
|
117
|
+
*
|
|
118
|
+
* `Properties` is an array that contains the directory of a filepath.
|
|
119
|
+
*
|
|
120
|
+
* `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.
|
|
121
|
+
* @return {Array} items
|
|
122
|
+
* @since 1.0.0
|
|
123
|
+
*/
|
|
124
|
+
parse() {
|
|
125
|
+
let files = this.options.match;
|
|
126
|
+
|
|
127
|
+
if (!files) {
|
|
128
|
+
files = (process.env.EE_TYPESCRIPT === 'true' && utils.extensions['.ts'])
|
|
129
|
+
? [ '**/*.(js|ts)', '!**/*.d.ts' ]
|
|
130
|
+
: [ '**/*.js' ];
|
|
131
|
+
} else {
|
|
132
|
+
files = Array.isArray(files) ? files : [ files ];
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
let ignore = this.options.ignore;
|
|
136
|
+
if (ignore) {
|
|
137
|
+
ignore = Array.isArray(ignore) ? ignore : [ ignore ];
|
|
138
|
+
ignore = ignore.filter(f => !!f).map(f => '!' + f);
|
|
139
|
+
files = files.concat(ignore);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
let directories = this.options.directory;
|
|
143
|
+
if (!Array.isArray(directories)) {
|
|
144
|
+
directories = [ directories ];
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const filter = is.function(this.options.filter) ? this.options.filter : null;
|
|
148
|
+
const items = [];
|
|
149
|
+
debug('parsing %j', directories);
|
|
150
|
+
|
|
151
|
+
for (const directory of directories) {
|
|
152
|
+
const filepaths = globby.sync(files, { cwd: directory });
|
|
153
|
+
|
|
154
|
+
for (const filepath of filepaths) {
|
|
155
|
+
const fullpath = path.join(directory, filepath);
|
|
156
|
+
if (!fs.statSync(fullpath).isFile()) continue;
|
|
157
|
+
// get properties
|
|
158
|
+
// app/service/foo/bar.js => [ 'foo', 'bar' ]
|
|
159
|
+
const properties = getProperties(filepath, this.options);
|
|
160
|
+
// app/service/foo/bar.js => service.foo.bar
|
|
161
|
+
const pathName = directory.split(/[/\\]/).slice(-1) + '.' + properties.join('.');
|
|
162
|
+
// get exports from the file
|
|
163
|
+
const exports = getExports(fullpath, this.options, pathName);
|
|
164
|
+
|
|
165
|
+
// ignore exports when it's null or false returned by filter function
|
|
166
|
+
if (exports == null || (filter && filter(exports) === false)) continue;
|
|
167
|
+
|
|
168
|
+
// set properties of class
|
|
169
|
+
if (is.class(exports)) {
|
|
170
|
+
exports.prototype.pathName = pathName;
|
|
171
|
+
exports.prototype.fullPath = fullpath;
|
|
172
|
+
}
|
|
173
|
+
//console.log('lll---------------pathName:', pathName);
|
|
174
|
+
items.push({ fullpath, properties, exports });
|
|
175
|
+
debug('parse %s, properties %j, export %O', fullpath, properties, exports);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return items;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
module.exports = FileLoader;
|
|
185
|
+
module.exports.EXPORTS = EXPORTS;
|
|
186
|
+
module.exports.FULLPATH = FULLPATH;
|
|
187
|
+
|
|
188
|
+
// convert file path to an array of properties
|
|
189
|
+
// a/b/c.js => ['a', 'b', 'c']
|
|
190
|
+
function getProperties(filepath, { caseStyle }) {
|
|
191
|
+
// if caseStyle is function, return the result of function
|
|
192
|
+
if (is.function(caseStyle)) {
|
|
193
|
+
const result = caseStyle(filepath);
|
|
194
|
+
assert(is.array(result), `caseStyle expect an array, but got ${result}`);
|
|
195
|
+
return result;
|
|
196
|
+
}
|
|
197
|
+
// use default camelize
|
|
198
|
+
return defaultCamelize(filepath, caseStyle);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Get exports from filepath
|
|
202
|
+
// If exports is null/undefined, it will be ignored
|
|
203
|
+
function getExports(fullpath, { initializer, call, inject }, pathName) {
|
|
204
|
+
let exports = utils.loadFile(fullpath);
|
|
205
|
+
// process exports as you like
|
|
206
|
+
if (initializer) {
|
|
207
|
+
exports = initializer(exports, { path: fullpath, pathName });
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// return exports when it's a class or generator
|
|
211
|
+
//
|
|
212
|
+
// module.exports = class Service {};
|
|
213
|
+
// or
|
|
214
|
+
// module.exports = function*() {}
|
|
215
|
+
if (is.class(exports) || is.generatorFunction(exports) || is.asyncFunction(exports)) {
|
|
216
|
+
return exports;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// return exports after call when it's a function
|
|
220
|
+
//
|
|
221
|
+
// module.exports = function(app) {
|
|
222
|
+
// return {};
|
|
223
|
+
// }
|
|
224
|
+
if (call && is.function(exports)) {
|
|
225
|
+
exports = exports(inject);
|
|
226
|
+
if (exports != null) {
|
|
227
|
+
return exports;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// return exports what is
|
|
232
|
+
return exports;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function defaultCamelize(filepath, caseStyle) {
|
|
236
|
+
const properties = filepath.substring(0, filepath.lastIndexOf('.')).split('/');
|
|
237
|
+
return properties.map(property => {
|
|
238
|
+
if (!/^[a-z][a-z0-9_-]*$/i.test(property)) {
|
|
239
|
+
throw new Error(`${property} is not match 'a-z0-9_-' in ${filepath}`);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// use default camelize, will capitalize the first letter
|
|
243
|
+
// foo_bar.js > FooBar
|
|
244
|
+
// fooBar.js > FooBar
|
|
245
|
+
// FooBar.js > FooBar
|
|
246
|
+
// FooBar.js > FooBar
|
|
247
|
+
// FooBar.js > fooBar (if lowercaseFirst is true)
|
|
248
|
+
property = property.replace(/[_-][a-z]/ig, s => s.substring(1).toUpperCase());
|
|
249
|
+
let first = property[0];
|
|
250
|
+
switch (caseStyle) {
|
|
251
|
+
case 'lower':
|
|
252
|
+
first = first.toLowerCase();
|
|
253
|
+
break;
|
|
254
|
+
case 'upper':
|
|
255
|
+
first = first.toUpperCase();
|
|
256
|
+
break;
|
|
257
|
+
case 'camel':
|
|
258
|
+
default:
|
|
259
|
+
}
|
|
260
|
+
return first + property.substring(1);
|
|
261
|
+
});
|
|
262
|
+
}
|