mm_statics 1.5.4 → 1.5.5
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 +641 -1229
- package/package.json +4 -3
package/index.js
CHANGED
|
@@ -1,1229 +1,641 @@
|
|
|
1
|
-
let send = require('koa-send');
|
|
2
|
-
const { EsToAmdConvert } = require('mm_es6_to_amd');
|
|
3
|
-
const { parse } = require('@vue/compiler-sfc');
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
this.
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
};
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
};
|
|
306
|
-
|
|
307
|
-
/**
|
|
308
|
-
*
|
|
309
|
-
* @param {
|
|
310
|
-
* @param {
|
|
311
|
-
* @returns {
|
|
312
|
-
*/
|
|
313
|
-
Static.prototype.
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
};
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
*
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
*
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
let
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
*
|
|
492
|
-
* @
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
return
|
|
513
|
-
};
|
|
514
|
-
|
|
515
|
-
/**
|
|
516
|
-
*
|
|
517
|
-
* @param {string}
|
|
518
|
-
* @returns {
|
|
519
|
-
*/
|
|
520
|
-
Static.prototype.
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
this.
|
|
557
|
-
|
|
558
|
-
this.
|
|
559
|
-
this.
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
)
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
* @param {string} opts 组件选项
|
|
643
|
-
* @param {string} template_content 模板内容
|
|
644
|
-
* @param {string} styles_content 样式内容
|
|
645
|
-
* @returns {Array} 代码部分数组
|
|
646
|
-
*/
|
|
647
|
-
Static.prototype._buildVueComponentHeader = function (
|
|
648
|
-
name, opts, template_content, styles_content
|
|
649
|
-
) {
|
|
650
|
-
return [
|
|
651
|
-
'// Vue组件: ', name, ' (使用@vue/compiler-sfc解析)\n',
|
|
652
|
-
'(function() {\n',
|
|
653
|
-
' var template = \`', template_content, '\`;\n',
|
|
654
|
-
' var style = \`', styles_content, '\`;\n',
|
|
655
|
-
' \n',
|
|
656
|
-
' // 组件选项\n',
|
|
657
|
-
' var options = ', opts, ';\n',
|
|
658
|
-
' \n',
|
|
659
|
-
' // 添加template\n',
|
|
660
|
-
' if (template) {\n',
|
|
661
|
-
' options.template = template;\n',
|
|
662
|
-
' }\n',
|
|
663
|
-
' \n',
|
|
664
|
-
' // 添加样式(如果存在)\n'
|
|
665
|
-
];
|
|
666
|
-
};
|
|
667
|
-
|
|
668
|
-
/**
|
|
669
|
-
* Vue文件解析失败时的回退转换方案
|
|
670
|
-
* @param {string} script_content script标签内容
|
|
671
|
-
* @param {string} vue_content 完整的Vue文件内容
|
|
672
|
-
* @returns {string} 可直接使用的Vue组件代码
|
|
673
|
-
*/
|
|
674
|
-
Static.prototype._fallbackVueConversion = function (script_content, vue_content) {
|
|
675
|
-
const template_content = this._extractTemplateContent(vue_content);
|
|
676
|
-
const style_content = this._extractStyleContent(vue_content);
|
|
677
|
-
const name = this._extractComponentName(script_content);
|
|
678
|
-
const clean_opts = this._simplifyComponentOptions(script_content);
|
|
679
|
-
|
|
680
|
-
return this._buildFallbackVueCode(name, clean_opts, template_content, style_content);
|
|
681
|
-
};
|
|
682
|
-
|
|
683
|
-
/**
|
|
684
|
-
* 提取模板内容
|
|
685
|
-
* @param {string} vue_content Vue文件内容
|
|
686
|
-
* @returns {string} 模板内容
|
|
687
|
-
*/
|
|
688
|
-
Static.prototype._extractTemplateContent = function (vue_content) {
|
|
689
|
-
if (!vue_content.includes('<template>')) {
|
|
690
|
-
return '';
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
const template_start = vue_content.indexOf('<template>');
|
|
694
|
-
const template_end = vue_content.indexOf('</template>', template_start);
|
|
695
|
-
|
|
696
|
-
if (template_start !== -1 && template_end !== -1) {
|
|
697
|
-
return vue_content.substring(template_start + 10, template_end).trim();
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
return '';
|
|
701
|
-
};
|
|
702
|
-
|
|
703
|
-
/**
|
|
704
|
-
* 提取样式内容
|
|
705
|
-
* @param {string} vue_content Vue文件内容
|
|
706
|
-
* @returns {string} 样式内容
|
|
707
|
-
*/
|
|
708
|
-
Static.prototype._extractStyleContent = function (vue_content) {
|
|
709
|
-
if (!vue_content.includes('<style')) {
|
|
710
|
-
return '';
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
const style_start = vue_content.indexOf('<style');
|
|
714
|
-
const style_end = vue_content.indexOf('</style>', style_start);
|
|
715
|
-
|
|
716
|
-
if (style_start !== -1 && style_end !== -1) {
|
|
717
|
-
return vue_content.substring(vue_content.indexOf('>', style_start) + 1, style_end).trim();
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
return '';
|
|
721
|
-
};
|
|
722
|
-
|
|
723
|
-
/**
|
|
724
|
-
* 简化组件选项
|
|
725
|
-
* @param {string} script_content 脚本内容
|
|
726
|
-
* @returns {string} 简化后的选项
|
|
727
|
-
*/
|
|
728
|
-
Static.prototype._simplifyComponentOptions = function (script_content) {
|
|
729
|
-
let clean_opts = script_content
|
|
730
|
-
.replace(/^\s*import\s+.*?from\s+['"].*?['"];?\s*$/gm, '')
|
|
731
|
-
.trim();
|
|
732
|
-
|
|
733
|
-
if (clean_opts.includes('export default')) {
|
|
734
|
-
clean_opts = clean_opts.replace(/^\s*export\s+default\s*/, '').trim();
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
return clean_opts
|
|
738
|
-
.replace(/setup\(\)\s*\{[\s\S]*?\}/, 'setup() { return {}; }')
|
|
739
|
-
.replace(/ref\([^)]*\)/g, '0')
|
|
740
|
-
.replace(/computed\([^)]*\)/g, 'function() { return 0; }')
|
|
741
|
-
.replace(/ChildComponent/g, 'null');
|
|
742
|
-
};
|
|
743
|
-
|
|
744
|
-
/**
|
|
745
|
-
* 构建回退方案Vue代码
|
|
746
|
-
* @param {string} name 组件名称
|
|
747
|
-
* @param {string} clean_opts 清理后的选项
|
|
748
|
-
* @param {string} template_content 模板内容
|
|
749
|
-
* @param {string} style_content 样式内容
|
|
750
|
-
* @returns {string} 生成的代码
|
|
751
|
-
*/
|
|
752
|
-
Static.prototype._buildFallbackVueCode = function (
|
|
753
|
-
name, clean_opts, template_content, style_content
|
|
754
|
-
) {
|
|
755
|
-
const parts = this._buildFallbackHeader(name, clean_opts, template_content, style_content);
|
|
756
|
-
|
|
757
|
-
this._addFallbackStyleCode(parts, style_content);
|
|
758
|
-
this._addComponentRegistration(parts, name);
|
|
759
|
-
this._addComponentExport(parts, name);
|
|
760
|
-
|
|
761
|
-
parts.push(' return options;\n', '})();\n');
|
|
762
|
-
|
|
763
|
-
return parts.join('');
|
|
764
|
-
};
|
|
765
|
-
|
|
766
|
-
/**
|
|
767
|
-
* 构建回退方案头部代码
|
|
768
|
-
* @param {string} name 组件名称
|
|
769
|
-
* @param {string} clean_opts 清理后的选项
|
|
770
|
-
* @param {string} template_content 模板内容
|
|
771
|
-
* @param {string} style_content 样式内容
|
|
772
|
-
* @returns {Array} 代码部分数组
|
|
773
|
-
*/
|
|
774
|
-
Static.prototype._buildFallbackHeader = function (
|
|
775
|
-
name, clean_opts, template_content, style_content
|
|
776
|
-
) {
|
|
777
|
-
return [
|
|
778
|
-
'// Vue组件: ', name, ' (回退方案)\n',
|
|
779
|
-
'(function() {\n',
|
|
780
|
-
' var template = \`', template_content, '\`;\n',
|
|
781
|
-
' var style = \`', style_content, '\`;\n',
|
|
782
|
-
' \n',
|
|
783
|
-
' // 组件选项\n',
|
|
784
|
-
' var options = ', clean_opts, ';\n',
|
|
785
|
-
' \n',
|
|
786
|
-
' // 添加template\n',
|
|
787
|
-
' if (template) {\n',
|
|
788
|
-
' options.template = template;\n',
|
|
789
|
-
' }\n',
|
|
790
|
-
' \n'
|
|
791
|
-
];
|
|
792
|
-
};
|
|
793
|
-
|
|
794
|
-
/**
|
|
795
|
-
* 添加回退方案样式代码
|
|
796
|
-
* @param {Array} parts 代码部分数组
|
|
797
|
-
* @param {string} style_content 样式内容
|
|
798
|
-
*/
|
|
799
|
-
Static.prototype._addFallbackStyleCode = function (parts, style_content) {
|
|
800
|
-
if (style_content) {
|
|
801
|
-
parts.push(
|
|
802
|
-
' // 添加样式(如果存在)\n',
|
|
803
|
-
' if (style) {\n',
|
|
804
|
-
' // 创建样式元素并添加到文档\n',
|
|
805
|
-
' if (typeof document !== \'undefined\') {\n',
|
|
806
|
-
' var styleElement = document.createElement(\'style\');\n',
|
|
807
|
-
' styleElement.textContent = style;\n',
|
|
808
|
-
' document.head.appendChild(styleElement);\n',
|
|
809
|
-
' }\n',
|
|
810
|
-
' }\n',
|
|
811
|
-
' \n'
|
|
812
|
-
);
|
|
813
|
-
}
|
|
814
|
-
};
|
|
815
|
-
|
|
816
|
-
/**
|
|
817
|
-
* 处理HTML文件,转换其中的script标签
|
|
818
|
-
* @param {string} html_content HTML文件内容
|
|
819
|
-
* @returns {string} 转换后的HTML文件
|
|
820
|
-
*/
|
|
821
|
-
Static.prototype._handleHtmlFile = function (html_content) {
|
|
822
|
-
// 快速检查是否需要处理
|
|
823
|
-
if (!this.config.convert_amd && !this.config.compile_vue) {
|
|
824
|
-
return html_content;
|
|
825
|
-
}
|
|
826
|
-
|
|
827
|
-
// 使用更高效的正则表达式匹配<script>标签
|
|
828
|
-
let script_regex = /<script[^>]*>([\s\S]*?)<\/script>/gi;
|
|
829
|
-
let conv_html = html_content;
|
|
830
|
-
let match;
|
|
831
|
-
|
|
832
|
-
while ((match = script_regex.exec(html_content)) !== null) {
|
|
833
|
-
let script_content = match[1].trim();
|
|
834
|
-
|
|
835
|
-
// 跳过空内容和已转换的内容
|
|
836
|
-
if (!script_content || script_content.includes('define(')) {
|
|
837
|
-
continue;
|
|
838
|
-
}
|
|
839
|
-
|
|
840
|
-
// 转换script内容
|
|
841
|
-
let conv_script = this.convert.toAmd(script_content);
|
|
842
|
-
|
|
843
|
-
// 直接替换,避免多次字符串操作
|
|
844
|
-
conv_html = conv_html.replace(match[0], `<script>\n${conv_script}\n</script>`);
|
|
845
|
-
}
|
|
846
|
-
|
|
847
|
-
return conv_html;
|
|
848
|
-
};
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
/**
|
|
853
|
-
* 获取MIME类型
|
|
854
|
-
* @param {string} path 文件路径
|
|
855
|
-
* @returns {string} MIME类型
|
|
856
|
-
*/
|
|
857
|
-
Static.prototype._getMimeType = function (path) {
|
|
858
|
-
if (path.endsWith('.js')) {
|
|
859
|
-
return 'application/javascript; charset=utf-8';
|
|
860
|
-
} else if (path.endsWith('.css')) {
|
|
861
|
-
return 'text/css; charset=utf-8';
|
|
862
|
-
} else if (path.endsWith('.html')) {
|
|
863
|
-
return 'text/html; charset=utf-8';
|
|
864
|
-
} else if (path.endsWith('.vue')) {
|
|
865
|
-
return 'application/javascript; charset=utf-8';
|
|
866
|
-
} else {
|
|
867
|
-
return 'text/plain; charset=utf-8';
|
|
868
|
-
}
|
|
869
|
-
};
|
|
870
|
-
|
|
871
|
-
/**
|
|
872
|
-
* 设置响应头
|
|
873
|
-
* @param {object} ctx 上下文
|
|
874
|
-
* @param {string} path 文件路径
|
|
875
|
-
*/
|
|
876
|
-
Static.prototype._setHeaders = function (ctx, path) {
|
|
877
|
-
if (!ctx.response.type) {
|
|
878
|
-
ctx.response.type = this._getMimeType(path);
|
|
879
|
-
}
|
|
880
|
-
|
|
881
|
-
if (this.config.max_age) {
|
|
882
|
-
let cache_control = 'max-age=' + this.config.max_age;
|
|
883
|
-
if (this.config.immutable) {
|
|
884
|
-
cache_control += ',immutable';
|
|
885
|
-
}
|
|
886
|
-
ctx.set('Cache-Control', cache_control);
|
|
887
|
-
}
|
|
888
|
-
};
|
|
889
|
-
|
|
890
|
-
/**
|
|
891
|
-
* 发送二进制文件
|
|
892
|
-
* @param {object} ctx 请求上下文
|
|
893
|
-
* @param {string} file_path 文件路径
|
|
894
|
-
*/
|
|
895
|
-
Static.prototype._sendBinaryFile = async function (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, {
|
|
901
|
-
root: this.config.root,
|
|
902
|
-
maxage: this.config.max_age || 7200000, // 2小时缓存
|
|
903
|
-
immutable: this.config.immutable || false
|
|
904
|
-
});
|
|
905
|
-
};
|
|
906
|
-
|
|
907
|
-
/**
|
|
908
|
-
* 从缓存发送文件内容
|
|
909
|
-
* @param {object} ctx 请求上下文
|
|
910
|
-
* @param {string} file_path 文件路径
|
|
911
|
-
* @param {object} data 缓存数据
|
|
912
|
-
*/
|
|
913
|
-
Static.prototype._sendFromCache = function (ctx, file_path, data) {
|
|
914
|
-
// 使用缓存内容
|
|
915
|
-
ctx.status = 200;
|
|
916
|
-
ctx.type = data.mime_type;
|
|
917
|
-
ctx.body = data.content;
|
|
918
|
-
|
|
919
|
-
// 设置缓存头
|
|
920
|
-
let headers = this._getCacheHeaders();
|
|
921
|
-
for (let k in headers) {
|
|
922
|
-
ctx.set(k, headers[k]);
|
|
923
|
-
}
|
|
924
|
-
};
|
|
925
|
-
|
|
926
|
-
/**
|
|
927
|
-
* 发送新文件内容并缓存
|
|
928
|
-
* @param {object} ctx 请求上下文
|
|
929
|
-
* @param {string} file_path 文件路径
|
|
930
|
-
* @param {string} full_path 完整文件路径
|
|
931
|
-
*/
|
|
932
|
-
Static.prototype._sendNewContent = async function (ctx, file_path, full_path) {
|
|
933
|
-
let original_content = full_path.loadText();
|
|
934
|
-
|
|
935
|
-
// 检查文件内容是否成功加载
|
|
936
|
-
if (original_content === undefined) {
|
|
937
|
-
ctx.status = 404;
|
|
938
|
-
return;
|
|
939
|
-
}
|
|
940
|
-
|
|
941
|
-
let mime_type = this._getMimeType(file_path);
|
|
942
|
-
|
|
943
|
-
// 检查是否需要转换
|
|
944
|
-
let final_content = original_content;
|
|
945
|
-
if (this._shouldConvert(file_path)) {
|
|
946
|
-
if (file_path.endsWith('.vue')) {
|
|
947
|
-
final_content = this._handleVueFile(original_content);
|
|
948
|
-
} else if (file_path.endsWith('.html')) {
|
|
949
|
-
final_content = this._handleHtmlFile(original_content);
|
|
950
|
-
} else if (this.config.convert_amd) {
|
|
951
|
-
// 只有启用AMD转换时才转换JS文件
|
|
952
|
-
final_content = this.convert.toAmd(original_content);
|
|
953
|
-
}
|
|
954
|
-
// 如果convert_amd为false但compile_vue为true,JS文件保持原样
|
|
955
|
-
}
|
|
956
|
-
|
|
957
|
-
// 获取文件修改时间戳
|
|
958
|
-
let file_mtime = this._getFileMtime(full_path);
|
|
959
|
-
|
|
960
|
-
// 缓存转换后的内容(包含文件修改时间戳和文件监听)
|
|
961
|
-
await this._setCache(file_path, final_content, mime_type, file_mtime, full_path);
|
|
962
|
-
|
|
963
|
-
// 设置响应
|
|
964
|
-
ctx.status = 200;
|
|
965
|
-
ctx.type = mime_type;
|
|
966
|
-
ctx.body = final_content;
|
|
967
|
-
|
|
968
|
-
// 设置缓存头
|
|
969
|
-
let headers = this._getCacheHeaders();
|
|
970
|
-
for (let k in headers) {
|
|
971
|
-
ctx.set(k, headers[k]);
|
|
972
|
-
}
|
|
973
|
-
};
|
|
974
|
-
|
|
975
|
-
Static.prototype._send = async function (ctx, file_path) {
|
|
976
|
-
try {
|
|
977
|
-
let relative_path = file_path.startsWith('/') ? file_path.substring(1) : file_path;
|
|
978
|
-
let full_path = relative_path.fullname(this.config.root);
|
|
979
|
-
|
|
980
|
-
if (!full_path.isFile()) {
|
|
981
|
-
ctx.status = 404;
|
|
982
|
-
return;
|
|
983
|
-
}
|
|
984
|
-
|
|
985
|
-
let is_text = this.isTextFile(file_path);
|
|
986
|
-
|
|
987
|
-
if (!is_text) {
|
|
988
|
-
await this._sendBinaryFile(ctx, file_path);
|
|
989
|
-
return;
|
|
990
|
-
}
|
|
991
|
-
|
|
992
|
-
// 先检查基于路径的缓存(不读取文件内容)
|
|
993
|
-
let data = await this._getCache(file_path, full_path);
|
|
994
|
-
if (data) {
|
|
995
|
-
this._sendFromCache(ctx, file_path, data);
|
|
996
|
-
return;
|
|
997
|
-
}
|
|
998
|
-
|
|
999
|
-
// 处理新内容并设置缓存
|
|
1000
|
-
await this._sendNewContent(ctx, file_path, full_path);
|
|
1001
|
-
} catch (error) {
|
|
1002
|
-
ctx.status = 500;
|
|
1003
|
-
ctx.body = 'Internal Server Error: ' + error.message;
|
|
1004
|
-
}
|
|
1005
|
-
};
|
|
1006
|
-
|
|
1007
|
-
/**
|
|
1008
|
-
* 获取文本文件扩展名列表
|
|
1009
|
-
* @returns {Array<string>} 文本文件扩展名数组
|
|
1010
|
-
*/
|
|
1011
|
-
Static.prototype._getTextExtensions = function () {
|
|
1012
|
-
return [
|
|
1013
|
-
// 代码文件
|
|
1014
|
-
'.js', '.jsx', '.ts', '.tsx', '.vue', '.html', '.htm', '.css', '.scss', '.less',
|
|
1015
|
-
'.json', '.xml', '.yaml', '.yml', '.md', '.markdown',
|
|
1016
|
-
|
|
1017
|
-
// 配置文件
|
|
1018
|
-
'.config', '.conf', '.ini', '.properties', '.env', '.gitignore',
|
|
1019
|
-
|
|
1020
|
-
// 脚本文件
|
|
1021
|
-
'.sh', '.bash', '.zsh', '.ps1', '.bat', '.cmd',
|
|
1022
|
-
|
|
1023
|
-
// 模板文件
|
|
1024
|
-
'.ejs', '.pug', '.jade', '.handlebars', '.hbs',
|
|
1025
|
-
|
|
1026
|
-
// 数据文件
|
|
1027
|
-
'.csv', '.txt', '.log', '.sql', '.graphql',
|
|
1028
|
-
|
|
1029
|
-
// 其他文本格式
|
|
1030
|
-
'.rtf', '.tex', '.rst', '.adoc'
|
|
1031
|
-
];
|
|
1032
|
-
};
|
|
1033
|
-
|
|
1034
|
-
/**
|
|
1035
|
-
* 通过扩展名判断是否为文本文件
|
|
1036
|
-
* @param {string} file_path 文件路径
|
|
1037
|
-
* @returns {boolean} 是否为文本文件
|
|
1038
|
-
*/
|
|
1039
|
-
Static.prototype._isTextByExtension = function (file_path) {
|
|
1040
|
-
let lower_path = file_path.toLowerCase();
|
|
1041
|
-
let text_exts = this._getTextExtensions();
|
|
1042
|
-
return text_exts.some(ext => lower_path.endsWith(ext));
|
|
1043
|
-
};
|
|
1044
|
-
|
|
1045
|
-
/**
|
|
1046
|
-
* 通过文件内容判断是否为文本文件
|
|
1047
|
-
* @param {string} file_path 文件路径
|
|
1048
|
-
* @returns {boolean} 是否为文本文件
|
|
1049
|
-
*/
|
|
1050
|
-
Static.prototype._isTextByContent = function (file_path) {
|
|
1051
|
-
try {
|
|
1052
|
-
if (!file_path.hasFile()) return false;
|
|
1053
|
-
|
|
1054
|
-
let stats = file_path.stat();
|
|
1055
|
-
if (!stats || !stats.isFile()) return false;
|
|
1056
|
-
|
|
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);
|
|
1062
|
-
|
|
1063
|
-
let count = 0;
|
|
1064
|
-
for (let i = 0; i < bytes_read; i++) {
|
|
1065
|
-
if ((buffer[i] >= 32 && buffer[i] <= 126) ||
|
|
1066
|
-
buffer[i] === 9 || buffer[i] === 10 || buffer[i] === 13) {
|
|
1067
|
-
count++;
|
|
1068
|
-
}
|
|
1069
|
-
}
|
|
1070
|
-
|
|
1071
|
-
return (count / bytes_read) > 0.9;
|
|
1072
|
-
} catch (_error) {
|
|
1073
|
-
return false;
|
|
1074
|
-
}
|
|
1075
|
-
};
|
|
1076
|
-
|
|
1077
|
-
Static.prototype.isTextFile = function (file_path) {
|
|
1078
|
-
let is_text_by_content = this._isTextByContent(file_path);
|
|
1079
|
-
let is_text_by_ext = this._isTextByExtension(file_path);
|
|
1080
|
-
|
|
1081
|
-
return is_text_by_content || is_text_by_ext;
|
|
1082
|
-
};
|
|
1083
|
-
|
|
1084
|
-
/**
|
|
1085
|
-
* 获取二进制文件扩展名列表
|
|
1086
|
-
* @returns {Array<string>} 二进制文件扩展名数组
|
|
1087
|
-
*/
|
|
1088
|
-
Static.prototype._getBinaryExtensions = function () {
|
|
1089
|
-
return [
|
|
1090
|
-
// 图片文件
|
|
1091
|
-
'.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp', '.ico', '.svg',
|
|
1092
|
-
'.tiff', '.tif', '.psd', '.ai', '.eps', '.raw',
|
|
1093
|
-
|
|
1094
|
-
// 视频文件
|
|
1095
|
-
'.mp4', '.avi', '.mov', '.wmv', '.flv', '.webm', '.mkv', '.m4v',
|
|
1096
|
-
'.3gp', '.mpeg', '.mpg', '.ts', '.m2ts',
|
|
1097
|
-
|
|
1098
|
-
// 音频文件
|
|
1099
|
-
'.mp3', '.wav', '.ogg', '.aac', '.flac', '.wma', '.m4a', '.ape',
|
|
1100
|
-
|
|
1101
|
-
// 字体文件
|
|
1102
|
-
'.woff', '.woff2', '.ttf', '.eot', '.otf',
|
|
1103
|
-
|
|
1104
|
-
// 文档和压缩文件
|
|
1105
|
-
'.pdf', '.zip', '.rar', '.7z', '.tar', '.gz', '.bz2', '.xz',
|
|
1106
|
-
'.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx',
|
|
1107
|
-
|
|
1108
|
-
// 可执行文件和安装包
|
|
1109
|
-
'.exe', '.dll', '.so', '.dmg', '.msi', '.deb', '.rpm', '.apk',
|
|
1110
|
-
|
|
1111
|
-
// 数据文件
|
|
1112
|
-
'.db', '.sqlite', '.dat', '.bin', '.iso', '.img'
|
|
1113
|
-
];
|
|
1114
|
-
};
|
|
1115
|
-
|
|
1116
|
-
/**
|
|
1117
|
-
* 通过扩展名判断是否为二进制文件
|
|
1118
|
-
* @param {string} file_path 文件路径
|
|
1119
|
-
* @returns {boolean} 是否为二进制文件
|
|
1120
|
-
*/
|
|
1121
|
-
Static.prototype._isBinaryByExtension = function (file_path) {
|
|
1122
|
-
let lower_path = file_path.toLowerCase();
|
|
1123
|
-
let binary_exts = this._getBinaryExtensions();
|
|
1124
|
-
return binary_exts.some(ext => lower_path.endsWith(ext));
|
|
1125
|
-
};
|
|
1126
|
-
|
|
1127
|
-
/**
|
|
1128
|
-
* 通过文件内容判断是否为二进制文件
|
|
1129
|
-
* @param {string} file_path 文件路径
|
|
1130
|
-
* @returns {boolean} 是否为二进制文件
|
|
1131
|
-
*/
|
|
1132
|
-
Static.prototype._isBinaryByContent = function (file_path) {
|
|
1133
|
-
try {
|
|
1134
|
-
if (!file_path.hasFile()) return false;
|
|
1135
|
-
|
|
1136
|
-
let stats = file_path.stat();
|
|
1137
|
-
if (!stats || !stats.isFile()) return false;
|
|
1138
|
-
|
|
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);
|
|
1144
|
-
|
|
1145
|
-
let has_binary_chars = false;
|
|
1146
|
-
for (let i = 0; i < bytes_read; i++) {
|
|
1147
|
-
if (buffer[i] === 0 ||
|
|
1148
|
-
(buffer[i] < 32 && buffer[i] !== 9 && buffer[i] !== 10 && buffer[i] !== 13)) {
|
|
1149
|
-
has_binary_chars = true;
|
|
1150
|
-
break;
|
|
1151
|
-
}
|
|
1152
|
-
}
|
|
1153
|
-
|
|
1154
|
-
return has_binary_chars;
|
|
1155
|
-
} catch (_error) {
|
|
1156
|
-
return false;
|
|
1157
|
-
}
|
|
1158
|
-
};
|
|
1159
|
-
|
|
1160
|
-
Static.prototype.isBinaryFile = function (file_path) {
|
|
1161
|
-
if (this.isTextFile(file_path)) {
|
|
1162
|
-
return false;
|
|
1163
|
-
}
|
|
1164
|
-
|
|
1165
|
-
let is_binary_by_content = this._isBinaryByContent(file_path);
|
|
1166
|
-
let is_binary_by_ext = this._isBinaryByExtension(file_path);
|
|
1167
|
-
|
|
1168
|
-
return is_binary_by_content || is_binary_by_ext;
|
|
1169
|
-
};
|
|
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
|
-
|
|
1196
|
-
/**
|
|
1197
|
-
* 执行静态文件处理
|
|
1198
|
-
* @param {object} ctx http请求上下文
|
|
1199
|
-
* @param {Function} next 跳过当前执行,先执行后面函数
|
|
1200
|
-
* @returns {boolean} 是否执行成功
|
|
1201
|
-
*/
|
|
1202
|
-
Static.prototype.run = async function (ctx, next) {
|
|
1203
|
-
await next();
|
|
1204
|
-
if (!this._shouldProcess(ctx)) return;
|
|
1205
|
-
|
|
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
|
-
}
|
|
1218
|
-
};
|
|
1219
|
-
|
|
1220
|
-
// 导出Static类用于测试
|
|
1221
|
-
module.exports = {
|
|
1222
|
-
Static,
|
|
1223
|
-
static(config) {
|
|
1224
|
-
let instance = new Static(config);
|
|
1225
|
-
return async function (ctx, next) {
|
|
1226
|
-
await instance.main(ctx, next);
|
|
1227
|
-
};
|
|
1228
|
-
}
|
|
1229
|
-
};
|
|
1
|
+
let send = require('koa-send');
|
|
2
|
+
const { EsToAmdConvert } = require('mm_es6_to_amd');
|
|
3
|
+
const { parse } = require('@vue/compiler-sfc');
|
|
4
|
+
const { compile } = require('@vue/compiler-dom');
|
|
5
|
+
const prettier = require('prettier');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 静态文件处理类
|
|
9
|
+
*/
|
|
10
|
+
class Static {
|
|
11
|
+
static config = {
|
|
12
|
+
index: 'index.html',
|
|
13
|
+
max_age: 7200,
|
|
14
|
+
key_prefix: 'static:',
|
|
15
|
+
cache: true,
|
|
16
|
+
cache_age: 7200,
|
|
17
|
+
immutable: true,
|
|
18
|
+
hidden: false,
|
|
19
|
+
format: true,
|
|
20
|
+
extensions: false,
|
|
21
|
+
brotli: false,
|
|
22
|
+
gzip: false,
|
|
23
|
+
root: './static',
|
|
24
|
+
compile_vue: true,
|
|
25
|
+
path: '/src',
|
|
26
|
+
files: ['.js', '.vue', '.html'],
|
|
27
|
+
convert_amd: true,
|
|
28
|
+
watch: false,
|
|
29
|
+
watch_files: ['.js', '.css', '.html', '.vue', '.json', '.md', '.txt', '.xml']
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* 初始化静态文件处理类
|
|
34
|
+
* @param {object} config 配置
|
|
35
|
+
*/
|
|
36
|
+
constructor(config) {
|
|
37
|
+
this.config = { ...Static.config };
|
|
38
|
+
this.convert = new EsToAmdConvert();
|
|
39
|
+
this._cache = null;
|
|
40
|
+
this._watcher = null;
|
|
41
|
+
this.setConfig(config);
|
|
42
|
+
this._init();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 设置配置
|
|
48
|
+
* @param {object} config 配置
|
|
49
|
+
*/
|
|
50
|
+
Static.prototype.setConfig = function (config) {
|
|
51
|
+
Object.assign(this.config, config || {});
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 初始化缓存
|
|
56
|
+
*/
|
|
57
|
+
Static.prototype._init = function () {
|
|
58
|
+
if (!$.cache) {
|
|
59
|
+
const { cacheAdmin } = require('mm_cache');
|
|
60
|
+
$.cache = cacheAdmin('sys', this.config);
|
|
61
|
+
}
|
|
62
|
+
this._cache = $.cache;
|
|
63
|
+
|
|
64
|
+
// 如果配置了文件监听,初始化文件监听器
|
|
65
|
+
if (this.config.watch && this.config.watch_files.length > 0) {
|
|
66
|
+
this._initWatcher();
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* 检测代码是否包含ES6语法
|
|
72
|
+
* @param {string} code 代码内容
|
|
73
|
+
* @returns {boolean} 是否包含ES6语法
|
|
74
|
+
*/
|
|
75
|
+
Static.prototype._isEs6Code = function (code) {
|
|
76
|
+
if (!code || typeof code !== 'string') {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ES6语法特征检测
|
|
81
|
+
const es6_patterns = [
|
|
82
|
+
/\bimport\s+[^;]+;/g, // import语句
|
|
83
|
+
/\bexport\s+[^;]+;/g, // export语句
|
|
84
|
+
/\bclass\s+\w+/g, // class声明
|
|
85
|
+
/\bconst\s+\w+/g, // const声明
|
|
86
|
+
/\blet\s+\w+/g, // let声明
|
|
87
|
+
/\([^)]*\)\s*=>/g, // 箭头函数
|
|
88
|
+
/`[^`]*`/g, // 模板字符串
|
|
89
|
+
/\{[^}]*\}\s*=\s*\w+/g, // 解构赋值
|
|
90
|
+
/\bPromise\b/g, // Promise对象
|
|
91
|
+
/\basync\s+function\b/g, // async函数
|
|
92
|
+
/\bawait\b/g // await关键字
|
|
93
|
+
];
|
|
94
|
+
|
|
95
|
+
for (let i = 0; i < es6_patterns.length; i++) {
|
|
96
|
+
if (es6_patterns[i].test(code)) {
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return false;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* 格式化代码
|
|
106
|
+
* @param {string} code 代码内容
|
|
107
|
+
* @param {string} file_type 文件类型
|
|
108
|
+
* @returns {Promise<string>} 格式化后的代码
|
|
109
|
+
*/
|
|
110
|
+
Static.prototype._formatCode = async function (code, file_type) {
|
|
111
|
+
if (!this.config.format || !code || typeof code !== 'string') {
|
|
112
|
+
return code;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
// 根据文件类型设置prettier配置
|
|
117
|
+
let parser = 'babel';
|
|
118
|
+
let plugins = [];
|
|
119
|
+
|
|
120
|
+
switch (file_type) {
|
|
121
|
+
case 'js':
|
|
122
|
+
parser = 'babel';
|
|
123
|
+
break;
|
|
124
|
+
case 'html':
|
|
125
|
+
parser = 'html';
|
|
126
|
+
break;
|
|
127
|
+
case 'vue':
|
|
128
|
+
parser = 'vue';
|
|
129
|
+
plugins = [require('prettier/parser-html'), require('prettier/parser-babel')];
|
|
130
|
+
break;
|
|
131
|
+
default:
|
|
132
|
+
parser = 'babel';
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// prettier.format是异步函数
|
|
136
|
+
const formatted_code = await prettier.format(code, {
|
|
137
|
+
parser: parser,
|
|
138
|
+
plugins: plugins,
|
|
139
|
+
semi: true,
|
|
140
|
+
singleQuote: true,
|
|
141
|
+
trailingComma: 'es5',
|
|
142
|
+
printWidth: 100,
|
|
143
|
+
tabWidth: 2,
|
|
144
|
+
useTabs: false,
|
|
145
|
+
bracketSpacing: true,
|
|
146
|
+
arrowParens: 'avoid'
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
return formatted_code;
|
|
150
|
+
} catch (error) {
|
|
151
|
+
// 格式化失败时返回原代码
|
|
152
|
+
console.warn(`格式化代码失败: ${error.message}`);
|
|
153
|
+
return code;
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* 转换为AMD模块
|
|
159
|
+
* @param {string} file_type 文件类型
|
|
160
|
+
* @param {string} text 代码
|
|
161
|
+
* @returns {Promise<string>} AMD模块代码
|
|
162
|
+
*/
|
|
163
|
+
Static.prototype._toAmd = async function (file_type, text) {
|
|
164
|
+
let txt = text;
|
|
165
|
+
if (file_type === 'js') {
|
|
166
|
+
// 对JS文件,先检测是否为ES6代码,只有ES6代码才进行转换
|
|
167
|
+
if (this._isEs6Code(txt)) {
|
|
168
|
+
let code = this.convert.toAmd(txt);
|
|
169
|
+
// 格式化代码
|
|
170
|
+
return await this._formatCode(code, file_type);
|
|
171
|
+
} else {
|
|
172
|
+
// 格式化非ES6代码
|
|
173
|
+
return await this._formatCode(txt, file_type);
|
|
174
|
+
}
|
|
175
|
+
} else if (file_type === 'html' || file_type === 'vue') {
|
|
176
|
+
// 获取html或vue中script标签的代码,将ES6语法的代码转换为AMD模块
|
|
177
|
+
let match = txt.match(/<script[^>]*>([\s\S]*?)<\/script>/g);
|
|
178
|
+
if (match) {
|
|
179
|
+
for (let i = 0; i < match.length; i++) {
|
|
180
|
+
let script = match[i];
|
|
181
|
+
// 直接提取script标签内容,避免重复匹配
|
|
182
|
+
let inner_code = script.replace(/^<script[^>]*>([\s\S]*?)<\/script>$/, '$1').trim();
|
|
183
|
+
if (inner_code) {
|
|
184
|
+
// 先检测script标签内的代码是否为ES6语法
|
|
185
|
+
if (this._isEs6Code(inner_code)) {
|
|
186
|
+
let amd_code = this.convert.toAmd(inner_code);
|
|
187
|
+
// 移除type="module"属性,因为AMD模块不需要这个属性
|
|
188
|
+
let new_script = script.replace(/type\s*=\s*["']module["']/g, '').replace(inner_code, amd_code);
|
|
189
|
+
// 清理多余的空格
|
|
190
|
+
new_script = new_script.replace(/<script\s+>/, '<script>').replace(/<script\s+([^>]+)>/, '<script $1>');
|
|
191
|
+
txt = txt.replace(script, new_script);
|
|
192
|
+
}
|
|
193
|
+
// 如果不是ES6代码,保持原样不进行转换
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// 格式化代码后返回
|
|
200
|
+
return await this._formatCode(txt, file_type);
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* 处理Vue模板部分
|
|
205
|
+
* @param {object} desc Vue组件描述符
|
|
206
|
+
* @returns {string} 编译后的render函数
|
|
207
|
+
*/
|
|
208
|
+
Static.prototype._processTemplate = function (desc) {
|
|
209
|
+
if (!desc.template) return '';
|
|
210
|
+
|
|
211
|
+
const template_result = compile(desc.template.content, {
|
|
212
|
+
mode: 'module'
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
// 提取render函数代码,去除import语句和export语句
|
|
216
|
+
let render_function = template_result.code
|
|
217
|
+
.replace(/^import[^;]+;\s*/gm, '')
|
|
218
|
+
.replace(/^export[^;]+;\s*/gm, '')
|
|
219
|
+
.trim();
|
|
220
|
+
|
|
221
|
+
// 如果render函数包含函数定义,提取函数体
|
|
222
|
+
let render_match = render_function.match(/function\s+render\s*\([^)]*\)\s*{([\s\S]*?)}\s*$/);
|
|
223
|
+
if (render_match) {
|
|
224
|
+
render_function = `function render(_ctx, _cache) {${render_match[1]}}`;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return render_function;
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* 处理Vue脚本部分
|
|
232
|
+
* @param {object} desc Vue组件描述符
|
|
233
|
+
* @param {string} render_function render函数
|
|
234
|
+
* @returns {string} 处理后的脚本代码
|
|
235
|
+
*/
|
|
236
|
+
Static.prototype._processScript = function (desc, render_function) {
|
|
237
|
+
if (!desc.script) {
|
|
238
|
+
return this._createDefaultComp(render_function);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
let script_content = desc.script.content;
|
|
242
|
+
|
|
243
|
+
// 提取import语句
|
|
244
|
+
let imports = '';
|
|
245
|
+
let import_match = script_content.match(/import[^;]+;/g);
|
|
246
|
+
if (import_match) {
|
|
247
|
+
imports = import_match.join('\n') + '\n';
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// 提取export default部分,保留完整的组件定义
|
|
251
|
+
let export_match = script_content.match(/export\s+default\s*({[\s\S]*?})\s*$/s);
|
|
252
|
+
if (export_match) {
|
|
253
|
+
return this._buildCompWithExport(export_match[1], imports, render_function);
|
|
254
|
+
} else {
|
|
255
|
+
return this._buildDefaultComp(imports, render_function);
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* 创建默认组件结构
|
|
261
|
+
* @param {string} render_function render函数
|
|
262
|
+
* @returns {string} 默认组件代码
|
|
263
|
+
*/
|
|
264
|
+
Static.prototype._createDefaultComp = function (render_function) {
|
|
265
|
+
return `
|
|
266
|
+
export default {
|
|
267
|
+
name: 'VueComponent',
|
|
268
|
+
data() {
|
|
269
|
+
return {}
|
|
270
|
+
}${render_function ? `,\n render: ${render_function}` : ''}\n};
|
|
271
|
+
`;
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* 构建带有export的组件
|
|
276
|
+
* @param {string} code_lib 组件代码
|
|
277
|
+
* @param {string} imports import语句
|
|
278
|
+
* @param {string} render_function render函数
|
|
279
|
+
* @returns {string} 完整的组件代码
|
|
280
|
+
*/
|
|
281
|
+
Static.prototype._buildCompWithExport = function (code_lib, imports, render_function) {
|
|
282
|
+
let proc_code = code_lib;
|
|
283
|
+
|
|
284
|
+
// 确保组件字符串是完整的对象结构
|
|
285
|
+
if (!proc_code.trim().endsWith('}')) {
|
|
286
|
+
proc_code = proc_code + '\n}';
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// 如果render函数存在,将其添加到组件选项中
|
|
290
|
+
if (render_function) {
|
|
291
|
+
// 在最后一个属性之前插入render函数
|
|
292
|
+
if (proc_code.trim().endsWith('}')) {
|
|
293
|
+
proc_code = proc_code.replace(/}\s*$/, '');
|
|
294
|
+
// 确保最后一个属性后有逗号
|
|
295
|
+
if (!proc_code.trim().endsWith(',')) {
|
|
296
|
+
proc_code += ',';
|
|
297
|
+
}
|
|
298
|
+
proc_code += `\n render: ${render_function}\n}`;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return `${imports}
|
|
303
|
+
export default ${proc_code};
|
|
304
|
+
`;
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* 构建默认组件
|
|
309
|
+
* @param {string} imports import语句
|
|
310
|
+
* @param {string} render_function render函数
|
|
311
|
+
* @returns {string} 默认组件代码
|
|
312
|
+
*/
|
|
313
|
+
Static.prototype._buildDefaultComp = function (imports, render_function) {
|
|
314
|
+
return `${imports}
|
|
315
|
+
export default {
|
|
316
|
+
name: 'VueComponent',
|
|
317
|
+
data() {
|
|
318
|
+
return {}
|
|
319
|
+
}${render_function ? `,\n render: ${render_function}` : ''}\n};
|
|
320
|
+
`;
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* 编译Vue组件
|
|
325
|
+
* @param {string} code Vue组件代码
|
|
326
|
+
* @returns {string} 可直接调用的JavaScript组件代码
|
|
327
|
+
*/
|
|
328
|
+
Static.prototype._compileVue = function (code) {
|
|
329
|
+
let desc = parse(code).descriptor;
|
|
330
|
+
let render_function = this._processTemplate(desc);
|
|
331
|
+
return this._processScript(desc, render_function);
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* 发送文件
|
|
336
|
+
* @param {object} ctx Koa上下文对象
|
|
337
|
+
* @param {string} path 文件路径
|
|
338
|
+
*/
|
|
339
|
+
Static.prototype._sendFile = async function (ctx, path) {
|
|
340
|
+
// koa-send会自动处理查询参数,直接传递路径
|
|
341
|
+
await send(ctx, path, this.config);
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* 运行AMD模块
|
|
346
|
+
* @param {string} path 文件路径
|
|
347
|
+
* @param {string} file_type 文件类型
|
|
348
|
+
* @returns {Promise<string>} AMD模块代码
|
|
349
|
+
*/
|
|
350
|
+
Static.prototype._runAmd = async function (path, file_type) {
|
|
351
|
+
let type = file_type || this._getType(path);
|
|
352
|
+
let code = null;
|
|
353
|
+
let text = `.${path}`.fullname(this.config.root).loadText();
|
|
354
|
+
if (text) {
|
|
355
|
+
code = await this._toAmd(type, text);
|
|
356
|
+
}
|
|
357
|
+
return code;
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* 运行Vue组件
|
|
362
|
+
* @param {string} path 文件路径
|
|
363
|
+
* @returns {string} 编译后的代码
|
|
364
|
+
*/
|
|
365
|
+
Static.prototype._runVue = function (path) {
|
|
366
|
+
let code = null;
|
|
367
|
+
let file = `.${path}`.fullname(this.config.root);
|
|
368
|
+
let text = file.loadText();
|
|
369
|
+
if (text) {
|
|
370
|
+
code = this._compileVue(text);
|
|
371
|
+
}
|
|
372
|
+
return code;
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* 返回响应
|
|
377
|
+
* @param {object} ctx Koa上下文对象
|
|
378
|
+
* @param {string} body 响应体
|
|
379
|
+
* @param {object} headers 响应头
|
|
380
|
+
* @returns {object} 响应对象
|
|
381
|
+
*/
|
|
382
|
+
Static.prototype._return = function (ctx, body, headers) {
|
|
383
|
+
ctx.body = body;
|
|
384
|
+
if (headers) {
|
|
385
|
+
ctx.set(headers);
|
|
386
|
+
}
|
|
387
|
+
return { body, headers };
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* 初始化文件监听器
|
|
392
|
+
*/
|
|
393
|
+
Static.prototype._initWatcher = function () {
|
|
394
|
+
try {
|
|
395
|
+
const chokidar = require('chokidar');
|
|
396
|
+
|
|
397
|
+
// 构建监听模式,只监听指定类型的文件
|
|
398
|
+
const watch_patterns = this.config.watch_files.map(ext =>
|
|
399
|
+
`${this.config.root}/**/*${ext}`
|
|
400
|
+
);
|
|
401
|
+
|
|
402
|
+
// 监听指定类型的文件
|
|
403
|
+
this._watcher = chokidar.watch(watch_patterns, {
|
|
404
|
+
ignored: /(^|[\/\\])\\./, // 忽略隐藏文件
|
|
405
|
+
persistent: true,
|
|
406
|
+
ignoreInitial: true // 忽略初始扫描事件
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
// 文件变化时的处理逻辑
|
|
410
|
+
this._watcher.on('all', (event, file_path) => {
|
|
411
|
+
this._handleFileChange(event, file_path);
|
|
412
|
+
});
|
|
413
|
+
} catch (error) {
|
|
414
|
+
console.error('文件监听初始化失败:', error.message);
|
|
415
|
+
}
|
|
416
|
+
};
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* 处理文件变化事件
|
|
420
|
+
* @param {string} event 事件类型
|
|
421
|
+
* @param {string} file_path 文件路径
|
|
422
|
+
*/
|
|
423
|
+
Static.prototype._handleFileChange = function (event, file_path) {
|
|
424
|
+
// 构建缓存键(相对于静态目录的路径)
|
|
425
|
+
const relative_path = file_path.replace(this.config.root, '').replace(/^[\\\/]/, '');
|
|
426
|
+
const cache_key = this.config.key_prefix + relative_path;
|
|
427
|
+
|
|
428
|
+
// 根据事件类型处理缓存
|
|
429
|
+
switch (event) {
|
|
430
|
+
case 'change':
|
|
431
|
+
case 'add':
|
|
432
|
+
// 文件修改或新增,删除缓存,下次请求时重新生成
|
|
433
|
+
this._cache.del(cache_key);
|
|
434
|
+
break;
|
|
435
|
+
case 'unlink':
|
|
436
|
+
// 文件删除,删除缓存
|
|
437
|
+
this._cache.del(cache_key);
|
|
438
|
+
break;
|
|
439
|
+
}
|
|
440
|
+
};
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* 获取响应头
|
|
444
|
+
* @param {string} file_type 文件类型
|
|
445
|
+
* @returns {object} 响应头
|
|
446
|
+
*/
|
|
447
|
+
Static.prototype._getHeaders = function (file_type) {
|
|
448
|
+
let headers = {};
|
|
449
|
+
|
|
450
|
+
// 缓存控制逻辑
|
|
451
|
+
if (this.config.max_age > 0) {
|
|
452
|
+
let cache_control = `max-age=${this.config.max_age}`;
|
|
453
|
+
if (this.config.immutable) {
|
|
454
|
+
cache_control += ', immutable';
|
|
455
|
+
}
|
|
456
|
+
headers['cache-control'] = cache_control;
|
|
457
|
+
}
|
|
458
|
+
switch (file_type) {
|
|
459
|
+
case 'js':
|
|
460
|
+
headers['content-type'] = 'application/javascript; charset=utf-8';
|
|
461
|
+
break;
|
|
462
|
+
case 'vue':
|
|
463
|
+
if (this.config.convert_amd) {
|
|
464
|
+
headers['content-type'] = 'text/html; charset=utf-8';
|
|
465
|
+
}
|
|
466
|
+
else if (this.config.compile_vue) {
|
|
467
|
+
headers['content-type'] = 'text/javascript; charset=utf-8';
|
|
468
|
+
}
|
|
469
|
+
break;
|
|
470
|
+
case 'html':
|
|
471
|
+
headers['content-type'] = 'text/html; charset=utf-8';
|
|
472
|
+
break;
|
|
473
|
+
default:
|
|
474
|
+
headers['content-type'] = 'text/plain; charset=utf-8';
|
|
475
|
+
break;
|
|
476
|
+
}
|
|
477
|
+
return headers;
|
|
478
|
+
};
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* 设置缓存
|
|
482
|
+
* @param {string} path 文件路径(包含查询参数)
|
|
483
|
+
* @param {object} ret 响应体和响应头
|
|
484
|
+
*/
|
|
485
|
+
Static.prototype._setCache = async function (path, ret) {
|
|
486
|
+
// 使用完整路径(包含查询参数)作为缓存键
|
|
487
|
+
await this._cache.set(this.config.key_prefix + path, ret, this.config.cache_age);
|
|
488
|
+
};
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* 获取文件类型
|
|
492
|
+
* @param {string} path 文件路径
|
|
493
|
+
* @returns {string} 文件类型(不含点号)
|
|
494
|
+
*/
|
|
495
|
+
Static.prototype._getType = function (path) {
|
|
496
|
+
|
|
497
|
+
// 使用mm_expand提供的extname方法获取扩展名
|
|
498
|
+
const ext = path.extname().toLowerCase();
|
|
499
|
+
if (ext) {
|
|
500
|
+
return ext.slice(1); // 去掉点号
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// 备用方法:处理多扩展名情况
|
|
504
|
+
const basename = path.basename();
|
|
505
|
+
const parts = basename.split('.');
|
|
506
|
+
|
|
507
|
+
// 如果文件名包含多个点号,取最后一个作为扩展名
|
|
508
|
+
if (parts.length > 1) {
|
|
509
|
+
return parts[parts.length - 1];
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
return ''; // 无扩展名
|
|
513
|
+
};
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* 获取缓存
|
|
517
|
+
* @param {string} path 文件路径(包含查询参数)
|
|
518
|
+
* @returns {object} 响应体和响应头
|
|
519
|
+
*/
|
|
520
|
+
Static.prototype._getCache = async function (path) {
|
|
521
|
+
// 使用完整路径(包含查询参数)作为缓存键
|
|
522
|
+
return await this._cache.get(this.config.key_prefix + path);
|
|
523
|
+
};
|
|
524
|
+
|
|
525
|
+
/**
|
|
526
|
+
* 删除缓存
|
|
527
|
+
* @param {string} path 文件路径(包含查询参数)
|
|
528
|
+
*/
|
|
529
|
+
Static.prototype._delCache = async function (path) {
|
|
530
|
+
await this._cache.del(this.config.key_prefix + path);
|
|
531
|
+
};
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* 处理缓存逻辑
|
|
535
|
+
* @param {string} url 完整URL
|
|
536
|
+
* @param {object} ctx Koa上下文对象
|
|
537
|
+
* @returns {boolean} 是否命中缓存
|
|
538
|
+
*/
|
|
539
|
+
Static.prototype._handleCache = async function (url, ctx) {
|
|
540
|
+
if (!this.config.cache) return false;
|
|
541
|
+
|
|
542
|
+
let ret = await this._getCache(url);
|
|
543
|
+
if (ret) {
|
|
544
|
+
this._return(ctx, ret.body, ret.headers);
|
|
545
|
+
return true;
|
|
546
|
+
}
|
|
547
|
+
return false;
|
|
548
|
+
};
|
|
549
|
+
|
|
550
|
+
/**
|
|
551
|
+
* 处理文件编译逻辑
|
|
552
|
+
* @param {string} path 文件路径
|
|
553
|
+
* @returns {string|null} 编译后的内容
|
|
554
|
+
*/
|
|
555
|
+
Static.prototype._handleFileCompile = function (path) {
|
|
556
|
+
if (!path.startsWith(this.config.path)) return null;
|
|
557
|
+
|
|
558
|
+
let file_type = this._getType(path);
|
|
559
|
+
let { compile_vue, convert_amd } = this.config;
|
|
560
|
+
|
|
561
|
+
if (convert_amd && this.config.files.includes('.' + file_type)) {
|
|
562
|
+
return this._runAmd(path, file_type);
|
|
563
|
+
}
|
|
564
|
+
else if (compile_vue && file_type === 'vue') {
|
|
565
|
+
return this._runVue(path);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
return null;
|
|
569
|
+
};
|
|
570
|
+
|
|
571
|
+
/**
|
|
572
|
+
* 处理编译后的文件响应
|
|
573
|
+
* @param {string} url 完整URL
|
|
574
|
+
* @param {string} path 文件路径
|
|
575
|
+
* @param {string} body 响应体
|
|
576
|
+
* @param {object} ctx Koa上下文对象
|
|
577
|
+
*/
|
|
578
|
+
Static.prototype._handleCompiledResp = async function (url, path, body, ctx) {
|
|
579
|
+
let file_type = this._getType(path);
|
|
580
|
+
let headers = this._getHeaders(file_type);
|
|
581
|
+
let ret = this._return(ctx, body, headers);
|
|
582
|
+
|
|
583
|
+
// 只有当缓存启用时才设置缓存
|
|
584
|
+
if (this.config.cache) {
|
|
585
|
+
await this._setCache(url, ret);
|
|
586
|
+
}
|
|
587
|
+
};
|
|
588
|
+
|
|
589
|
+
/**
|
|
590
|
+
* 主处理函数
|
|
591
|
+
* @param {object} ctx Koa上下文对象
|
|
592
|
+
* @param {Function} next Koa中间件函数
|
|
593
|
+
*/
|
|
594
|
+
Static.prototype.main = async function (ctx, next) {
|
|
595
|
+
const url = ctx.url; // 使用完整URL(包含查询参数)
|
|
596
|
+
const path = ctx.path; // 仅用于文件路径检查
|
|
597
|
+
|
|
598
|
+
// 检查缓存
|
|
599
|
+
if (await this._handleCache(url, ctx)) {
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
// 处理文件编译
|
|
604
|
+
let body = this._handleFileCompile(path);
|
|
605
|
+
if (body) {
|
|
606
|
+
await this._handleCompiledResp(url, path, body, ctx);
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
// 直接发送文件
|
|
611
|
+
try {
|
|
612
|
+
await this._sendFile(ctx, path);
|
|
613
|
+
}
|
|
614
|
+
catch {
|
|
615
|
+
await next();
|
|
616
|
+
}
|
|
617
|
+
};
|
|
618
|
+
|
|
619
|
+
/**
|
|
620
|
+
* 关闭文件监听器
|
|
621
|
+
*/
|
|
622
|
+
Static.prototype.closeWatcher = function () {
|
|
623
|
+
if (this._watcher) {
|
|
624
|
+
this._watcher.close();
|
|
625
|
+
this._watcher = null;
|
|
626
|
+
}
|
|
627
|
+
};
|
|
628
|
+
|
|
629
|
+
/**
|
|
630
|
+
* 创建静态文件处理中间件
|
|
631
|
+
* @param {object} config 配置
|
|
632
|
+
* @returns {Function} Koa中间件函数
|
|
633
|
+
*/
|
|
634
|
+
function statics(config) {
|
|
635
|
+
const statics = new Static(config);
|
|
636
|
+
return async function (ctx, next) {
|
|
637
|
+
await statics.main(ctx, next);
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
module.exports = { Static, statics };
|