mm_tpl 2.7.0 → 2.7.2

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/index.js CHANGED
@@ -1,471 +1,46 @@
1
- const engine = require('art-template');
2
- const {
3
- Base
4
- } = require('mm_expand');
5
- const Hook = require('./hook.js');
6
- const {
7
- nativeRule,
8
- artRule,
9
- htmlRule,
10
- jsRule,
11
- pyRule,
12
- pyRule2
13
- } = require('./tpl_rule');
14
-
15
- if (!$.globalBag) {
16
- $.globalBag = {};
17
- }
18
-
19
- if (!$.hook) {
20
- $.hook = new Hook();
21
- }
22
-
23
- /**
24
- * 模板类
25
- * @class
26
- */
27
- class Tpl extends Base {
28
- static config = {
29
- /**
30
- * 模板根目录
31
- */
32
- root: $.run_path + '',
33
- /**
34
- * 默认模板目录
35
- */
36
- default_dir: "./static/template/",
37
- /**
38
- * 默认主题
39
- */
40
- default_theme: 'default',
41
- /**
42
- * 模板文件扩展名
43
- */
44
- extname: ".html",
45
- /**
46
- * 模板规则
47
- */
48
- rules: [nativeRule, artRule, htmlRule, jsRule, pyRule, pyRule2],
49
- /**
50
- * 缓存根目录
51
- */
52
- cache_root: './cache'.fullname(),
53
- /**
54
- * 缓存文件扩展名
55
- */
56
- cache_extname: '.cache.html',
57
- /**
58
- * 变换字符串
59
- */
60
- change_str: ['\\#', '\\<', '\\${', '\\*', '\\"""'],
61
- };
62
- /**
63
- * 创建模板实例
64
- * @param {Object} config - 配置选项
65
- */
66
- constructor(config) {
67
- super(config);
68
-
69
- /**
70
- * 模板引擎
71
- */
72
- this.engine = engine;
73
-
74
- /**
75
- * 视图数据
76
- */
77
- this.viewBag = {};
78
-
79
- /**
80
- * 模板根目录
81
- */
82
- this.dir = './static/template/';
83
-
84
- /**
85
- * 当前主题
86
- */
87
- this.current_theme = this.config.default_theme;
88
-
89
- /**
90
- * 错误提示
91
- */
92
- this.error = null;
93
-
94
- this.regex_in = [];
95
- this.str_in = [];
96
-
97
- this.regex_out = [];
98
- this.str_out = [];
99
-
100
- /**
101
- * 初始化
102
- */
103
- this._init();
104
- }
105
- }
106
-
107
- /**
108
- * 初始化模板
109
- */
110
- Tpl.prototype._initTpl = function () {
111
- var cg = this.config;
112
- for (var key in cg) {
113
- this.engine.defaults[key] = cg[key];
114
- }
115
- if (!cg.cache_root.hasDir()) {
116
- cg.cache_root.addDir()
117
- }
118
- };
119
-
120
- /**
121
- * 执行钩子函数
122
- * @param {string} name - 钩子名称
123
- * @param {Object} req - 请求参数
124
- * @param {...any} args - 附加参数
125
- * @returns {any} 执行结果
126
- */
127
- Tpl.prototype.runFunc = function (name, req, ...args) {
128
- return $.hook.runFunc(name, this.viewBag, req, ...args);
129
- }
130
-
131
- /**
132
- * 执行钩子函数
133
- * @param {string} name - 钩子名称
134
- * @param {Object} req - 请求参数
135
- * @param {...any} args - 附加参数
136
- * @returns {any} 执行结果
137
- */
138
- Tpl.prototype.runHook = Tpl.prototype.runFunc;
139
-
140
- /**
141
- * 初始化前处理
142
- * @param {Object} config - 配置参数
143
- */
144
- Tpl.prototype._initBefore = function (config) { }
145
-
146
- /**
147
- * 初始化主处理
148
- * @param {Object} config - 配置参数
149
- */
150
- Tpl.prototype._initMain = function (config) {
151
- this.setConfig(config);
152
- this._initTpl();
153
- };
154
-
155
- /**
156
- * 初始化后处理
157
- * @param {Object} config - 配置参数
158
- */
159
- Tpl.prototype._initAfter = function (config) {
160
- var lt = this.config.change_str;
161
- for (var i = 0; i < lt.length; i++) {
162
- var str = lt[i];
163
- var rx_in = str.replace("\\", "\\\\").replace("$", "\\$").replace("[", "\\[").replace("]", "\\]").replace(
164
- "{", "\\{").replace("}", "\\}").replace("/", "\\/").replace("*", "\\*");
165
- this.regex_in.push(new RegExp(rx_in, "gi"));
166
- var str_in = str.split("").join("·") + "·";
167
- this.str_in.push(str_in);
168
- var rx_out = str_in.replace("\\", "\\\\").replace("$", "\\$").replace("[", "\\[").replace("]", "\\]").replace(
169
- "{", "\\{").replace("}", "\\}").replace("/", "\\/").replace("*", "\\*");
170
- this.regex_out.push(new RegExp(rx_out, "gi"));
171
- this.str_out.push(str.replace("\\", ""));
172
- }
173
- };
174
-
175
- /**
176
- * 初始化模板
177
- * @param {Object} config - 配置参数
178
- */
179
- Tpl.prototype._init = function (config) {
180
- this._initBefore(config);
181
- this._initMain(config);
182
- this._initAfter(config);
183
- };
184
-
185
- /**
186
- * 转义字符
187
- * @param {string} body - 字符串
188
- * @param {boolean} restore - 是否还原
189
- * @returns {string} 转义后的字符串
190
- */
191
- Tpl.prototype.escape = function (body, restore) {
192
- if (!body) {
193
- return body
194
- }
195
- if (restore) {
196
- for (var i = 0; i < this.regex_out.length; i++) {
197
- body = body.replace(this.regex_out[i], this.str_out[i]);
198
- }
199
- } else {
200
- for (var i = 0; i < this.regex_in.length; i++) {
201
- body = body.replace(this.regex_in[i], this.str_in[i]);
202
- }
203
- }
204
- return body;
205
- }
206
-
207
- /**
208
- * 渲染前处理
209
- * @param {string} body - 模板内容
210
- * @param {Object} model - 数据模型
211
- * @param {Object} options - 配置参数
212
- * @returns {string} 渲染结果
213
- */
214
- Tpl.prototype._renderBefore = function (body, model, options) {
215
- var ret = "";
216
- if (options && options.cache) {
217
- var f = options.cache_filename;
218
- if (f) {
219
- if (f.replace('./', '').indexOf('.') == -1 && !f.endsWith(this.config.cache_extname)) {
220
- f += this.config.cache_extname;
221
- }
222
- var file = f.fullname(this.config.cache_root);
223
- // 如果文件存在
224
- if (file.hasFile()) {
225
- ret = file.loadText();
226
- }
227
- }
228
- }
229
- return ret;
230
- };
231
-
232
- /**
233
- * 注入全局函数前处理
234
- * @param {Object} m - 模型对象
235
- * @returns {Object} 处理后的模型
236
- */
237
- Tpl.prototype._loadFuncBefore = function (m) {
238
- // m.$i18n = function(key) {
239
- // return $.i18n(key);
240
- // }
241
- return m;
242
- }
243
-
244
- /**
245
- * 加载函数到模型
246
- * @param {Object} m - 模型对象
247
- * @returns {Object} 处理后的模型
248
- */
249
- Tpl.prototype.loadFunc = function (m) {
250
- this._loadFuncBefore(m);
251
- var _this = this;
252
- m.view = function (file, model, options) {
253
- return _this.view(file, Object.assign({}, m, model), options);
254
- }
255
- m.render = function (body, model, options) {
256
- return _this.render(body, Object.assign({}, m, model), options);
257
- }
258
- m.hookAction = function (name, ...args) {
259
- return $.hook.runAction(name, m, ...args);
260
- }
261
- m.hookFilter = function (name, content, ...args) {
262
- if (!content) {
263
- content = "";
264
- }
265
- return $.hook.runFilter(name, m, content, ...args);
266
- }
267
- if (global.$ && $.lang) {
268
- m.$t = function (key, ...args) {
269
- return $.lang.get(key, ...args);
270
- }
271
- }
272
- return m;
273
- }
274
-
275
- /**
276
- * 渲染主处理
277
- * @param {string} body - 模板内容
278
- * @param {Object} model - 数据模型
279
- * @param {Object} options - 配置参数
280
- * @returns {string} 渲染结果
281
- */
282
- Tpl.prototype._renderMain = function (body, model, options) {
283
- var ret = "";
284
- if (body) {
285
- var m = Object.assign({}, {
286
- viewBag: this.viewBag,
287
- globalBag: $.globalBag
288
- }, model);
289
- this.loadFunc(m);
290
- ret = this.engine.render(body, m, options);
291
- }
292
- return ret;
293
- };
294
-
295
- /**
296
- * 渲染后处理
297
- * @param {string} body - 模板内容
298
- * @param {Object} model - 数据模型
299
- * @param {Object} options - 配置参数
300
- * @returns {string} 渲染结果
301
- */
302
- Tpl.prototype._renderAfter = function (body, model, options) {
303
- if (body) {
304
- if (options) {
305
- var f = options.cache_filename;
306
- if (f) {
307
- if (f.replace('./', '').indexOf('.') == -1 && !f.endsWith(this.config.cache_extname)) {
308
- f += this.config.cache_extname;
309
- }
310
- var file = f.fullname(this.config.cache_root);
311
- file.saveText(body);
312
- }
313
- }
314
- }
315
- return body;
316
- };
317
-
318
- /**
319
- * 渲染子模板
320
- * @param {string} body - 模板内容
321
- * @param {Object} model - 数据模型
322
- * @param {Object} options - 配置参数
323
- * @returns {string} 渲染结果
324
- */
325
- Tpl.prototype._renderSub = function (body, model, options) {
326
- var ret = this._renderBefore(body, model, options);
327
- if (!ret) {
328
- ret = this._renderMain(body, model, options);
329
- if (ret) {
330
- ret = this._renderAfter(ret, model, options);
331
- }
332
- }
333
- return ret;
334
- };
335
-
336
- /**
337
- * 渲染模板
338
- * @param {string} body - 模板内容
339
- * @param {Object} model - 数据模型
340
- * @param {Object} options - 配置参数
341
- * @returns {string} 渲染结果
342
- */
343
- Tpl.prototype.render = function (body, model, options) {
344
- body = this.escape(body);
345
- body = this._renderSub(body, model, options);
346
- return this.escape(body, true);
347
- };
348
-
349
- /**
350
- * 编译模板
351
- * @param {string} body - 模板内容
352
- * @param {Object} options - 配置参数
353
- * @returns {Function} 渲染函数
354
- */
355
- Tpl.prototype.compile = function (body, options) {
356
- return this.engine.compile(body, options);
357
- };
358
-
359
- /**
360
- * 获取模板文件
361
- * @param {string} file - 模板文件路径
362
- * @returns {string} 模板文件路径
363
- */
364
- Tpl.prototype.getFile = function (file) {
365
- if (typeof (file) !== 'string') {
366
- throw new Error('模板文件路径必须是字符串');
367
- }
368
- if (file.replace('./', '').indexOf('.') === -1 && !file.endsWith(this.config.extname)) {
369
- file += this.config.extname;
370
- }
371
- var f = file.fullname('diy'.fullname(this.dir));
372
- if (!f.hasFile()) {
373
- f = file.fullname('diy'.fullname(this.config.default_dir));
374
- if (!f.hasFile()) {
375
- f = file.fullname(this.current_theme.fullname(this.dir));
376
- if (!f.hasFile()) {
377
- f = file.fullname(this.config.default_theme.fullname(this.dir));
378
- if (!f.hasFile()) {
379
- f = file.fullname(this.current_theme.fullname(this.config.default_dir));
380
- if (!f.hasFile()) {
381
- f = file.fullname(this.config.default_theme.fullname(this.config.default_dir));
382
- }
383
- }
384
- }
385
- }
386
- }
387
- return f;
388
- };
389
-
390
- /**
391
- * 渲染模板文件
392
- * @param {string} file - 模板文件路径
393
- * @param {Object} model - 数据模型
394
- * @param {Object} options - 配置参数
395
- * @returns {string} 渲染结果
396
- */
397
- Tpl.prototype.view = function (file, model, options) {
398
- var f = this.getFile(file);
399
- if (!f.hasFile()) {
400
- this.error = {
401
- code: 10000,
402
- message: f + "文件不存在!"
403
- };
404
- }
405
- else {
406
- var body = f.loadText();
407
- if (body) {
408
- body = this.escape(body);
409
- body = this._renderSub(body, model, options);
410
- body = this.escape(body, true);
411
- return body;
412
- }
413
- }
414
- return "";
415
- };
416
-
417
- /**
418
- * 设置视图数据
419
- * @param {Object} obj - 要设置的数据对象
420
- */
421
- Tpl.prototype.set = function (obj) {
422
- $.push(this.viewBag, obj, true);
423
- };
424
-
425
- /**
426
- * 删除视图数据
427
- * @param {Array|string} keys - 要删除的键或键数组
428
- */
429
- Tpl.prototype.del = function (keys) {
430
- if (typeof (keys) == 'string') {
431
- delete this.viewBag[keys];
432
- } else {
433
- keys.map(function (k) {
434
- delete this.viewBag[k];
435
- });
436
- }
437
- };
438
-
439
- /**
440
- * 获取视图数据
441
- * @param {string} key - 数据键名
442
- * @returns {any} 数据值
443
- */
444
- Tpl.prototype.get = function (key) {
445
- return this.viewBag[key];
446
- };
447
-
448
- /**
449
- * 清除缓存
450
- * @param {string} path - 缓存路径
451
- * @param {string} file - 文件名
452
- */
453
- Tpl.prototype.clearCache = function (path, file) {
454
- var cg = this.config;
455
- var root = cg.cache_root;
456
- var dir = path ? path.fullname(root) : root;
457
- var keyword = file || '*' + cg.cache_extname;
458
- var list_file = $.file.getAll(dir, keyword);
459
- list_file.forEach((f) => {
460
- f.delFile(root);
461
- });
462
- };
463
-
464
- if (global.$ && !$.Tpl) {
465
- $.Tpl = Tpl;
466
- $.tpl = new Tpl();
467
- }
468
-
469
- module.exports = {
470
- Tpl
471
- };
1
+ const Hook = require('./lib/hook.js');
2
+ const {
3
+ Engine
4
+ } = require('./lib/engine.js');
5
+ const {
6
+ Memory
7
+ } = require('./lib/memory.js');
8
+
9
+ /**
10
+ * SQLite 存储
11
+ * 依赖 Node 内置的 node:sqlite(Node 22.5 起提供),低版本加载失败时仅不导出,其余功能不受影响
12
+ */
13
+ let Sqlite = null;
14
+
15
+ try {
16
+ Sqlite = require('./lib/sqlite.js').Sqlite;
17
+ } catch {
18
+ Sqlite = null;
19
+ }
20
+
21
+ if (!$.globalBag) {
22
+ $.globalBag = {};
23
+ }
24
+
25
+ if (!$.hook) {
26
+ $.hook = new Hook();
27
+ }
28
+
29
+ /**
30
+ * 模板类
31
+ * 对外主类与模块入口,在引擎实例基础上供业务侧扩展
32
+ * 渲染、主题/风格/语言编排与派生实例见 lib/engine.js,默认配置项由 Engine 提供
33
+ * @class
34
+ */
35
+ class Tpl extends Engine { }
36
+
37
+ if (global.$ && !$.Tpl) {
38
+ $.Tpl = Tpl;
39
+ $.tpl = new Tpl();
40
+ }
41
+
42
+ module.exports = {
43
+ Tpl,
44
+ Memory,
45
+ Sqlite
46
+ };
package/lib/asset.js ADDED
@@ -0,0 +1,129 @@
1
+ /**
2
+ * 资源加载基类
3
+ * 样式与脚本同属「引用多个文件」类资源:按目录优先级收集,同名文件只保留优先级最高的一份
4
+ * 输出顺序按目录优先级从低到高排列,使高优先级资源位于后方,覆盖与依赖关系才成立
5
+ * @class
6
+ */
7
+ class Asset {
8
+ /**
9
+ * 创建资源加载器
10
+ * @param {Object} tpl - 所属模板实例
11
+ * @param {string} extname - 资源扩展名,如 .css、.js
12
+ */
13
+ constructor(tpl, extname) {
14
+ /**
15
+ * 所属模板实例
16
+ */
17
+ this.tpl = tpl;
18
+
19
+ /**
20
+ * 资源扩展名
21
+ */
22
+ this.extname = extname;
23
+ }
24
+ }
25
+
26
+ /**
27
+ * 获取资源目录列表
28
+ * 由子类按资源类型实现,返回顺序为优先级从高到低
29
+ * @param {string} theme - 主题名称
30
+ * @param {string} style - 风格名称
31
+ * @returns {Array<string>} 目录列表
32
+ */
33
+ Asset.prototype.getDirs = function (theme, style) {
34
+ throw new Error('资源加载器必须实现 getDirs 方法');
35
+ };
36
+
37
+ /**
38
+ * 匹配单个目录下的资源文件
39
+ * @param {string} dir - 目录路径
40
+ * @param {string} keyword - 文件名或通配符
41
+ * @returns {Array<string>} 资源文件路径列表,同目录内按文件名排序
42
+ */
43
+ Asset.prototype._matchDir = function (dir, keyword) {
44
+ if (!dir || !dir.hasDir()) {
45
+ return [];
46
+ }
47
+ // 排序保证同目录内的层叠与执行顺序稳定,不依赖文件系统的枚举顺序
48
+ var files = $.file.get(dir, keyword).sort();
49
+ var list = [];
50
+ for (var i = 0; i < files.length; i++) {
51
+ if (files[i].extname() === this.extname) {
52
+ list.push(files[i]);
53
+ }
54
+ }
55
+ return list;
56
+ };
57
+
58
+ /**
59
+ * 按名称或通配符匹配资源文件
60
+ * 名称支持 * 通配:*后缀 为结尾匹配,前缀* 为开头匹配,*包含* 为包含匹配
61
+ * 从高优先级到低优先级收集,同名文件只保留优先级最高的一份
62
+ * 结果按优先级升序输出,低优先级在前、高优先级在后,便于直接按序引用
63
+ * @param {string} pattern - 资源文件名或通配符,为空时等同于全部资源
64
+ * @param {string} theme - 主题名称,不传时取当前主题
65
+ * @param {string} style - 风格名称,不传时取当前风格
66
+ * @returns {Array<string>} 命中的资源文件全路径列表
67
+ */
68
+ Asset.prototype.matchFiles = function (pattern, theme, style) {
69
+ var dirs = this.getDirs(theme, style);
70
+ var keyword = pattern || '*' + this.extname;
71
+ var map = {};
72
+ for (var i = 0; i < dirs.length; i++) {
73
+ var files = this._matchDir(dirs[i], keyword);
74
+ for (var j = 0; j < files.length; j++) {
75
+ var name = files[j].basename();
76
+ // 高优先级目录已收录同名文件时跳过,实现同名覆盖
77
+ if (map[name]) {
78
+ continue;
79
+ }
80
+ map[name] = {
81
+ file: files[j],
82
+ name: name,
83
+ level: i
84
+ };
85
+ }
86
+ }
87
+ var list = [];
88
+ for (var key in map) {
89
+ list.push(map[key]);
90
+ }
91
+ list.sort(function (a, b) {
92
+ // level 越大优先级越低,因此排在越前面
93
+ if (a.level !== b.level) {
94
+ return b.level - a.level;
95
+ }
96
+ return a.name < b.name ? -1 : (a.name > b.name ? 1 : 0);
97
+ });
98
+ return list.map(function (item) {
99
+ return item.file;
100
+ });
101
+ };
102
+
103
+ /**
104
+ * 获取单个资源文件的路径
105
+ * 名称精确匹配,同名文件由高优先级目录胜出,因此结果至多一个
106
+ * @param {string} name - 资源文件名,如 main.css
107
+ * @param {string} theme - 主题名称,不传时取当前主题
108
+ * @param {string} style - 风格名称,不传时取当前风格
109
+ * @returns {string|null} 资源文件全路径,不存在时返回 null
110
+ */
111
+ Asset.prototype.getFile = function (name, theme, style) {
112
+ if (!name || typeof (name) !== 'string') {
113
+ return null;
114
+ }
115
+ var list = this.matchFiles(name, theme, style);
116
+ return list.length ? list[0] : null;
117
+ };
118
+
119
+ /**
120
+ * 获取全部资源文件路径列表
121
+ * @returns {Array<string>} 资源文件全路径列表
122
+ */
123
+ Asset.prototype.getFiles = function () {
124
+ return this.matchFiles('*' + this.extname);
125
+ };
126
+
127
+ module.exports = {
128
+ Asset
129
+ };
package/lib/css.js ADDED
@@ -0,0 +1,34 @@
1
+ const {
2
+ Asset
3
+ } = require('./asset.js');
4
+
5
+ /**
6
+ * 样式文件加载器
7
+ * 样式放主题下的 style/{风格}/ 目录,与模板不同目录:样式按风格分层,模板不按风格分层
8
+ * 按目录优先级引用(只回退主题,不回退风格),同名文件只保留优先级最高的一份
9
+ * @class
10
+ */
11
+ class Css extends Asset {
12
+ /**
13
+ * 创建样式文件加载器
14
+ * @param {Object} tpl - 所属模板实例
15
+ */
16
+ constructor(tpl) {
17
+ super(tpl, '.css');
18
+ }
19
+ }
20
+
21
+ /**
22
+ * 获取样式目录列表
23
+ * 由引擎的 getStyleDirs 给出,含风格与基础风格两级;返回顺序为优先级从高到低
24
+ * @param {string} theme - 主题名称,不传时取当前主题
25
+ * @param {string} style - 风格名称,不传时取当前风格
26
+ * @returns {Array<string>} 样式目录列表
27
+ */
28
+ Css.prototype.getDirs = function (theme, style) {
29
+ return this.tpl.getStyleDirs(theme, style);
30
+ };
31
+
32
+ module.exports = {
33
+ Css
34
+ };