不用 Typora 的 html 导出功能,手搓纯 HTML5 转换器
<h1 id="不用-typora-的-html-导出功能手搓纯-html5-转换器">不用 Typora 的 html 导出功能,手搓纯 HTML5 转换器</h1><p>原创 夏群林 2025.12.23</p>
<h2 id="一缘起">一、缘起</h2>
<p>我日常工作使用 Typora, 一款很好的 Markdown 编辑器。建网站,写博文,用 Typora 打底稿。然后导出成 html 格式文件,所见即所得,一个静态网站就成了!</p>
<p>不过,Typora 自带的 HTML 导出功能存在核心缺陷:夹带 Typora编辑器 UI 冗余代码、HTML 语义不合规、依赖非标准化样式体系、导出文件体积大、可维护性差。基于此,我们手搓纯HTML5转换器,核心目标:</p>
<ol>
<li>剔除 UI 冗余,仅保留 Markdown 内容渲染逻辑;</li>
<li>纯 ES6+ 原生实现,无第三方库依赖;</li>
<li>符合HTML5语义标准;</li>
<li>还原 Typora 纯 Markdown 内容的渲染风格。</li>
</ol>
<h2 id="二纯-es6-实现无第三方库">二、纯 ES6+ 实现,无第三方库</h2>
<h3 id="21-核心优势">2.1 核心优势</h3>
<ul>
<li>轻量化:产物仅包含核心业务逻辑,无冗余依赖代码,体积较 Typora 导出缩减 80% 以上;</li>
<li>可控性:全流程掌控 Markdown 语法解析、HTML 重构、样式生成;</li>
<li>无依赖风险:避免第三方库版本迭代、兼容性问题,转换器长期稳定可用。</li>
</ul>
<h3 id="22-es6-核心特性应用">2.2 ES6+ 核心特性应用</h3>
<table>
<thead>
<tr>
<th>ES6+特性</th>
<th>应用场景</th>
<th>价值</th>
</tr>
</thead>
<tbody>
<tr>
<td>模块化(ES Module)</td>
<td>拆分解析、工具、入口模块</td>
<td>解耦代码,便于维护</td>
</tr>
<tr>
<td>模板字符串</td>
<td>HTML/CSS生成</td>
<td>替代字符串拼接,提升可读性</td>
</tr>
<tr>
<td>正则表达式增强</td>
<td>Markdown解析、合规修复</td>
<td>精准匹配语法,解决反向引用冲突</td>
</tr>
</tbody>
</table>
<h2 id="三整体架构设计">三、整体架构设计</h2>
<h3 id="31-目录结构">3.1 目录结构</h3>
<pre><code>md2html/
├── index.html // 交互界面(MD输入、HTML预览/导出)
├── js/
│ ├── app.js // 入口(DOM操作、转换/下载逻辑)
│ ├── utils.js // 工具函数(格式化、提示)
│ └── marked.es6.js // MD核心解析(合规修复核心)
└── css/
├── style.css // 转换器界面样式
├── concise.css // 简洁版MD渲染样式
└── typora.css // Typora纯内容渲染样式
</code></pre>
<h3 id="32-核心流程">3.2 核心流程</h3>
<div class="mermaid">flowchart TD
A --> B
B --> C
C --> D
D --> E[预览/导出纯HTML5文件]
</div><h2 id="四核心技术点">四、核心技术点</h2>
<h3 id="41-基于正则的-markdown-语法解析">4.1 基于正则的 Markdown 语法解析</h3>
<p>放弃第三方库,通过精准的正则表达式匹配Markdown核心语法,是实现“纯原生”的基础。</p>
<p>核心思路为:按“块级语法(标题、列表、表格)→ 行内语法(加粗、链接、代码)”的顺序解析,确保语法嵌套的正确性。</p>
<p>示例:表格语法解析正则</p>
<pre><code class="language-javascript">// 解析表格
const parseTables = (md, options) => {
if (!md.includes('|') || md.includes('<table>')) return md;
const tableRegex = /^(\|.*\|)\n(\|[-:| ]*\|)\n((?:\|.*\|\n?)+)/gm;
return md.replace(tableRegex, (match, headerLine, separatorLine, bodyLines) => {
const alignments = separatorLine.split('|')
.filter(cell => cell.trim() !== '')
.map(cell => {
const trimCell = cell.trim();
return trimCell.startsWith(':') && trimCell.endsWith(':') ? 'center' :
trimCell.startsWith(':') ? 'left' :
trimCell.endsWith(':') ? 'right' : 'left';
});
const headerCells = headerLine.split('|').filter(cell => cell.trim() !== '');
let headerHtml = '<thead><tr>';
headerCells.forEach((cell, index) => {
const align = alignments || 'left';
const inlineContent = parseInlineOnly(cell, options);
headerHtml += `<th class="text-${align}">${inlineContent}</th>`;
});
headerHtml += '</tr></thead>';
const bodyRows = bodyLines.split('\n').filter(row => row.trim() !== '');
let bodyHtml = '<tbody>';
bodyRows.forEach(row => {
const cells = row.split('|').filter(cell => cell.trim() !== '');
bodyHtml += '<tr>';
cells.forEach((cell, index) => {
const align = alignments || 'left';
const inlineContent = parseInlineOnly(cell, options);
bodyHtml += `<td class="text-${align}">${inlineContent}</td>`;
});
bodyHtml += '</tr>';
});
bodyHtml += '</tbody>';
return `<table>${headerHtml}${bodyHtml}</table>`;
});
};
</code></pre>
<h3 id="42-html语义合规化重构">4.2 HTML语义合规化重构</h3>
<p>例如,Typora 导出的<code><ul>/<ol></code>被<code><p></code>包裹,换行生成空<code><p></code>标签,违反HTML语义规范。</p>
<p>核心逻辑:解析列表时仅生成<code><li></code>子元素,段落解析排除列表标签,避免误判。</p>
<pre><code class="language-javascript">// marked.es6.js 核心修复代码
const parseLists = (md) => {
// 生成纯<li>,移除多余换行
let listItems = md.replace(/^( {0,3})(-|\*|\+)\s+(.*?$)/gm, (_, indent, marker, text) => {
const inlineContent = parseInlineOnly(text).replace(/\n+/g, ' ');
return `${indent}<li>${inlineContent}</li>`;
});
// 包裹列表容器,清理换行干扰
return listItems.replace(/((?: {0,3}<li>[\s\S]*?<\/li>\s*)+)/gm, (_, items) => {
const cleanItems = items.replace(/\n+/g, '').replace(/>\s+</g, '><');
return /[-*+]/.test(_) ? `<ul>${cleanItems}</ul>` : `<ol>${cleanItems}</ol>`;
});
};
// 段落解析排除列表标签,避免空<p>
const parseParagraphs = (md) => {
const paraRegex = /^(?!<(ul|ol|li)>)(?!<\/(ul|ol|li)>)([^<\n]+?)(?=\n{2,}|$)/gm;
return md.replace(paraRegex, (_, __, ___, content) => {
const trimmed = content?.trim().replace(/\n+/g, ' ') || '';
return trimmed ? `<p>${trimmed}</p>` : '';
});
};
</code></pre>
<h3 id="43-仅保留-typora--内容渲染核心样式">4.3 仅保留 Typora内容渲染核心样式</h3>
<p>Typora 导出样式包含编辑器 UI 规则,冗余且非标准化,予以剥离。基于Typora原生的Markdown渲染风格,构建模块化的CSS体系:</p>
<pre><code class="language-css">/* typora.css 核心代码 */
:root {
--typora-text-color: #333;
--typora-heading-color: #2c3e50;
--typora-code-bg: #f8f8f8;
--typora-table-border: #ddd;
}
/* 暗黑模式适配 */
@media (prefers-color-scheme: dark) {
:root {
--typora-text-color: #e0e0e0;
--typora-code-bg: #2d2d2d;
--typora-table-border: #444;
}
}
/* 列表合规兜底 */
ul>li, ol>li { margin: 0.4em 0; }
ul>br, ol>br, ul>p, ol>p { display: none; }
/* 表格对齐类 */
.text-left { text-align: left !important; }
.text-center { text-align: center !important; }
.text-right { text-align: right !important; }
</code></pre>
<h3 id="44-原生-es6-模块化整合">4.4 原生 ES6+ 模块化整合</h3>
<pre><code class="language-javascript">// app.js 核心逻辑
import { parseMarkdown } from './marked.es6.js';
import { formatHtml, showAlert } from './utils.js';
// 转换逻辑
dom.convertBtn.addEventListener('click', () => {
const mdContent = dom.mdInput.value.trim();
if (!mdContent) return showAlert('请输入Markdown内容');
// 核心流程:解析(合规)→ 格式化 → 预览/导出
const htmlFragment = parseMarkdown(mdContent); // 生成即合规
const finalHtml = formatHtml(`<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><link rel="stylesheet" href="./css/typora.css"></head>
<body>${htmlFragment}</body></html>`);
dom.preview.innerHTML = htmlFragment;
dom.htmlCode.value = finalHtml;
dom.downloadBtn.disabled = false;
});
</code></pre>
<h2 id="五使用说明">五、使用说明</h2>
<ol>
<li>按指定目录结构存放文件,确保<code>js/</code>和<code>css/</code>子文件夹路径正确;</li>
<li>用支持ES6模块的浏览器(Chrome/Firefox/Edge)打开<code>index.html</code>;</li>
<li>输入Markdown内容,勾选“保留 Typora 原格式”,点击“转换为 HTML5”;</li>
<li>预览区查看效果,点击“下载 HTML 文件”获取符合 HTML5 规范的文件。</li>
</ol>
<h2 id="六总结">六、总结</h2>
<ol>
<li>核心价值:精准解决 Typora 导出的编辑器UI样式冗余痛点,生成纯内容的符合 HTML5 规范的文件;</li>
<li>技术亮点:纯 ES6+ 原生实现;</li>
<li>优势:轻量化、可控性强、跨环境兼容,可直接部署使用。</li>
</ol>
<p>本方案源代码开源,按照 MIT 协议许可。地址: xiaql/md2html5: A typora to pure html5 converter</p><br><br>
来源:https://www.cnblogs.com/zhally/p/19389248
頁:
[1]