Markdown 导出 Word 时图片丢失?我们是如何解决跨域图片和 WebP 兼容性问题的
在开发 md-to.com 的 Markdown 转 Word 功能时,我们遇到了一个让人头疼的问题:用户粘贴的 Markdown 里的图片,导出成 Word 后全部丢失了。
这个问题最终涉及两个层面:浏览器安全策略(CORS)和图片格式兼容性(WebP)。本文详细记录问题的排查过程和最终解决方案。
问题现象
用户在编辑器中粘贴了这样的 Markdown:


预览区域图片正常显示,但导出的 Word 文件中两张图片都不见了,只剩下 [编辑器界面]、[网站图标] 这样的占位文字。
问题一:WebP 格式不被 Word 支持
原因分析
我们使用 docx 库生成 Word 文档。这个库的 ImageRun 组件只支持四种图片格式:
type DocxImageType = 'jpg' | 'png' | 'gif' | 'bmp';
而现代网站大量使用 WebP 格式(体积比 PNG 小 25-35%)。当我们的代码检测到不支持的格式时,直接抛出了 Unsupported image type 错误,图片就丢失了。
解决方案:Canvas 自动转换
浏览器原生支持渲染 WebP,所以我们可以利用 Canvas API 把 WebP 转成 PNG:
function loadImageViaCanvas(
source: Blob | string,
): Promise<{ blob: Blob; width: number; height: number }> {
return new Promise((resolve, reject) => {
const img = new Image();
const isBlobSource = source instanceof Blob;
if (!isBlobSource) {
img.crossOrigin = 'anonymous';
}
const blobUrl = isBlobSource ? URL.createObjectURL(source) : null;
img.onload = () => {
if (blobUrl) URL.revokeObjectURL(blobUrl);
const { naturalWidth: width, naturalHeight: height } = img;
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d')!;
ctx.drawImage(img, 0, 0);
canvas.toBlob(
(pngBlob) => {
if (pngBlob) resolve({ blob: pngBlob, width, height });
else reject(new Error('Canvas toBlob failed'));
},
'image/png',
);
};
img.onerror = () => reject(new Error('Failed to load image'));
img.src = blobUrl ?? (source as string);
});
}
这个函数接受两种输入:
- Blob:用于已经 fetch 到的图片做纯格式转换
- URL 字符串:用于 CORS 兜底(后面详述)
无论输入是什么格式(WebP、AVIF 等),输出始终是 PNG——Word 完美支持。
问题二:CORS 阻止获取跨域图片数据
解决了 WebP 问题后,我们发现另一个更棘手的问题:跨域图片完全无法获取。
原因分析
我们的代码使用 fetch() 获取图片数据:
const response = await fetch(imageUrl);
const blob = await response.blob();
当图片 URL 和网站不在同一域名(比如网站是 md-to.com,图片在 example.com)时,浏览器的 CORS(跨源资源共享) 安全策略会拦截请求:
Access to fetch at 'https://example.com/image.png'
from origin 'https://md-to.com' has been blocked by CORS policy
为什么预览能显示但导出不行?
这是因为浏览器对不同操作有不同的安全级别:
| 操作 | 是否允许跨域 | 原因 |
|---|---|---|
<img src="外部URL"> 渲染 | ✅ 允许 | 浏览器渲染图片不需要 CORS |
fetch() 获取数据 | ❌ 拦截 | JS 读取二进制数据需要 CORS |
<img crossOrigin> + Canvas 提取 | ❌ 拦截 | Canvas 被”污染”后无法导出 |
fetch({mode: 'no-cors'}) | ❌ 无法读取 | 返回空 body |
预览区用的是 <img> 标签(允许跨域渲染),而导出需要用 JS 获取图片的二进制数据(被 CORS 拦截)。这就是为什么”看得到但导不出”。
为什么 Base64 也救不了?
有人可能会想:“把图片转成 Base64 不就行了?”
但 Base64 只是一种编码格式,不是获取数据的手段。问题不在于”存成什么格式”,而在于”浏览器不让你拿到原始字节”。无论你想编码成 Base64、ArrayBuffer 还是任何形式,第一步都是要拿到数据——而这一步被 CORS 彻底封死了。
解决方案:服务端图片代理
既然浏览器端无解,就让服务器来请求图片。服务器没有 CORS 限制,可以自由请求任何 URL。
我们创建了一个轻量级的 Edge Function 作为图片代理:
浏览器 → /api/image-proxy?url=https://example.com/image.png → 我们的服务器 → example.com
↑ 没有CORS限制
代理函数(部署在 Edge):
export async function onRequest({ request }) {
const { searchParams } = new URL(request.url);
const target = searchParams.get('url');
// 安全校验:只允许 http(s) 协议、只代理图片类型、限制大小
const upstream = await fetch(target);
const contentType = upstream.headers.get('content-type');
return new Response(await upstream.arrayBuffer(), {
headers: {
'Content-Type': contentType,
'Cache-Control': 'public, max-age=86400',
'Access-Control-Allow-Origin': '*',
},
});
}
客户端自动路由:
function resolveImageSrc(src: string): string {
const resolved = new URL(src, window.location.href).toString();
// 跨域图片走代理
if (new URL(resolved).origin !== window.location.origin) {
return `/api/image-proxy?url=${encodeURIComponent(resolved)}`;
}
return resolved;
}
这样所有跨域图片请求变成了同域请求,浏览器不再拦截。
最终架构
Markdown 中的图片 URL
│
▼
resolveImageSrc()
│
├── 同域图片 → 直接 fetch
│
└── 跨域图片 → /api/image-proxy?url=xxx
│
▼
Edge Function 转发
│
▼
获取到 Blob
│
▼
getDocxImageType() 检查格式
│
┌─────────┴──────────┐
▼ ▼
jpg/png/gif/bmp webp/avif/其他
直接嵌入 Word Canvas 转 PNG 后嵌入
开发环境
Vite dev server 中添加了同路径的代理中间件,保证开发和生产行为一致。
安全措施
图片代理不是无限制的开放代理:
- 只允许
http://和https://协议 - 只放行图片 Content-Type(png/jpeg/gif/bmp/webp/avif/svg)
- 单张图片大小限制 10 MB
- 响应带 24 小时缓存
总结
| 问题 | 原因 | 方案 |
|---|---|---|
| WebP 图片在 Word 中不显示 | docx 库不支持 WebP 格式 | Canvas API 转为 PNG |
| 跨域图片完全无法获取 | 浏览器 CORS 安全策略 | 服务端 Edge Function 代理 |
| 开发环境图片也失败 | localhost → 外部域名是跨域 | Vite dev server 代理中间件 |
核心教训:浏览器的安全模型区分”渲染”和”数据访问”。<img> 能显示跨域图片不代表 JS 能读取它的数据。遇到需要在客户端处理跨域资源的场景,服务端代理几乎是唯一可靠的方案。
想试试效果?访问 md-to.com Markdown 转 Word,粘贴带有外部图片的 Markdown,导出看看。
相关工具
免费 Markdown 转 Word 转换器 - 在线将 MD 转换为 DOCX
免费在线 Markdown 转 Word 转换器,实时预览,一键下载 DOCX 文档。完美保留格式,无需注册,本地处理保护隐私。立即体验!
免费 Word 转 Markdown 转换器 - 在线将 DOCX 转换为 MD
免费在线将 Word 文档转换为 Markdown。DOCX 转 MD 转换器完美保留标题、表格、列表和格式。无需注册,立即体验!
免费 Markdown 转 LaTeX 工具 - 在线将 MD 转换为 TeX
在线免费将 Markdown 转换为 LaTeX。将 MD 文档转换为 TeX 格式,适合学术论文和技术文档。无需注册!