使用 Bun 和 React 进行 SSR 服务器端渲染

家好,很高兴又见面了,我是"高级前端 进阶 ",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发,您的支持是我不断创作的动力。

今天给大家带来的主题是使用 Bun 和 React 实现 SSR 服务端渲染,文章大部分内容来自于Alex Kates发布的 《Server-Side Rendering (SSR) with Bun and React》,但是对部分内容进行了修改,欢迎大家指正。

关于Bun的更多内容可以在我的主页查看,后续也会持续关注Bun等各种运行时的最新动态。话不多说,直接进入正题!

前言

2023 年 9 月 8 日,期待已久的 JavaScript 运行时 Bun 终于发布了 1.0 版本,Bun 可以作为一体化 JavaScript 运行时和工具包,专为运行速度而设计。 它配备了打包器、测试运行器、本机 TypeScript 和 JSX 支持,甚至还有 Node.js 兼容的包管理器。

本文将从 Bun 开发服务端渲染的简单示例展开,主要包括以下几个方面:

项目设置

安装 Bun

可以将 Bun 与 Nodejs 一起安装从而不影响其他存储库。

// Install Bun
curl -fsSL https://bun.sh/install | bash

初始化 Bun 项目

接下来可以初始化一个新的 Bun 项目。

// Project setup
mkdir bun-httpserver
cd bun-httpserver
bun init

使用 bun init 将构建一个新项目,生成一个文件 bun.lockb,它取代了 yarn、npm 或 pnpm 锁定文件。 此外,index.ts 和 tsconfig.json 都是默认生成的,这意味着 TypeScript 支持已内置,无需额外设置。

第一个 Bun 服务器

设置 Bun 服务器非常简单,只需几行代码即可启动并运行。

const server = Bun.serve({
  // Bun.serve() 初始化服务器并将其设置为侦听端口 3000
  port: 3000,
    // 定义一个处理所有传入 HTTP 请求的函数。 当请求到来时,它会返回一个新的 HTTP 响应,
    // 其中包含文本“Bun!”
  fetch(req) {
    return new Response(`Bun!`);
  },
});
console.log(`Listening on http://localhost:${server.port} ...`);

使用 React 和 Bun 实现服务器端渲染 (SSR)

以 Bun 方式添加包

要在 Bun 中添加包只需使用 bun add 命令即可, 如果想要将其作为开发依赖项只需添加 -d 标志即可,这一点于 Node.js 极其相似。

bun add react react-dom
bun add @types/react-dom -d
// 添加react等相关包,用于服务端渲染

切换到 JSX

为了使用JSX的能力,下面命令将现有的 index.ts 服务器文件转换为 index.tsx,从而允许开发者直接返回 JSX 元素。

mv index.ts index.tsx

分析 index.tsx

修改后的 index.tsx 使用 react-dom/server 中的 renderToReadableStream 来渲染 Pokemon 组件。然后,将此Stream流包装在 Response 对象中,同时需要确保内容类型设置为“text/html”。

import { renderToReadableStream } from 'react-dom/server';
// 从react-dom/server包中导入函数renderToReadableStream用于服务器端React渲染。
import Pokemon from './components/Pokemon';
// 从相对文件路径导入名为 Pokemon 的 React 组件。
Bun.serve({
  // 使用 Bun.serve() 方法设置 HTTP 服务器。 它包括一个异步获取函数来处理传入的 HTTP 请求。
  async fetch(request) {
    // 一个异步函数,每个到达服务器的 HTTP 请求都会触发该函数。

    const stream = await renderToReadableStream();
    // 将 Pokemon React 组件异步渲染到可读流。
    return new Response(stream, {
      headers: { 'Content-Type': 'text/html' },
    });
    // 返回具有可读流的新 HTTP 响应对象,并将“Content-Type”标头设置为“text/html”。
  },
});

console.log('Listening ...');

生成可流式反应组件

这一步将构建一个简单的 React 组件,该组件将在服务器端渲染(SSR)并直接流回客户端。

// Pokemon组件
import React from 'react';
type PokemonProps = {
  name?: string,
};
function Pokemon() {
  return Bun Forrest, Bun!;
}
export default Pokemon;

启动 Bun 服务

bun index.tsx
// bun --watch index.tsx
// 也可以使用--watch标志位启用HMR热更新

浏览器打开地址 http://localhost:3000 将会看到 SSR 后的 Pokemon 组件!

利用 Pokémon Twist 构建动态路由

接下来将创建两条不同的路由,即/pokemon 和 /pokemon/[pokemonName]。

增强型 index.tsx

到这一步,当前应用将包含动态路由,可以显示从 Pokémon API 获取的 Pokémon 列表,也可以根据 URL 显示特定的 Pokémon。 两个路由的组件都会在服务器端渲染,然后流回客户端。

import { PokemonResponse } from "./types/PokemonResponse";
import { PokemonsResponse } from "./types/PokemonsResponse";
import { renderToReadableStream } from "react-dom/server";
import Pokemon from "./components/Pokemon";
import PokemonList from "./components/PokemonList";
// 设置 HTTP 服务器并指定异步获取函数来处理传入请求,有效地充当所有 HTTP 流量的入口点。
Bun.serve({
  async fetch(request) {
    const url = new URL(request.url);

    if (url.pathname === "/pokemon") {
      const response = await fetch("https://pokeapi.co/api/v2/pokemon");

      const { results } = (await response.json()) as PokemonsResponse;

      const stream = await renderToReadableStream();

      return new Response(stream, {
        headers: { "Content-Type": "text/html" },
      });
    }
    const pokemonNameRegex = /^/pokemon/([a-zA-Z0-9_-]+)$/;
    const match = url.pathname.match(pokemonNameRegex);
  // 使用正则表达式匹配指定特定 Pokémon 名称的 URL 路径(例如 /pokemon/pikachu)。
  // 如果检测到这样的路径,服务器会获取该特定 Pokémon 的详细信息并使用 Pokemon React 组件进行渲染。
    if (match) {
      const pokemonName = match[1];
      const response = await fetch(`https://pokeapi.co/api/v2/pokemon/${pokemonName}`);

      if (response.status === 404) {
        return new Response("Not Found", { status: 404 });
      }

      const {
        height,
        name,
        weight,
        sprites: { front_default },
      } = (await response.json()) as PokemonResponse;

      const stream = await renderToReadableStream();
      // renderToReadableStream 函数将 React 组件转换为可读流,然后作为 HTML 响应返回。
      return new Response(stream, {
        headers: { "Content-Type": "text/html" },
      });
    }
  // 404状态码处理
    return new Response("Not Found", { status: 404 });
  },
});

console.log("Listening ...");

PokemonList 组件

PokemonList 组件获取 Pokemon 列表并将它们转换为可点击的列表项。每个列表项都是一个锚标记,单击时将用户路由到 /pokemon/[name],渲染各个 Pokémon 详细信息。

import React from 'react';

function PokemonList({
  pokemon,
}: {
  pokemon: { name: string, url: string }[],
}) {
  return (
    
    {pokemon.map(({ name }) => (
  • {name}
  • ))}
); } export default PokemonList;

Pokemon 组件

Pokemon 组件负责获取单个 Pokemon 的高度、重量、名称和图像 URL,并准确返回显示单个 Pokemon 的方式。

import React from 'react';

function Pokemon({
  height,
  weight,
  name,
  img,
}: {
  height: number,
  weight: number,
  name: string,
  img: string,
}) {
  return (
    
      

{name}

{name}

Height: {height}

Weight: {weight}

); } export default Pokemon;

参考资料

https://alexkates.dev/server-side-rendering-ssr-with-bun-and-react

展开阅读全文

页面更新:2024-03-07

标签:服务器端   路由   初始化   服务端   函数   组件   服务器   文件   项目   列表

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2008-2024 All Rights Reserved. Powered By bs178.com 闽ICP备11008920号-3
闽公网安备35020302034844号

Top