首頁 >web前端 >js教程 >使用 React 和 esbuild 實作 SSR 的簡單方法

使用 React 和 esbuild 實作 SSR 的簡單方法

Barbara Streisand
Barbara Streisand原創
2024-12-30 20:56:14640瀏覽

本部落格探討如何利用 React 的最新功能,使用最少的工具和框架創建輕量級、靈活的 React 應用程式。雖然像 Next.js 和 Remix 這樣的框架非常適合許多用例,但本指南適合有興趣在保留類似功能的同時擁有完全控制權的人。

隨著React 19 的發布及其支援SSR 的新功能,我決定嘗試使用最少的工具創建一個支援SSR 的React 應用程序,但首先讓我們探索Next.js 提供的我們需要考慮的基本功能:

Feature Next.js
SSR (Server-Side Rendering) Built-in with minimal setup.
SSG (Static Site Generation) Built-in with getStaticProps.
Routing File-based routing.
Code Splitting Automatic.
Image Optimization Built-in with next/image.
Performance Optimizations Automatic (e.g., prefetching, critical CSS).
SEO Built-in tools like next/head.

基於這些功能,我們將創建一個最小的設定;這是逐步指南:

目錄

  • 設定
  • 配置 esbuild
  • Express 應用程式與路由
  • 反應應用程式
  • 奔跑
  • 後續步驟
  • 參考文獻

注意:您可以在我的倉庫中找到本教程的內容 https://github.com/willyelm/react-app

設定

作為設定的先決條件,我們需要安裝 Node.js 和以下軟體包:

  • react:基於元件的互動式 UI 函式庫。
  • react-dom:使用 React 函數來水合 SSR 內容。
  • react-router-dom:使用 React 處理路由。
  • express:用於靜態和 REST API 的 Node.js 簡單伺服器。
  • esbuild:轉譯 TypeScript 並捆綁 JS、CSS、SVG 和其他檔案。
  • typescript:將打字功能加入我們的原始程式碼。

我們的設定將使用 Express 處理路由,以提供靜態檔案、公用檔案和 REST API。然後使用react-router-dom處理所有請求。一旦載入到瀏覽器中,我們的客戶端捆綁包將水合我們的預渲染內容並使我們的元件具有互動性。這是這個想法的圖表表示:

A Simple Approach to SSR with React and esbuild

該圖說明了 Express 應用程式如何透過在伺服器上預先渲染 React 元件並傳回 HTML 來處理請求。在客戶端,React 水合這些預渲染元件以實現互動性。

記住這個圖,讓我們建立一個資料夾結構:

react-app/             # This will be our workspace directory.
  - public/            
  - scripts/           
    - build.js         # Bundle our server and client scripts.
    - config.js        # esbuild config to bundle.
    - dev.js           # Bundle on watch mode and run server.
  - src/
    - App/             # Our components will be here.
      - App.tsx        # The main application with browser routing.
      - Home.tsx.      # Our default page component.
      - NotFound.tsx   # Fallback page for unmatched routes.
    - index.tsx        # Hydrate our pre-rendered client app.
    - main.tsx         # Server app with SSR components.
    - style.css        # Initial stylesheet.   
  package.json
  tsconfig.json

讓我們新增依賴項並設定 package.json:

{
  "name": "react-app",
  "type": "module",
  "devDependencies": {
    "@types/express": "^5.0.0",
    "@types/node": "^22.10.2",
    "@types/react": "^19.0.2",
    "@types/react-dom": "^19.0.2",
    "esbuild": "^0.24.2",
    "typescript": "^5.7.2"
  },
  "dependencies": {
    "express": "^4.21.2",
    "react": "^19.0.0",
    "react-dom": "^19.0.0",
    "react-router-dom": "^7.1.0"
  },
  "scripts": {
    "build": "node scripts/build",
    "dev": "node scripts/dev",
    "start": "node dist/main.js"
  }
}

注意:「type」:「module」屬性是允許node.js執行ESM腳本所必需的。

由於我們將使用 Typescript,因此我們將配置一個 tsconfig.json 檔案:

{
  "compilerOptions": {
    "esModuleInterop": true,
    "verbatimModuleSyntax": true,
    "noEmit": true,
    "resolveJsonModule": true,
    "skipLibCheck": true,
    "strict": true,
    "lib": ["DOM", "DOM.Iterable", "ES2022"],
    "target": "ES2022",
    "module": "ES2022",
    "moduleResolution": "bundler",
    "jsx": "react-jsx",
    "baseUrl": ".",
    "paths": {
      "src": [
        "./src/"
      ]
    }
  },
  "include": [
    "src"
  ],
  "exclude": [
    "node_modules"
  ]
}

配置 esbuild

為什麼要用 esbuild?與其他工具相比,esbuild 保持了最小化,它非常快(迄今為止最快的捆綁器)並且預設支援 typescript 和 esm。

在此設定中,我們將使用 esbuild 建立我們的開發和建置腳本,並轉譯客戶端和伺服器套件。在本節中,我們將在工作區的腳本資料夾中工作。

scripts/config.js:此檔案將包含客戶端和伺服器套件的基本配置,該配置將為我們的腳本共用。

import path from 'node:path';
// Working dir
const workspace = process.cwd();
// Server bundle configuration
export const serverConfig = {
  bundle: true,
  platform: 'node', 
  format: 'esm',        // Support esm packages
  packages: 'external', // Omit node packages from our node bundle
  logLevel: 'error',
  sourcemap: 'external',
  entryPoints: {
    main: path.join(workspace, 'src', 'main.tsx') // Express app
  },
  tsconfig: path.join(workspace, 'tsconfig.json'),
  outdir: path.join(workspace, 'dist')
};

// Client bundle configuration
export const clientConfig = {
  bundle: true,
  platform: 'browser',
  format: 'esm',
  sourcemap: 'external',
  logLevel: 'error',
  tsconfig: path.join(workspace, 'tsconfig.json'),
  entryPoints: {
    index: path.join(workspace, 'src', 'index.tsx'), // Client react app
    style: path.join(workspace, 'src', 'style.css')  // Stylesheet
  },
  outdir: path.join(workspace, 'dist', 'static'),    // Served as /static by express
};

scripts/dev.js:此腳本捆綁了客戶端和伺服器應用程序,並在監視模式下運行主伺服器腳本。

react-app/             # This will be our workspace directory.
  - public/            
  - scripts/           
    - build.js         # Bundle our server and client scripts.
    - config.js        # esbuild config to bundle.
    - dev.js           # Bundle on watch mode and run server.
  - src/
    - App/             # Our components will be here.
      - App.tsx        # The main application with browser routing.
      - Home.tsx.      # Our default page component.
      - NotFound.tsx   # Fallback page for unmatched routes.
    - index.tsx        # Hydrate our pre-rendered client app.
    - main.tsx         # Server app with SSR components.
    - style.css        # Initial stylesheet.   
  package.json
  tsconfig.json

使用此腳本,我們應該能夠按照工作區中 package.json 中的配置運行 npm run dev。

scripts/build.js:與 dev 類似,但我們只需要啟用 minify。

{
  "name": "react-app",
  "type": "module",
  "devDependencies": {
    "@types/express": "^5.0.0",
    "@types/node": "^22.10.2",
    "@types/react": "^19.0.2",
    "@types/react-dom": "^19.0.2",
    "esbuild": "^0.24.2",
    "typescript": "^5.7.2"
  },
  "dependencies": {
    "express": "^4.21.2",
    "react": "^19.0.0",
    "react-dom": "^19.0.0",
    "react-router-dom": "^7.1.0"
  },
  "scripts": {
    "build": "node scripts/build",
    "dev": "node scripts/dev",
    "start": "node dist/main.js"
  }
}

此腳本將透過執行 npm run build 並使用 npm start 執行應用程式來產生準備用於生產的 dist 套件。

現在我們已經將 esbuild 配置為捆綁我們的節點和客戶端應用程序,讓我們開始創建一個 Express 伺服器並實現 React SSR。

Express 應用程式和路由

這是一個簡單的應用程序,使用 Express 靜態和中間件方法來提供靜態檔案、處理伺服器路由以及使用 React-router-dom 進行路由。

src/main.tsx:這是主要的 Node.js 應用程序,用於初始化伺服器、使用 Express 處理路由並實作 React SSR。

{
  "compilerOptions": {
    "esModuleInterop": true,
    "verbatimModuleSyntax": true,
    "noEmit": true,
    "resolveJsonModule": true,
    "skipLibCheck": true,
    "strict": true,
    "lib": ["DOM", "DOM.Iterable", "ES2022"],
    "target": "ES2022",
    "module": "ES2022",
    "moduleResolution": "bundler",
    "jsx": "react-jsx",
    "baseUrl": ".",
    "paths": {
      "src": [
        "./src/"
      ]
    }
  },
  "include": [
    "src"
  ],
  "exclude": [
    "node_modules"
  ]
}

反應應用程式

React應用程式將使用react-router-dom處理路由,我們的應用程式將包含一個主頁和一個NotFound頁面來測試水合作用,我們將在主頁上新增一個計數器按鈕並利用React 19,我們將更新元標籤標題和描述。

src/App/Home.tsx:一個非常小的 FunctionComponent。

import path from 'node:path';
// Working dir
const workspace = process.cwd();
// Server bundle configuration
export const serverConfig = {
  bundle: true,
  platform: 'node', 
  format: 'esm',        // Support esm packages
  packages: 'external', // Omit node packages from our node bundle
  logLevel: 'error',
  sourcemap: 'external',
  entryPoints: {
    main: path.join(workspace, 'src', 'main.tsx') // Express app
  },
  tsconfig: path.join(workspace, 'tsconfig.json'),
  outdir: path.join(workspace, 'dist')
};

// Client bundle configuration
export const clientConfig = {
  bundle: true,
  platform: 'browser',
  format: 'esm',
  sourcemap: 'external',
  logLevel: 'error',
  tsconfig: path.join(workspace, 'tsconfig.json'),
  entryPoints: {
    index: path.join(workspace, 'src', 'index.tsx'), // Client react app
    style: path.join(workspace, 'src', 'style.css')  // Stylesheet
  },
  outdir: path.join(workspace, 'dist', 'static'),    // Served as /static by express
};

src/App/NotFound.tsx:找不到頁面時的預設 FunctionComponent。

import { spawn } from 'node:child_process';
import path from 'node:path';
import { context } from 'esbuild';
import { serverConfig, clientConfig } from './config.js';
// Working dir
const workspace = process.cwd();
// Dev process
async function dev() {
  // Build server in watch mode
  const serverContext = await context(serverConfig);
  serverContext.watch();
  // Build client in watch mode
  const clientContext = await context(clientConfig);
  clientContext.watch();
  // Run server
  const childProcess = spawn('node', [
    '--watch',
    path.join(workspace, 'dist', 'main.js')
  ], {
    stdio: 'inherit'
  });
  // Kill child process on program interruption
  process.on('SIGINT', () => {
    if (childProcess) {
      childProcess.kill();
    }
    process.exit(0);
  });
}
// Start the dev process
dev();

src/App/App.tsx:使用react-router-dom設定我們的應用程式。

import { build } from 'esbuild';
import { clientConfig, serverConfig } from './config.js';
// build process
async function bundle() {
  // Build server
  await build({
    ...serverConfig,
    minify: true
  });
  // Build client
  await build({
    ...clientConfig,
    minify: true
  });
}
// Start the build process
bundle();

跑步

最後,設定了 esbuild 腳本,設定了 Express 伺服器,並實作了 React SSR。我們可以運行我們的伺服器:

import path from 'node:path';
import express, { type Request, type Response } from 'express';
import { renderToPipeableStream } from 'react-dom/server';
import { StaticRouter } from 'react-router';
import { App } from './App/App';

const app = express(); // Create Express App
const port = 3000; // Port to listen
const workspace = process.cwd(); // workspace
// Serve static files like js bundles and css files
app.use('/static', express.static(path.join(workspace, 'dist', 'static')));
// Server files from the /public folder
app.use(express.static(path.join(workspace, 'public')));
// Fallback to render the SSR react app
app.use((request: Request, response: Response) => {
  // React SSR rendering as a stream
  const { pipe } = renderToPipeableStream(
    <html lang="en">
      <head>
        <meta charSet="UTF-8" />
        <link rel='stylesheet' href={`/static/style.css`} />
      </head>
      <body>
        <base href="/" />
        <div>


<p>src/index.tsx: On the client side to activate our components and make them interactive we need to "hydrate".<br>
</p>
<pre class="brush:php;toolbar:false">import { hydrateRoot } from 'react-dom/client';
import { BrowserRouter } from 'react-router';
import { App } from './App/App';
// Hydrate pre-renderer #app element
hydrateRoot(
  document.getElementById('app') as HTMLElement,
  <BrowserRouter>
    <App />
  </BrowserRouter>
);

這將顯示一則訊息 [app] 正在連接埠 3000 上偵聽。導航到
http://localhost:3000 來看看。

測試 SSR 和 SEO 元標記:

A Simple Approach to SSR with React and esbuild

您也可以在我的倉庫 willyelm/react-app 中找到本教學的原始碼

A Simple Approach to SSR with React and esbuild 威廉 / 反應應用程式

使用 SSR 和 esbuild 的簡單 React 應用程式設定

專案利用了react@19、react-router-dom@7等的最新功能來設定SSR。

依賴關係

  • react:基於元件的互動式 UI 函式庫。
  • react-dom:使用 React 函數來水合 SSR 內容。
  • react-router-dom:使用 React 處理路由。
  • express:用於靜態和 REST API 的 Node.js 簡單伺服器。
  • esbuild:轉換 TypeScript 並捆綁 JS、CSS、SVG 和其他檔案。
  • typescript:將打字功能加入我們的原始程式碼。

專案結構

react-app/             # This will be our workspace directory.
  - public/            
  - scripts/           
    - build.js         # Bundle our server and client scripts.
    - config.js        # esbuild config to bundle.
    - dev.js           # Bundle on watch mode and run server.
  - src/
    - App/             # Our components will be here.
      - App.tsx        # The main application with browser routing.
      - Home.tsx.      # Our default page component.
      - NotFound.tsx   # Fallback page for unmatched routes.
    - index.tsx        # Hydrate our pre-rendered client app.
    - main.tsx         # Server app with SSR components.
    - style.css        # Initial stylesheet.   
  package.json
  tsconfig.json
進入全螢幕模式 退出全螢幕模式
在 GitHub 上查看

下一步

在本指南中,我們使用 esbuild 和 Express 建立了一個帶有 SSR 的 React 應用程序,重點關注最小且靈活的設定。使用 React 19、React Router DOM 7 和 esbuild 等現代化工具,我們實現了快速且有效率的工作流程,同時避免了大型框架的開銷。

我們可以增強此實現,包括以下內容:

  • TailwindCSS:設定 PostCSS 和 esbuild 以支援 tailwindcss 樣式。
  • Jest:使用 Jest 和 @testing-library 新增單元測試。
  • 劇作家:與劇作家一起配置端到端測試。
  • SSG:實作SSG(靜態站點產生)以提高效能和伺服器負載。
  • HMR:開發模式下使用esbuild支援HMR(熱模組替換),提高效率。

感謝您的閱讀並祝您編碼愉快。

參考

  • 反應 19
  • React 路由器 DOM
  • 反應 SSR
  • Express.js
  • 打字稿
  • esbuild

以上是使用 React 和 esbuild 實作 SSR 的簡單方法的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn