☰
rax-document 组件详解:用 Rax 构建 SSR 与多页应用的 Document 层
2026/10/15 17:36:23 网站建设 项目流程
  • 前端

【免费下载链接】rax

🐰 Rax is a progressive framework for building universal application. https://rax.js.org

项目地址:https://gitcode.com/gh_mirrors/ra/rax
点击查看免费下载

本文围绕 Rax 生态中的rax-document包展开,讲解如何用声明式 JSX 组件搭建页面 Document(<html>/<head>/<body>骨架)。你将掌握Root、Data、Style、Script、App五个核心组件的职责与用法,理解 SSR 场景下初始 HTML 与初始数据如何注入页面,以及多页应用(MPA)中如何通过App组件按路由注入pageInfo,并配合仓库源码与实际示例获得可直接落地的实战方案。

一、rax-document 是什么

rax-document是 Rax 框架体系中用于**构建 Document(HTML 文档骨架)**的组件集合。它的定位非常聚焦:把传统 HTML 模板中html/head/body的结构、样式表与脚本的注入、SSR 初始数据的挂载等重复性工作,抽象成一组声明式 JSX 组件。

在 Rax 应用中,src/document/index.jsx是一个特殊文件,它导出一个默认的Document组件,用来描述整张页面的外壳。仓库中所有示例项目(如 app-lifecycle、page-lifecycle-with-router、with-miniapp-native-page 等)都遵循同一套写法:从rax-document引入Root, Style, Script,拼装出页面骨架并导出。

从 package.json 可以看到,该包将rax@^1.0.0声明为peerDependencies,即它本身不捆绑渲染内核,而是运行在 Rax 的虚拟 DOM 之上,组件内直接使用createElement创建元素。

二、快速开始:最小可运行的 Document 组件

rax-document的典型使用方式是编写一个函数组件,返回标准的 HTML 结构。README 给出的完整示例如下(注意这段代码同时覆盖了 SSR 场景的初始数据注入):

import { createElement } from 'rax'; import { Root, Data, Style, Script } from 'rax-document'; export default () => { return ( <html> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width,initial-scale=1"/> <title>ssr-document-demo</title> <Style /> </head> <body> {/* root container */} <Root /> {/* initial data from server side */} <Data /> <Script /> </body> </html> ); }

这段代码的要点:

  • <Style />放在<head>中,负责输出页面的样式表link标签;
  • <Root />放在<body>中,作为页面根容器;
  • <Data />紧跟根容器,在 SSR 项目里输出服务端下发的初始数据;
  • <Script />放在<body>末尾,输出页面脚本标签。

值得注意的是,这里的html、head、body、meta、title都是普通 JSX 标签,说明rax-document允许(并且鼓励)在 Rax 组件树中直接书写文档级标签,再由渲染器输出到真实 HTML。

三、五个核心组件逐一拆解

rax-document一共导出六个组件,其中五个在 README 中做了说明。它们的实现全部位于 src/index.js,下面结合源码讲解各自职责。

3.1 Root:页面根容器

Root是页面的根容器节点。官方文档的定义是:

Root container for page. In normal Web app, it will be an empty node. In SSR project, the initial HTML will be output in this node.

在普通 Web 应用中它只是一个空节点;在 SSR 项目中,服务端渲染出来的初始 HTML 会被输出到这个节点中。源码实现如下:

function Root(props, context) { const { __initialHtml } = context; // comment node for ssr parse root node position if (process.env.__IS_SERVER__) { return [ { __html: '<!--__BEFORE_ROOT__-->' }, <div id="root" key="root" dangerouslySetInnerHTML={{ __html: __initialHtml || '' }} />, { __html: '<!--__AFTER_ROOT__-->' } ]; } return <div id="root" key="root" dangerouslySetInnerHTML={{ __html: __initialHtml || '' }} />; }

从源码结构可以推断几个关键事实:

  • 节点 id 固定为root:<div id="root">是约定好的挂载点,客户端的rax-app等运行时会基于该节点完成水合(hydrate)或接管渲染;
  • SSR 与非 SSR 的差异:当process.env.__IS_SERVER__为真时,Root会在#root前后额外输出<!--__BEFORE_ROOT__-->与<!--__AFTER_ROOT__-->两个 HTML 注释节点,用于让服务端解析器定位根节点的确切位置;
  • 初始 HTML 通过 context 注入:__initialHtml来自渲染上下文(context),在客户端渲染时通常为空字符串,因此输出一个空的#root,等待运行时挂载。

3.2 Data:SSR 初始数据注入

Data只在 SSR 项目中需要,作用是将服务端计算好的初始数据挂到window上。源码:

function Data(props, context) { const { __initialData } = context; if (__initialData) { return <script>// Named by role rather than implementationm, so component name are `Style` rather than `Styles`. function Style(props, context) { const { __styles = [] } = context; return __styles.map((src, index) => <link {...props} rel="stylesheet" href={src} key={`style_${index}`} />); }

源码揭示的实现细节:

  • 样式资源从 context 的__styles数组中读取,每个地址对应一个<link rel="stylesheet" href="...">;
  • props会被透传到link标签上,意味着你可以给<Style />传任意 HTML 属性(如media、data-*等)来定制输出;
  • 组件名为Style(单数)而非Styles(复数),源码注释明确说明这是按“职责”而非“实现”命名——它代表一个样式注入插槽,而不是一个样式集合;
  • 自动分析的机制意味着:只要你的页面组件中引入了 CSS(如示例中常见的import './index.css'),构建阶段就会把产物地址填入__styles,<Style />处便会自动输出对应的link标签。

3.4 Script:自动分析的脚本标签

Script输出页面脚本。官方文档:

Scripts for page. Automatic analyzed from your project.

源码:

function Script(props, context) { const { __scripts = [] } = context; // props such as type can be passed to script tag // script default crossorigin value is anonymous return __scripts.map( (src, index) => <script crossorigin="anonymous" {...props} src={src} key={`script_${index}`}> {/* self-closing script element will not work in HTML */} </script> ); }

实现要点:

  • 脚本资源从 context 的__scripts数组读取,每个地址输出一个<script src="...">;
  • 默认带上crossorigin="anonymous",保证跨域加载脚本时不携带凭据,符合现代 Web 安全实践;该默认值可以被传入的props覆盖;
  • props透传:例如需要输出type="module"的脚本时,直接写<Script type="module" />即可;
  • 源码注释特别指出一个 HTML 细节:自闭合的<script />在 HTML 中不会生效,因此组件刻意输出成带内容的成对标签(内容为空),确保浏览器正确解析。

3.5 App:多页应用的 pageInfo 提供者

App组件服务于多页应用(Multi Page Application)场景,官方描述为:

ProvidepageInfofor Multi Page Application.

它的工作方式是接收app.json配置,根据当前页面路径查找到对应页面的信息,并通过render props(函数作为子元素)把pageInfo传给内部结构。README 中的完整示例:

import { createElement } from 'rax'; import { Root, Style, Script, App} from 'rax-document'; import appJSON from '../app.json'; function Document() { return ( <App config={appJSON}> { pageInfo => <html> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,minimum-scale=1,user-scalable=no,viewport-fit=cover"/> <title>{pageInfo.title}</title> <Style /> </head> <body> {/* root container */} <Root /> <Script /> </body> </html> } </App> ); } export default Document;

对比上一节的用法,这里<App config={appJSON}>包裹了一个函数子元素,函数接收pageInfo并返回完整的 HTML 结构,<title>{pageInfo.title}</title>说明页面标题可以直接取自当前路由配置。viewport也扩展为移动端常用的完整配置(含maximum-scale、user-scalable=no、viewport-fit=cover等,适配 WebView 与刘海屏场景)。

App的源码逻辑如下:

function App(props, context) { const config = props.config || {}; const routes = config.routes || []; const pagePath = context.__pagePath; const currentPageInfo = routes.find((route) => { return route.path == pagePath; }); const consumer = Array.isArray(props.children) ? props.children[0] : props.children; if (typeof consumer === 'function') { return consumer(currentPageInfo); } return props.children; }

从源码可以推断其工作机制:

  • config即app.json的解析结果,核心是其中的routes数组;
  • 当前页面路径__pagePath由渲染上下文(context)提供;
  • 组件用route.path == pagePath在路由表中查找当前页面的配置项,找到的整个路由对象就是pageInfo;
  • 如果子元素是函数,则调用它并把pageInfo传入(render props 模式);否则原样返回子元素。

作为对照,可以查看 page-lifecycle-with-router 示例的 app.json:其中routes数组的每一项包含path与source(如{ "path": "/", "source": "pages/Home/index" }),此外还有window(标题、下拉刷新等)与tabBar配置。App组件正是从这样的路由表中提取title等字段来填充pageInfo的。

3.6 未在 README 中详述的 Manifest

除 README 说明的五个组件外,src/index.js 还导出了Manifest组件:当 context 中存在__manifests数组时,它根据__pagePath查找匹配项,若有数据则输出<script type="application/manifest+json">,用于声明 Web App Manifest(PWA 相关)。该组件可作为进阶能力了解,其调用方式与Data类似,同样依赖渲染上下文注入数据。

四、在真实项目中的使用位置

rax-document的组件并不是在页面组件里直接使用,而是集中用于src/document/index.jsx。仓库中几乎每个示例项目都包含这个文件,例如:

  • examples/app-lifecycle/src/document/index.jsx —— 基础应用骨架;
  • examples/page-lifecycle-with-router/src/document/index.jsx —— 带路由的多页应用骨架;
  • examples/event-handler/src/document/index.jsx —— 事件处理示例骨架;
  • examples/get-element/src/document/index.jsx —— 元素获取示例骨架;
  • examples/with-miniapp-native-page/src/document/index.jsx —— 混入小程序原生页面的示例骨架;
  • examples/with-miniapp-plugin-component/src/document/index.jsx —— 使用小程序插件组件的示例骨架;
  • examples/with-miniapp-native-custom-component/src/document/index.jsx —— 使用小程序原生自定义组件的示例骨架;
  • examples/use-rax-compiled-component-in-runtime-miniapp/src/document/index.jsx —— 运行时小程序中复用编译组件的示例骨架。

这些文件内容高度一致,均以Root, Style, Script为核心(Data仅 SSR 场景需要),并默认采用面向移动端优化的 viewport 配置:

import { createElement } from 'rax'; import { Root, Style, Script} from 'rax-document'; function Document() { return ( <html> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,minimum-scale=1,user-scalable=no,viewport-fit=cover" /> <title>rax-materials-basic-app</title> <Style /> </head> <body> {/* root container */} <Root /> <Script /> </body> </html> ); } export default Document;

这说明一个事实:在 Rax 的标准工程结构里,document目录与pages、components平级(见 src/document 所在目录),Document组件默认导出后由构建与运行时统一消费,页面组件本身不需要关心 Document 的拼装。

五、底层机制:context 注入与渲染流程

理解rax-document的关键在于它依赖的渲染上下文(context)。从源码可见,Root、Data、Style、Script、Manifest、App都会从context中解构出以下字段:

组件读取的 context 字段作用
Root__initialHtmlSSR 初始 HTML,注入#root节点
Data__initialDataSSR 初始数据,输出window.__INITIAL_DATA__
Style__styles自动分析的样式资源地址数组
Script__scripts自动分析的脚本资源地址数组
Manifest__manifests、__pagePathWeb App Manifest 数据与当前页面路径
App__pagePath当前页面路径,用于在路由表中定位 pageInfo

这些__前缀字段由服务端渲染器(如rax-server-renderer)在渲染 Document 时注入,因此:

  • 服务端渲染(SSR):渲染器把编译产物分析出的__styles、__scripts,以及页面渲染得到的__initialHtml、__initialData写入 context,rax-document组件据此输出完整的 HTML 骨架;
  • 客户端渲染(CSR):context 中这些字段通常为空或缺省(__styles = []、__initialHtml || ''),组件输出空根节点等待客户端挂载,Data直接返回null。

这也是为什么文档中反复强调Data是“Required only in SSR project”,而Root在普通 Web 应用中“will be an empty node”——它们的差异正是由渲染上下文在不同场景下的注入内容决定的。

六、组件选型速查与注意事项

场景推荐组合说明
普通 Web 应用Root+Style+Script空#root等待运行时挂载,样式脚本自动注入
SSR 应用Root+Data+Style+Script初始 HTML 与初始数据随首屏输出,Data必须放在 body 内
多页应用(MPA)App+Root+Style+Script用App包住结构,以 render props 方式消费pageInfo
PWA / Manifest可选引入Manifest依据__pagePath输出application/manifest+json

使用中的注意事项(均来自源码行为):

  1. Root输出的 id 固定为root,运行时按此约定挂载,勿在页面中重复定义同名节点;
  2. Style、Script支持 props 透传,可利用此特性补充media、type等属性,但Script默认的crossorigin="anonymous"需要显式传值才能覆盖;
  3. 自闭合<script />在 HTML 中无效,Script组件已通过成对标签规避此问题,手写模板时也需留意;
  4. Data输出的全局变量名固定为window.__INITIAL_DATA__,与 rax-app 运行时的读取逻辑一一对应,改名会导致 SSR 数据在客户端丢失;
  5. App要求config中提供routes,且route.path需要与运行时的__pagePath精确匹配,路由配置参考 app.json。

七、总结

rax-document用六个声明式组件把 Rax 应用的 Document 层标准化:Root管理挂载点与 SSR 初始 HTML,Data注入服务端初始数据,Style与Script自动输出构建分析出的资源标签,App为多页应用提供基于路由的pageInfo,Manifest补充 PWA 能力。它们的共同特点是零配置接入——资源列表与页面数据均由构建器和渲染上下文自动注入,开发者只需在src/document/index.jsx中声明结构即可。结合仓库中的 核心实现 与各 示例项目,即可在任意 Rax 工程中快速搭建出支持 CSR、SSR 与 MPA 三种形态的页面骨架。

  • 前端

【免费下载链接】rax

🐰 Rax is a progressive framework for building universal application. https://rax.js.org

项目地址:https://gitcode.com/gh_mirrors/ra/rax
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询