feat: v0.2.5 — 内置语法高亮 + exportPDF + benchmark + CI 并行
- 新增 src/highlight.ts 零依赖轻量高亮器:13 种语言,先转义后 着色保证安全,输出 me-hl-* 类,MeEditor.highlight 即插即用 - exportTool 新增 editor.exportPDF()(隐藏 iframe + window.print), exportHTML/exportPDF 共用 HTML 构建逻辑 - imagePaste 支持 maxSizeKB 限制,超限 toast 警告 - npm run bench 性能基准:parseMarkdown 1MB ≈ 51ms - CI 测试拆分为 parser/core/rest 三个并行 job + 跨版本 verify job - docs.html/README 补齐新 API、事件、高亮文档;取消跟踪 dist 产物; 版本 0.2.4 → 0.2.5,测试 725 → 768
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* MetonaEditor 性能基准 — 解析器吞吐测试
|
||||
* 用法: npm run build && npm run bench
|
||||
*/
|
||||
|
||||
const MeEditor = require('../dist/metona-editor.cjs');
|
||||
const { parseMarkdown } = MeEditor;
|
||||
|
||||
const buildSample = (kb) => {
|
||||
const chunk = [
|
||||
'# 性能基准文档',
|
||||
'',
|
||||
'这是一个 **粗体** 与 *斜体* 的段落,包含 `inline code` 和 [链接](https://example.com)。',
|
||||
'',
|
||||
'- 列表项一',
|
||||
'- 列表项二',
|
||||
' - 嵌套项',
|
||||
' - 嵌套项二',
|
||||
'- 列表项三',
|
||||
'',
|
||||
'1. 有序一',
|
||||
'2. 有序二',
|
||||
'',
|
||||
'> 引用内容',
|
||||
'> 继续引用',
|
||||
'',
|
||||
'```js',
|
||||
'function hello(name) {',
|
||||
' const msg = `Hello, ${name}!`;',
|
||||
' return msg;',
|
||||
'}',
|
||||
'```',
|
||||
'',
|
||||
'| 列一 | 列二 | 列三 |',
|
||||
'| --- | :---: | ---: |',
|
||||
'| A | B | C |',
|
||||
'| D | E | F |',
|
||||
'',
|
||||
'---',
|
||||
'',
|
||||
'引用链接:[文档][ref]',
|
||||
'',
|
||||
'[ref]: https://example.com/docs "参考"',
|
||||
'',
|
||||
'脚注示例[^1]',
|
||||
'',
|
||||
'[^1]: 脚注内容',
|
||||
'',
|
||||
'## 二级标题',
|
||||
'',
|
||||
'$$E = mc^2$$',
|
||||
'',
|
||||
'H~2~O 与 x^2^,还有 :rocket: emoji。',
|
||||
'',
|
||||
].join('\n');
|
||||
const repeat = Math.max(1, Math.ceil((kb * 1024) / chunk.length));
|
||||
return chunk.repeat(repeat);
|
||||
};
|
||||
|
||||
const median = (arr) => {
|
||||
const s = [...arr].sort((a, b) => a - b);
|
||||
const mid = Math.floor(s.length / 2);
|
||||
return s.length % 2 ? s[mid] : (s[mid - 1] + s[mid]) / 2;
|
||||
};
|
||||
|
||||
const benchmark = (label, fn, iterations = 5) => {
|
||||
// warmup
|
||||
fn();
|
||||
const times = [];
|
||||
for (let i = 0; i < iterations; i++) {
|
||||
const t0 = process.hrtime.bigint();
|
||||
fn();
|
||||
const t1 = process.hrtime.bigint();
|
||||
times.push(Number(t1 - t0) / 1e6);
|
||||
}
|
||||
const ms = median(times);
|
||||
return { label, ms: ms.toFixed(1), opsPerSec: (1000 / ms).toFixed(1) };
|
||||
};
|
||||
|
||||
console.log('MetonaEditor 性能基准');
|
||||
console.log('=====================');
|
||||
console.log(`版本: ${MeEditor.VERSION}`);
|
||||
console.log('');
|
||||
|
||||
for (const size of [64, 256, 1024]) {
|
||||
const sample = buildSample(size);
|
||||
const r = benchmark(`parseMarkdown ${size}KB (${sample.length.toLocaleString()} chars)`, () => {
|
||||
const html = parseMarkdown(sample);
|
||||
if (!html) throw new Error('empty output');
|
||||
});
|
||||
console.log(` ${r.label}: ${r.ms} ms (${r.opsPerSec} ops/s)`);
|
||||
}
|
||||
|
||||
console.log('');
|
||||
|
||||
// 语法高亮基准
|
||||
try {
|
||||
const code = Array.from({ length: 200 }, (_, i) =>
|
||||
`function f${i}(a, b) { const s = "str${i}"; // comment\n return a + b + ${i}; }`,
|
||||
).join('\n');
|
||||
const r = benchmark('highlight 200 functions (js)', () => {
|
||||
MeEditor.highlight(code, 'js');
|
||||
});
|
||||
console.log(` ${r.label}: ${r.ms} ms (${r.opsPerSec} ops/s)`);
|
||||
} catch (_) {
|
||||
console.log(' (highlight benchmark skipped)');
|
||||
}
|
||||
|
||||
console.log('');
|
||||
console.log('完成。');
|
||||
Reference in New Issue
Block a user