1004 字
5 分钟
fuwari(Astro) 添加评论功能实践记录

背景#

Fuwari 是基于 Astro + Svelte 的博客主题,默认没有评论模块。为了保留静态站点的性能与部署体验,我选择集成自建的 Twikoo 服务。本篇记录最终方案、遇到的问题和改善建议。

目标与约束#

  • 运行环境:Astro + Fuwari(Svelte 渲染层),部署到 Cloudflare Pages。
  • 评论服务:Twikoo(腾讯云云函数部署,兼容 LeanCloud)。
  • 要求
    • 首次进入文章页即可渲染评论,无需刷新;
    • 不影响静态渲染,尽量避免打破 Astro 的 islands 架构;
    • 支持暗色模式、移动端适配;
    • 保留无评论时的占位文案,避免布局突兀。

Twikoo 部署概要#

  1. 准备数据库:我使用腾讯云云函数 + 云开发环境 env-xxx
  2. 部署云函数:按照官方指引上传 twikoo,记录部署后的 envId
  3. 配置域名:自定义域名 + HTTPS,方便前端调用。
  4. 后台初始化:在云开发控制台中创建管理员用户、配置默认主题色。

如果不想维护服务,可选择第三方托管,但需关注隐私与速率限制。

在 Fuwari 中引入评论组件#

Fuwari 的文章详情使用 Svelte 组件 Post.astro。为了保持 SSR 与事件绑定,我采用“前端懒加载”的做法:

  1. 新增 Twikoo 入口组件 src/components/post/TwikooComments.svelte
<script lang="ts">
  import { onMount } from 'svelte';

  export let envId: string;
  export let path: string;
  export let darkSelector = 'html[data-theme="dark"]';

  let container: HTMLDivElement | null = null;
  let twikooLoaded = false;

  const loadTwikoo = async () => {
    if (twikooLoaded || !container) return;
    twikooLoaded = true;

    const { default: twikoo } = await import('twikoo');
    await twikoo.init({
      envId,
      el: container,
      path,
      lang: 'zh-CN',
      theme: matchMedia(`(prefers-color-scheme: dark)`).matches ? 'dark' : 'light',
    });
  };

  onMount(() => {
    loadTwikoo();

    const observer = new MutationObserver(() => {
      const isDark = document.querySelector(darkSelector);
      container?.setAttribute('data-twikoo-theme', isDark ? 'dark' : 'light');
    });

    observer.observe(document.documentElement, {
      attributes: true,
      attributeFilter: ['data-theme'],
    });

    return () => observer.disconnect();
  });
</script>

<div bind:this={container} class="tk-container">
  <p class="tk-loading">评论加载中...</p>
</div>

<style>
  .tk-container {
    margin-top: 3rem;
  }

  .tk-loading {
    text-align: center;
    color: var(--muted-foreground, #888);
    font-size: 14px;
  }
</style>
  1. Post.astro 中注入组件
---
import TwikooComments from '@/components/post/TwikooComments.svelte';

const { frontmatter } = Astro.props;
const twikooEnvId = Astro.site ? 'your-env-id' : import.meta.env.PUBLIC_TWIKOO_ENV_ID;
---

<article>
  <!-- 文章内容 -->
</article>

<TwikooComments client:idle envId={twikooEnvId} path={Astro.url.pathname} />
  • client:idle 确保组件在空闲时加载,避免阻塞首屏。
  • path 传入文章路径用于评论定位。
  • envId 可以通过环境变量注入。

解决“首次进入不渲染”的问题#

一开始我把 Twikoo 的初始脚本直接写在 client:load 的组件里,结果在首次导航到文章页时不会执行,必须刷新才出现。这与 Astro 的 SPA 切换有关:页面通过 @astrojs/view-transitions 保留 DOM,导致 Twikoo 初始化被错过。

最终改进方案:

  1. 监听路由切换:Fuwari 使用客户端导航,需在 onMount 中关心 Astro.url.pathname
  2. 使用 Astro.props 动态变更 path:当文章切换时,path 随 props 更新,组件重新初始化。
  3. 自定义事件重载:如果妙用 <ViewTransitions />,可以在切换时派发事件:
document.addEventListener('astro:page-load', () => {
  twikooLoaded = false;
  loadTwikoo();
});

Fuwari 目前(2024.11)没有暴露事件,我通过在 src/layouts/Base.astro 添加:

<script>
  document.addEventListener('astro:page-load', () => {
    window.dispatchEvent(new CustomEvent('fuwari:page-loaded'));
  });
</script>

然后在组件里监听:

onMount(() => {
  const rerender = () => {
    twikooLoaded = false;
    loadTwikoo();
  };
  window.addEventListener('fuwari:page-loaded', rerender);
  loadTwikoo();
  return () => window.removeEventListener('fuwari:page-loaded', rerender);
});

这样任何一次页面跳转都会重新初始化 Twikoo,解决首屏不显示的问题。

样式与暗色模式#

Twikoo 提供 data-theme 属性控制主题。Fuwari 使用 data-theme="dark" 切换,我在 MutationObserver 里同步这个状态:

  • 切换时更新容器属性;
  • 通过自定义 CSS 重写输入框、按钮的颜色;
  • 在暗色模式下调低边框与阴影。

示例:

.tk-container[data-twikoo-theme='dark'] {
  --twikoo-font-color: #d4d4d8;
  --twikoo-card-bg: rgba(24, 24, 27, 0.6);
}

性能与懒加载#

  • client:idle + await import('twikoo') 组合确保在主线程空闲时加载脚本。
  • 可以进一步使用 IntersectionObserver,只有当评论区域进入视口时才加载。
  • Twikoo 默认加载表情包等资源,必要时使用 twikoo.init({ reaction: false }) 关闭。
  • 若担心 bundle 体积,考虑通过 CDN 注入 <script src=".../twikoo.min.js" defer>,再调用 window.twikoo.init

参考资料#

fuwari(Astro) 添加评论功能实践记录
https://bangwu.top/posts/fuwari-comment/
作者
棒无
发布于
2024-11-20
许可协议
CC BY-NC-SA 4.0