From c789262d5c7b7b06dedcf04eb0e9d4e1fc36250b Mon Sep 17 00:00:00 2001 From: QIU Date: Mon, 12 Jan 2026 14:41:32 +0000 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E8=87=AA=E5=8A=A8=E5=8C=96?= =?UTF-8?q?=E6=8F=92=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astro.config.mjs | 2 + rehype-image-optimization.mjs | 74 +++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 rehype-image-optimization.mjs diff --git a/astro.config.mjs b/astro.config.mjs index f246ddb..6597c0e 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -24,6 +24,7 @@ import { remarkExcerpt } from "./src/plugins/remark-excerpt.js"; import { remarkReadingTime } from "./src/plugins/remark-reading-time.mjs"; import { pluginCustomCopyButton } from "./src/plugins/expressive-code/custom-copy-button.js"; import rehypeExternalLinks from 'rehype-external-links'; +import { rehypeImageOptimization } from "./rehype-image-optimization.mjs"; // https://astro.build/config export default defineConfig({ @@ -114,6 +115,7 @@ export default defineConfig({ parseDirectiveNode, ], rehypePlugins: [ + rehypeImageOptimization, rehypeKatex, rehypeSlug, [ diff --git a/rehype-image-optimization.mjs b/rehype-image-optimization.mjs new file mode 100644 index 0000000..cd5df30 --- /dev/null +++ b/rehype-image-optimization.mjs @@ -0,0 +1,74 @@ +import { visit } from 'unist-util-visit'; + +export function rehypeImageOptimization() { + return (tree) => { + // 1. 严谨写法:只遍历 element 类型的节点 + visit(tree, 'element', (node) => { + + // 2. 防御检查:必须是 img 标签,且必须有属性 + if (node.tagName !== 'img' || !node.properties) { + return; + } + + const src = node.properties.src; + + // 3. 防御检查:没有 src 属性直接跳过 + if (!src) return; + + let urlObj; + try { + // 4. 防御检查:尝试解析 URL,如果是相对路径会报错进入 catch + urlObj = new URL(src); + } catch (e) { + // 解析失败(相对路径),只加懒加载,不处理链接 + node.properties.loading = 'lazy'; + node.properties.decoding = 'async'; + return; + } + + // ============================================================ + // 业务逻辑:只处理 img.mckero.com + // ============================================================ + if (urlObj.hostname !== 'img.mckero.com') { + node.properties.loading = 'lazy'; + node.properties.decoding = 'async'; + return; + } + + // ============================================================ + // 业务逻辑:跳过 WebP/AVIF + // ============================================================ + const pathname = urlObj.pathname.toLowerCase(); + if (pathname.endsWith('.webp') || pathname.endsWith('.avif')) { + node.properties.loading = 'lazy'; + node.properties.decoding = 'async'; + return; + } + + // ============================================================ + // 业务逻辑:构造 Worker 链接 + // ============================================================ + const generateWorkerUrl = (originalSrc, width) => { + const workerUrl = new URL('https://opt.mckero.com/'); + workerUrl.searchParams.set('url', originalSrc); + if (width) { + workerUrl.searchParams.set('width', width); + } + return workerUrl.toString(); + }; + + // 修改 src (默认 800px) + node.properties.src = generateWorkerUrl(src, 800); + + // 生成 srcset (500px 手机, 800px 电脑) + const srcSmall = generateWorkerUrl(src, 500); + const srcMedium = generateWorkerUrl(src, 800); + node.properties.srcset = `${srcSmall} 500w, ${srcMedium} 800w`; + + // 补充标准属性 + node.properties.sizes = '(max-width: 600px) 500px, 800px'; + node.properties.loading = 'lazy'; + node.properties.decoding = 'async'; + }); + }; +} \ No newline at end of file