mm_os 4.3.6 → 5.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/README.md +101 -504
- package/index.js +26 -19
- package/lib/loader.js +787 -0
- package/lib/os.js +369 -0
- package/package.json +55 -80
- package/LICENSE +0 -21
- package/README_EN.md +0 -505
- package/adapter/adapter.js +0 -484
- package/adapter/custom_persistence.js +0 -661
- package/adapter/mqtt.js +0 -254
- package/adapter/socket.js +0 -244
- package/adapter/web.js +0 -92
- package/adapter/websocket.js +0 -43
- package/ai.js +0 -130
- package/com/api/README.md +0 -3
- package/com/api/com.json +0 -5
- package/com/api/config.tpl.json +0 -41
- package/com/api/drive.js +0 -732
- package/com/api/index.js +0 -201
- package/com/api/oauth.js +0 -200
- package/com/api/script.tpl.js +0 -32
- package/com/cmd/README.md +0 -11
- package/com/cmd/com.json +0 -5
- package/com/cmd/config.tpl.json +0 -48
- package/com/cmd/drive.js +0 -176
- package/com/cmd/index.js +0 -287
- package/com/cmd/job/config.json +0 -66
- package/com/cmd/job/index.js +0 -241
- package/com/cmd/job/test.js +0 -188
- package/com/cmd/msg.json +0 -48
- package/com/cmd/script.tpl.js +0 -12
- package/com/db/README.md +0 -3
- package/com/db/com.json +0 -5
- package/com/db/drive.js +0 -2206
- package/com/db/index.js +0 -241
- package/com/event/README.md +0 -4
- package/com/event/com.json +0 -5
- package/com/event/config.tpl.json +0 -20
- package/com/event/drive.js +0 -61
- package/com/event/index.js +0 -438
- package/com/event/script.tpl.js +0 -23
- package/com/mqtt/README.md +0 -47
- package/com/mqtt/com.json +0 -5
- package/com/mqtt/config.tpl.json +0 -42
- package/com/mqtt/drive.js +0 -696
- package/com/mqtt/index.js +0 -1017
- package/com/mqtt/index.test.js +0 -503
- package/com/mqtt/mm_mqtt.js +0 -446
- package/com/mqtt/script.tpl.js +0 -719
- package/com/nav/README.md +0 -2
- package/com/nav/com.json +0 -5
- package/com/nav/config.tpl.json +0 -84
- package/com/nav/drive.js +0 -702
- package/com/nav/index.js +0 -228
- package/com/nav/tpl/admin_pc/page_channel.vue +0 -305
- package/com/nav/tpl/admin_pc/page_config.vue +0 -280
- package/com/nav/tpl/admin_pc/page_config_form.vue +0 -194
- package/com/nav/tpl/admin_pc/page_default.vue +0 -247
- package/com/nav/tpl/admin_pc/page_form.vue +0 -180
- package/com/nav/tpl/admin_pc/page_lang.vue +0 -264
- package/com/nav/tpl/admin_pc/page_nav.vue +0 -321
- package/com/nav/tpl/admin_pc/page_table.vue +0 -296
- package/com/nav/tpl/admin_pc/page_type.vue +0 -321
- package/com/nav/tpl/admin_pc/page_view.vue +0 -124
- package/com/nav/tpl/dev_pc/page_channel.vue +0 -253
- package/com/nav/tpl/dev_pc/page_config.vue +0 -253
- package/com/nav/tpl/dev_pc/page_default.vue +0 -247
- package/com/nav/tpl/dev_pc/page_form.vue +0 -162
- package/com/nav/tpl/dev_pc/page_nav.vue +0 -253
- package/com/nav/tpl/dev_pc/page_table.vue +0 -247
- package/com/nav/tpl/dev_pc/page_type.vue +0 -313
- package/com/nav/tpl/home_pc/page_channel.vue +0 -234
- package/com/nav/tpl/home_pc/page_default.vue +0 -234
- package/com/nav/tpl/home_pc/page_form.vue +0 -137
- package/com/nav/tpl/home_pc/page_list.vue +0 -234
- package/com/nav/tpl/home_pc/page_nav.vue +0 -221
- package/com/nav/tpl/home_pc/page_type.vue +0 -234
- package/com/nav/tpl/home_pc/page_view.vue +0 -125
- package/com/nav/tpl/home_phone/page_channel.vue +0 -234
- package/com/nav/tpl/home_phone/page_default.vue +0 -234
- package/com/nav/tpl/home_phone/page_form.vue +0 -137
- package/com/nav/tpl/home_phone/page_list.vue +0 -234
- package/com/nav/tpl/home_phone/page_nav.vue +0 -237
- package/com/nav/tpl/home_phone/page_type.vue +0 -234
- package/com/nav/tpl/home_phone/page_view.vue +0 -125
- package/com/nav/viewmodel.js +0 -446
- package/com/param/README.md +0 -2
- package/com/param/com.json +0 -5
- package/com/param/config.tpl.json +0 -136
- package/com/param/drive.js +0 -502
- package/com/param/index.js +0 -158
- package/com/param/script.tpl.js +0 -12
- package/com/pendant/README.md +0 -3
- package/com/pendant/com.json +0 -5
- package/com/pendant/config.tpl.json +0 -32
- package/com/pendant/drive.js +0 -204
- package/com/pendant/index.js +0 -467
- package/com/pendant/pendant.html +0 -16
- package/com/pendant/script.tpl.js +0 -18
- package/com/socket/README.md +0 -47
- package/com/socket/com.json +0 -5
- package/com/socket/config.tpl.json +0 -12
- package/com/socket/drive.js +0 -679
- package/com/socket/index.js +0 -402
- package/com/socket/script.tpl.js +0 -41
- package/com/sql/README.md +0 -3
- package/com/sql/com.json +0 -5
- package/com/sql/config.tpl.json +0 -74
- package/com/sql/drive.js +0 -1283
- package/com/sql/index.js +0 -152
- package/com/sql/script.tpl.js +0 -47
- package/com/static/README.md +0 -2
- package/com/static/com.json +0 -5
- package/com/static/config.tpl.json +0 -24
- package/com/static/drive.js +0 -194
- package/com/static/index.js +0 -237
- package/com/static/script.tpl.js +0 -28
- package/com/task/README.md +0 -3
- package/com/task/com.json +0 -5
- package/com/task/config.tpl.json +0 -24
- package/com/task/drive.js +0 -428
- package/com/task/index.js +0 -159
- package/com/task/script.tpl.js +0 -40
- package/com/template/com.json +0 -5
- package/com/template/config.tpl.json +0 -16
- package/com/template/drive.js +0 -81
- package/com/template/index.js +0 -141
- package/com.js +0 -237
- package/common/README.md +0 -2
- package/common/middleware/web_after/index.js +0 -26
- package/common/middleware/web_after/middleware.json +0 -9
- package/common/middleware/web_base/index.js +0 -113
- package/common/middleware/web_base/middleware.json +0 -19
- package/common/middleware/web_before/index.js +0 -33
- package/common/middleware/web_before/middleware.json +0 -9
- package/common/middleware/web_cors/index.js +0 -87
- package/common/middleware/web_cors/middleware.json +0 -24
- package/common/middleware/web_error/index.js +0 -131
- package/common/middleware/web_error/middleware.json +0 -19
- package/common/middleware/web_ip/index.js +0 -15
- package/common/middleware/web_ip/middleware.json +0 -14
- package/common/middleware/web_logger/index.js +0 -156
- package/common/middleware/web_logger/middleware.json +0 -14
- package/common/middleware/web_main/index.js +0 -26
- package/common/middleware/web_main/middleware.json +0 -9
- package/common/middleware/web_static/index.js +0 -73
- package/common/middleware/web_static/middleware.json +0 -54
- package/common/middleware/web_waf/index.js +0 -385
- package/common/middleware/web_waf/middleware.json +0 -13
- package/core/app/config.tpl.json +0 -67
- package/core/app/index.js +0 -710
- package/core/app/script.tpl.js +0 -52
- package/core/channel/index.js +0 -899
- package/core/channel/matcher.js +0 -585
- package/core/com/config.tpl.json +0 -16
- package/core/com/index.js +0 -77
- package/core/com/script.tpl.js +0 -5
- package/core/component/component.js +0 -42
- package/core/component/config.tpl.json +0 -63
- package/core/component/index.js +0 -273
- package/core/component/script.tpl.js +0 -19
- package/core/controller/config.tpl.json +0 -14
- package/core/controller/index.js +0 -371
- package/core/controller/script.tpl.js +0 -27
- package/core/factory/config.tpl.json +0 -14
- package/core/factory/entity.js +0 -275
- package/core/factory/index.js +0 -241
- package/core/factory/script.tpl.js +0 -16
- package/core/game/bat/index.js +0 -137
- package/core/game/bat/world.js +0 -622
- package/core/game/config.tpl.json +0 -16
- package/core/game/entity_admin.js +0 -230
- package/core/game/index.js +0 -186
- package/core/handler/config.tpl.json +0 -22
- package/core/handler/index.js +0 -181
- package/core/handler/script.tpl.js +0 -23
- package/core/logic/config.tpl.json +0 -14
- package/core/logic/index.js +0 -59
- package/core/logic/script.tpl.js +0 -19
- package/core/middleware/config.tpl.json +0 -16
- package/core/middleware/index.js +0 -117
- package/core/middleware/script.tpl.js +0 -37
- package/core/mod/config.tpl.json +0 -22
- package/core/mod/index.js +0 -136
- package/core/mod/script.tpl.js +0 -34
- package/core/model/config.tpl.json +0 -219
- package/core/model/index.js +0 -272
- package/core/model/model.js +0 -27
- package/core/model/script.tpl.js +0 -20
- package/core/notifier/config.tpl.json +0 -14
- package/core/notifier/index.js +0 -77
- package/core/notifier/script.tpl.js +0 -20
- package/core/plugin/config.tpl.json +0 -36
- package/core/plugin/index.js +0 -297
- package/core/plugin/script.tpl.js +0 -51
- package/core/pusher/config.tpl.json +0 -14
- package/core/pusher/index.js +0 -158
- package/core/pusher/script.tpl.js +0 -20
- package/core/room/bat/index.js +0 -170
- package/core/room/bat/room.js +0 -524
- package/core/room/config.tpl.json +0 -20
- package/core/room/index.js +0 -249
- package/core/room/room.js +0 -61
- package/core/scene/config.tpl.json +0 -14
- package/core/scene/index.js +0 -467
- package/core/scene/loop.js +0 -1306
- package/core/scene/map.js +0 -28
- package/core/scene/script.tpl.js +0 -22
- package/core/sender/config.tpl.json +0 -14
- package/core/sender/index.js +0 -79
- package/core/sender/script.tpl.js +0 -20
- package/core/service/config.tpl.json +0 -14
- package/core/service/index.js +0 -100
- package/core/service/script.tpl.js +0 -25
- package/core/store/config.tpl.json +0 -26
- package/core/store/index.js +0 -1794
- package/core/store/script.tpl.js +0 -22
- package/core/store/sql.js +0 -1488
- package/core/system/config.tpl.json +0 -18
- package/core/system/index.js +0 -312
- package/core/system/script.tpl.js +0 -77
- package/core/view/config.tpl.json +0 -14
- package/core/view/index.js +0 -91
- package/core/view/script.tpl.js +0 -20
- package/core/zone/bat/index.js +0 -744
- package/core/zone/config.tpl.json +0 -54
- package/core/zone/index.js +0 -655
- package/core/zone/script.tpl.js +0 -10
- package/core/zone/zone_bat.js +0 -136
- package/core//345/237/272/347/261/273/346/250/241/345/235/227/346/270/205/345/215/225.md +0 -24
- package/os.js +0 -53
- package/server.js +0 -595
- package/ulits/id_gen.js +0 -134
- package/ulits/word.js +0 -206
package/lib/loader.js
ADDED
|
@@ -0,0 +1,787 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const vm = require('vm');
|
|
6
|
+
const { Base } = require('mm_corejs');
|
|
7
|
+
const { appAdmin } = require('mm_app');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 应用加载器类
|
|
11
|
+
*
|
|
12
|
+
* 扫描应用根目录下的各个应用,读取每个应用的配置文件(app.json),
|
|
13
|
+
* 通过 mm_app 的应用管理器(AppManager / App 驱动)加载应用并驱动其生命周期,
|
|
14
|
+
* 同时把应用下的资源清单(event.json / socket.json / mqtt.json / pendant.json /
|
|
15
|
+
* static.json)注册到 OS 对应的资源管理器。
|
|
16
|
+
*
|
|
17
|
+
* 应用与插件均由驱动对象承载:
|
|
18
|
+
* App 驱动 由 this.apps(AppManager)管理,暴露 config / dir / file /
|
|
19
|
+
* methods / resources,并通过 this.plugins(PluginManager)承载插件
|
|
20
|
+
* Plugin 驱动 由 App 的 this.plugins 管理,结构与应用驱动类似
|
|
21
|
+
*
|
|
22
|
+
* 插件位于应用目录下的 plugin 目录中,结构为:
|
|
23
|
+
* plugin/{插件名}/plugin.json 插件配置(可选,缺失时以目录名作为插件名)
|
|
24
|
+
* plugin/{插件名}/index.js 插件入口(可选)
|
|
25
|
+
* plugin/{插件名}/api_*\/... 插件级 api.json
|
|
26
|
+
* plugin/{插件名}/event_api/... 插件级 event.json
|
|
27
|
+
* plugin/{插件名}/static/... 插件级 static.json
|
|
28
|
+
*/
|
|
29
|
+
class Loader extends Base {
|
|
30
|
+
/**
|
|
31
|
+
* 加载器默认配置
|
|
32
|
+
* @type {object}
|
|
33
|
+
*/
|
|
34
|
+
static config = {
|
|
35
|
+
// 名称
|
|
36
|
+
name: 'loader',
|
|
37
|
+
// 标题
|
|
38
|
+
title: '应用加载器',
|
|
39
|
+
// 描述
|
|
40
|
+
description: '扫描应用目录,加载应用配置、生命周期与资源清单',
|
|
41
|
+
// 应用根目录,为空时取 mm_os/example/app
|
|
42
|
+
dir: '',
|
|
43
|
+
// 应用与资源管理器作用域
|
|
44
|
+
scope: 'sys',
|
|
45
|
+
// 应用入口文件名
|
|
46
|
+
main: 'index.js',
|
|
47
|
+
// 应用配置文件名
|
|
48
|
+
config_file: 'app.json',
|
|
49
|
+
// 资源目录名 -> 管理器名称 / 模块名 / 清单文件名
|
|
50
|
+
resources: {
|
|
51
|
+
event_api: { admin: 'event', module: 'mm_event', file: 'event.json' },
|
|
52
|
+
socket: { admin: 'socket', module: 'mm_socket', file: 'socket.json' },
|
|
53
|
+
mqtt: { admin: 'mqtt', module: 'mm_mqtt', file: 'mqtt.json' },
|
|
54
|
+
pendant: { admin: 'pendant', module: 'mm_pendant', file: 'pendant.json' },
|
|
55
|
+
static: { admin: 'statics', module: 'mm_statics', file: 'static.json' }
|
|
56
|
+
},
|
|
57
|
+
// 生命周期阶段 -> 应用方法名(应用未自定义时的回落值)
|
|
58
|
+
lifecycle: {
|
|
59
|
+
load: ['install', 'init'],
|
|
60
|
+
start: ['start'],
|
|
61
|
+
stop: ['stop', 'uninstall']
|
|
62
|
+
},
|
|
63
|
+
// 插件配置
|
|
64
|
+
plugin: {
|
|
65
|
+
// 插件目录名
|
|
66
|
+
dir: 'plugin',
|
|
67
|
+
// 插件配置文件名,可缺失
|
|
68
|
+
config_file: 'plugin.json',
|
|
69
|
+
// 插件入口文件名,可缺失
|
|
70
|
+
main: 'index.js',
|
|
71
|
+
// 资源目录名 -> 管理器名称 / 模块名 / 清单文件名 / 子目录名(空串表示以插件目录为根)
|
|
72
|
+
resources: {
|
|
73
|
+
api: { admin: 'api', module: 'mm_api', file: 'api.json', sub_dir: '' },
|
|
74
|
+
event_api: { admin: 'event', module: 'mm_event', file: 'event.json' },
|
|
75
|
+
static: { admin: 'statics', module: 'mm_statics', file: 'static.json' }
|
|
76
|
+
},
|
|
77
|
+
// 生命周期阶段 -> 插件方法名(插件未自定义时的回落值)
|
|
78
|
+
lifecycle: {
|
|
79
|
+
load: ['install', '_init'],
|
|
80
|
+
start: ['start'],
|
|
81
|
+
stop: ['stop', 'uninstall']
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* 构造函数
|
|
88
|
+
* @param {object} config 配置参数
|
|
89
|
+
* @param {object} server 框架实例
|
|
90
|
+
*/
|
|
91
|
+
constructor(config, server) {
|
|
92
|
+
super(config, server);
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* 应用管理器,按作用域创建并托管应用驱动
|
|
96
|
+
* @type {object}
|
|
97
|
+
*/
|
|
98
|
+
this.apps = appAdmin(this.config.scope);
|
|
99
|
+
this.apps.setServer(this.getServer());
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* 资源管理器缓存,键为资源目录名
|
|
103
|
+
* @type {Map}
|
|
104
|
+
*/
|
|
105
|
+
this._managers = new Map();
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* 已输出过警告的资源目录名集合
|
|
109
|
+
* @type {Set}
|
|
110
|
+
*/
|
|
111
|
+
this._warned = new Set();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ---------- 生命周期钩子 ----------
|
|
115
|
+
/**
|
|
116
|
+
* 初始化钩子:加载全部应用
|
|
117
|
+
* @returns {Promise<void>} 无返回值
|
|
118
|
+
*/
|
|
119
|
+
async onInit() {
|
|
120
|
+
await this.load();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* 启动钩子:启动全部应用
|
|
125
|
+
* @returns {Promise<void>} 无返回值
|
|
126
|
+
*/
|
|
127
|
+
async onStart() {
|
|
128
|
+
await this.startAll();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* 停止钩子:停止全部应用
|
|
133
|
+
* @returns {Promise<void>} 无返回值
|
|
134
|
+
*/
|
|
135
|
+
async onStop() {
|
|
136
|
+
await this.stopAll();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* 释放钩子:清空应用与资源管理器缓存
|
|
141
|
+
* @returns {Promise<void>} 无返回值
|
|
142
|
+
*/
|
|
143
|
+
async onDispose() {
|
|
144
|
+
await this.clear();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ---------- 公开方法 ----------
|
|
148
|
+
/**
|
|
149
|
+
* 加载应用根目录下的全部应用
|
|
150
|
+
* @returns {Promise<void>} 无返回值
|
|
151
|
+
*/
|
|
152
|
+
async load() {
|
|
153
|
+
if (this.getLogger()) {
|
|
154
|
+
this.apps.setLogger(this.getLogger());
|
|
155
|
+
}
|
|
156
|
+
const app_dir = this._getAppDir();
|
|
157
|
+
const dirs = this._listAppDirs(app_dir);
|
|
158
|
+
await this._loadApps(dirs);
|
|
159
|
+
this.log('info', `应用加载完成,共 ${this.apps.size()} 个`);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* 启动全部应用
|
|
164
|
+
* @returns {Promise<void>} 无返回值
|
|
165
|
+
*/
|
|
166
|
+
async startAll() {
|
|
167
|
+
for (const app of this.apps.values()) {
|
|
168
|
+
await this._startApp(app);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* 停止全部应用
|
|
174
|
+
* @returns {Promise<void>} 无返回值
|
|
175
|
+
*/
|
|
176
|
+
async stopAll() {
|
|
177
|
+
for (const app of this.apps.values()) {
|
|
178
|
+
await this._stopApp(app);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* 获取应用
|
|
184
|
+
* @param {string} name 应用名称
|
|
185
|
+
* @returns {object} 应用驱动,不存在时返回 undefined
|
|
186
|
+
*/
|
|
187
|
+
get(name) {
|
|
188
|
+
return this.apps.get(name);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* 判断应用是否存在
|
|
193
|
+
* @param {string} name 应用名称
|
|
194
|
+
* @returns {boolean} 是否存在
|
|
195
|
+
*/
|
|
196
|
+
has(name) {
|
|
197
|
+
return this.apps.has(name);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* 获取应用列表
|
|
202
|
+
* @returns {Array} 应用驱动列表
|
|
203
|
+
*/
|
|
204
|
+
list() {
|
|
205
|
+
return this.apps.values();
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* 重启应用:删除后重新加载,曾运行则再次启动
|
|
210
|
+
* @param {string} name 应用名称
|
|
211
|
+
* @returns {Promise<boolean>} 是否执行成功
|
|
212
|
+
*/
|
|
213
|
+
async reload(name) {
|
|
214
|
+
const app = this.get(name);
|
|
215
|
+
if (!app) {
|
|
216
|
+
this.log('warn', `应用 ${name} 不存在,无法重启`);
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
const app_dir = app.dir;
|
|
220
|
+
const was_running = app.isRunning();
|
|
221
|
+
await this.apps.del(name);
|
|
222
|
+
const next = await this._loadApp(app_dir);
|
|
223
|
+
if (!next) return false;
|
|
224
|
+
if (was_running) {
|
|
225
|
+
await this._startApp(next);
|
|
226
|
+
}
|
|
227
|
+
return true;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* 删除应用:停止并释放后从应用管理器移除
|
|
232
|
+
* @param {string} name 应用名称
|
|
233
|
+
* @returns {Promise<boolean>} 是否删除成功
|
|
234
|
+
*/
|
|
235
|
+
async del(name) {
|
|
236
|
+
if (!this.has(name)) return false;
|
|
237
|
+
await this.apps.del(name);
|
|
238
|
+
return true;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* 清空应用列表与资源管理器缓存
|
|
243
|
+
* @returns {Promise<void>} 无返回值
|
|
244
|
+
*/
|
|
245
|
+
async clear() {
|
|
246
|
+
await this.apps.clear();
|
|
247
|
+
this._managers.clear();
|
|
248
|
+
this._warned.clear();
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// ---------- 应用加载 ----------
|
|
252
|
+
/**
|
|
253
|
+
* 获取应用根目录,未配置时回落到 mm_os/example/app
|
|
254
|
+
* @returns {string} 应用根目录绝对路径
|
|
255
|
+
*/
|
|
256
|
+
_getAppDir() {
|
|
257
|
+
if (this.config.dir) {
|
|
258
|
+
return path.resolve(this.config.dir);
|
|
259
|
+
}
|
|
260
|
+
return path.join(__dirname, '..', 'example', 'app');
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* 列出目录下的全部子目录,目录不存在时静默返回空列表
|
|
265
|
+
* @param {string} dir 目录绝对路径
|
|
266
|
+
* @returns {Array<string>} 子目录绝对路径列表
|
|
267
|
+
*/
|
|
268
|
+
_listSubDirs(dir) {
|
|
269
|
+
if (!fs.existsSync(dir)) return [];
|
|
270
|
+
const items = fs.readdirSync(dir, { withFileTypes: true });
|
|
271
|
+
return items.filter(item => item.isDirectory()).map(item => path.join(dir, item.name));
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* 列出应用根目录下的全部应用目录
|
|
276
|
+
* @param {string} dir 应用根目录绝对路径
|
|
277
|
+
* @returns {Array<string>} 应用目录绝对路径列表
|
|
278
|
+
*/
|
|
279
|
+
_listAppDirs(dir) {
|
|
280
|
+
if (!fs.existsSync(dir)) {
|
|
281
|
+
this.log('warn', `应用根目录不存在: ${dir}`);
|
|
282
|
+
return [];
|
|
283
|
+
}
|
|
284
|
+
return this._listSubDirs(dir);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* 批量加载应用目录
|
|
289
|
+
* @param {Array<string>} dirs 应用目录列表
|
|
290
|
+
* @returns {Promise<void>} 无返回值
|
|
291
|
+
*/
|
|
292
|
+
async _loadApps(dirs) {
|
|
293
|
+
for (const app_dir of dirs) {
|
|
294
|
+
await this._loadApp(app_dir);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* 加载单个应用:创建驱动、注册资源、加载插件并初始化
|
|
300
|
+
* @param {string} app_dir 应用目录绝对路径
|
|
301
|
+
* @returns {Promise<object>} 应用驱动,跳过或失败时返回 null
|
|
302
|
+
*/
|
|
303
|
+
async _loadApp(app_dir) {
|
|
304
|
+
const config_file = path.join(app_dir, this.config.config_file);
|
|
305
|
+
if (!fs.existsSync(config_file)) return null;
|
|
306
|
+
|
|
307
|
+
const app_config = this._resolveFileTemplates(this._readJson(config_file), config_file);
|
|
308
|
+
if (!app_config) return null;
|
|
309
|
+
|
|
310
|
+
const app_name = app_config.name || path.basename(app_dir);
|
|
311
|
+
if (this.has(app_name)) {
|
|
312
|
+
this.log('warn', `应用 ${app_name} 已存在,跳过: ${app_dir}`);
|
|
313
|
+
return null;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const app = this.apps.add(this._buildAppConfig(app_dir, app_config, app_name));
|
|
317
|
+
if (this.getLogger()) {
|
|
318
|
+
app.setLogger(this.getLogger());
|
|
319
|
+
}
|
|
320
|
+
this._registerResources(app, app.dir, this.config.resources);
|
|
321
|
+
this._loadPlugins(app, app_dir);
|
|
322
|
+
|
|
323
|
+
try {
|
|
324
|
+
await app.init();
|
|
325
|
+
} catch (err) {
|
|
326
|
+
this.log('error', `应用 ${app_name} 初始化失败: ${err.message}`);
|
|
327
|
+
}
|
|
328
|
+
return app;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* 构建应用驱动配置:补齐名称、目录、入口与生命周期
|
|
333
|
+
* @param {string} app_dir 应用目录绝对路径
|
|
334
|
+
* @param {object} app_config 应用配置(app.json 内容)
|
|
335
|
+
* @param {string} app_name 应用名称
|
|
336
|
+
* @returns {object} 应用驱动配置
|
|
337
|
+
*/
|
|
338
|
+
_buildAppConfig(app_dir, app_config, app_name) {
|
|
339
|
+
return {
|
|
340
|
+
...app_config,
|
|
341
|
+
name: app_name,
|
|
342
|
+
dir: app_dir,
|
|
343
|
+
main: app_config.main || this.config.main,
|
|
344
|
+
lifecycle: app_config.lifecycle || this.config.lifecycle
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* 启动单个应用,失败时记录日志
|
|
350
|
+
* @param {object} app 应用驱动
|
|
351
|
+
* @returns {Promise<void>} 无返回值
|
|
352
|
+
*/
|
|
353
|
+
async _startApp(app) {
|
|
354
|
+
try {
|
|
355
|
+
await app.start();
|
|
356
|
+
} catch (err) {
|
|
357
|
+
this.log('error', `启动应用 ${app.config.name} 失败: ${err.message}`);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* 停止单个应用,失败时记录日志
|
|
363
|
+
* @param {object} app 应用驱动
|
|
364
|
+
* @returns {Promise<void>} 无返回值
|
|
365
|
+
*/
|
|
366
|
+
async _stopApp(app) {
|
|
367
|
+
try {
|
|
368
|
+
await app.stop();
|
|
369
|
+
} catch (err) {
|
|
370
|
+
this.log('error', `停止应用 ${app.config.name} 失败: ${err.message}`);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// ---------- 插件加载 ----------
|
|
375
|
+
/**
|
|
376
|
+
* 加载应用下的全部插件
|
|
377
|
+
* @param {object} app 应用驱动
|
|
378
|
+
* @param {string} app_dir 应用目录绝对路径
|
|
379
|
+
*/
|
|
380
|
+
_loadPlugins(app, app_dir) {
|
|
381
|
+
const plugin_config = this.config.plugin;
|
|
382
|
+
if (!plugin_config) return;
|
|
383
|
+
|
|
384
|
+
const plugin_root = path.join(app_dir, plugin_config.dir);
|
|
385
|
+
const dirs = this._listSubDirs(plugin_root);
|
|
386
|
+
for (const plugin_dir of dirs) {
|
|
387
|
+
this._loadPlugin(app, plugin_dir);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* 加载单个插件:创建驱动并注册资源
|
|
393
|
+
* @param {object} app 应用驱动
|
|
394
|
+
* @param {string} plugin_dir 插件目录绝对路径
|
|
395
|
+
* @returns {object} 插件驱动,跳过时返回 null
|
|
396
|
+
*/
|
|
397
|
+
_loadPlugin(app, plugin_dir) {
|
|
398
|
+
const plugin_config = this.config.plugin;
|
|
399
|
+
const config_file = path.join(plugin_dir, plugin_config.config_file);
|
|
400
|
+
const raw = fs.existsSync(config_file) ? this._readJson(config_file) : null;
|
|
401
|
+
const config = raw ? this._resolveFileTemplates(raw, config_file) : null;
|
|
402
|
+
|
|
403
|
+
const plugin_name = config && config.name ? config.name : path.basename(plugin_dir);
|
|
404
|
+
if (config && config.state === 0) {
|
|
405
|
+
this.log('info', `插件 ${app.config.name}.${plugin_name} 已禁用,跳过`);
|
|
406
|
+
return null;
|
|
407
|
+
}
|
|
408
|
+
if (app.plugins.has(plugin_name)) {
|
|
409
|
+
this.log('warn', `插件 ${app.config.name}.${plugin_name} 已存在,跳过`);
|
|
410
|
+
return null;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const plugin = app.plugins.add(this._buildPluginConfig(app, plugin_dir, config, plugin_name));
|
|
414
|
+
if (this.getLogger()) {
|
|
415
|
+
plugin.setLogger(this.getLogger());
|
|
416
|
+
}
|
|
417
|
+
this._registerResources(plugin, plugin_dir, plugin_config.resources);
|
|
418
|
+
return plugin;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* 构建插件驱动配置:补齐名称、所属应用、目录、入口与生命周期
|
|
423
|
+
* @param {object} app 应用驱动
|
|
424
|
+
* @param {string} plugin_dir 插件目录绝对路径
|
|
425
|
+
* @param {object} config 插件配置,可为空
|
|
426
|
+
* @param {string} plugin_name 插件名称
|
|
427
|
+
* @returns {object} 插件驱动配置
|
|
428
|
+
*/
|
|
429
|
+
_buildPluginConfig(app, plugin_dir, config, plugin_name) {
|
|
430
|
+
const cfg = config || {};
|
|
431
|
+
return {
|
|
432
|
+
...cfg,
|
|
433
|
+
name: plugin_name,
|
|
434
|
+
app: cfg.app || app.config.name,
|
|
435
|
+
dir: plugin_dir,
|
|
436
|
+
main: cfg.main || this.config.plugin.main,
|
|
437
|
+
lifecycle: cfg.lifecycle || this.config.plugin.lifecycle
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// ---------- 资源注册 ----------
|
|
442
|
+
/**
|
|
443
|
+
* 注册归属对象下的全部资源
|
|
444
|
+
* @param {object} owner 归属对象(应用或插件驱动)
|
|
445
|
+
* @param {string} base_dir 基础目录绝对路径
|
|
446
|
+
* @param {object} resources 资源目录名 -> 资源配置
|
|
447
|
+
*/
|
|
448
|
+
_registerResources(owner, base_dir, resources) {
|
|
449
|
+
const map = resources || {};
|
|
450
|
+
for (const res_name of Object.keys(map)) {
|
|
451
|
+
const res = map[res_name];
|
|
452
|
+
// 未声明子目录时以资源目录名为子目录,空串时以基础目录为根
|
|
453
|
+
const sub_dir = res.sub_dir === undefined ? res_name : res.sub_dir;
|
|
454
|
+
const root = sub_dir ? path.join(base_dir, sub_dir) : base_dir;
|
|
455
|
+
this._registerResource(owner, res_name, res, root);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* 注册某一类资源
|
|
461
|
+
* @param {object} owner 归属对象(应用或插件驱动)
|
|
462
|
+
* @param {string} res_name 资源目录名
|
|
463
|
+
* @param {object} res 资源配置
|
|
464
|
+
* @param {string} root 资源搜索根目录绝对路径
|
|
465
|
+
*/
|
|
466
|
+
_registerResource(owner, res_name, res, root) {
|
|
467
|
+
if (!fs.existsSync(root)) return;
|
|
468
|
+
|
|
469
|
+
const manager = this._getManager(owner, res_name, res);
|
|
470
|
+
if (!manager) return;
|
|
471
|
+
|
|
472
|
+
const files = this._listConfigFiles(root, res.file);
|
|
473
|
+
for (const file of files) {
|
|
474
|
+
this._addResource(owner, res_name, manager, file);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* 注册单个资源清单
|
|
480
|
+
* @param {object} owner 归属对象(应用或插件驱动)
|
|
481
|
+
* @param {string} res_name 资源目录名
|
|
482
|
+
* @param {object} manager 资源管理器
|
|
483
|
+
* @param {string} file 资源清单绝对路径
|
|
484
|
+
*/
|
|
485
|
+
_addResource(owner, res_name, manager, file) {
|
|
486
|
+
const config = this._resolveFileTemplates(this._readJson(file), file);
|
|
487
|
+
if (!config) return;
|
|
488
|
+
// 驱动依赖 dir 解析 app 名与入口脚本
|
|
489
|
+
config.dir = path.dirname(file);
|
|
490
|
+
const name = config.name;
|
|
491
|
+
if (name && typeof manager.has === 'function' && manager.has(name)) {
|
|
492
|
+
this.log('warn', `资源 ${res_name} 已存在,跳过 ${file}: ${name}`);
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
try {
|
|
496
|
+
const drive = manager.add(config);
|
|
497
|
+
owner.resources.push({ type: res_name, name: drive.config.name, file, drive });
|
|
498
|
+
} catch (err) {
|
|
499
|
+
this.log('error', `注册资源 ${res_name} 失败 ${file}: ${err.message}`);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* 递归列出目录下指定名称的清单文件
|
|
505
|
+
* @param {string} dir 目录绝对路径
|
|
506
|
+
* @param {string} filename 清单文件名
|
|
507
|
+
* @returns {Array<string>} 清单文件绝对路径列表
|
|
508
|
+
*/
|
|
509
|
+
_listConfigFiles(dir, filename) {
|
|
510
|
+
const files = [];
|
|
511
|
+
const items = fs.readdirSync(dir, { withFileTypes: true });
|
|
512
|
+
for (const item of items) {
|
|
513
|
+
const full = path.join(dir, item.name);
|
|
514
|
+
if (item.isDirectory()) {
|
|
515
|
+
files.push(...this._listConfigFiles(full, filename));
|
|
516
|
+
} else if (item.name === filename) {
|
|
517
|
+
files.push(full);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
return files;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/**
|
|
524
|
+
* 获取归属对象所在作用域下的资源管理器:按 owner 作用域惰性加载
|
|
525
|
+
* @param {object} owner 归属对象(应用或插件驱动)
|
|
526
|
+
* @param {string} res_name 资源目录名
|
|
527
|
+
* @param {object} res 资源配置
|
|
528
|
+
* @returns {object} 资源管理器,不可用时返回 null
|
|
529
|
+
*/
|
|
530
|
+
_getManager(owner, res_name, res) {
|
|
531
|
+
const scope = this._getOwnerScope(owner);
|
|
532
|
+
const key = `${scope}:${res_name}`;
|
|
533
|
+
if (this._managers.has(key)) {
|
|
534
|
+
return this._managers.get(key);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
const manager = this._getModuleManager(res, scope);
|
|
538
|
+
if (!manager) {
|
|
539
|
+
this._warnOnce(key, `未找到资源管理器 ${res.admin}(${res.module}),跳过 ${res_name} 资源`);
|
|
540
|
+
return null;
|
|
541
|
+
}
|
|
542
|
+
if (typeof manager.add !== 'function') {
|
|
543
|
+
this._warnOnce(key, `资源管理器 ${res.admin} 不支持 add 方法,跳过 ${res_name} 资源`);
|
|
544
|
+
return null;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
this._managers.set(key, manager);
|
|
548
|
+
return manager;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* 获取归属对象的作用域名:插件取所属应用名,应用取自身名称
|
|
553
|
+
* @param {object} owner 归属对象(应用或插件驱动)
|
|
554
|
+
* @returns {string} 作用域名
|
|
555
|
+
*/
|
|
556
|
+
_getOwnerScope(owner) {
|
|
557
|
+
const config = owner && owner.config ? owner.config : {};
|
|
558
|
+
return config.app || config.name || this.config.scope;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
/**
|
|
562
|
+
* 按模块名惰性加载,并在指定作用域创建资源管理器
|
|
563
|
+
* @param {object} res 资源配置
|
|
564
|
+
* @param {string} scope 作用域名
|
|
565
|
+
* @returns {object} 资源管理器,加载失败时返回 null
|
|
566
|
+
*/
|
|
567
|
+
_getModuleManager(res, scope) {
|
|
568
|
+
if (!res.module) return null;
|
|
569
|
+
|
|
570
|
+
let mod;
|
|
571
|
+
try {
|
|
572
|
+
const server = this.getServer();
|
|
573
|
+
mod = server && typeof server.require === 'function'
|
|
574
|
+
? server.require(res.module)
|
|
575
|
+
: require(res.module);
|
|
576
|
+
} catch (err) {
|
|
577
|
+
return null;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
const admin = mod && mod.admin;
|
|
581
|
+
if (!admin || typeof admin.get !== 'function') return null;
|
|
582
|
+
|
|
583
|
+
if (typeof admin.setServer === 'function') {
|
|
584
|
+
try {
|
|
585
|
+
admin.setServer(this.getServer());
|
|
586
|
+
} catch (err) {
|
|
587
|
+
// 服务器实例已设置,忽略
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
return admin.get(scope, { name: scope });
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
/**
|
|
594
|
+
* 输出一次性警告
|
|
595
|
+
* @param {string} key 去重键
|
|
596
|
+
* @param {string} message 警告内容
|
|
597
|
+
*/
|
|
598
|
+
_warnOnce(key, message) {
|
|
599
|
+
if (this._warned.has(key)) return;
|
|
600
|
+
this._warned.add(key);
|
|
601
|
+
this.log('warn', message);
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// ---------- 配置读取 ----------
|
|
605
|
+
/**
|
|
606
|
+
* 对配置中的模板字段求值(如 `{name || 'api'}`),以上下文求值后返回新配置
|
|
607
|
+
* @param {object} config 原始配置对象
|
|
608
|
+
* @param {string} file 配置文件绝对路径
|
|
609
|
+
* @returns {object} 求值后的配置对象
|
|
610
|
+
*/
|
|
611
|
+
_resolveFileTemplates(config, file) {
|
|
612
|
+
if (!config) return config;
|
|
613
|
+
const ctx = this._buildTemplateContext(path.dirname(file));
|
|
614
|
+
return this._resolveTemplates(config, ctx);
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
/**
|
|
618
|
+
* 由资源目录推导模板上下文(名称、应用名、插件名、接口路径)
|
|
619
|
+
* @param {string} dir 资源目录绝对路径
|
|
620
|
+
* @returns {object} 模板上下文
|
|
621
|
+
*/
|
|
622
|
+
_buildTemplateContext(dir) {
|
|
623
|
+
const name = path.basename(dir);
|
|
624
|
+
const app_name = this._extractPathSegment(dir, 'app');
|
|
625
|
+
const plugin_name = this._extractPathSegment(dir, 'plugin');
|
|
626
|
+
return {
|
|
627
|
+
name,
|
|
628
|
+
app: app_name,
|
|
629
|
+
plugin: plugin_name,
|
|
630
|
+
path: app_name ? `/${app_name}/${name}` : `/${name}`
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
/**
|
|
635
|
+
* 提取路径中指定目录名之后的段落值
|
|
636
|
+
* @param {string} dir 目录绝对路径
|
|
637
|
+
* @param {string} key 段落名(如 app / plugin)
|
|
638
|
+
* @returns {string} 段落值,未找到时返回空串
|
|
639
|
+
*/
|
|
640
|
+
_extractPathSegment(dir, key) {
|
|
641
|
+
const parts = dir.split(path.sep);
|
|
642
|
+
const index = parts.indexOf(key);
|
|
643
|
+
if (index !== -1 && index + 1 < parts.length) {
|
|
644
|
+
return parts[index + 1];
|
|
645
|
+
}
|
|
646
|
+
return '';
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
/**
|
|
650
|
+
* 递归求值配置对象中的模板字段
|
|
651
|
+
* @param {*} config 配置对象、数组或基础值
|
|
652
|
+
* @param {object} ctx 模板上下文
|
|
653
|
+
* @returns {*} 求值后的配置
|
|
654
|
+
*/
|
|
655
|
+
_resolveTemplates(config, ctx) {
|
|
656
|
+
if (Array.isArray(config)) {
|
|
657
|
+
return config.map(item => this._resolveTemplates(item, ctx));
|
|
658
|
+
}
|
|
659
|
+
if (config && typeof config === 'object') {
|
|
660
|
+
const result = {};
|
|
661
|
+
for (const key of Object.keys(config)) {
|
|
662
|
+
result[key] = this._resolveTemplates(config[key], ctx);
|
|
663
|
+
}
|
|
664
|
+
return result;
|
|
665
|
+
}
|
|
666
|
+
return this._renderTemplateValue(config, ctx);
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
/**
|
|
670
|
+
* 求值单个字符串模板,非模板或纯数字占位(如 `{0}`)时原样返回
|
|
671
|
+
* @param {*} value 待求值内容
|
|
672
|
+
* @param {object} ctx 模板上下文
|
|
673
|
+
* @returns {*} 求值结果
|
|
674
|
+
*/
|
|
675
|
+
_renderTemplateValue(value, ctx) {
|
|
676
|
+
if (typeof value !== 'string') return value;
|
|
677
|
+
const matched = value.match(/^\{\s*([^{}]+?)\s*\}$/);
|
|
678
|
+
if (!matched) return value;
|
|
679
|
+
const expr = matched[1];
|
|
680
|
+
if (/^[+-]?\d+(\.\d+)?$/.test(expr)) return value;
|
|
681
|
+
try {
|
|
682
|
+
return vm.runInNewContext(`(${expr})`, this._buildSandbox(ctx));
|
|
683
|
+
} catch (err) {
|
|
684
|
+
this.log('warn', `模板字段求值失败 ${value}: ${err.message}`);
|
|
685
|
+
return value;
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
/**
|
|
690
|
+
* 构建模板求值沙箱,未定义的标识符解析为 undefined,便于 `||` 取默认值
|
|
691
|
+
* @param {object} ctx 模板上下文
|
|
692
|
+
* @returns {Proxy} 沙箱对象
|
|
693
|
+
*/
|
|
694
|
+
_buildSandbox(ctx) {
|
|
695
|
+
return new Proxy(ctx, {
|
|
696
|
+
has: () => true,
|
|
697
|
+
get: (target, key) => (key in target ? target[key] : undefined)
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
/**
|
|
702
|
+
* 读取并解析 JSON 配置文件,失败时记录日志
|
|
703
|
+
* @param {string} file 配置文件绝对路径
|
|
704
|
+
* @returns {object} 解析结果,失败时返回 null
|
|
705
|
+
*/
|
|
706
|
+
_readJson(file) {
|
|
707
|
+
try {
|
|
708
|
+
const text = fs.readFileSync(file, 'utf8');
|
|
709
|
+
return JSON.parse(this._stripComments(this._stripBom(text)));
|
|
710
|
+
} catch (err) {
|
|
711
|
+
this.log('error', `解析配置失败 ${file}: ${err.message}`);
|
|
712
|
+
return null;
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
/**
|
|
717
|
+
* 去除文本开头的字节顺序标记(BOM)
|
|
718
|
+
* @param {string} text 原始文本
|
|
719
|
+
* @returns {string} 去除 BOM 后的文本
|
|
720
|
+
*/
|
|
721
|
+
_stripBom(text) {
|
|
722
|
+
return text.charCodeAt(0) === 0xFEFF ? text.slice(1) : text;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
/**
|
|
726
|
+
* 去除 JSON 文本中的注释(支持 // 与 /* *\/,不处理字符串内部)
|
|
727
|
+
* @param {string} text 原始文本
|
|
728
|
+
* @returns {string} 去除注释后的文本
|
|
729
|
+
*/
|
|
730
|
+
_stripComments(text) {
|
|
731
|
+
let out = '';
|
|
732
|
+
let in_string = false;
|
|
733
|
+
let in_line = false;
|
|
734
|
+
let in_block = false;
|
|
735
|
+
let quote = '';
|
|
736
|
+
|
|
737
|
+
for (let i = 0; i < text.length; i++) {
|
|
738
|
+
const char = text[i];
|
|
739
|
+
const next = text[i + 1];
|
|
740
|
+
|
|
741
|
+
if (in_line) {
|
|
742
|
+
if (char === '\n') {
|
|
743
|
+
in_line = false;
|
|
744
|
+
out += char;
|
|
745
|
+
}
|
|
746
|
+
continue;
|
|
747
|
+
}
|
|
748
|
+
if (in_block) {
|
|
749
|
+
if (char === '*' && next === '/') {
|
|
750
|
+
in_block = false;
|
|
751
|
+
i++;
|
|
752
|
+
}
|
|
753
|
+
continue;
|
|
754
|
+
}
|
|
755
|
+
if (in_string) {
|
|
756
|
+
out += char;
|
|
757
|
+
if (char === '\\') {
|
|
758
|
+
out += next || '';
|
|
759
|
+
i++;
|
|
760
|
+
} else if (char === quote) {
|
|
761
|
+
in_string = false;
|
|
762
|
+
}
|
|
763
|
+
continue;
|
|
764
|
+
}
|
|
765
|
+
if (char === '"' || char === "'") {
|
|
766
|
+
in_string = true;
|
|
767
|
+
quote = char;
|
|
768
|
+
out += char;
|
|
769
|
+
continue;
|
|
770
|
+
}
|
|
771
|
+
if (char === '/' && next === '/') {
|
|
772
|
+
in_line = true;
|
|
773
|
+
i++;
|
|
774
|
+
continue;
|
|
775
|
+
}
|
|
776
|
+
if (char === '/' && next === '*') {
|
|
777
|
+
in_block = true;
|
|
778
|
+
i++;
|
|
779
|
+
continue;
|
|
780
|
+
}
|
|
781
|
+
out += char;
|
|
782
|
+
}
|
|
783
|
+
return out;
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
module.exports = Loader;
|