mm_statics 1.6.1 → 1.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +142 -8
- package/package.json +3 -2
- package/static/test.txt +0 -1
package/index.js
CHANGED
|
@@ -3,29 +3,52 @@ const { EsToAmdConvert } = require('mm_es6_to_amd');
|
|
|
3
3
|
const { parse } = require('@vue/compiler-sfc');
|
|
4
4
|
const { compile } = require('@vue/compiler-dom');
|
|
5
5
|
const prettier = require('prettier');
|
|
6
|
+
const { marked } = require('marked');
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* 静态文件处理类
|
|
9
10
|
*/
|
|
10
11
|
class Static {
|
|
11
12
|
static config = {
|
|
13
|
+
// 默认索引文件
|
|
12
14
|
index: 'index.html',
|
|
15
|
+
// 默认缓存过期时间
|
|
13
16
|
max_age: 7200,
|
|
14
|
-
|
|
15
|
-
|
|
17
|
+
// 静态文件缓存键前缀
|
|
18
|
+
prefix: 'static:',
|
|
19
|
+
// 缓存过期时间
|
|
16
20
|
cache_age: 7200,
|
|
21
|
+
// 是否开启缓存
|
|
22
|
+
cache: true,
|
|
23
|
+
// 是否开启不可变缓存
|
|
17
24
|
immutable: true,
|
|
25
|
+
// 是否隐藏静态文件
|
|
18
26
|
hidden: false,
|
|
27
|
+
// 是否格式化静态文件内容
|
|
19
28
|
format: true,
|
|
29
|
+
// 是否开启文件扩展名
|
|
20
30
|
extensions: false,
|
|
31
|
+
// 是否开启Brotli压缩
|
|
21
32
|
brotli: false,
|
|
33
|
+
// 是否压缩静态文件
|
|
22
34
|
gzip: false,
|
|
35
|
+
// 静态文件根目录
|
|
23
36
|
root: './static',
|
|
37
|
+
// 是否编译Vue文件为JS
|
|
24
38
|
compile_vue: true,
|
|
39
|
+
// 是否编译Markdown文件为HTML
|
|
40
|
+
compile_md: true,
|
|
41
|
+
// 编译Markdown文件时,是否添加引入css和js文件
|
|
42
|
+
markdown_link: ['/css/common.css', '/css/markdown.css', '/js/markdown.js'],
|
|
43
|
+
// 需要转换的静态文件路径前缀
|
|
25
44
|
path: '/src',
|
|
45
|
+
// 需要转换的静态文件扩展名
|
|
26
46
|
files: ['.js', '.vue', '.html'],
|
|
47
|
+
// 是否转换ES6模块为AMD模块
|
|
27
48
|
convert_amd: true,
|
|
49
|
+
// 是否开启文件监听
|
|
28
50
|
watch: false,
|
|
51
|
+
// 监听的文件扩展名
|
|
29
52
|
watch_files: ['.js', '.css', '.html', '.vue', '.json', '.md', '.txt', '.xml']
|
|
30
53
|
};
|
|
31
54
|
|
|
@@ -371,7 +394,17 @@ Static.prototype._compileVue = async function (code) {
|
|
|
371
394
|
*/
|
|
372
395
|
Static.prototype._sendFile = async function (ctx, path) {
|
|
373
396
|
// koa-send会自动处理查询参数,直接传递路径
|
|
374
|
-
|
|
397
|
+
const config = { ...this.config };
|
|
398
|
+
|
|
399
|
+
// 为markdown文件设置正确的MIME类型
|
|
400
|
+
if (path.endsWith('.md')) {
|
|
401
|
+
config.setHeaders = (res) => {
|
|
402
|
+
// 当代码走到_sendFile路径时,说明文件没有被编译,应该返回原始markdown类型
|
|
403
|
+
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
await send(ctx, path, config);
|
|
375
408
|
};
|
|
376
409
|
|
|
377
410
|
/**
|
|
@@ -405,6 +438,93 @@ Static.prototype._runVue = async function (path) {
|
|
|
405
438
|
return code;
|
|
406
439
|
};
|
|
407
440
|
|
|
441
|
+
/**
|
|
442
|
+
* 从Markdown内容中提取第一个一级标题
|
|
443
|
+
* @param {string} text Markdown文本内容
|
|
444
|
+
* @returns {string|null} 提取的标题,未找到时返回null
|
|
445
|
+
*/
|
|
446
|
+
Static.prototype._extractMarkdownTitle = function (text) {
|
|
447
|
+
if (!text || typeof text !== 'string') {
|
|
448
|
+
return null;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// 匹配一级标题格式:# 标题
|
|
452
|
+
var title_regex = /^#\s+(.+)$/m;
|
|
453
|
+
var match = text.match(title_regex);
|
|
454
|
+
|
|
455
|
+
if (match && match[1]) {
|
|
456
|
+
return match[1].trim();
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
return null;
|
|
460
|
+
};
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* 处理Markdown文件转换
|
|
464
|
+
* @param {string} path 文件路径
|
|
465
|
+
* @returns {string|null} 转换后的HTML内容
|
|
466
|
+
*/
|
|
467
|
+
Static.prototype._runMarkdown = async function (path) {
|
|
468
|
+
let html = null;
|
|
469
|
+
let file = `.${path}`.fullname(this.config.root);
|
|
470
|
+
let text = file.loadText();
|
|
471
|
+
|
|
472
|
+
if (text) {
|
|
473
|
+
try {
|
|
474
|
+
// 使用marked将markdown转换为HTML
|
|
475
|
+
let markdown_html = marked.parse(text);
|
|
476
|
+
|
|
477
|
+
// 优先从Markdown内容中提取一级标题,后备使用文件名
|
|
478
|
+
var title_from_content = this._extractMarkdownTitle(text);
|
|
479
|
+
var title_from_file = path.split('/').pop().replace('.md', '');
|
|
480
|
+
var title = title_from_content || title_from_file;
|
|
481
|
+
// 构建外部资源链接
|
|
482
|
+
let links = '';
|
|
483
|
+
if (this.config.markdown_link && Array.isArray(this.config.markdown_link)) {
|
|
484
|
+
this.config.markdown_link.forEach(path => {
|
|
485
|
+
if (path.endsWith('.css')) {
|
|
486
|
+
links += ` <link rel="stylesheet" href="${path}">\n`;
|
|
487
|
+
} else if (path.endsWith('.js')) {
|
|
488
|
+
links += ` <script src="${path}"></script>\n`;
|
|
489
|
+
}
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// 构建完整的HTML结构
|
|
494
|
+
html = `<!DOCTYPE html>
|
|
495
|
+
<html>
|
|
496
|
+
<head>
|
|
497
|
+
<meta charset="UTF-8">
|
|
498
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
499
|
+
<title>${title}</title>
|
|
500
|
+
${links}
|
|
501
|
+
</head>
|
|
502
|
+
<body>
|
|
503
|
+
${markdown_html}
|
|
504
|
+
</body>
|
|
505
|
+
</html>`;
|
|
506
|
+
|
|
507
|
+
// 如果启用了格式化,使用prettier格式化完整的HTML
|
|
508
|
+
if (this.config.format) {
|
|
509
|
+
try {
|
|
510
|
+
html = prettier.format(html, {
|
|
511
|
+
parser: 'html',
|
|
512
|
+
printWidth: 100,
|
|
513
|
+
tabWidth: 2
|
|
514
|
+
});
|
|
515
|
+
} catch (error) {
|
|
516
|
+
console.warn('HTML格式化失败,使用原始HTML:', error.message);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
} catch (error) {
|
|
520
|
+
console.error('Markdown转换错误:', error);
|
|
521
|
+
return null;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
return html;
|
|
526
|
+
};
|
|
527
|
+
|
|
408
528
|
/**
|
|
409
529
|
* 返回响应
|
|
410
530
|
* @param {object} ctx Koa上下文对象
|
|
@@ -456,7 +576,7 @@ Static.prototype._initWatcher = function () {
|
|
|
456
576
|
Static.prototype._handleFileChange = function (event, file_path) {
|
|
457
577
|
// 构建缓存键(相对于静态目录的路径)
|
|
458
578
|
const relative_path = file_path.replace(this.config.root, '').replace(/^[\\\/]/, '');
|
|
459
|
-
const cache_key = this.config.
|
|
579
|
+
const cache_key = this.config.prefix + relative_path;
|
|
460
580
|
|
|
461
581
|
// 根据事件类型处理缓存
|
|
462
582
|
switch (event) {
|
|
@@ -503,6 +623,14 @@ Static.prototype._getHeaders = function (file_type) {
|
|
|
503
623
|
case 'html':
|
|
504
624
|
headers['content-type'] = 'text/html; charset=utf-8';
|
|
505
625
|
break;
|
|
626
|
+
case 'md':
|
|
627
|
+
if (this.config.compile_md) {
|
|
628
|
+
headers['content-type'] = 'text/html; charset=utf-8';
|
|
629
|
+
}
|
|
630
|
+
else {
|
|
631
|
+
headers['content-type'] = 'text/plain; charset=utf-8';
|
|
632
|
+
}
|
|
633
|
+
break;
|
|
506
634
|
default:
|
|
507
635
|
headers['content-type'] = 'text/plain; charset=utf-8';
|
|
508
636
|
break;
|
|
@@ -517,7 +645,7 @@ Static.prototype._getHeaders = function (file_type) {
|
|
|
517
645
|
*/
|
|
518
646
|
Static.prototype._setCache = async function (path, ret) {
|
|
519
647
|
// 使用完整路径(包含查询参数)作为缓存键
|
|
520
|
-
await this._cache.set(this.config.
|
|
648
|
+
await this._cache.set(this.config.prefix + path, ret, this.config.cache_age);
|
|
521
649
|
};
|
|
522
650
|
|
|
523
651
|
/**
|
|
@@ -552,7 +680,7 @@ Static.prototype._getType = function (path) {
|
|
|
552
680
|
*/
|
|
553
681
|
Static.prototype._getCache = async function (path) {
|
|
554
682
|
// 使用完整路径(包含查询参数)作为缓存键
|
|
555
|
-
return await this._cache.get(this.config.
|
|
683
|
+
return await this._cache.get(this.config.prefix + path);
|
|
556
684
|
};
|
|
557
685
|
|
|
558
686
|
/**
|
|
@@ -560,7 +688,7 @@ Static.prototype._getCache = async function (path) {
|
|
|
560
688
|
* @param {string} path 文件路径(包含查询参数)
|
|
561
689
|
*/
|
|
562
690
|
Static.prototype._delCache = async function (path) {
|
|
563
|
-
await this._cache.del(this.config.
|
|
691
|
+
await this._cache.del(this.config.prefix + path);
|
|
564
692
|
};
|
|
565
693
|
|
|
566
694
|
/**
|
|
@@ -597,7 +725,9 @@ Static.prototype._handleFileCompile = async function (path) {
|
|
|
597
725
|
else if (compile_vue && file_type === 'vue') {
|
|
598
726
|
return await this._runVue(path);
|
|
599
727
|
}
|
|
600
|
-
|
|
728
|
+
else if (this.config.compile_md && file_type === 'md') {
|
|
729
|
+
return await this._runMarkdown(path);
|
|
730
|
+
}
|
|
601
731
|
return null;
|
|
602
732
|
};
|
|
603
733
|
|
|
@@ -610,6 +740,10 @@ Static.prototype._handleFileCompile = async function (path) {
|
|
|
610
740
|
*/
|
|
611
741
|
Static.prototype._handleCompiledResp = async function (url, path, body, ctx) {
|
|
612
742
|
let file_type = this._getType(path);
|
|
743
|
+
// 只有当启用了Markdown转换时,才将markdown文件类型设置为HTML
|
|
744
|
+
if (file_type === 'md' && this.config.compile_md) {
|
|
745
|
+
file_type = 'html';
|
|
746
|
+
}
|
|
613
747
|
let headers = this._getHeaders(file_type);
|
|
614
748
|
let ret = this._return(ctx, body, headers);
|
|
615
749
|
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mm_statics",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.2",
|
|
4
4
|
"description": "这是超级美眉statics函数模块,用于web服务端statics缓存",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
7
|
-
"test": "
|
|
7
|
+
"test": "mocha tests/index.js",
|
|
8
8
|
"tests": "mocha tests/*.js"
|
|
9
9
|
},
|
|
10
10
|
"repository": {
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
"chokidar": "^5.0.0",
|
|
42
42
|
"koa": "^3.1.1",
|
|
43
43
|
"koa-send": "^5.0.1",
|
|
44
|
+
"marked": "^17.0.1",
|
|
44
45
|
"mm_cache": "^1.4.8",
|
|
45
46
|
"mm_es6_to_amd": "^1.4.7",
|
|
46
47
|
"prettier": "^3.7.4"
|
package/static/test.txt
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
This is a test static file for mm_statics module.
|