本教程将帮助你从零开始搭建一个基于 Esmx 的 HTML SSR 应用。我们将通过一个完整的示例来展示如何使用 Esmx 框架创建服务端渲染应用。
首先,让我们了解项目的基本结构:
1.
2├── package.json # 项目配置文件,定义依赖和脚本命令
3├── tsconfig.json # TypeScript 配置文件,设置编译选项
4└── src # 源代码目录
5 ├── app.ts # 主应用组件,定义页面结构和交互逻辑
6 ├── create-app.ts # 应用实例创建工厂,负责初始化应用
7 ├── entry.client.ts # 客户端入口文件,处理浏览器端渲染
8 ├── entry.node.ts # Node.js 服务器入口文件,负责开发环境配置和服务器启动
9 └── entry.server.ts # 服务端入口文件,处理 SSR 渲染逻辑
创建 package.json
文件,配置项目依赖和脚本:
1{
2 "name": "ssr-demo-html",
3 "version": "1.0.0",
4 "type": "module",
5 "private": true,
6 "scripts": {
7 "dev": "esmx dev",
8 "build": "npm run build:dts && npm run build:ssr",
9 "build:ssr": "esmx build",
10 "preview": "esmx preview",
11 "start": "NODE_ENV=production node dist/index.mjs",
12 "build:dts": "tsc --declaration --emitDeclarationOnly --outDir dist/src"
13 },
14 "dependencies": {
15 "@esmx/core": "*"
16 },
17 "devDependencies": {
18 "@esmx/rspack": "*",
19 "@types/node": "22.8.6",
20 "typescript": "^5.7.3"
21 }
22}
创建完 package.json
文件后,需要安装项目依赖。你可以使用以下任一命令来安装:
1pnpm install
2# 或
3yarn install
4# 或
5npm install
这将安装所有必需的依赖包,包括 TypeScript 和 SSR 相关的依赖。
创建 tsconfig.json
文件,配置 TypeScript 编译选项:
1{
2 "compilerOptions": {
3 "module": "ESNext",
4 "moduleResolution": "node",
5 "isolatedModules": true,
6 "resolveJsonModule": true,
7
8 "target": "ESNext",
9 "lib": ["ESNext", "DOM"],
10
11 "strict": true,
12 "skipLibCheck": true,
13 "types": ["@types/node"],
14
15 "experimentalDecorators": true,
16 "allowSyntheticDefaultImports": true,
17
18 "baseUrl": ".",
19 "paths": {
20 "ssr-demo-html/src/*": ["./src/*"],
21 "ssr-demo-html/*": ["./*"]
22 }
23 },
24 "include": ["src"],
25 "exclude": ["dist", "node_modules"]
26}
创建主应用组件 src/app.ts
,实现页面结构和交互逻辑:
1/**
2 * @file 示例组件
3 * @description 展示一个带有自动更新时间的页面标题,用于演示 Esmx 框架的基本功能
4 */
5
6export default class App {
7 /**
8 * 当前时间,使用 ISO 格式
9 * @type {string}
10 */
11 public time = '';
12
13 /**
14 * 创建应用实例
15 * @param {SsrContext} [ssrContext] - 服务端上下文,包含导入元数据集合
16 */
17 public constructor(public ssrContext?: SsrContext) {
18 // 构造函数中不需要额外初始化
19 }
20
21 /**
22 * 渲染页面内容
23 * @returns {string} 返回页面 HTML 结构
24 */
25 public render(): string {
26 // 确保在服务端环境下正确收集导入元数据
27 if (this.ssrContext) {
28 this.ssrContext.importMetaSet.add(import.meta);
29 }
30
31 return `
32 <div id="app">
33 <h1><a href="https://www.esmnext.com/guide/frameworks/html.html" target="_blank">Esmx 快速开始</a></h1>
34 <time datetime="${this.time}">${this.time}</time>
35 </div>
36 `;
37 }
38
39 /**
40 * 客户端初始化
41 * @throws {Error} 当找不到时间显示元素时抛出错误
42 */
43 public onClient(): void {
44 // 获取时间显示元素
45 const time = document.querySelector('#app time');
46 if (!time) {
47 throw new Error('找不到时间显示元素');
48 }
49
50 // 设置定时器,每秒更新一次时间
51 setInterval(() => {
52 this.time = new Date().toISOString();
53 time.setAttribute('datetime', this.time);
54 time.textContent = this.time;
55 }, 1000);
56 }
57
58 /**
59 * 服务端初始化
60 */
61 public onServer(): void {
62 this.time = new Date().toISOString();
63 }
64}
65
66/**
67 * 服务端上下文接口
68 * @interface
69 */
70export interface SsrContext {
71 /**
72 * 导入元数据集合
73 * @type {Set<ImportMeta>}
74 */
75 importMetaSet: Set<ImportMeta>;
76}
创建 src/create-app.ts
文件,负责创建应用实例:
1/**
2 * @file 应用实例创建
3 * @description 负责创建和配置应用实例
4 */
5
6import App from './app';
7
8export function createApp() {
9 const app = new App();
10 return {
11 app
12 };
13}
创建客户端入口文件 src/entry.client.ts
:
1/**
2 * @file 客户端入口文件
3 * @description 负责客户端交互逻辑和动态更新
4 */
5
6import { createApp } from './create-app';
7
8// 创建应用实例并初始化
9const { app } = createApp();
10app.onClient();
创建 entry.node.ts
文件,配置开发环境和服务器启动:
1/**
2 * @file Node.js 服务器入口文件
3 * @description 负责开发环境配置和服务器启动,提供 SSR 运行时环境
4 */
5
6import http from 'node:http';
7import type { EsmxOptions } from '@esmx/core';
8
9export default {
10 /**
11 * 配置开发环境的应用创建器
12 * @description 创建并配置 Rspack 应用实例,用于开发环境的构建和热更新
13 * @param esmx Esmx 框架实例,提供核心功能和配置接口
14 * @returns 返回配置好的 Rspack 应用实例,支持 HMR 和实时预览
15 */
16 async devApp(esmx) {
17 return import('@esmx/rspack').then((m) =>
18 m.createRspackHtmlApp(esmx, {
19 config(context) {
20 // 在此处自定义 Rspack 编译配置
21 }
22 })
23 );
24 },
25
26 /**
27 * 配置并启动 HTTP 服务器
28 * @description 创建 HTTP 服务器实例,集成 Esmx 中间件,处理 SSR 请求
29 * @param esmx Esmx 框架实例,提供中间件和渲染功能
30 */
31 async server(esmx) {
32 const server = http.createServer((req, res) => {
33 // 使用 Esmx 中间件处理请求
34 esmx.middleware(req, res, async () => {
35 // 执行服务端渲染
36 const rc = await esmx.render({
37 params: { url: req.url }
38 });
39 res.end(rc.html);
40 });
41 });
42
43 server.listen(3000, () => {
44 console.log('服务启动: http://localhost:3000');
45 });
46 }
47} satisfies EsmxOptions;
这个文件是开发环境配置和服务器启动的入口文件,主要包含两个核心功能:
devApp
函数:负责创建和配置开发环境的 Rspack 应用实例,支持热更新和实时预览功能。server
函数:负责创建和配置 HTTP 服务器,集成 Esmx 中间件处理 SSR 请求。创建服务端渲染入口文件 src/entry.server.ts
:
1/**
2 * @file 服务端渲染入口文件
3 * @description 负责服务端渲染流程、HTML 生成和资源注入
4 */
5
6import type { RenderContext } from '@esmx/core';
7import type App from './app';
8import type { SsrContext } from './app';
9import { createApp } from './create-app';
10
11// 封装页面内容生成逻辑
12const renderToString = (app: App, ssrContext: SsrContext): string => {
13 // 将服务端渲染上下文注入到应用实例中
14 app.ssrContext = ssrContext;
15 // 初始化服务端
16 app.onServer();
17
18 // 生成页面内容
19 return app.render();
20};
21
22export default async (rc: RenderContext) => {
23 // 创建应用实例,返回包含 app 实例的对象
24 const { app } = createApp();
25 // 使用 renderToString 生成页面内容
26 const html = renderToString(app, {
27 importMetaSet: rc.importMetaSet
28 });
29
30 // 提交依赖收集,确保所有必要资源都被加载
31 await rc.commit();
32
33 // 生成完整的 HTML 结构
34 rc.html = `<!DOCTYPE html>
35<html lang="zh-CN">
36<head>
37 ${rc.preload()}
38 <title>Esmx 快速开始</title>
39 ${rc.css()}
40</head>
41<body>
42 ${html}
43 ${rc.importmap()}
44 ${rc.moduleEntry()}
45 ${rc.modulePreload()}
46</body>
47</html>
48`;
49};
完成上述文件配置后,你可以使用以下命令来运行项目:
1npm run dev
1npm run build
1npm run start
现在,你已经成功创建了一个基于 Esmx 的 HTML SSR 应用!访问 http://localhost:3000 即可看到效果。