mm_tpl 2.7.2 → 2.7.4
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 +39 -30
- package/README_EN.md +38 -29
- package/index.js +20 -38
- package/lib/asset.js +8 -4
- package/lib/base.js +84 -0
- package/lib/base_lang.js +200 -0
- package/lib/file.js +258 -0
- package/lib/hook.js +15 -8
- package/lib/lang.js +20 -16
- package/lib/memory.js +1 -3
- package/lib/sqlite.js +1 -3
- package/lib/template.js +14 -10
- package/lib/{engine.js → tpl.js} +121 -98
- package/lib/tpl_rule.js +1 -1
- package/lib/util.js +117 -0
- package/package.json +65 -62
package/README.md
CHANGED
|
@@ -93,7 +93,7 @@ const tpl = new Tpl({
|
|
|
93
93
|
plural_rules: {
|
|
94
94
|
'en_us': function (count) { return count === 1 ? 0 : 1; }
|
|
95
95
|
},
|
|
96
|
-
//
|
|
96
|
+
// 复数规则,键为语言代码;内置语言核心已带 zh_cn、en 规则,其他语言代码(如 en_us)在此登记,未登记时会回退到内置 en 规则
|
|
97
97
|
rules: [nativeRule, artRule, htmlRule, jsRule, pyRule, pyRule2] // 模板规则
|
|
98
98
|
});
|
|
99
99
|
|
|
@@ -176,8 +176,8 @@ const eachResult = tpl.render(`
|
|
|
176
176
|
### 5. 视图渲染
|
|
177
177
|
|
|
178
178
|
```javascript
|
|
179
|
-
//
|
|
180
|
-
|
|
179
|
+
// 设置全局数据
|
|
180
|
+
Tpl.global_bag.siteName = "我的网站";
|
|
181
181
|
|
|
182
182
|
// 设置视图变量
|
|
183
183
|
tpl.set({
|
|
@@ -363,7 +363,7 @@ const tpl = new Tpl({
|
|
|
363
363
|
|
|
364
364
|
#### 添加函数钩子
|
|
365
365
|
```javascript
|
|
366
|
-
|
|
366
|
+
Tpl.global_hook.addFunc('list', async function(viewBag, req, type) {
|
|
367
367
|
// 处理列表逻辑
|
|
368
368
|
var query = req.query;
|
|
369
369
|
if (query.user_id) {
|
|
@@ -375,14 +375,14 @@ $.hook.addFunc('list', async function(viewBag, req, type) {
|
|
|
375
375
|
|
|
376
376
|
#### 添加动作钩子
|
|
377
377
|
```javascript
|
|
378
|
-
|
|
378
|
+
Tpl.global_hook.addAction('header', function(viewBag, param) {
|
|
379
379
|
return "<header>网站头部</header>";
|
|
380
380
|
}, 1, "headerAction");
|
|
381
381
|
```
|
|
382
382
|
|
|
383
383
|
#### 添加过滤器钩子
|
|
384
384
|
```javascript
|
|
385
|
-
|
|
385
|
+
Tpl.global_hook.addFilter('content', function(viewBag, content, param) {
|
|
386
386
|
return content.replace("旧文本", "新文本");
|
|
387
387
|
}, 1, "contentFilter");
|
|
388
388
|
```
|
|
@@ -407,12 +407,12 @@ const html = tpl.render(`
|
|
|
407
407
|
#### 删除钩子
|
|
408
408
|
```javascript
|
|
409
409
|
// 删除指定钩子
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
410
|
+
Tpl.global_hook.delFunc('list', 'listFunc');
|
|
411
|
+
Tpl.global_hook.delAction('header', 'headerAction');
|
|
412
|
+
Tpl.global_hook.delFilter('content', 'contentFilter');
|
|
413
413
|
|
|
414
414
|
// 清空所有钩子
|
|
415
|
-
|
|
415
|
+
Tpl.global_hook.clear();
|
|
416
416
|
```
|
|
417
417
|
|
|
418
418
|
### 8. 多用户风格与语言
|
|
@@ -519,7 +519,7 @@ tpl.tc('items', 5); // 英文为 "1 item" / "5 items"
|
|
|
519
519
|
{ "items": ["{count} item", "{count} items"] }
|
|
520
520
|
```
|
|
521
521
|
|
|
522
|
-
复数形式由语言的复数规则决定。语言代码统一用小写下划线(如 `zh_cn`、`en_us`),与语言包文件名一致,也便于浏览器端引用。`zh_cn`、`en`
|
|
522
|
+
复数形式由语言的复数规则决定。语言代码统一用小写下划线(如 `zh_cn`、`en_us`),与语言包文件名一致,也便于浏览器端引用。`zh_cn`、`en` 是内置语言核心带的规则,可直接命中;其他语言代码(如 `en_us`)建议在配置项 `plural_rules` 中显式登记,`tc()` 才能按该语言取到正确的复数形式;未登记时会回退到内置的 `en` 规则。
|
|
523
523
|
|
|
524
524
|
#### 语言包文件
|
|
525
525
|
|
|
@@ -738,13 +738,19 @@ const restored = tpl.escape(escaped, true);
|
|
|
738
738
|
// 输出: <div>${content}</div>
|
|
739
739
|
```
|
|
740
740
|
|
|
741
|
-
### 5.
|
|
741
|
+
### 5. 全局静态属性
|
|
742
|
+
|
|
743
|
+
模块不挂载任何全局变量,跨实例共享的状态统一放在主类的静态属性上:
|
|
744
|
+
|
|
742
745
|
```javascript
|
|
743
|
-
//
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
746
|
+
// 全局钩子单例,往里加的钩子对所有实例生效
|
|
747
|
+
Tpl.global_hook.addAction('header', function (m) { return '<header>头部</header>'; });
|
|
748
|
+
|
|
749
|
+
// 全局数据袋,渲染时注入模型的 globalBag 字段
|
|
750
|
+
Tpl.global_bag.siteName = '我的网站';
|
|
751
|
+
|
|
752
|
+
// 全局语言包字典,lang_dir 与 langs 都为空时实例按深拷贝副本兜底复用
|
|
753
|
+
Tpl.global_dict = { zh_cn: { name: '超级美眉' }, en: { name: 'MM' } };
|
|
748
754
|
```
|
|
749
755
|
|
|
750
756
|
### 6. 多用户风格与语言的使用方式
|
|
@@ -918,7 +924,7 @@ tpl3.getStore(); // 取到该存储对象
|
|
|
918
924
|
|
|
919
925
|
```javascript
|
|
920
926
|
// 错误:钩子把请求数据写进 viewBag,同一实例的后续请求会读到
|
|
921
|
-
|
|
927
|
+
Tpl.global_hook.addFunc('login', async function (m, req) {
|
|
922
928
|
m.user_id = req.user_id; // m 就是 this.viewBag
|
|
923
929
|
});
|
|
924
930
|
|
|
@@ -985,7 +991,7 @@ tpl.render('${$md(\'**加粗**\')}'); // <b>加粗</b>
|
|
|
985
991
|
|
|
986
992
|
#### 实例级钩子
|
|
987
993
|
|
|
988
|
-
|
|
994
|
+
`Tpl.global_hook` 是**全局单例**,往里加的钩子对所有实例生效;`tpl.hook` 是**实例级钩子**,只影响当前实例及其派生实例。渲染时两级都会执行,**全局先执行、实例级后执行**:动作钩子的输出全局在前,过滤器钩子的返回值由全局传给实例级继续处理。
|
|
989
995
|
|
|
990
996
|
```javascript
|
|
991
997
|
tpl.hook.addAction('header', function (m) { return '<header>实例级</header>'; });
|
|
@@ -1058,13 +1064,13 @@ tpl.hook.addAction('header', function (m) { return '<header>实例级</header>';
|
|
|
1058
1064
|
- `lang` - 当前语言实例(`Lang`),语言包字典在实例间共享,当前语言各自独立
|
|
1059
1065
|
- `store` - 用户偏好存储(`Memory` 或 `Sqlite`),通过 `user_id` 映射用户的风格与语言,派生实例共享同一份存储
|
|
1060
1066
|
- `funcs` - 实例级模板函数表,由 `setFunc` 写入
|
|
1061
|
-
- `hook` - 实例级钩子(`Hook`),只影响当前实例及其派生实例;与全局
|
|
1067
|
+
- `hook` - 实例级钩子(`Hook`),只影响当前实例及其派生实例;与全局 `Tpl.global_hook` 一起执行,全局先执行
|
|
1062
1068
|
- `dir` - 应用根目录,即 `config.root`
|
|
1063
1069
|
- `error` - 错误提示
|
|
1064
1070
|
|
|
1065
1071
|
### 配置项说明
|
|
1066
1072
|
|
|
1067
|
-
- `root` -
|
|
1073
|
+
- `root` - 应用根目录,默认当前工作目录,主题、语言包、自定义覆盖目录均相对它定位
|
|
1068
1074
|
- `theme_dir` - 主题目录,相对应用根目录,默认 `'./static/template/'`
|
|
1069
1075
|
- `style_dir` - 风格目录,相对主题目录,默认 `'./style/'`
|
|
1070
1076
|
- `component_dir` - 组件目录,相对主题目录(`static/diy/` 下取同名子目录),默认 `'./component/'`,存放可被功能页面复用的公共片段;为空则不启用组件,模板内的 `$component` 取不到文件
|
|
@@ -1079,8 +1085,8 @@ tpl.hook.addAction('header', function (m) { return '<header>实例级</header>';
|
|
|
1079
1085
|
- `default_lang` - 默认语言代码,默认 `'zh_cn'`
|
|
1080
1086
|
- `defaults` - 应用配置文件 `config.js` 的简写,形如 `{theme, style, lang}`,创建实例时展开为 `default_theme` / `default_style` / `default_lang`;显式传入的 `default_*` 优先,`defaults` 不会残留在 `config` 中,因此 `fork()` 不会重复展开
|
|
1081
1087
|
- `extname` - 模板与组件文件的扩展名,默认 `'.html'`,可自行设定为 `'.art'` 等其他后缀;`getFile`、`getComponentFile`、`view` 与 `component` 在入参省略扩展名时会自动补全,因此 `view('./article/list')` 与 `view('./article/list.html')` 等价
|
|
1082
|
-
- `plural_rules` - 复数规则,键为语言代码,默认登记 `'en_us'
|
|
1083
|
-
- `langs` - 内存语言包字典,形如 `{'zh_cn': {...}, 'en_us': {...}}`,优先级高于语言包文件;它与 `lang_dir` 都为空时复用全局
|
|
1088
|
+
- `plural_rules` - 复数规则,键为语言代码,默认登记 `'en_us'`;内置语言核心已带 `zh_cn`、`en` 规则,其他语言代码需在此登记,`tc()` 才能按该语言取到正确的复数形式
|
|
1089
|
+
- `langs` - 内存语言包字典,形如 `{'zh_cn': {...}, 'en_us': {...}}`,优先级高于语言包文件;它与 `lang_dir` 都为空时复用全局 `Tpl.global_dict`
|
|
1084
1090
|
- `user_store` - 用户偏好存储,默认 `null`(用进程内存的 `Memory`);传入实现 `getUser` / `setUser` / `delUser` / `hasUser` / `listUsers` / `clear` 六个同步方法的对象即可替换为文件、数据库或缓存服务,也可直接用内置的 `Memory` / `Sqlite`,派生实例复用同一存储
|
|
1085
1091
|
- `cache_dims` - 缓存分层维度,默认 `['style', 'lang']`,可选 `theme`、`style`、`lang`;顺序即目录层级,只应包含**用户可选**的维度
|
|
1086
1092
|
- `cache_type` - 缓存方式,默认 `'file'`(物理文件),可选 `'memory'`(进程内存)
|
|
@@ -1099,11 +1105,11 @@ tpl.hook.addAction('header', function (m) { return '<header>实例级</header>';
|
|
|
1099
1105
|
- `delFilter(name, alias)` - 删除过滤器钩子
|
|
1100
1106
|
- `clear(name)` - 清空钩子
|
|
1101
1107
|
|
|
1102
|
-
以上方法在全局单例
|
|
1108
|
+
以上方法在全局单例 `Tpl.global_hook` 与实例级 `tpl.hook` 上用法一致。区别在于作用范围:`Tpl.global_hook` 影响所有实例,`tpl.hook` 只影响当前实例及其派生实例;渲染时两级都会执行,全局先执行、实例级后执行。
|
|
1103
1109
|
|
|
1104
1110
|
## 注意事项
|
|
1105
1111
|
|
|
1106
|
-
1. **依赖要求**:
|
|
1112
|
+
1. **依赖要求**: 运行时仅依赖 `art-template`,路径、语言、日志等能力已内置,无需其他外部模块与全局变量
|
|
1107
1113
|
2. **目录权限**: 确保模板目录具有读写权限
|
|
1108
1114
|
3. **缓存管理**: 缓存按 `cache_dims` 分层存放,清除时注意路径层级;`clearCache` 不传路径时只清除当前实例的缓存目录,清除全部需显式传 `'./'`。模板或语言包改动后缓存会自动失效,通常不需要手动清理
|
|
1109
1115
|
4. **钩子优先级**: 数值越小优先级越高,执行顺序越靠前
|
|
@@ -1111,14 +1117,14 @@ tpl.hook.addAction('header', function (m) { return '<header>实例级</header>';
|
|
|
1111
1117
|
6. **文件查找与输出顺序**: 模板按**主题**三段查找:`static/diy/` → 当前主题 → 默认主题,按功能路径在主题目录下拼接(如 `static/template/{主题}/article/list.html`),**不按风格分层**,同名覆盖取优先级最高的文件。组件走独立的**组件目录链**,与模板链同构、每级多一个固定的 `component/` 前缀:`static/diy/component/` → 当前主题 `component/` → 默认主题 `component/`,组件名不相对调用方文件解析,当前主题缺组件时回退默认主题。样式按「主题 + 风格」五段查找:`static/diy/` → 当前主题+当前风格 → 当前主题+基础风格 → 默认主题+当前风格 → 默认主题+基础风格;只回退主题,不回退风格,其他风格目录的文件不会被继承,公共样式放基础风格目录(`base_style`,默认 `base`)。脚本只按主题分层:`static/diy/` → 当前主题 → 默认主题 → 应用级 `static/js/`。样式与脚本同名只保留优先级最高的一份,属「引用多个」类资源,`getCssFiles` / `getJsFiles` 等按优先级**升序**返回(低优先级在前、高优先级在后),因为 CSS 后加载者生效、JS 顺序决定执行;语言包相反,按优先级降序合并
|
|
1112
1118
|
7. **字符转义**: 自动处理模板语法字符,避免冲突
|
|
1113
1119
|
8. **多用户隔离**: 主题是部署级配置,站点内所有用户一致,不参与用户选择;风格和语言是用户级配置。渲染可用**传参方式**(单实例,`view` 的 `options` 传 `style` / `lang`,渲染后自动恢复)或**派生实例**(`fork`),见[多用户风格与语言的使用方式](#6-多用户风格与语言的使用方式)。**`viewBag` 是实例级共享的**:用户级数据必须通过 `model` 传递,模板写 `${user_id}`,不可写入 `viewBag`(模板写 `${viewBag.user_id}`);只有为每个用户各派生一个独立实例时 `viewBag` 才是该用户私有的。不要把用户级数据 `set` 进共用实例的 `viewBag`,也不要让多个用户共用同一实例再切换 `current_style` / `lang`,否则会互相污染
|
|
1114
|
-
9. **语言包共享**: 配置项 `langs` 中已存在的语言对象在实例间共享,设计上只用于"选择";若在实例上通过 `lang.set()` 写入自定义词条,可能污染其他用户,确需写入请在 `fork` 后自行深拷贝。语言包文件每次读取都会生成新对象,不受此影响;`lang_dir` 与 `langs` 都为空时兜底复用的全局
|
|
1120
|
+
9. **语言包共享**: 配置项 `langs` 中已存在的语言对象在实例间共享,设计上只用于"选择";若在实例上通过 `lang.set()` 写入自定义词条,可能污染其他用户,确需写入请在 `fork` 后自行深拷贝。语言包文件每次读取都会生成新对象,不受此影响;`lang_dir` 与 `langs` 都为空时兜底复用的全局 `Tpl.global_dict` 也按深拷贝副本使用,实例上写入不会污染全局
|
|
1115
1121
|
10. **缓存数量**: 缓存文件数 ≤ (命名风格数 + 1) × 语言数 × 被缓存的模板数,**与用户数无关**;未选风格单独算一种组合但不占目录层级(`default_style` 也为空时才会出现),未被请求的组合不产生文件。`cache_dims` 只应包含**用户可选**的维度(默认风格和语言),多一个维度就是成倍增长;可选值为 `theme`、`style`、`lang`,`user_id` 已不再支持,传入会告警并忽略该维度
|
|
1116
1122
|
11. **按用户缓存的正确性风险**: 缓存内容依赖用户数据时,用户数据在缓存有效期内变化会读到过期内容(如充值后仍显示旧余额)。这种页面本就不适合整页缓存,应改为前端异步加载;确实要分用户存放时,显式指定 `cache_filename` 区分,或用内存缓存 + 较小 `cache_max`
|
|
1117
1123
|
12. **主题不参与缓存分层**: 主题为部署级配置,默认不在 `cache_dims` 中。运行时切换主题会复用旧主题的缓存,`setTheme` 会输出告警;切换后需 `clearCache()`,或把 `theme` 纳入维度
|
|
1118
1124
|
13. **主题与风格校验**: `setTheme` / `setStyle` 默认校验目录存在性,不存在时保持原值并告警;目录尚未创建时可用 `force` 参数跳过校验。风格为空字符串表示不设置风格,查找与缓存按默认风格回退
|
|
1119
1125
|
14. **风格与主题回退(仅样式)**: `setStyle` 只校验该风格目录是否存在于当前主题或默认主题下,目录缺失时保持原值并告警;直接给 `current_style` 赋值则不校验。样式查找时**只回退主题**(当前主题+当前风格 → 当前主题+基础风格 → 默认主题+当前风格 → 默认主题+基础风格),**不回退风格**,其他风格目录的文件不会被继承。公共样式放入基础风格目录(`base_style`,默认 `base`),各风格目录只放差异文件;若某风格需要不同于其他风格的公共样式,可在自己的风格目录内放同名文件覆盖基础风格。模板不按风格分层,换风格不改变命中的模板
|
|
1120
1126
|
15. **模板取词**: 模板文件需通过 `$t('key')` 才能输出多语言内容,硬编码文案不会随语言切换;文案应放在语言包目录下的 JSON 语言包中(文件名即语言代码,如 `zh_cn.json`、`en_us.json`),模板文件只负责取词
|
|
1121
|
-
16. **钩子作用域**:
|
|
1127
|
+
16. **钩子作用域**: `Tpl.global_hook` 是全局单例,钩子对所有实例生效;`tpl.hook` 只作用于当前实例及其派生实例,渲染时两级都执行(全局先执行、实例级后执行)。模板函数优先级为「本次 `model` 同名字段 > 内置函数 > 已注册的函数」
|
|
1122
1128
|
16. **语言缓存开销**: 未从 `cache_dims` 去掉 `lang` 时,同一份模板会按语言各存一份缓存。若模板本身不做多语言,移除 `lang` 维度可减少缓存文件数量
|
|
1123
1129
|
17. **语言文件变更**: 语言包文件仅在创建实例时读取,已创建的实例不会自动刷新;新建实例或 `fork` 会按文件指纹自动判断是否需要重新读取。需让已创建的实例生效时调用 `reloadLangs()` 或 `loadLang(lang)`。`setTheme` 会自动重新加载,使主题级语言包生效
|
|
1124
1130
|
18. **语言包缓存**: 语言包按「目录组 + 文件指纹」缓存在静态属性 `Tpl.lang_cache` 中,文件未变化时 `fork` 不会重复解析;缓存仅在内存中,进程重启后重新读取。指纹依赖文件修改时间,同一时间粒度内的改动可能识别不到,此时可用 `reloadLangs(true)` 强制刷新
|
|
@@ -1135,7 +1141,7 @@ tpl.hook.addAction('header', function (m) { return '<header>实例级</header>';
|
|
|
1135
1141
|
|
|
1136
1142
|
模块文件布局:
|
|
1137
1143
|
|
|
1138
|
-
- `index.js` - 模块入口,定义对外主类 `Tpl
|
|
1144
|
+
- `index.js` - 模块入口,定义对外主类 `Tpl`,并导出 `Engine` / `Hook` 与存储类 `Memory` / `Sqlite`,不挂载任何全局变量
|
|
1139
1145
|
- `lib/engine.js` - `Engine`,单个引擎实例:渲染、缓存、主题/风格/语言编排、派生实例(`fork`),以及 `getThemeDir` / `getStyleDir` / `getTemplateDirs` / `getComponentDirs` / `getComponentFile` / `getStyleDirs` / `getJsDirs` / `getCssFiles` / `getJsFiles`
|
|
1140
1146
|
- `lib/template.js` - `Template`,模板文件查找:按功能路径在主题目录下查找(`static/diy/` → 当前主题 → 默认主题),同时负责组件的目录链查找(`getComponent`)
|
|
1141
1147
|
- `lib/asset.js` - `Asset`,资源加载基类:按目录优先级收集、同名去重、按优先级升序输出
|
|
@@ -1144,8 +1150,11 @@ tpl.hook.addAction('header', function (m) { return '<header>实例级</header>';
|
|
|
1144
1150
|
- `lib/lang.js` - `Lang`,语言包合并与缓存
|
|
1145
1151
|
- `lib/store.js` - `Memory`,内存存储:`user_id` → `{style, lang}`
|
|
1146
1152
|
- `lib/sqlite.js` - `Sqlite`,SQLite 存储:与 `Memory` 同契约,数据持久化到文件(依赖 `node:sqlite`)
|
|
1147
|
-
- `lib/util.js` - `TplUtil
|
|
1148
|
-
- `
|
|
1153
|
+
- `lib/util.js` - `TplUtil`,纯辅助静态方法(深合并、深拷贝、md5、取中间文本等)
|
|
1154
|
+
- `lib/base.js` - `Base`,最小基类:静态配置与实例配置合并、日志输出
|
|
1155
|
+
- `lib/file.js` - `FileUtil`,内置路径与文件工具:全路径解析、目录/文件校验、文件读取
|
|
1156
|
+
- `lib/base_lang.js` - `BaseLang`,内置语言核心:取词、复数规则、日期/数字格式化
|
|
1157
|
+
- `lib/hook.js`、`lib/tpl_rule.js` - 钩子与模板语法规则
|
|
1149
1158
|
|
|
1150
1159
|
```bash
|
|
1151
1160
|
# 运行测试
|
package/README_EN.md
CHANGED
|
@@ -174,8 +174,8 @@ const eachResult = tpl.render(`
|
|
|
174
174
|
### 5. View Rendering
|
|
175
175
|
|
|
176
176
|
```javascript
|
|
177
|
-
// Set global
|
|
178
|
-
|
|
177
|
+
// Set global data
|
|
178
|
+
Tpl.global_bag.siteName = "My Website";
|
|
179
179
|
|
|
180
180
|
// Set view variables
|
|
181
181
|
tpl.set({
|
|
@@ -361,7 +361,7 @@ In-memory entries live in `Tpl.cache_store` keyed by the full cache file path an
|
|
|
361
361
|
|
|
362
362
|
#### Add Function Hook
|
|
363
363
|
```javascript
|
|
364
|
-
|
|
364
|
+
Tpl.global_hook.addFunc('list', async function(viewBag, req, type) {
|
|
365
365
|
// Process list logic
|
|
366
366
|
var query = req.query;
|
|
367
367
|
if (query.user_id) {
|
|
@@ -373,14 +373,14 @@ $.hook.addFunc('list', async function(viewBag, req, type) {
|
|
|
373
373
|
|
|
374
374
|
#### Add Action Hook
|
|
375
375
|
```javascript
|
|
376
|
-
|
|
376
|
+
Tpl.global_hook.addAction('header', function(viewBag, param) {
|
|
377
377
|
return "<header>Website Header</header>";
|
|
378
378
|
}, 1, "headerAction");
|
|
379
379
|
```
|
|
380
380
|
|
|
381
381
|
#### Add Filter Hook
|
|
382
382
|
```javascript
|
|
383
|
-
|
|
383
|
+
Tpl.global_hook.addFilter('content', function(viewBag, content, param) {
|
|
384
384
|
return content.replace("old text", "new text");
|
|
385
385
|
}, 1, "contentFilter");
|
|
386
386
|
```
|
|
@@ -405,12 +405,12 @@ const html = tpl.render(`
|
|
|
405
405
|
#### Delete Hooks
|
|
406
406
|
```javascript
|
|
407
407
|
// Delete specific hook
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
408
|
+
Tpl.global_hook.delFunc('list', 'listFunc');
|
|
409
|
+
Tpl.global_hook.delAction('header', 'headerAction');
|
|
410
|
+
Tpl.global_hook.delFilter('content', 'contentFilter');
|
|
411
411
|
|
|
412
412
|
// Clear all hooks
|
|
413
|
-
|
|
413
|
+
Tpl.global_hook.clear();
|
|
414
414
|
```
|
|
415
415
|
|
|
416
416
|
### 8. Multi-user Styles and Languages
|
|
@@ -514,7 +514,7 @@ Plural entries are arrays in the language pack, `{count}` is the count placehold
|
|
|
514
514
|
{ "items": ["{count} item", "{count} items"] }
|
|
515
515
|
```
|
|
516
516
|
|
|
517
|
-
The plural form is decided by the plural rule of the language, registered by language code in the `plural_rules` option. Language codes are lowercase with underscores; the
|
|
517
|
+
The plural form is decided by the plural rule of the language, registered by language code in the `plural_rules` option. Language codes are lowercase with underscores; the built-in language core already ships `zh_cn` and `en` rules, so those two work directly, while other codes such as `en_us` should be registered explicitly in `plural_rules`. When a code is not registered, `tc()` falls back to the built-in `en` rule. Codes match language-pack filenames and are easier to reference from the browser.
|
|
518
518
|
|
|
519
519
|
#### Language Pack Files
|
|
520
520
|
|
|
@@ -744,13 +744,19 @@ const restored = tpl.escape(escaped, true);
|
|
|
744
744
|
// Output: <div>${content}</div>
|
|
745
745
|
```
|
|
746
746
|
|
|
747
|
-
### 5. Global
|
|
747
|
+
### 5. Global Static Properties
|
|
748
|
+
|
|
749
|
+
The module mounts no global variables; state shared across instances lives on static properties of the main class:
|
|
750
|
+
|
|
748
751
|
```javascript
|
|
749
|
-
//
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
752
|
+
// Global hook singleton; hooks added here affect every instance
|
|
753
|
+
Tpl.global_hook.addAction('header', function (m) { return '<header>Header</header>'; });
|
|
754
|
+
|
|
755
|
+
// Global data bag, injected into the model's globalBag field at render time
|
|
756
|
+
Tpl.global_bag.siteName = 'My Website';
|
|
757
|
+
|
|
758
|
+
// Global language dictionary, used as a deep-copied fallback when both lang_dir and langs are empty
|
|
759
|
+
Tpl.global_dict = { zh_cn: { name: 'MM Girl' }, en: { name: 'MM' } };
|
|
754
760
|
```
|
|
755
761
|
|
|
756
762
|
### 6. Using Styles and Languages Across Users
|
|
@@ -928,7 +934,7 @@ Data written into `viewBag` is left behind for later requests using the same ins
|
|
|
928
934
|
|
|
929
935
|
```javascript
|
|
930
936
|
// Wrong: the hook writes request data into viewBag, and later requests on the same instance read it
|
|
931
|
-
|
|
937
|
+
Tpl.global_hook.addFunc('login', async function (m, req) {
|
|
932
938
|
m.user_id = req.user_id; // m is this.viewBag
|
|
933
939
|
});
|
|
934
940
|
|
|
@@ -995,7 +1001,7 @@ Precedence: **a field of the same name in this call's `model` > built-in functio
|
|
|
995
1001
|
|
|
996
1002
|
#### Instance-level Hooks
|
|
997
1003
|
|
|
998
|
-
|
|
1004
|
+
`Tpl.global_hook` is a **global singleton**, and hooks added to it affect every instance; `tpl.hook` is an **instance-level hook** affecting only the current instance and the instances derived from it. Both levels run during rendering, **global first, instance-level second**: for action hooks the global output comes first, and for filter hooks the global return value is passed on to the instance level.
|
|
999
1005
|
|
|
1000
1006
|
```javascript
|
|
1001
1007
|
tpl.hook.addAction('header', function (m) { return '<header>instance-level</header>'; });
|
|
@@ -1068,13 +1074,13 @@ tpl.hook.addAction('header', function (m) { return '<header>instance-level</head
|
|
|
1068
1074
|
- `lang` - Current language instance (`Lang`); the language pack dictionary is shared, while the current language is independent
|
|
1069
1075
|
- `store` - User preference store (`Memory` or `Sqlite`) mapping a `user_id` to the user's style and language; derived instances share the same store
|
|
1070
1076
|
- `funcs` - Instance-level template function table, written by `setFunc`
|
|
1071
|
-
- `hook` - Instance-level hooks (`Hook`) affecting only the current instance and the instances derived from it; they run together with the global
|
|
1077
|
+
- `hook` - Instance-level hooks (`Hook`) affecting only the current instance and the instances derived from it; they run together with the global `Tpl.global_hook`, global first
|
|
1072
1078
|
- `dir` - Application root directory, i.e. `config.root`
|
|
1073
1079
|
- `error` - Error message
|
|
1074
1080
|
|
|
1075
1081
|
### Configuration Options
|
|
1076
1082
|
|
|
1077
|
-
- `root` - Application root directory, defaults to
|
|
1083
|
+
- `root` - Application root directory, defaults to the current working directory; themes, language packs and the override directory are resolved relative to it
|
|
1078
1084
|
- `theme_dir` - Theme directory, relative to the application root, defaults to `'./static/template/'`
|
|
1079
1085
|
- `style_dir` - Style directory, relative to the theme directory, defaults to `'./style/'`
|
|
1080
1086
|
- `component_dir` - Component directory, relative to the theme directory (the sub-directory of the same name under `static/diy/`), defaults to `'./component/'`, holding shared partials reusable by feature pages; empty disables components, so `$component` in templates finds no file
|
|
@@ -1089,8 +1095,8 @@ tpl.hook.addAction('header', function (m) { return '<header>instance-level</head
|
|
|
1089
1095
|
- `default_lang` - Default language code, defaults to `'zh_cn'`
|
|
1090
1096
|
- `defaults` - Shorthand for the `config.js` file, shaped as `{theme, style, lang}`, expanded into `default_theme` / `default_style` / `default_lang` when the instance is created; an explicit `default_*` wins, and `defaults` is not kept in `config`, so `fork()` never expands it twice
|
|
1091
1097
|
- `extname` - Extension of template and component files, defaults to `'.html'` and is configurable (e.g. `'.art'`); `getFile`, `getComponentFile`, `view` and `component` append it automatically when the argument omits the extension, so `view('./article/list')` and `view('./article/list.html')` are equivalent
|
|
1092
|
-
- `plural_rules` - Plural rules keyed by language code, registering `'en_us'` by default; the
|
|
1093
|
-
- `langs` - In-memory language pack dictionary, e.g. `{'zh_cn': {...}, 'en_us': {...}}`, takes precedence over language files; when both it and `lang_dir` are empty, the global
|
|
1098
|
+
- `plural_rules` - Plural rules keyed by language code, registering `'en_us'` by default; the built-in language core already ships `zh_cn` and `en` rules, so other language codes are registered here
|
|
1099
|
+
- `langs` - In-memory language pack dictionary, e.g. `{'zh_cn': {...}, 'en_us': {...}}`, takes precedence over language files; when both it and `lang_dir` are empty, the global `Tpl.global_dict` is used
|
|
1094
1100
|
- `user_store` - User preference store, `null` by default (an in-process `Memory`); pass an object implementing the six synchronous methods `getUser` / `setUser` / `delUser` / `hasUser` / `listUsers` / `clear` to replace it with a file, a database or a cache service, or use the built-in `Memory` / `Sqlite` directly; derived instances reuse the same store
|
|
1095
1101
|
- `cache_dims` - Cache layering dimensions, defaults to `['style', 'lang']`; values may be `theme`, `style`, `lang`. The order is the directory order, and it should only contain **user-selectable** dimensions
|
|
1096
1102
|
- `cache_type` - Cache type, defaults to `'file'` (physical files); `'memory'` keeps the cache in process memory
|
|
@@ -1109,11 +1115,11 @@ tpl.hook.addAction('header', function (m) { return '<header>instance-level</head
|
|
|
1109
1115
|
- `delFilter(name, alias)` - Delete filter hook
|
|
1110
1116
|
- `clear(name)` - Clear hooks
|
|
1111
1117
|
|
|
1112
|
-
The methods above behave identically on the global
|
|
1118
|
+
The methods above behave identically on the global `Tpl.global_hook` singleton and on the instance-level `tpl.hook`. They differ in scope: `Tpl.global_hook` affects every instance, while `tpl.hook` affects only the current instance and the instances derived from it; both levels run during rendering, global first and instance-level second.
|
|
1113
1119
|
|
|
1114
1120
|
## Notes
|
|
1115
1121
|
|
|
1116
|
-
1. **Dependencies**:
|
|
1122
|
+
1. **Dependencies**: Only `art-template` at runtime; path, language and logging capabilities are built in, with no other external modules or global variables required
|
|
1117
1123
|
2. **Directory Permissions**: Ensure template directories have read/write permissions
|
|
1118
1124
|
3. **Cache Management**: The cache is layered by `cache_dims`, mind the directory levels when clearing; `clearCache` without a path clears only the cache directory of the current instance, pass `'./'` to clear everything. Template or language pack changes invalidate the cache automatically, so manual clearing is usually unnecessary
|
|
1119
1125
|
4. **Hook Priority**: Lower numerical values indicate higher priority and earlier execution
|
|
@@ -1121,7 +1127,7 @@ The methods above behave identically on the global `$.hook` singleton and on the
|
|
|
1121
1127
|
6. **File Search and Output Order**: Each kind of resource has its own directory chain. Templates use **three** levels, `static/diy/` → current theme → default theme, and are **not layered by style**, so a style never changes which template is used; the path is relative to the theme directory and may include feature directories such as `article/list.html`. Components use **three** levels of their own, isomorphic to the template chain with one extra fixed `component/` prefix on every level: `static/diy/component/` → current theme `component/` → default theme `component/`; a component name is not resolved relative to the calling file, and a component missing from the current theme falls back to the default theme. Styles use **five** levels: `static/diy/` → current theme + current style → current theme + base style → default theme + current style → default theme + base style. Scripts use **four** levels: `static/diy/` → current theme → default theme → application-level `static/js/`. For templates the highest-priority same-named file wins, while styles and scripts keep only the highest-priority copy per filename. Styles and scripts are "reference many" resources, so `getCssFiles` / `getJsFiles` and the like return them in **ascending** priority order (lower priority first, higher priority after), because a later-loaded CSS rule is the one that takes effect and the order of JS decides execution; language packs are the opposite, merged in descending priority order
|
|
1122
1128
|
7. **Character Escaping**: Automatic handling of template syntax characters to avoid conflicts
|
|
1123
1129
|
8. **Multi-user Isolation**: The theme is a deployment-level setting, uniform for every user on the site and not user-selectable; style and language are user-level. Rendering can use **passing options** (a single instance, with `style` / `lang` in the `view` options, restored afterwards) or **deriving instances** (`fork`); see [Using Styles and Languages Across Users](#6-using-styles-and-languages-across-users). **`viewBag` is shared at instance level**: user-level data must be passed through `model` (templates read `${user_id}`) and never written into `viewBag` (templates read `${viewBag.user_id}`); only when one separate instance is derived per user is `viewBag` private to that user. Do not `set` user-level data into a shared instance's `viewBag`, and do not let multiple users share one instance and switch `current_style` / `lang` on it, otherwise they will pollute each other
|
|
1124
|
-
9. **Shared Language Packs**: Language objects already present in the `langs` option are shared across instances and are meant for "picking" only; writing custom entries via `lang.set()` on an instance may pollute other users — deep-copy `langs` after `fork` if you really need to write. Language pack files are re-read into new objects, so they are not affected; the global
|
|
1130
|
+
9. **Shared Language Packs**: Language objects already present in the `langs` option are shared across instances and are meant for "picking" only; writing custom entries via `lang.set()` on an instance may pollute other users — deep-copy `langs` after `fork` if you really need to write. Language pack files are re-read into new objects, so they are not affected; the global `Tpl.global_dict` used as a fallback when both `lang_dir` and `langs` are empty is also copied deeply, so writing on an instance never pollutes the global one
|
|
1125
1131
|
10. **Cache Count**: Cache files ≤ (named styles + 1) × languages × cached templates, **independent of the number of users**; "no style selected" counts as a distinct combination but occupies no directory level (it only appears when `default_style` is empty too), and combinations that are never requested produce no file. `cache_dims` should only contain **user-selectable** dimensions (style and language by default), since every extra dimension multiplies the count; the accepted values are `theme`, `style` and `lang`, and `user_id` is no longer supported — passing it logs a warning and the dimension is ignored
|
|
1126
1132
|
11. **Correctness Risk of Per-user Caching**: When cached content depends on user data, such content is served stale for as long as the cache lives (a user who tops up still sees the old balance). Such pages are not suitable for full-page caching; fetch the user-specific regions asynchronously. When per-user separation is genuinely required, pass an explicit `cache_filename`, or use the in-memory cache with a small `cache_max`
|
|
1127
1133
|
12. **Theme Not in Cache Layering**: The theme is a deployment-level setting and is excluded from `cache_dims` by default. Switching the theme at runtime reuses the previous theme's cache and `setTheme` logs a warning; call `clearCache()` after switching, or include `theme` in the dimensions
|
|
@@ -1137,7 +1143,7 @@ The methods above behave identically on the global `$.hook` singleton and on the
|
|
|
1137
1143
|
22. **In-memory and File Caches Are Separate**: The two modes read and write different locations, so switching `cache_type` leaves the old cache behind — call `clearCache()` as needed
|
|
1138
1144
|
23. **Temporary Options of Passing-Render**: `options.style` and `options.lang` on `view` / `render` / `getCacheFile` apply to that call only and are restored afterwards without changing the instance state; `view` renders synchronously, so it is safe under concurrent requests. Keep them distinct from `setStyle` / `setLang`, which change the instance state persistently
|
|
1139
1145
|
24. **User Preference Store**: `user_store` defaults to an in-process `Memory` and dies with the process; inject shared storage (the built-in `Sqlite`, or an object implementing the six synchronous methods `getUser` / `setUser` / `delUser` / `hasUser` / `listUsers` / `clear`) for multi-process or multi-machine deployments, otherwise each process sees only its own data. The store performs no authorisation, so the caller must ensure `user_id` is trustworthy
|
|
1140
|
-
25. **Hook Scope**:
|
|
1146
|
+
25. **Hook Scope**: `Tpl.global_hook` is a global singleton whose hooks affect every instance, while `tpl.hook` affects only the current instance and the instances derived from it; both levels run during rendering (global first, instance-level second). Template function precedence is "a same-named field in this call's `model` > built-in functions > registered functions"
|
|
1141
1147
|
26. **Object Literals Inside Expressions**: An object literal inside `${ }` is parsed in full, so `${@$component('bar', { id: 1 })}` and `${$t('welcome', { name: 'Tom' })}` can be written directly without assigning to a variable first. Only **non-nested** literals are supported: nested forms such as `{a:{b:1}}` are not recognised as an expression and are emitted as plain text (no error is thrown) — use the comment-style `<!--{@ }-->` delimiter or assign to a variable instead
|
|
1142
1148
|
27. **Component Model and Inheritance**: The `$component` model is merged with the caller's data (`Object.assign`); the caller's data and template helpers such as `$view` / `$css` / `$t` are all passed down to the component, and fields in the passed model override the caller's
|
|
1143
1149
|
|
|
@@ -1145,7 +1151,7 @@ The methods above behave identically on the global `$.hook` singleton and on the
|
|
|
1145
1151
|
|
|
1146
1152
|
Module layout:
|
|
1147
1153
|
|
|
1148
|
-
- `index.js` - Module entry, defines the public main class `Tpl` and exports the store classes `Memory` / `Sqlite
|
|
1154
|
+
- `index.js` - Module entry, defines the public main class `Tpl` and exports `Engine` / `Hook` plus the store classes `Memory` / `Sqlite`, mounting no global variables
|
|
1149
1155
|
- `lib/engine.js` - `Engine`, a single engine instance: rendering, caching, the theme/style/language orchestration and instance derivation (`fork`), plus `getThemeDir` / `getStyleDir` / `getTemplateDirs` / `getComponentDirs` / `getComponentFile` / `getStyleDirs` / `getJsDirs` / `getCssFiles` / `getJsFiles`
|
|
1150
1156
|
- `lib/template.js` - `Template`, template file lookup: resolves a template under the theme directory by feature path (`static/diy/` → current theme → default theme), and also resolves components along their own directory chain (`getComponent`)
|
|
1151
1157
|
- `lib/asset.js` - `Asset`, the asset loading base class for styles and scripts: collecting by directory priority, de-duplicating by filename and emitting in ascending priority order
|
|
@@ -1154,8 +1160,11 @@ Module layout:
|
|
|
1154
1160
|
- `lib/lang.js` - `Lang`, language pack merging and caching
|
|
1155
1161
|
- `lib/store.js` - `Memory`, the in-memory store: `user_id` → `{style, lang}`
|
|
1156
1162
|
- `lib/sqlite.js` - `Sqlite`, the SQLite store: same contract as `Memory`, persisting to a file (relies on `node:sqlite`)
|
|
1157
|
-
- `lib/util.js` - `TplUtil`, pure static helper methods
|
|
1158
|
-
- `
|
|
1163
|
+
- `lib/util.js` - `TplUtil`, pure static helper methods (deep merge, deep clone, md5, text between markers, etc.)
|
|
1164
|
+
- `lib/base.js` - `Base`, minimal base class: merges static and instance config, logs output
|
|
1165
|
+
- `lib/file.js` - `FileUtil`, built-in path and file utilities: full-path resolution, directory/file checks, file reading
|
|
1166
|
+
- `lib/base_lang.js` - `BaseLang`, built-in language core: word lookup, plural rules, date/number formatting
|
|
1167
|
+
- `lib/hook.js`, `lib/tpl_rule.js` - hooks and template syntax rules
|
|
1159
1168
|
|
|
1160
1169
|
```bash
|
|
1161
1170
|
# Run tests
|
package/index.js
CHANGED
|
@@ -1,46 +1,28 @@
|
|
|
1
|
-
const Hook = require('./lib/hook.js');
|
|
2
1
|
const {
|
|
3
|
-
|
|
4
|
-
} = require('./lib/
|
|
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
|
-
}
|
|
2
|
+
Tpl
|
|
3
|
+
} = require('./lib/tpl.js');
|
|
4
|
+
const Admin = require('mm_corejs').Admin;
|
|
28
5
|
|
|
29
6
|
/**
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* 渲染、主题/风格/语言编排与派生实例见 lib/engine.js,默认配置项由 Engine 提供
|
|
33
|
-
* @class
|
|
7
|
+
* 模板引擎实例(Admin 工厂)
|
|
8
|
+
* 支持 os.js 调用:require('mm_tpl').admin.setServer(os) -> admin.get('os', config)
|
|
34
9
|
*/
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
if (global.$ && !$.Tpl) {
|
|
38
|
-
$.Tpl = Tpl;
|
|
39
|
-
$.tpl = new Tpl();
|
|
40
|
-
}
|
|
10
|
+
let admin = new Admin();
|
|
11
|
+
admin.setClass(Tpl);
|
|
41
12
|
|
|
42
13
|
module.exports = {
|
|
43
14
|
Tpl,
|
|
44
|
-
|
|
45
|
-
|
|
15
|
+
Hook: require('./lib/hook.js'),
|
|
16
|
+
Memory: require('./lib/memory.js'),
|
|
17
|
+
Sqlite: require('./lib/sqlite.js'),
|
|
18
|
+
admin,
|
|
19
|
+
tplAdmin(scope, config) {
|
|
20
|
+
if (!scope) {
|
|
21
|
+
scope = 'os';
|
|
22
|
+
}
|
|
23
|
+
return admin.get(scope, config);
|
|
24
|
+
},
|
|
25
|
+
getTpl() {
|
|
26
|
+
return admin.get('os');
|
|
27
|
+
}
|
|
46
28
|
};
|
package/lib/asset.js
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
const {
|
|
2
|
+
FileUtil
|
|
3
|
+
} = require('./file.js');
|
|
4
|
+
|
|
1
5
|
/**
|
|
2
6
|
* 资源加载基类
|
|
3
7
|
* 样式与脚本同属「引用多个文件」类资源:按目录优先级收集,同名文件只保留优先级最高的一份
|
|
@@ -41,14 +45,14 @@ Asset.prototype.getDirs = function (theme, style) {
|
|
|
41
45
|
* @returns {Array<string>} 资源文件路径列表,同目录内按文件名排序
|
|
42
46
|
*/
|
|
43
47
|
Asset.prototype._matchDir = function (dir, keyword) {
|
|
44
|
-
if (!dir || !
|
|
48
|
+
if (!dir || !FileUtil.hasDir(dir)) {
|
|
45
49
|
return [];
|
|
46
50
|
}
|
|
47
51
|
// 排序保证同目录内的层叠与执行顺序稳定,不依赖文件系统的枚举顺序
|
|
48
|
-
var files =
|
|
52
|
+
var files = FileUtil.listFiles(dir, keyword).sort();
|
|
49
53
|
var list = [];
|
|
50
54
|
for (var i = 0; i < files.length; i++) {
|
|
51
|
-
if (files[i]
|
|
55
|
+
if (FileUtil.extname(files[i]) === this.extname) {
|
|
52
56
|
list.push(files[i]);
|
|
53
57
|
}
|
|
54
58
|
}
|
|
@@ -72,7 +76,7 @@ Asset.prototype.matchFiles = function (pattern, theme, style) {
|
|
|
72
76
|
for (var i = 0; i < dirs.length; i++) {
|
|
73
77
|
var files = this._matchDir(dirs[i], keyword);
|
|
74
78
|
for (var j = 0; j < files.length; j++) {
|
|
75
|
-
var name = files[j]
|
|
79
|
+
var name = FileUtil.basename(files[j]);
|
|
76
80
|
// 高优先级目录已收录同名文件时跳过,实现同名覆盖
|
|
77
81
|
if (map[name]) {
|
|
78
82
|
continue;
|
package/lib/base.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 基础类
|
|
3
|
+
* 提供配置合并与日志输出的最小能力,不依赖任何外部模块
|
|
4
|
+
* @class
|
|
5
|
+
*/
|
|
6
|
+
class Base {
|
|
7
|
+
/**
|
|
8
|
+
* 配置参数
|
|
9
|
+
* @type {Object}
|
|
10
|
+
*/
|
|
11
|
+
static config = {};
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 创建实例
|
|
15
|
+
* 实例配置为类静态配置的浅拷贝,再合并传入的配置
|
|
16
|
+
* @param {Object} config - 配置参数
|
|
17
|
+
*/
|
|
18
|
+
constructor(config) {
|
|
19
|
+
/**
|
|
20
|
+
* 配置参数
|
|
21
|
+
* @type {Object}
|
|
22
|
+
*/
|
|
23
|
+
this.config = { ...this.constructor.config };
|
|
24
|
+
this.setConfig(config);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 设置配置参数
|
|
30
|
+
* @param {Object} config - 配置参数
|
|
31
|
+
* @returns {Object} 当前实例的配置
|
|
32
|
+
*/
|
|
33
|
+
Base.prototype.setConfig = function (config) {
|
|
34
|
+
if (config) {
|
|
35
|
+
if (typeof (config) !== 'object') {
|
|
36
|
+
throw new TypeError('配置参数必须是对象');
|
|
37
|
+
}
|
|
38
|
+
this._setConfig(config);
|
|
39
|
+
}
|
|
40
|
+
this._preset();
|
|
41
|
+
return this.config;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* 获取当前配置
|
|
46
|
+
* @returns {Object} 配置对象
|
|
47
|
+
*/
|
|
48
|
+
Base.prototype.getConfig = function () {
|
|
49
|
+
return this.config;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* 设置配置参数 - 内部方法,子类可覆盖以实现自定义合并逻辑
|
|
54
|
+
* @param {Object} config - 配置参数
|
|
55
|
+
* @private
|
|
56
|
+
*/
|
|
57
|
+
Base.prototype._setConfig = function (config) {
|
|
58
|
+
Object.assign(this.config, config);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* 预设值 - 内部方法,子类可覆盖,在配置设置完成后执行
|
|
63
|
+
* @private
|
|
64
|
+
*/
|
|
65
|
+
Base.prototype._preset = function () { };
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* 输出日志
|
|
69
|
+
* 使用配置项 logger 指定的日志对象,未配置时回退到 console
|
|
70
|
+
* @param {string} level - 日志级别,如 warn、error
|
|
71
|
+
* @param {string} message - 日志消息
|
|
72
|
+
* @param {...any} args - 附加参数
|
|
73
|
+
*/
|
|
74
|
+
Base.prototype.log = function (level, message, ...args) {
|
|
75
|
+
var logger = this.config.logger || console;
|
|
76
|
+
var fn = logger[level] || logger.log;
|
|
77
|
+
if (typeof (fn) === 'function') {
|
|
78
|
+
fn.call(logger, message, ...args);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
module.exports = {
|
|
83
|
+
Base
|
|
84
|
+
};
|