LiveRe 评论插件适配 Gatsby 网站

目录

最佳选择 LiveRe

LiveRe 的优点:

  • 商业化,成熟,稳定
  • 国内外都可以正常访问
  • 众多的第三方登陆:国内主流的 微信、微博、QQ、百度、豆瓣 和国外五个主流平台。
  • 申请容易,对网站没有要求

LiveRe 的缺点:

  • 技术支持文档少

LiveRe 控制台报错

LiveRe 官方没有提供 React 组件,所以只能自己适配 React。在适配过程中发现直接使用官方提供的安装脚本存在问题:如果快速在启用评论和没有启用评论的页面之间跳转,控制台就会出现如下异常信息:

Uncaught TypeError: Cannot read properties of undefined (reading 'contentWindow')
    at a.sendCustomLivereOption (VM2683 embed.dist.js:2:15359)
    at Object.requestCustomLivereOption (VM2683 embed.dist.js:2:10068)
    at VM2683 embed.dist.js:2:13530

VM2683 embed.dist.js:2 Uncaught TypeError: Cannot read properties of undefined (reading 'style')
    at a.resize (VM2683 embed.dist.js:2:15027)
    at Object.loaded (VM2683 embed.dist.js:2:10130)
    at VM2683 embed.dist.js:2:13530

Uncaught TypeError: Cannot read properties of undefined (reading 'style')
    at a.resize (VM2683 embed.dist.js:2:15027)
    at Object.resize (VM2683 embed.dist.js:2:10307)
    at VM2683 embed.dist.js:2:13530

虽然没有发现影响评论功能的正常使用,但是这其中有很多隐患。这是因为 LiveRe 官方并不支持 Gatsby 这种使用 React 的单页 Web 应用。下面是官方的脚本:

<!-- 来必力City版安装代码 -->
<div id="lv-container" data-id="city" data-uid="[你自己的uid]">
<script type="text/javascript">
   (function(d, s) {
       var j, e = d.getElementsByTagName(s)[0];

       if (typeof LivereTower === 'function') { return; }

       j = d.createElement(s);
       j.src = 'https://cdn-city.livere.com/js/embed.dist.js';
       j.async = true;

       e.parentNode.insertBefore(j, e);
   })(document, 'script');
</script>
<noscript>为正常使用来必力评论功能请激活JavaScript</noscript>
</div>
<!-- City版安装代码已完成 -->

代码的逻辑简单,主要逻辑在 第5行第13行。当代码加载到浏览器后,其将在当前页面的第一个 <script> 标签之前插入一行 <script src='https://cdn-city.livere.com/js/embed.dist.js'></script>。接下去浏览器就会下载并运行 https://cdn-city.livere.com/js/embed.dist.js ,代码的主要结构如下:

/*! livere.tower 2022-10-28, 3:40:04 PM */
var LivereTower = function() {
    "use strict";
    function serial(a) {
        return a + "-" + Math.floor(1e3 * Math.random())
    }
    /**
     * 
     * 这里省略了很多代码
     * 
     */
}();
LivereTower.init();

主要逻辑为:创建 LivereTower 对象,然后调用 LivereTower 对象的 init() 方法。这个方法会初始化 idlv-container<div> 标签,生成评论的界面。

容易看到问题,代码逻辑都是在整个网页全局执行的,而单页网站在跳转的时候并不刷新,所以全局变量不会被清除,导致反复加载代码逻辑出现问题。问题有:

  • 重复生成 <script> 标签
  • 重复生成全局对象 LivereTower
  • 在 React 组件移除的时候无法移除事件,可能导致内存泄露
  • LiveRe 还会动态加载一些广告脚本,这些脚本也没有对单页网站适配

所以,LiveRe 评论插件并不适合直接引入 React 类型的应用中。下面将通过比较巧妙的方法解决这个问题。

解决方案:使用 iframe 标签

解决思路:为了让 LiveRe 的代码不影响整个网页的运行时环境,使用 iframe 标签独立加载 LiveRe 评论页面,当 LiveRe 组件移除的时候,从页面移除 iframe 即可。由于 LiveRe 是独立的页面,因此需要为每一个使用 LiveRe 的页面都生成一个独立的 LiveRe 评论页。例如:/blog/first-post 这个博文页面需要添加 LiveRe 评论框,那就需要为这个页面创建一个独立的 LiveRe 评论页,路径可以是:/livere/blog/first-post。然后在 /blog/first-post 博文页面引入 /livere/blog/first-post 的 iframe 即可。下面是具体步骤:

1. 生成 LiveRe 独立页面

利用 Gatsby 的 Node API 生成独立的包含 LiveRe 脚本的页面。下面为示例代码(gatsby-node.ts):

import type { GatsbyNode } from "gatsby"
import path from "path"
import packageInfo from "./package.json"

export const createPages: GatsbyNode["createPages"] = async ({ actions, graphql, reporter }) => {

    const { createPage } = actions

    // 如果在 package.json 里配置 LiveRe 的 UID 则生成 LiveRe 独立页面
    if (packageInfo.livere) {
        // 使用 GraphQL 查询到你需要使用 LiveRe 的页面路径,实际查询代码需要视你个人的项目情况
        const result = await graphql<{
            allPost: {
                nodes: {
                    slug: string
                    title: string
                }[]
            }
        }>(`
            {
                allPost {
                    nodes {
                        slug
                        title
                    }
                }
            }
        `)

        if (result.errors) {
            reporter.panicOnBuild(`There was an error loading your posts`, result.errors)
            return
        }

        // LiveRe 独立页面模板
        const livereTemplate = path.resolve("src/templates/livere-template.tsx")

        function createLiveRe(slug: string, title: string, uid: string) {
            createPage({
                path: path.posix.join("/livere", slug),
                component: livereTemplate,
                context: {
                    title,
                    uid,
                }
            })
        }

        const posts = result.data!.allPost.nodes

        // 为每个页面路径生成一个 LiveRe 评论页面
        posts.forEach(post => {
            createLiveRe(post.slug, post.title, packageInfo.livere)
        })
    }
}

Gatsby Node API 生成 LiveRe 独立页面时使用的模板(src/templates/livere-template.tsx):

import * as React from "react"
import { Script, HeadFC, PageProps } from "gatsby"

const LiveRe: React.FC<PageProps> = ({ pageContext }) => {
    React.useEffect(() => {
        (function (d, s) {
            var j, e = d.getElementsByTagName(s)[0];

            // @ts-ignore
            if (typeof LivereTower !== 'undefined') { return; }

            j = d.createElement(s);
            j.src = 'https://cdn-city.livere.com/js/embed.dist.js';
            j.async = true;

            // @ts-ignore
            e.parentNode.insertBefore(j, e);
        })(document, 'script');
    })
    return (
        <>
            // 需要在 iframe 页面内引入 iframeResizer 实现 iframe 窗口高度随内容自动调整
            <Script src="/iframeResizer.contentWindow.min.js" />
            <style>
                {"#taboola-livere {display: none !important;}"}
            </style>
            <div
                id="lv-container"
                data-id="city"
                // @ts-ignore
                data-uid={pageContext.uid} >
                <noscript>为正常使用来必力评论功能请激活JavaScript</noscript>
            </div>
        </>
    )
}

export const Head: HeadFC = (props) => {
    // @ts-ignore
    const { title } = props.pageContext
    return (
        <>
            // 可以修改标题,让标题在 LiveRe 管理后台更易读
            <title>{title}</title>
        </>
    )
}

export default LiveRe

注意:为了让 iframe 的高度会自动跟随 iframe 的内容变化,需要引入 iframe-resizer 这个工具。为此我们需要在 LiveRe 独立页面引入 iframeResizer.contentWindow.min.js 脚本,并且在下面的 LiveRe React 组件中引入 iframe-resizer-react 组件。

2. LiveRe React 组件

最后再利用 iframe-resizer-react 生成 iframe 标签,即可得到最后可用的 LiveRe React 组件(livere.tsx)。

import * as React from "react"
import * as path from "path-browserify"
import IframeResizer from "iframe-resizer-react"
import packageInfo from "../../package.json"

const LiveRe = ({ slug }) => {
    if (!packageInfo.livere) {
        return <></>
    }
    return (
        <IframeResizer
            src={path.join("/livere", slug)}
            style={{ width: "1px", minWidth: "100%", border: "0px" }}
        />
    )
}

export default LiveRe

最后

基于使用 iframe 的思路,这个方案算是完美解决了 LiveRe 与 Gatsby 的集成问题。唯独还有一点小缺点就是在 LiveRe 的管理后台看到评论的路径会事有 /livere/ 前缀。