mm_statics 1.5.3 → 1.5.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/eslint.config.js +0 -3
- package/index.js +150 -71
- package/package.json +2 -2
package/eslint.config.js
CHANGED
|
@@ -32,11 +32,8 @@ module.exports = [
|
|
|
32
32
|
'naming-convention/method-name': 'error',
|
|
33
33
|
'naming-convention/variable-name': 'warn',
|
|
34
34
|
'naming-convention/constant-name': 'error',
|
|
35
|
-
'naming-convention/private-method-naming': 'warn',
|
|
36
|
-
'naming-convention/private-variable-naming': 'warn',
|
|
37
35
|
'naming-convention/param-name': 'error', // 启用入参名规则
|
|
38
36
|
'naming-convention/property-name': 'warn',
|
|
39
|
-
'naming-convention/instance-property': 'warn',
|
|
40
37
|
|
|
41
38
|
// 禁用与命名规范插件冲突的默认规则
|
|
42
39
|
'camelcase': 'off',
|
package/index.js
CHANGED
|
@@ -2,6 +2,14 @@ let send = require('koa-send');
|
|
|
2
2
|
const { EsToAmdConvert } = require('mm_es6_to_amd');
|
|
3
3
|
const { parse } = require('@vue/compiler-sfc');
|
|
4
4
|
|
|
5
|
+
// 初始化mm_expand模块
|
|
6
|
+
require('mm_expand');
|
|
7
|
+
|
|
8
|
+
// 确保$对象已初始化
|
|
9
|
+
if (!global.$) {
|
|
10
|
+
throw new Error('mm_expand模块初始化失败,$对象不存在');
|
|
11
|
+
}
|
|
12
|
+
|
|
5
13
|
/**
|
|
6
14
|
* 静态文件处理类
|
|
7
15
|
*/
|
|
@@ -16,7 +24,9 @@ class Static {
|
|
|
16
24
|
cache: true,
|
|
17
25
|
// 后端缓存时间,单位秒
|
|
18
26
|
cache_age: 7200,
|
|
27
|
+
// 是否开启immutable缓存头
|
|
19
28
|
immutable: true,
|
|
29
|
+
//
|
|
20
30
|
hidden: false,
|
|
21
31
|
format: true,
|
|
22
32
|
extensions: false,
|
|
@@ -27,11 +37,14 @@ class Static {
|
|
|
27
37
|
compile_vue: true,
|
|
28
38
|
// 指定路径文件需要转换
|
|
29
39
|
path: '/src',
|
|
40
|
+
//
|
|
30
41
|
files: ['.js', '.vue'],
|
|
31
42
|
// 是否将files中的文件的js转换为amd格式
|
|
32
43
|
convert_amd: true,
|
|
33
44
|
// 是否启用文件修改监听(实时检测文件修改)
|
|
34
|
-
watch_files: false
|
|
45
|
+
watch_files: false,
|
|
46
|
+
// 是否检查文件修改时间(当watch_files为false时生效)
|
|
47
|
+
check_file_mtime: false
|
|
35
48
|
};
|
|
36
49
|
|
|
37
50
|
/**
|
|
@@ -41,8 +54,10 @@ class Static {
|
|
|
41
54
|
constructor(config) {
|
|
42
55
|
this.config = { ...Static.config };
|
|
43
56
|
this.convert = new EsToAmdConvert();
|
|
44
|
-
this.setConfig(config);
|
|
45
57
|
this._cache = null;
|
|
58
|
+
this._cache_config_prefix = this._generateConfigPrefix(); // 初始化配置前缀
|
|
59
|
+
this._cache_headers = this._generateCacheHeaders(); // 初始化缓存头
|
|
60
|
+
this.setConfig(config);
|
|
46
61
|
this._init();
|
|
47
62
|
}
|
|
48
63
|
}
|
|
@@ -94,9 +109,7 @@ Static.prototype._initFileWatcher = function () {
|
|
|
94
109
|
if (this.config.watch_files) {
|
|
95
110
|
try {
|
|
96
111
|
this._chokidar = require('chokidar');
|
|
97
|
-
console.log('[Static] 文件监听功能已启用(使用chokidar)');
|
|
98
112
|
} catch (error) {
|
|
99
|
-
console.error('[Static] 加载chokidar模块失败:', error.message);
|
|
100
113
|
this.config.watch_files = false;
|
|
101
114
|
}
|
|
102
115
|
}
|
|
@@ -130,7 +143,6 @@ Static.prototype._addFileWatcher = function (file_path, cache_key) {
|
|
|
130
143
|
|
|
131
144
|
// 监听文件修改事件
|
|
132
145
|
watcher.on('change', (path) => {
|
|
133
|
-
console.log(`[Static] 检测到文件修改: ${path}`);
|
|
134
146
|
// 文件修改时清除缓存
|
|
135
147
|
this._cache.del(cache_key).catch(error => {
|
|
136
148
|
console.error(`[Static] 清除缓存失败: ${error.message}`);
|
|
@@ -176,21 +188,14 @@ Static.prototype._getCache = async function (path, full_path) {
|
|
|
176
188
|
// 生成缓存键
|
|
177
189
|
let cache_key = this._generateCacheKey(path);
|
|
178
190
|
|
|
179
|
-
// 统一获取缓存数据
|
|
191
|
+
// 统一获取缓存数据 - 缓存模块会自动处理TTL过期
|
|
180
192
|
let data = await this._cache.get(cache_key);
|
|
181
193
|
|
|
182
194
|
if (!data) return null;
|
|
183
195
|
|
|
184
|
-
//
|
|
185
|
-
|
|
186
|
-
if (
|
|
187
|
-
// 缓存已过期,清除缓存
|
|
188
|
-
await this._cache.del(cache_key);
|
|
189
|
-
return null;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// 检查文件是否已修改(仅在未启用文件监听时检查)
|
|
193
|
-
if (!this.config.watch_files && full_path && data.file_mtime) {
|
|
196
|
+
// 仅在未启用文件监听且需要检查文件修改时进行检查
|
|
197
|
+
// 减少文件系统操作,提高性能
|
|
198
|
+
if (!this.config.watch_files && this.config.check_file_mtime && full_path && data.file_mtime) {
|
|
194
199
|
let current_mtime = this._getFileMtime(full_path);
|
|
195
200
|
if (current_mtime && current_mtime !== data.file_mtime) {
|
|
196
201
|
// 文件已修改,清除缓存
|
|
@@ -209,10 +214,9 @@ Static.prototype._getCache = async function (path, full_path) {
|
|
|
209
214
|
*/
|
|
210
215
|
Static.prototype._getFileMtime = function (file_path) {
|
|
211
216
|
try {
|
|
212
|
-
let
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
} catch (error) {
|
|
217
|
+
let stats = file_path.stat();
|
|
218
|
+
return stats ? stats.mtime.getTime() : null;
|
|
219
|
+
} catch (_error) {
|
|
216
220
|
return null;
|
|
217
221
|
}
|
|
218
222
|
};
|
|
@@ -238,7 +242,8 @@ Static.prototype._setCache = async function (path, content, mime_type, file_mtim
|
|
|
238
242
|
file_mtime: file_mtime || Date.now() // 如果没有提供文件修改时间,使用当前时间
|
|
239
243
|
};
|
|
240
244
|
|
|
241
|
-
// 统一设置缓存数据
|
|
245
|
+
// 统一设置缓存数据 - 充分利用缓存模块的TTL机制
|
|
246
|
+
// 缓存模块会自动处理过期,无需手动检查时间戳
|
|
242
247
|
await this._cache.set(cache_key, data, this.config.cache_age);
|
|
243
248
|
|
|
244
249
|
// 如果启用文件监听,添加文件监听器
|
|
@@ -247,17 +252,40 @@ Static.prototype._setCache = async function (path, content, mime_type, file_mtim
|
|
|
247
252
|
}
|
|
248
253
|
};
|
|
249
254
|
|
|
255
|
+
/**
|
|
256
|
+
* 生成配置前缀
|
|
257
|
+
* @returns {string} 配置前缀
|
|
258
|
+
*/
|
|
259
|
+
Static.prototype._generateConfigPrefix = function () {
|
|
260
|
+
return `${this.config.convert_amd ? 'amd' : 'noamd'}_${this.config.files.join('-')}_${this.config.path.replace(/[^a-zA-Z0-9]/g, '_')}`;
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* 生成缓存头信息
|
|
265
|
+
* @returns {object} 缓存头对象
|
|
266
|
+
*/
|
|
267
|
+
Static.prototype._generateCacheHeaders = function () {
|
|
268
|
+
let headers = {};
|
|
269
|
+
|
|
270
|
+
if (this.config.max_age) {
|
|
271
|
+
let cache_control = 'max-age=' + this.config.max_age;
|
|
272
|
+
if (this.config.immutable) {
|
|
273
|
+
cache_control += ',immutable';
|
|
274
|
+
}
|
|
275
|
+
headers['Cache-Control'] = cache_control;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return headers;
|
|
279
|
+
};
|
|
280
|
+
|
|
250
281
|
/**
|
|
251
282
|
* 生成包含配置信息和文件内容哈希的缓存键
|
|
252
283
|
* @param {string} path 文件路径
|
|
253
284
|
* @returns {string} 缓存键
|
|
254
285
|
*/
|
|
255
286
|
Static.prototype._generateCacheKey = function (path) {
|
|
256
|
-
//
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
// 直接使用路径作为唯一标识,不依赖文件内容
|
|
260
|
-
return this.config.key_prefix + config_prefix + ':' + path.replace(/[^a-zA-Z0-9]/g, '_');
|
|
287
|
+
// 直接使用缓存的配置前缀
|
|
288
|
+
return this.config.key_prefix + this._cache_config_prefix + ':' + path.replace(/[^a-zA-Z0-9]/g, '_');
|
|
261
289
|
};
|
|
262
290
|
|
|
263
291
|
/**
|
|
@@ -267,6 +295,9 @@ Static.prototype._generateCacheKey = function (path) {
|
|
|
267
295
|
Static.prototype.setConfig = function (config) {
|
|
268
296
|
if (config) {
|
|
269
297
|
Object.assign(this.config, config);
|
|
298
|
+
// 配置改变时,立即重新计算缓存配置前缀和缓存头
|
|
299
|
+
this._cache_config_prefix = this._generateConfigPrefix();
|
|
300
|
+
this._cache_headers = this._generateCacheHeaders();
|
|
270
301
|
}
|
|
271
302
|
if (this.config.extensions && this.config.extensions.length === 0) {
|
|
272
303
|
this.config.extensions = false;
|
|
@@ -437,7 +468,7 @@ Static.prototype._convertToDirectVueComponent = function (script_content, vue_co
|
|
|
437
468
|
const opts = this._cleanScriptContent(script_parsed);
|
|
438
469
|
|
|
439
470
|
return this._buildVueComponentCode(name, opts, desc);
|
|
440
|
-
} catch (
|
|
471
|
+
} catch (_error) {
|
|
441
472
|
// Vue文件解析失败,使用回退方案
|
|
442
473
|
return this._fallbackVueConversion(script_content, vue_content);
|
|
443
474
|
}
|
|
@@ -461,7 +492,7 @@ Static.prototype._shouldParseVue = function (vue_content) {
|
|
|
461
492
|
* @returns {object} Vue描述符
|
|
462
493
|
*/
|
|
463
494
|
Static.prototype._parseVueDescriptor = function (vue_content) {
|
|
464
|
-
|
|
495
|
+
let { descriptor, errors } = parse(vue_content);
|
|
465
496
|
|
|
466
497
|
if (errors && errors.length > 0) {
|
|
467
498
|
// Vue文件解析错误
|
|
@@ -856,27 +887,20 @@ Static.prototype._setHeaders = function (ctx, path) {
|
|
|
856
887
|
}
|
|
857
888
|
};
|
|
858
889
|
|
|
859
|
-
/**
|
|
860
|
-
* 发送静态文件
|
|
861
|
-
* @param {object} ctx 上下文
|
|
862
|
-
* @param {string} file_path 文件路径
|
|
863
|
-
*/
|
|
864
890
|
/**
|
|
865
891
|
* 发送二进制文件
|
|
866
892
|
* @param {object} ctx 请求上下文
|
|
867
893
|
* @param {string} file_path 文件路径
|
|
868
894
|
*/
|
|
869
895
|
Static.prototype._sendBinaryFile = async function (ctx, file_path) {
|
|
870
|
-
// 处理二进制文件
|
|
871
|
-
|
|
896
|
+
// 处理二进制文件 - 使用koa-send优化大文件传输
|
|
897
|
+
let relative_path = file_path.startsWith('/') ? file_path.substring(1) : file_path;
|
|
898
|
+
|
|
899
|
+
// 使用koa-send发送文件,支持流式传输、断点续传等优化
|
|
900
|
+
await send(ctx, relative_path, {
|
|
872
901
|
root: this.config.root,
|
|
873
|
-
maxage: this.config.max_age,
|
|
874
|
-
immutable: this.config.immutable
|
|
875
|
-
hidden: this.config.hidden,
|
|
876
|
-
format: this.config.format,
|
|
877
|
-
extensions: this.config.extensions,
|
|
878
|
-
brotli: this.config.brotli,
|
|
879
|
-
gzip: this.config.gzip
|
|
902
|
+
maxage: this.config.max_age || 7200000, // 2小时缓存
|
|
903
|
+
immutable: this.config.immutable || false
|
|
880
904
|
});
|
|
881
905
|
};
|
|
882
906
|
|
|
@@ -888,9 +912,15 @@ Static.prototype._sendBinaryFile = async function (ctx, file_path) {
|
|
|
888
912
|
*/
|
|
889
913
|
Static.prototype._sendFromCache = function (ctx, file_path, data) {
|
|
890
914
|
// 使用缓存内容
|
|
891
|
-
ctx.
|
|
915
|
+
ctx.status = 200;
|
|
916
|
+
ctx.type = data.mime_type;
|
|
892
917
|
ctx.body = data.content;
|
|
893
|
-
|
|
918
|
+
|
|
919
|
+
// 设置缓存头
|
|
920
|
+
let headers = this._getCacheHeaders();
|
|
921
|
+
for (let k in headers) {
|
|
922
|
+
ctx.set(k, headers[k]);
|
|
923
|
+
}
|
|
894
924
|
};
|
|
895
925
|
|
|
896
926
|
/**
|
|
@@ -901,6 +931,13 @@ Static.prototype._sendFromCache = function (ctx, file_path, data) {
|
|
|
901
931
|
*/
|
|
902
932
|
Static.prototype._sendNewContent = async function (ctx, file_path, full_path) {
|
|
903
933
|
let original_content = full_path.loadText();
|
|
934
|
+
|
|
935
|
+
// 检查文件内容是否成功加载
|
|
936
|
+
if (original_content === undefined) {
|
|
937
|
+
ctx.status = 404;
|
|
938
|
+
return;
|
|
939
|
+
}
|
|
940
|
+
|
|
904
941
|
let mime_type = this._getMimeType(file_path);
|
|
905
942
|
|
|
906
943
|
// 检查是否需要转换
|
|
@@ -918,14 +955,21 @@ Static.prototype._sendNewContent = async function (ctx, file_path, full_path) {
|
|
|
918
955
|
}
|
|
919
956
|
|
|
920
957
|
// 获取文件修改时间戳
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
958
|
+
let file_mtime = this._getFileMtime(full_path);
|
|
959
|
+
|
|
960
|
+
// 缓存转换后的内容(包含文件修改时间戳和文件监听)
|
|
961
|
+
await this._setCache(file_path, final_content, mime_type, file_mtime, full_path);
|
|
925
962
|
|
|
926
|
-
|
|
963
|
+
// 设置响应
|
|
964
|
+
ctx.status = 200;
|
|
965
|
+
ctx.type = mime_type;
|
|
927
966
|
ctx.body = final_content;
|
|
928
|
-
|
|
967
|
+
|
|
968
|
+
// 设置缓存头
|
|
969
|
+
let headers = this._getCacheHeaders();
|
|
970
|
+
for (let k in headers) {
|
|
971
|
+
ctx.set(k, headers[k]);
|
|
972
|
+
}
|
|
929
973
|
};
|
|
930
974
|
|
|
931
975
|
Static.prototype._send = async function (ctx, file_path) {
|
|
@@ -933,7 +977,7 @@ Static.prototype._send = async function (ctx, file_path) {
|
|
|
933
977
|
let relative_path = file_path.startsWith('/') ? file_path.substring(1) : file_path;
|
|
934
978
|
let full_path = relative_path.fullname(this.config.root);
|
|
935
979
|
|
|
936
|
-
if (!full_path.
|
|
980
|
+
if (!full_path.isFile()) {
|
|
937
981
|
ctx.status = 404;
|
|
938
982
|
return;
|
|
939
983
|
}
|
|
@@ -1005,16 +1049,16 @@ Static.prototype._isTextByExtension = function (file_path) {
|
|
|
1005
1049
|
*/
|
|
1006
1050
|
Static.prototype._isTextByContent = function (file_path) {
|
|
1007
1051
|
try {
|
|
1008
|
-
|
|
1009
|
-
if (!fs.existsSync(file_path)) return false;
|
|
1052
|
+
if (!file_path.hasFile()) return false;
|
|
1010
1053
|
|
|
1011
|
-
let stats =
|
|
1012
|
-
if (!stats.isFile()) return false;
|
|
1054
|
+
let stats = file_path.stat();
|
|
1055
|
+
if (!stats || !stats.isFile()) return false;
|
|
1013
1056
|
|
|
1014
|
-
let
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1057
|
+
let content = file_path.loadText('binary');
|
|
1058
|
+
if (!content) return false;
|
|
1059
|
+
|
|
1060
|
+
let buffer = Buffer.from(content, 'binary');
|
|
1061
|
+
let bytes_read = Math.min(buffer.length, 512);
|
|
1018
1062
|
|
|
1019
1063
|
let count = 0;
|
|
1020
1064
|
for (let i = 0; i < bytes_read; i++) {
|
|
@@ -1025,7 +1069,7 @@ Static.prototype._isTextByContent = function (file_path) {
|
|
|
1025
1069
|
}
|
|
1026
1070
|
|
|
1027
1071
|
return (count / bytes_read) > 0.9;
|
|
1028
|
-
} catch (
|
|
1072
|
+
} catch (_error) {
|
|
1029
1073
|
return false;
|
|
1030
1074
|
}
|
|
1031
1075
|
};
|
|
@@ -1087,16 +1131,16 @@ Static.prototype._isBinaryByExtension = function (file_path) {
|
|
|
1087
1131
|
*/
|
|
1088
1132
|
Static.prototype._isBinaryByContent = function (file_path) {
|
|
1089
1133
|
try {
|
|
1090
|
-
|
|
1091
|
-
if (!fs.existsSync(file_path)) return false;
|
|
1134
|
+
if (!file_path.hasFile()) return false;
|
|
1092
1135
|
|
|
1093
|
-
let stats =
|
|
1094
|
-
if (!stats.isFile()) return false;
|
|
1136
|
+
let stats = file_path.stat();
|
|
1137
|
+
if (!stats || !stats.isFile()) return false;
|
|
1095
1138
|
|
|
1096
|
-
let
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1139
|
+
let content = file_path.loadText('binary');
|
|
1140
|
+
if (!content) return false;
|
|
1141
|
+
|
|
1142
|
+
let buffer = Buffer.from(content, 'binary');
|
|
1143
|
+
let bytes_read = Math.min(buffer.length, 512);
|
|
1100
1144
|
|
|
1101
1145
|
let has_binary_chars = false;
|
|
1102
1146
|
for (let i = 0; i < bytes_read; i++) {
|
|
@@ -1108,7 +1152,7 @@ Static.prototype._isBinaryByContent = function (file_path) {
|
|
|
1108
1152
|
}
|
|
1109
1153
|
|
|
1110
1154
|
return has_binary_chars;
|
|
1111
|
-
} catch (
|
|
1155
|
+
} catch (_error) {
|
|
1112
1156
|
return false;
|
|
1113
1157
|
}
|
|
1114
1158
|
};
|
|
@@ -1124,6 +1168,31 @@ Static.prototype.isBinaryFile = function (file_path) {
|
|
|
1124
1168
|
return is_binary_by_content || is_binary_by_ext;
|
|
1125
1169
|
};
|
|
1126
1170
|
|
|
1171
|
+
/**
|
|
1172
|
+
* 获取缓存头信息
|
|
1173
|
+
* @returns {object} 缓存头对象
|
|
1174
|
+
*/
|
|
1175
|
+
Static.prototype._getCacheHeaders = function () {
|
|
1176
|
+
// 直接使用缓存的头对象
|
|
1177
|
+
return this._cache_headers;
|
|
1178
|
+
};
|
|
1179
|
+
|
|
1180
|
+
/**
|
|
1181
|
+
* 读取二进制文件
|
|
1182
|
+
* @param {string} file_path 文件路径
|
|
1183
|
+
* @returns {Buffer} 文件内容
|
|
1184
|
+
*/
|
|
1185
|
+
Static.prototype._readBinaryFile = async function (file_path) {
|
|
1186
|
+
let relative_path = file_path.startsWith('/') ? file_path.substring(1) : file_path;
|
|
1187
|
+
let full_path = relative_path.fullname(this.config.root);
|
|
1188
|
+
|
|
1189
|
+
if (full_path.isFile() && full_path.hasFile()) {
|
|
1190
|
+
return full_path.loadText('binary');
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
return null;
|
|
1194
|
+
};
|
|
1195
|
+
|
|
1127
1196
|
/**
|
|
1128
1197
|
* 执行静态文件处理
|
|
1129
1198
|
* @param {object} ctx http请求上下文
|
|
@@ -1134,8 +1203,18 @@ Static.prototype.run = async function (ctx, next) {
|
|
|
1134
1203
|
await next();
|
|
1135
1204
|
if (!this._shouldProcess(ctx)) return;
|
|
1136
1205
|
|
|
1137
|
-
//
|
|
1138
|
-
await this._send(ctx
|
|
1206
|
+
// 处理静态文件请求
|
|
1207
|
+
let result = await this._send(ctx.path);
|
|
1208
|
+
|
|
1209
|
+
// 设置响应状态和内容
|
|
1210
|
+
ctx.status = result.status;
|
|
1211
|
+
if (result.type) ctx.type = result.type;
|
|
1212
|
+
if (result.body) ctx.body = result.body;
|
|
1213
|
+
if (result.headers) {
|
|
1214
|
+
for (let key in result.headers) {
|
|
1215
|
+
ctx.set(key, result.headers[key]);
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1139
1218
|
};
|
|
1140
1219
|
|
|
1141
1220
|
// 导出Static类用于测试
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mm_statics",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.4",
|
|
4
4
|
"description": "这是超级美眉statics函数模块,用于web服务端statics缓存",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"eslint": "^9.39.2",
|
|
48
48
|
"eslint-plugin-jsdoc": "^61.5.0",
|
|
49
|
-
"mm_eslint": "^1.3.
|
|
49
|
+
"mm_eslint": "^1.3.8",
|
|
50
50
|
"mocha": "^11.7.5",
|
|
51
51
|
"supertest": "^7.1.4"
|
|
52
52
|
}
|