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 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
- let current_time = Date.now();
186
- if (current_time - data.timestamp > this.config.cache_age * 1000) {
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 fs = require('fs');
213
- let stats = fs.statSync(file_path);
214
- return stats.mtime.getTime();
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
- let config_prefix = `${this.config.convert_amd ? 'amd' : 'noamd'}_${this.config.files.join('-')}_${this.config.path.replace(/[^a-zA-Z0-9]/g, '_')}`;
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 (error) {
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
- const { descriptor, errors } = parse(vue_content);
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
- await send(ctx, file_path, {
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.response.type = data.mime_type;
915
+ ctx.status = 200;
916
+ ctx.type = data.mime_type;
892
917
  ctx.body = data.content;
893
- this._setHeaders(ctx, file_path);
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
- let file_mtime = this._getFileMtime(full_path);
922
-
923
- // 缓存转换后的内容(包含文件修改时间戳和文件监听)
924
- await this._setCache(file_path, final_content, mime_type, file_mtime, full_path);
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
- ctx.response.type = mime_type;
963
+ // 设置响应
964
+ ctx.status = 200;
965
+ ctx.type = mime_type;
927
966
  ctx.body = final_content;
928
- this._setHeaders(ctx, file_path);
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.hasFile()) {
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
- let fs = require('fs');
1009
- if (!fs.existsSync(file_path)) return false;
1052
+ if (!file_path.hasFile()) return false;
1010
1053
 
1011
- let stats = fs.statSync(file_path);
1012
- if (!stats.isFile()) return false;
1054
+ let stats = file_path.stat();
1055
+ if (!stats || !stats.isFile()) return false;
1013
1056
 
1014
- let fd = fs.openSync(file_path, 'r');
1015
- let buffer = Buffer.alloc(512);
1016
- let bytes_read = fs.readSync(fd, buffer, 0, 512, 0);
1017
- fs.closeSync(fd);
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 (error) {
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
- let fs = require('fs');
1091
- if (!fs.existsSync(file_path)) return false;
1134
+ if (!file_path.hasFile()) return false;
1092
1135
 
1093
- let stats = fs.statSync(file_path);
1094
- if (!stats.isFile()) return false;
1136
+ let stats = file_path.stat();
1137
+ if (!stats || !stats.isFile()) return false;
1095
1138
 
1096
- let fd = fs.openSync(file_path, 'r');
1097
- let buffer = Buffer.alloc(512);
1098
- let bytes_read = fs.readSync(fd, buffer, 0, 512, 0);
1099
- fs.closeSync(fd);
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 (error) {
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
- // 直接使用koa-send处理路径映射
1138
- await this._send(ctx, ctx.path);
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",
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.7",
49
+ "mm_eslint": "^1.3.8",
50
50
  "mocha": "^11.7.5",
51
51
  "supertest": "^7.1.4"
52
52
  }