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 格式,適合學術論文和技術文件。無需註冊!