Page constructor
@gravity-ui/page-constructor ·

Page constructor
Page-constructor 是一个用于根据 JSON 数据渲染网页或其部分的库(稍后将支持 YAML 格式)。
在创建页面时,会采用组件化方法:页面由一组可按任意顺序排列的现成块构建而成。每个块都有特定的类型和一组输入数据参数。
有关输入数据格式和可用块列表,请参阅文档。
安装
npm install @gravity-ui/page-constructor
快速入门
首先,我们需要一个 React 项目和某种服务器。例如,您可以使用 Vite 和 Express 服务器创建一个 React 项目,或者创建一个 Next.js 应用程序——它将同时拥有客户端和服务器端。
安装所需的依赖项:
npm install @gravity-ui/page-constructor @diplodoc/transform @gravity-ui/uikit
将 Page Constructor 插入页面。为了正常工作,它必须包装在 PageConstructorProvider 中:
import {PageConstructor, PageConstructorProvider} from '@gravity-ui/page-constructor';
import '@gravity-ui/page-constructor/styles/styles.scss';
const App = () => {
const content = {
blocks: [
{
type: 'header-block',
title: 'Hello world',
background: {color: '#f0f0f0'},
description:
'**Congratulations!** Have you built a [page-constructor](https://github.com/gravity-ui/page-constructor) into your website',
},
],
};
return (
<PageConstructorProvider>
<PageConstructor content={content} />
</PageConstructorProvider>
);
};
export default App;
这是最简单的连接示例。为了使 YFM 标记生效,您需要在服务器上处理内容并在客户端接收它。
如果您的服务器是独立的应用程序,则需要安装 page-constructor:
npm install @gravity-ui/page-constructor
要处理所有基础块中的 YFM,请调用 contentTransformer 并将内容和选项传递给它:
const express = require('express');
const app = express();
const {contentTransformer} = require('@gravity-ui/page-constructor/server');
const content = {
blocks: [
{
type: 'header-block',
title: 'Hello world',
background: {color: '#f0f0f0'},
description:
'**Congratulations!** Have you built a [page-constructor](https://github.com/gravity-ui/page-constructor) into your website',
},
],
};
app.get('/content', (req, res) => {
res.send({content: contentTransformer({content, options: {lang: 'en'}})});
});
app.listen(3000);
在客户端,添加一个端点调用以接收内容:
import {PageConstructor, PageConstructorProvider} from '@gravity-ui/page-constructor';
import '@gravity-ui/page-constructor/styles/styles.scss';
import {useEffect, useState} from 'react';
const App = () => {
const [content, setContent] = useState();
useEffect(() => {
(async () => {
const response = await fetch('http://localhost:3000/content').then((r) => r.json());
setContent(response.content);
})();
}, []);
return (
<PageConstructorProvider>
<PageConstructor content={content} />
</PageConstructorProvider>
);
};
export default App;
现成模板
要启动新项目,您可以使用我们准备的基于 Next.js 的现成模板。
静态站点生成器
Page Constructor Builder - 使用 @gravity-ui/page-constructor 从 YAML 配置构建静态页面的命令行实用程序。
文档
参数
interface PageConstructorProps {
content: PageContent; // 块数据,JSON 格式。
shouldRenderBlock?: ShouldRenderBlock; // 在渲染每个块时调用的函数,允许您设置其显示条件。
custom?: Custom; // 自定义块(参见“自定义”)。
renderMenu?: () => React.ReactNode; // 渲染页面菜单(包含导航)的函数(我们计划添加默认菜单版本的渲染)。
navigation?: NavigationData; // 用于在 JSON 格式中使用导航组件的导航数据
isBranded?: boolean; // 如果为 true,则添加一个链接到 https://gravity-ui.com/ 的页脚。尝试使用 BrandFooter 组件进行更多自定义。
}
interface PageConstructorProviderProps {
isMobile?: boolean; // 指示代码在移动模式下执行的标志。
locale?: LocaleContextProps; // 关于语言和域的信息(用于生成和格式化链接)。
location?: Location; // 浏览器或路由历史记录的 API,页面 URL。
analytics?: AnalyticsContextProps; // 用于处理分析事件的函数
ssrConfig?: SSR; // 指示代码在服务器端运行的标志。
theme?: 'light' | 'dark'; // 用于渲染页面的主题。
mapsContext?: MapsContextType; // 地图参数:apikey、type、scriptSrc、nonce
}
export interface PageContent extends Animatable {
blocks: Block[];
menu?: Menu;
background?: MediaProps;
}
interface Custom {
blocks?: CustomItems;
subBlocks?: CustomItems;
headers?: CustomItems;
loadable?: LoadableConfig;
}
type ShouldRenderBlock = (block: Block, blockKey: string) => Boolean;
interface Location {
history?: History;
search?: string;
hash?: string;
pathname?: string;
hostname?: string;
}
interface Locale {
lang?: Lang;
tld?: string;
}
interface SSR {
isServer?: boolean;
}
interface NavigationData {
logo: NavigationLogo;
header: HeaderData;
}
interface NavigationLogo {
icon: ImageProps;
text?: string;
url?: string;
}
interface HeaderData {
leftItems: NavigationItem[];
rightItems?: NavigationItem[];
}
interface NavigationLogo {
icon: ImageProps;
text?: string;
url?: string;
}
服务器工具
该包提供了一套服务器工具,用于转换您的内容。
const {fullTransform} = require('@gravity-ui/page-constructor/server');
const {html} = fullTransform(content, {
lang,
extractTitle: true,
allowHTML: true,
path: __dirname,
plugins,
});
底层使用 diplodoc/transfrom 包将 Yandex Flavored Markdown 转换为 HTML,因此它也是对等依赖项。
您也可以在需要的地方使用有用的工具,例如在自定义组件中。
const {
typografToText,
typografToHTML,
yfmTransformer,
} = require('@gravity-ui/page-constructor/server');
const post = {
title: typografToText(title, lang),
content: typografToHTML(content, lang),
description: yfmTransformer(lang, description, {plugins}),
};
您可以在此部分找到更多工具。
服务器工具和转换器的详细文档
有关使用服务器工具的全面指南,包括详细的解释和高级用例,请访问服务器工具使用附加章节。
自定义块
页面构造器允许您使用应用程序中用户定义的块。块是常规的 React 组件。
要将自定义块传递给构造器:
-
在您的应用程序中创建一个块。
-
在您的代码中,创建一个对象,以块类型(字符串)作为键,以导入的块组件作为值。
-
将您创建的对象传递给
PageConstructor组件的custom.blocks、custom.headers或custom.subBlocks参数(custom.headers指定将在常规内容上方单独渲染的块标题)。 -
现在您可以通过指定类型和数据在输入数据(
content参数)中使用创建的块。
要在创建自定义块时使用 mixins 和构造器样式变量,请在您的文件中添加导入:
@import '~@gravity-ui/page-constructor/styles/styles.scss';
要使用默认字体,请在您的文件中添加导入:
@import '~@gravity-ui/page-constructor/styles/fonts.scss';
可加载块
有时需要一个块根据要加载的数据来渲染自身。在这种情况下,将使用可加载块。
要添加自定义 loadable 块,请将 custom.loadable 属性传递给 PageConstructor,其中数据源名称(字符串)作为键,对象作为值。
export interface LoadableConfigItem {
fetch: FetchLoadableData; // 数据加载方法
component: React.ComponentType; // 用于传递加载数据的块
}
type FetchLoadableData<TData = any> = (blockKey: string) => Promise<TData>;
Grid
页面构造器使用 bootstrap 网格及其基于 React 组件的实现,您可以在自己的项目中(包括独立于构造器)使用它们。
使用示例:
import {Grid, Row, Col} from '@gravity-ui/page-constructor';
const Page = ({children}: PropsWithChildren<PageProps>) => (
<Grid>
<Row>
<Col sizes={{lg: 4, sm: 6, all: 12}}>{children}</Col>
</Row>
</Grid>
);
Navigation
页面导航也可以独立于构造器使用:
import {Navigation} from '@gravity-ui/page-constructor';
const Page= ({data, logo}: React.PropsWithChildren<PageProps>) => <Navigation data={data} logo={logo} />;
Blocks
每个块都是一个原子顶级组件。它们存储在 src/units/constructor/blocks 目录中。
Sub-blocks
子块是可以在块 children 属性中使用的组件。在配置中,指定了子块的列表。渲染后,这些子块将作为 children 传递给块。
如何向 page-constructor 添加新块
-
在
src/blocks或src/sub-blocks目录中,创建一个包含块或子块代码的文件夹。 -
将块或子块名称添加到
BlockType或SubBlockType枚举中,并在src/models/constructor-items/blocks.ts或src/models/constructor-items/sub-blocks.ts文件中以与现有项类似的方式描述其属性。 -
在
src/blocks/index.ts文件中为块添加导出,在src/sub-blocks/index.ts文件中为子块添加导出。 -
在
src/constructor-items.ts文件中的映射中添加新组件或块。 -
为新块添加验证器:
- 在块或子块目录中添加
schema.ts文件。在此文件中,使用json-schema格式描述组件的参数验证器。 - 在
schema/validators/blocks.ts或schema/validators/sub-blocks.ts文件中导出它。 - 在
schema/index.ts文件中的enum或selectCases中添加它。
- 在块或子块目录中添加
-
在块目录中,添加
README.md文件,其中包含输入参数的描述。 -
在块目录中,在
__stories__文件夹中添加 storybook 演示。故事的所有演示内容应放在故事目录中的data.json文件中。泛型Story必须接受块属性的类型,否则 Storybook 中将显示不正确的块属性。 -
将块数据模板添加到
src/editor/data/templates/文件夹,文件名应与块类型匹配。 -
(可选)将块预览图标添加到
src/editor/data/previews/文件夹,文件名应与块类型匹配。
Themes
PageConstructor 允许您使用主题:您可以根据应用程序中选择的主题为各个块属性设置不同的值。
要为块属性添加主题支持:
-
在
models/blocks.ts文件中,使用ThemeSupporting<T>泛型定义相应块属性的类型,其中T是属性的类型。 -
在块的
react组件所在的文件中,使用getThemedValue和useThemehook 获取带有主题的属性值(请参阅MediaBlock.tsx块中的示例)。 -
为属性验证器添加主题支持:在块的
schema.ts文件中,将该属性包装在withTheme中。
i18n
page-constructor 是一个 uikit-based 库,我们使用 uikit 的 i18n 实例。要设置国际化,您只需使用 uikit 的 configure:
import {configure} from '@gravity-ui/uikit';
configure({
lang: 'ru',
});
Maps
要使用地图,请将地图类型、scriptSrc 和 apiKey 放在 PageConstructorProvider 的 mapContext 字段中。
您可以在项目根目录下的 .env.development 文件中为开发模式定义环境变量。
STORYBOOK_GMAP_API_KEY - google maps 的 apiKey
Analytics
Init
要开始使用分析,请将一个处理程序传递给构造器。该处理程序必须在项目端创建。它接收三类事件:
- **默认事件** 是由 Page Constructor 为按钮、链接、导航和控件交互生成的通用事件。将 `autoEvents.enabled` 设置为 `true` 以触发它们。
- **扩展事件** 是由组合库提供的已注册事件。`autoEvents.extendedEvents` 的存在会独立于 `enabled` 启用它们,并可选择添加前缀和计数器。
- **自定义事件** 由消费者通过 `analyticsEvents` 提供。自动事件配置不会改变它们。
对象形式是推荐的配置:
```ts
function sendEvents(events: MyEventType []) {
...
}
<PageConstructorProvider
...
analytics={{
sendEvents,
autoEvents: {
enabled: true,
extendedEvents: {
prefix: 'LIBRARY_',
counter: 'secondary',
},
},
}}
...
/>
type ExtendedEventsConfig = {
prefix?: string;
counter?: string;
};
type AutoEventsConfig = {
enabled: boolean;
extendedEvents?: ExtendedEventsConfig;
};
为了向后兼容,仍然支持旧的布尔形式:true 等同于 {enabled: true},false 等同于 {enabled: false}。如果省略 autoEvents,则默认事件和扩展事件都将被禁用。extendedEvents 对象即使在 enabled 为 false 时也会启用提供的扩展事件。
扩展事件必须具有 type: 'extended-event'。它们的前缀将按照配置精确拼接,不会改变大小写、分隔符或空格。如果设置了 counter,它将为扩展事件定义 counters.include:
// 提供的事件
{name: 'REGISTERED_CLICK', type: 'extended-event'}
// 使用上述配置在 sendEvents 中接收到的事件
{
name: 'LIBRARY_REGISTERED_CLICK',
type: 'extended-event',
counters: {include: ['secondary']},
}
事件的发送顺序是:首先是生成的默认事件(如果启用),然后是提供的扩展事件和自定义事件,按其原始顺序。如果未配置 extendedEvents,则会省略扩展事件。任何特定于交互的附加上下文将在最后合并到每个触发的事件中。
事件对象只有一个必需字段 - name。它还包含预定义字段,用于帮助管理复杂逻辑。例如,counter.include 可以帮助在项目中使用多个分析系统时将事件发送到特定的计数器。
type AnalyticsEvent<T = {}> = T & {
name: string;
type?: string;
counters?: AnalyticsCounters;
context?: string;
};
可以为项目配置所需的事件类型。
type MyEventType = AnalyticsEvent<{
[key: string]?: string; // 只支持 'string' 类型
}>;
Counter 选择器
可以配置事件以确定发送到哪个分析系统。
type AnalyticsCounters = {
include?: string[]; // 将应用的分析计数器 ID 数组
exclude?: string[]; // 不会应用的分析计数器 ID 数组
};
context 参数
传递 context 值以定义事件触发的项目中的位置。
使用下面的选择器或创建满足项目需求的逻辑。
// analyticsHandler.ts
if (isCounterAllowed(counterName, counters)) {
analyticsCounter.reachGoal(counterName, name, parameters);
}
保留的事件类型
几个预定义的事件类型用于标记自动配置的事件。例如,可以使用这些类型来过滤默认事件。
enum PredefinedEventTypes {
Default = 'default-event', // 在每次按钮点击时触发的默认事件
Extended = 'extended-event', // 由组合库提供的事件
Play = 'play', // React 播放器事件
Stop = 'stop', // React 播放器事件
}
开发
npm ci
npm run dev
关于 Vite 的说明
import react from '@vitejs/plugin-react-swc';
import dynamicImport from 'vite-plugin-dynamic-import';
export default defineConfig({
plugins: [
react(),
dynamicImport({
filter: (id) => id.includes('/node_modules/@gravity-ui/page-constructor'),
}),
],
});
对于 Vite,您需要安装 vite-plugin-dynamic-import 插件并配置 config 以便动态导入正常工作。
发布流程
通常情况下,我们使用两种类型的提交:
fix:fix类型的提交用于修复代码中的错误(这对应于语义化版本控制中的 PATCH)。feat:feat类型的提交在代码库中引入新功能(这对应于语义化版本控制中的 MINOR)。BREAKING CHANGE: 带有BREAKING CHANGE:页脚的提交,或在类型/范围后附加!的提交,会引入破坏性的 API 更改(对应于语义化版本控制中的 MAJOR)。BREAKING CHANGE可以是任何类型提交的一部分。- 要手动设置发布包版本,您需要在提交消息中添加
Release-As: <version>,例如:
git commit -m 'chore: bump release
Release-As: 1.2.3'
您可以在此处查看所有信息。
当您的 pull request 获得代码所有者的批准并通过所有检查后,请执行以下操作:
- 检查是否存在来自其他贡献者的机器人发布的 release pull request(看起来像
chore(main): release 0.0.0)。如果存在,请检查它未被合并的原因。如果贡献者同意发布共享版本,请继续下一步。如果不同意,请要求他发布自己的版本,然后继续下一步。 - 对您的 PR 进行 Squash and merge(发布新版本时使用 Github-Actions 非常重要)。
- 等待机器人创建一个包含新包版本和
CHANGELOG.md中您更改信息的 PR。您可以在Actions 标签页上查看该过程。 - 检查
CHANGELOG.md中的更改并批准机器人的 PR。 - 对 PR 进行 Squash and merge。您可以在Actions 标签页上查看发布过程。
Alpha 版本发布
如果您想从您的分支发布包的 alpha 版本,您可以手动进行:
- 转到 Actions 标签页。
- 在左侧页面选择 "Release alpha version" 工作流。
- 在右侧,您会看到 "Run workflow" 按钮。在这里您可以选择分支。
- 您还可以看到一个手动版本字段。如果您是第一次在您的分支发布 alpha 版本,请不要在此处设置任何内容。首次发布后,您必须手动设置新版本,因为我们不会更改
package.json,以防分支很快过期。否则,您将收到错误。请在您的手动版本中使用alpha前缀。 - 点击 "Run workflow" 并等待操作完成。您可以发布任意次数的版本,但不要滥用它,只有在您真正需要时才发布版本。在其他情况下,请使用 npm pack。
Beta-major 版本发布
如果您想发布新 major 版本,在稳定版本之前您可能需要 beta 版本,请执行以下操作:
- 创建或更新
beta分支。 - 将您的更改添加到其中。
- 当您准备好发布新的 beta 版本时,手动使用一个空提交进行发布(或者您可以将此提交消息和页脚添加到最后一个提交中):
git commit -m 'fix: last commit
Release-As: 3.0.0-beta.0' --allow-empty
4. Release please robot 将会创建一个新的 PR 到 `beta` 分支,并更新 `CHANGELOG.md` 和包的版本号。
5. 你可以重复这个过程任意多次。当你准备好发布不带 beta 标签的最新主版本时,你需要从 `beta` 分支创建一个 PR 到 `main` 分支。请注意,你的包版本带有 beta 标签是正常的。Robot 会识别这一点并正确处理。`3.0.0-beta.0` 将会变成 `3.0.0`。
### 之前主版本的发布流程
如果你想在提交到 main 后发布之前主版本的最新版本,请执行以下操作:
1. 更新必要的分支,之前的发布分支名称如下:
1. `version-1.x.x/fixes` - 用于主版本 1.x.x
2. `version-2.x.x` - 用于主版本 2.x.x
2. 从之前的发布分支检出一个新分支。
3. 从 `main` 分支 cherry-pick 你的提交。
4. 创建 PR,获得批准后合并到之前的发布分支。
5. Squash and merge 你的 PR(使用 Github-Actions 发布新版本非常重要)。
6. 等待 robot 创建一个 PR,其中包含新版本的包以及 `CHANGELOG.md` 中关于你更改的信息。你可以在 [Actions 标签页](https://github.com/gravity-ui/page-constructor/actions) 上查看该过程。
7. 检查 `CHANGELOG.md` 中的更改并批准 robot 的 PR。
8. Squash and merge PR。你可以在 [Actions 标签页](https://github.com/gravity-ui/page-constructor/actions) 上查看发布过程。
## Page constructor editor
Editor 提供了一个用户界面,用于页面内容的管理和实时预览。
如何使用:
```tsx
import {Editor} from '@gravity-ui/page-constructor/editor';
interface MyAppEditorProps {
initialContent: PageContent;
transformContent: ContentTransformer;
onChange: (content: PageContent) => void;
}
export const MyAppEditor = ({initialContent, onChange, transformContent}: MyAppEditorProps) => (
<Editor content={initialContent} onChange={onChange} transformContent={transformContent} />
);
Memory Bank
本项目包含一个全面的 Memory Bank - 一系列 Markdown 文档文件,提供了关于项目架构、组件和使用模式的详细信息。Memory Bank 在使用 AI 代理时尤其有用,因为它包含关于以下内容的结构化信息:
- 项目概述: 核心需求、目标和背景
- 组件文档: 所有组件的详细使用指南
- 系统架构: 技术模式和设计决策
- 开发进度: 当前状态和实现细节
使用 Memory Bank
Memory Bank 位于 memory-bank/ 目录中,由常规的 Markdown 文件组成,可以像阅读其他文档一样阅读:
projectbrief.md- 包含核心需求的基础文档productContext.md- 项目目的和用户体验目标systemPatterns.md- 架构和技术决策techContext.md- 技术栈、设置和限制activeContext.md- 当前工作重点和近期更改progress.md- 实现状态和已知问题usage/- 组件特定的使用文档storybookComponents.md- Storybook 集成细节
Tests
完整的文档可在提供的 链接 中找到。
License
根据 MIT 许可证分发。详情请参阅 LICENSE。
For AI agents
一个用于从声明式 JSON/YAML 配置渲染整个网页或页面部分的库,使用一组现成的、可排序的块 — 使用它来构建营销/着陆页,而不是通用的应用程序 UI。
何时使用
- 数据驱动的页面:使用
PageConstructorProvider包裹的PageConstructor来渲染类型化块的content配置。 - 由预构建块(标题、媒体、卡片等)组成的营销、着陆页和文档页面。
- 通过
@gravity-ui/page-constructor/server工具 (contentTransformer,fullTransform) 进行服务器端 YFM 处理。 - 单独重用响应式网格 (
Grid/Row/Col) 或Navigation组件。
何时不要使用
- 通用应用程序 UI(按钮、表单、模态框)— 使用
@gravity-ui/uikit。 - 编辑 Markdown/YFM 内容 — 使用
@gravity-ui/markdown-editor。 - 应用导航外壳(侧边栏、页头)— 使用
@gravity-ui/navigation;此包中的Navigation是页面级别的顶部导航。
常见陷阱
PageConstructor必须被PageConstructorProvider包裹。 直接渲染会破坏上下文(区域设置、主题、SSR、分析)。contentprop 是content,形状为{blocks: [...]}。 每个块对象都需要一个type来匹配已知的块及其数据字段;没有data/configprop。- 块文本中的 YFM 需要服务器处理。 除非你通过
@gravity-ui/page-constructor/server中的contentTransformer/fullTransform运行内容,否则 Markdown 风格的字段将呈现为纯文本;@diplodoc/transform是必需的对等依赖项。 - 导入 SCSS 样式。 添加
@gravity-ui/page-constructor/styles/styles.scss(SCSS,不是 CSS);自定义块导入相同的文件以重用 mixins/变量。 - Vite 需要
vite-plugin-dynamic-import。 没有它,动态块导入在 Vite 下会失败。
For AI agents 的文档
已安装版本的 Agent 可读文档位于 node_modules/@gravity-ui/page-constructor/build/docs/INDEX.md。