雪山深处

使用 react-tweet 来嵌入推特推文

使用 react-tweet 来嵌入推特推文

2023 年 05 月 07 日 • 6 分钟

技术

最近发现了一个开源库:react-tweet,由 Vercel Labs 开发。可部署为 Serveless 函数、为网页嵌入推特推文卡片、支持夜间模式、还使用了 SWR 和 React Server Components 在内的新技术,这实在是太酷啦!我整整把玩了一天!

Vercel 使用 Next.js 搭建的 Demo,很好的展示了其所有特性,可以去体验下:

传统方法与其弊端

将推特推文嵌入网页不是什么新鲜事,如下图,只要点一下“嵌入推文”就能拿到一串“神秘代码”,把代码粘贴在想要的地方就可以了。

嵌入推文示意图

这种方式自然有其弊端:

首先,代码是需要从 platform.twitter.com 加载 JS 脚本的,虽然在海外访问推特的服务器非常迅速,但在内地因为众所周知的原因这段脚本是加载不出来的,因此嵌入的推文会一直是默认 blockquote 的样子。

其次,嵌入的卡片样式一直是默认的白色背景,这在网页切换为夜间模式时十分不协调。

最后,每嵌入一个推文都要粘贴不少的代码在文章或程序中,不够简洁。

如何使用 react-tweet

前端

在 react-tweet 的仓库中已经放了 Next.js、Vite 和 Create React App 的使用方法,可以去看看。

基本用法:

import { Tweet } from 'react-tweet'

export const IndexPage = () => <Tweet id="1628832338187636740" />

只需要传递一个推文 ID 即可。

在 SSR 框架下可能会遇到 React 的水合报错,可以自行封装一个组件做类似处理:

import React from 'react';
import {Tweet as ReactTweet, TweetSkeleton} from 'react-tweet';

export default function Tweet({id = ''}) {
  const [mounted, setMounted] = React.useState(false);

  React.useEffect(()=>{
    setMounted(true);
  }, []);

  return mounted ? (
    <ReactTweet id={id} />
  ) : (
    <TweetSkeleton />
  );
}

在 Gatsby 下使用的方法

我在 Gatsby 中使用时出了问题,控制台输出了一堆 CSS 样式导入的问题,幸好在这篇 issue 中开发者已经给出了解决办法。

需要在 gatsby-node.js 下修改 Webpack 配置:

export const onCreateWebpackConfig = ({
  actions,
  getConfig,
}) => {
  const config = getConfig();

  config.module.rules.forEach((rule) => {
    rule.oneOf?.forEach((rule) => {
      rule.use?.forEach((plugin) => {
        // 我将原代码中的"/"删掉了,因为 Windows 的路径使用的是"\",会无法匹配
        if (
          plugin.loader.includes('css-loader') ||
          plugin.loader.includes('mini-css-extract-plugin')
        ) {
          if (plugin.options.modules?.namedExport) {
            plugin.options.modules.namedExport = false;
          }
        }
      });
    });
  });
  actions.replaceWebpackConfig(config);
};

以及还需要在 gatsby-browser.js 中手动导入 theme.css:

import 'react-tweet/theme.css';

做完这些工作,react-tweet 才算能在 Gatsby 中使用,但仅限 SWR 下。根据该 issue 所说,在开启了 PARTIAL_HYDRATION 后 Gatsby 报错了(Gatsby 开启该选项后会使用 React Server Components),由于我并不使用该特性就不继续处理这个问题了(毕竟开发者都没有找到解决办法,最后甚至将整个 Gatsby 示例给删掉了😕)

Serverless 函数

根据文档所说:

若 Tweet 组件的使用环境不支持 React Server Components,它将使用 SWR 作为代替,且推文数据会向跨域友好的 https://react-tweet.vercel.app/api/tweet/:id 获取。

同样因为众所周知的原因 Vercel 在内地也是无法访问的,为此可以选择自行部署一个 Vercel Function 并使用自己的域名进行访问,开发者也给出了示例代码,你可以在 apps/vite-app/api/tweet/ site preview image 找到。

我的博客是部署在 Netlify 上的,因此实现一个 Netlify Function 会是更好的选择,我的实现如下:

import {
  Handler,
  HandlerEvent,
  HandlerContext,
  HandlerResponse,
} from "@netlify/functions";
import { getTweet } from "react-tweet/api";

// 配置跨域,方便本地开发
const headers: HandlerResponse["headers"] = {
  "Access-Control-Allow-Origin": "http://localhost:8000",
};

export const handler: Handler = async (
  event: HandlerEvent,
  _context: HandlerContext
) => {
  const tweetId = event.path.split("/").pop();

  console.log({ tweetId });

  if (event.httpMethod !== "GET" || typeof tweetId !== "string") {
    return {
      headers,
      statusCode: 400,
      body: JSON.stringify({ error: "Bad Request." }),
    };
  }

  try {
    const tweet = await getTweet(tweetId);

    return {
      headers,
      statusCode: tweet ? 200 : 404,
      body: JSON.stringify({ data: tweet ?? null }),
    };
  } catch (error) {
    console.error(error);
    return {
      headers,
      statusCode: 400,
      body: JSON.stringify({ error: error.message ?? "Bad request." }),
    };
  }
};

我在 yarn berry 的 pnp 模式下使用 Netlify Cli 有点问题,因此单独建了一个仓库用于放置 Function 的代码以便单独打包后再部署。

此外,因为 react-tweet 用到了 Fetch API 因此需要使用 Node.js 18 才能运行,所以在部署前还要将 Netlify 的环境变量 AWS_LAMBDA_JS_RUNTIME 设为 nodejs18.x

如果想让访问的 URL 更简洁,还可添加一条配置到 _redirects 中,这样一来直接请求 /api/tweet/:id 就可以了。

/api/*  /.netlify/functions/:splat  200

最后修改一下之前包装的组件,siteUrl 是站点的首页地址,为了方便本地调试因此没有使用绝对路径。

- <ReactTweet id={id} />
+ <ReactTweet apiUrl={`${siteUrl}/api/tweet/${id}`} />

大功告成!🎉

后记

关于 React Server Components 部分的特性没能体验到,比较可惜,但这也是我第一次体验 Serverless Functions 也是蛮有趣的。

最后嵌入一条可爱的二次元纸片人推文:

本文最后于2023 年 05 月 18 日更新,一些操作可能已经过时