你需要一个完整的编辑基础
Canvas、Blocks、Style Manager、Asset Manager、Layer Manager、命令和存储是核心模块,而非积压。如果你的路线图目前包含四个模块,那就是信号。
比较 Craft.js 和 GrapesJS,了解架构差异,探索实时可视化编辑器,并学习如何将基于 Craft.js 的编辑器迁移到 GrapesJS。
你拥有节点树周围的所有东西。
编辑子系统组装完毕。
| 软件包 | 版本 | 许可 | 最新发布 | GitHub 星标 | npm 下载 | |
|---|---|---|---|---|---|---|
| @craftjs/core | 0.2.12 | MIT | 2025-02-14 | 8,738 | 267k/mo | 资料来源 |
| grapesjs | 0.23.6 | BSD-3-Clause | 2026-08-25 | 26,188 | 1.4M/mo | 资料来源 |
请阅读npm注册表和GitHub上的API在2026-09-03上的资料。星数和下载数据是生态系统信号,不是衡量哪个项目适合你的产品。两个仓库都是活跃发布的,且都没有归档。
GrapesJS 和 Craft.js 解决了相关但不同的问题。Craft.js 是一个专注于 React 构建可定制页面编辑器的框架,而 GrapesJS 则提供了更广泛的可视化编辑器基础,包含组件、块、样式、资产、存储和插件。
当你需要更多开箱即用的编辑器基础设施时,GrapesJS 是一个强有力的替代方案,尤其是针对基于 HTML/CSS 的视觉编辑器、SaaS 页面构建器、CMS 编辑曲面和可嵌入构建器。
它并非普遍更好。如果你的产品非常以React为中心,且编辑器必须运行React组件树,那么Craft.js更为贴合——下面的章节详细说明了这一点,而非免责声明。
没有分数,因为行数值不一致。每个能力都标注了它的来源:包含在项目中、通过插件访问,还是由你自己的代码定义。
| 能力 | Craft.js | GrapesJS |
|---|---|---|
| 主要关注点 | 一个用于构建自己页面编辑器的React框架。 | 一个带有编辑界面的可视化编辑器框架。 |
| React优先 | 内置是的。Components 是 React 成分;树是 React 树。 | 附加组件不。核心是框架无关的;React 通过 @grapesjs/react 到达。 |
| 视觉画布 | 内置你的组件是实时渲染的,直接是selectable。 | 内置一个独立画布,支持直接选择和内联文本编辑。 |
| 自定义组件 | 内置任何由静态Craft.js描述符描述的React成分。 | 内置任何组件类型,用模型、traits和默认值描述。 |
| 拖放 | 内置包含在内。连接器组成一个元件 draggable 和 droppable。 | 内置包含。Components 在画布上的容器间移动。 |
| 拖放调色板 | 自定义你构建工具箱,列出可以拖拽的物品。 | 内置包含。Blocks与Block Manager一起注册,并出现在面板中。 |
| 样式管理 | 自定义应用定义。样式就是你的组件已经做的事。 | 内置包含在内。Style Manager 会根据选择器和设备编辑 CSS。 |
| 资产管理 | 自定义应用定义。你自己提供媒体选择器。 | 内置包含。Asset Manager负责上传和媒体库。 |
| 序列化 | 内置包含。节点树串行化到 JSON 并加载回去。 | 内置包含。项目数据序列化为JSON并返回。 |
| 存储层 | 自定义应用定义。你决定序列化的JSON放哪里。 | 内置包含。Storage Manager 会在本地或你的终端上持续存在。 |
| 撤销并重做 | 内置包含在内。撤销和重做历史是 API 核心的一部分。 | 内置附带。Undo Manager,带有可以绑定到面板上的命令。 |
| 图层树面板 | 附加组件官方插件包渲染了这棵树。 | 内置包含在内。Layer Manager面板自带核心。 |
| Plugin 架构 | 自定义没有插件系统。你自己编写包,也为社区编写。 | 内置包含。插件是一个接收 Editor 并扩展它的函数。 |
| 框架方法 | 设计上就是以React为重点。 | 框架无关的核心,按框架集成。 |
| HTML 和 CSS 工作流程 | 自定义应用定义。你自己渲染React并生成标记。 | 内置本地。主要输出是干净的HTML和CSS。 |
| SaaS 页面构建器 | 有可能。你要搭建周围的编辑表面。 | 贴合度很强。大部分表面已经在那里了。 |
| 白标编辑器器器 | 有可能。界面从第一次提交起就归你所有。 | 高度贴合。面板、图标、标签和翻译均可更换。 |
与两个项目在2026-09-03上的文档对照:Craft.js 0.2.12和GrapesJS 0.23.6。“自定义”是描述,而非批评——在几行中,这正是团队选择无主见框架的原因。
看看这在架构上意味着什么最大的区别不仅仅是功能数量。这两个框架为开发者提供了不同的视觉编辑器起点。
一条线性链。Craft.js拥有节点树;其他每一层都是你的。
一个带有子系统的链条。引擎配备了自己的编辑模块。
Craft.js 起始位置较低。它提供节点树、拖放连接器、序列化和历史记录,并且有意不对样式、资源、存储或画布周围的面板采取任何立场。这是设计决策,对于一个想要拥有完整编辑体验的团队来说,这是正确的选择。
GrapesJS 起始位置较高。画布、Blocks 面板、Style Manager、Asset Manager、Layer Manager、命令和 Storage Manager 都是核心模块,且存在一个插件 API 可以替换或扩展它们。
这两个框架都不是平台。它们都不提供托管、账户、权限、计费、多租户或发布流程。这些都不属于两个框架,最终还是你的责任。
所以问题不是哪个项目功能更多。而是哪个起点让你真正想拥有的零件。
如果你需要建造的层在第一列中高于Craft.js,那么GrapesJS值得评估。如果层级低于Craft.js,迁移几乎没有什么好处。
Craft.js是一个设计良好的框架,目的明确,且有几种产品形状更适合它。这些不是附加条件——而是选择GrapesJS是错误选择的情况。
如果从路由、状态到渲染等所有内容都存在于React中,那么由React原语构建的编辑器就只会留在一个心理模型里。没有任何东西必须跨越边界。
Craft.js 编辑的是实际的 React 树。如果你的用户排列的是你的 React 组件——而不是标记——那么这个对应就是全部值,而 HTML 优先模型不会重现它。
Craft.js不会强加面板、工具栏或设置表面。一个有强烈设计理念的团队可以获得一个全新的起点,而不是被强制覆盖。
备忘录、上下文、悬疑边界、渲染调度:当这些细节重要时,将可编辑树保留在React中,可以让你掌控这些细节。
一个拥有真实用户和真实内容的可用编辑器是宝贵的资产。仅靠功能列表很少能证明更换编辑器的成本。
如果你的解析器、设置面板和内容流水线都假设是Craft.js节点模型,那耦合关系很深,在别处重现它才是真正的工作。
在这种情况下,替换Craft.js可能会带来不必要的迁移工作。
详见完整决策矩阵下面的信号大致说明你本来需要制作的内容。每张卡片收集其中几个信号;如果有几个信号描述了你的项目,评估就值得你花时间。
Canvas、Blocks、Style Manager、Asset Manager、Layer Manager、命令和存储是核心模块,而非积压。如果你的路线图目前包含四个模块,那就是信号。
GrapesJS 编辑文档并生成干净的标记和样式表。当用户创建的工件必须在 React 应用外渲染时,那是原生工作流程,而不是你自己写的导出步骤。
你们团队的精力主要用于账目、计划、模板和发布——而不是重建样式面板。GrapesJS是产品内部的编辑层,而不是产品本身。
SaaS 页面构建指南编辑器安装在你控制的DOM元素中,而这个元素是你不一定写的应用。这个约束就是嵌入指南的意义所在。
可嵌入建造者指南分镜、图标、标签、翻译以及整个视觉语言都是可以替换的,所以编辑可以承载客户的品牌,而不是你的品牌。
白标指南电子邮件需要基于表格的标记和内嵌的CSS——一种不同的文档模型,但编辑表面相同。正好有预设可以满足这一点。
电子邮件编辑指南不要只是比较功能。自己试试编辑器——拖动一个块,选取一个元素,重新样式,然后把画布切换成手机宽度。
原装的GrapesJS编辑器,没有插件,也没有设计系统。这是核心在你配置任何东西之前给你的。
本页加载帧。
这两个项目都把用户拖动的东西称为“组件”,但这两个定义不能互换。这就是决定迁移工作量的区别。
编辑器通过连接器驱动的React组件。其可编辑表面是React道具。
编辑器自身文档模型中的定义。其可编辑表面是 traits、属性和样式。
带有Craft.js描述符的React组件会成为带有模型和默认值的注册组件类型。它产生的标记会进入该模型。
编辑器用户可以更改的道具变成traits。道具只有你的代码集变成属性或固定模型属性。
序列化的节点树变成了项目数据。两者都是JSON,但形状不同,所以这是一个你写入并测试的变换。
你在React里写的设置面板被内置的特性面板取代,还有自定义命令和核心没有的行为面板。
这是一种概念映射,不是自动转换。没有工具读取Craft.js解析器并输出GrapesJS组件类型——上述对应关系是你实现的,每个组件类型一次。
同样的理念——一个带有可编辑标题和描述的hero部分——在两侧都定义了。把它们当作一个概念的两个定义,而不是同一文件的对比和之后。
// Craft.js: a component is a React component plus a `craft` descriptor.
// The editor drives it through connectors from useNode().
import { useNode } from '@craftjs/core';
export const Hero = ({ title, description }) => {
const { connectors: { connect, drag } } = useNode();
return (
<section ref={(ref) => connect(drag(ref))} className="hero">
<h1>{title}</h1>
<p>{description}</p>
</section>
);
};
Hero.craft = {
props: {
title: 'Build faster',
description: 'Create beautiful pages',
},
related: {
// The panel that edits those props is a React component you write.
settings: HeroSettings,
},
};// GrapesJS: a component type owns its markup and its editable traits.
// Traits are the closest analogue to Craft.js props.
editor.Components.addType('hero', {
model: {
defaults: {
tagName: 'section',
attributes: { class: 'hero' },
traits: [
{ name: 'title', type: 'text', changeProp: true },
{ name: 'description', type: 'text', changeProp: true },
],
components: [
{ tagName: 'h1', type: 'text', content: 'Build faster' },
{ tagName: 'p', type: 'text', content: 'Create beautiful pages' },
],
},
},
});
// A block is what makes the type draggable from the panel. Craft.js derives
// the equivalent from your toolbox; in GrapesJS it is a separate registration.
editor.Blocks.add('hero', {
label: 'Hero',
category: 'Sections',
content: { type: 'hero' },
});
// The trait panel edits those traits. You do not write it.Component 定义只是其中一半。你用户已经创建的所有内容都存储在 Craft.js 节点格式中,迁移它意味着要走过那棵树,并输出你新组件类型理解的定义。
// Craft.js persists `query.serialize()` — a JSON map of nodes keyed by id,
// each with { type: { resolvedName }, props, nodes }. Migrating it means
// walking that map and emitting the GrapesJS component definitions your new
// types understand: a transform you write once, per component type.
const toGrapesJs = {
Hero: ({ title, description }) => ({ type: 'hero', title, description }),
// …one entry per resolver entry in your Craft.js editor
};
function craftNodeToGjs(nodes, id) {
const node = nodes[id];
const map = toGrapesJs[node.type.resolvedName];
if (!map) throw new Error(`Unmapped Craft.js component: ${node.type.resolvedName}`);
return {
...map(node.props),
components: (node.nodes ?? []).map((child) => craftNodeToGjs(nodes, child)),
};
}
const nodes = JSON.parse(craftSerializedJson);
editor.setComponents(craftNodeToGjs(nodes, 'ROOT').components);这两个样本都是说明性的。它们展示了映射的形状和变换的形状;这两个都不是插入迁移,项目中也没有生成任何一个。
大致分为三个层级。这些层面都没有持续时间,因为真正的答案完全取决于你的代码库——而一个承诺给你一个无法得知的数字的页面,并不能帮你规划。
有几种组件类型,一个小的设置表面,以及尚未积累太多变体的内容。
你可能在这里,如果
通常是相对有针对性的迁移。
一个真正的编辑器,拥有自己的面板、模板库、保存的文档和一个组件集,并且已经扩展到适合产品的组件集。
你可能在这里,如果
需要组件和数据映射。
Components 依赖于应用上下文、编辑器行为在不明显的地方扩展,以及嵌入在编辑体验中的业务规则。
你可能在这里,如果
需要架构审查和分阶段迁移。
迁移复杂度取决于现有应用与Craft.js的深度耦合程度。
按照清单工作迁移的顺序,分为四个阶段。无论是自己动手还是交给别人,都非常有用。
15 步骤在重建任何东西之前,先弄清楚到底需要移动的是什么。
Component 类型、调色板以及用户期望的控件。
现有内容、存储地点以及它引用的媒体。
证明它有效,然后不经过硬性切换就把人调过去。
这些方框是可打印的计划,不是保存状态——这里没有存储在浏览器里。把步骤复制到你团队已经用的追踪器里。
和有经验的人聊聊吧不是自动的。
Craft.js 和 GrapesJS 使用不同的组件和编辑器模型。Craft.js 组件是编辑器通过连接器驱动的 React 组件;GrapesJS 组件类型是编辑器自身文档模型中的定义。没有适配器能将两种模式转换成另一种,任何告诉你相反的页面都是描述你后来会发现的工作。
把你的 React 组件产生的标记和样式,用 traits 表示为组件类型。这是默认路径,通常比听起来省事,因为标记已经存在。
费用: 每个组件类型都有一个定义,手写并像其他代码一样审查。
定价规则、验证、数据获取和格式化很少属于编辑者。将它们整合进编辑调用的模块中,迁移后它们依然完好无损。
费用: 这需要先把逻辑和渲染分开,这通常无论如何都值得做。
有些组件在渲染时确实需要React。这些可以渲染到编辑器拥有的容器中,编辑者将结果视为一个组件。
费用: 最贵的选择。用它来处理少数需要的部分,而不是作为通用策略。
用户关心的是可编辑表面。将每个可编辑道具映射到一个特征或属性,即使底层实现完全是新的,也能保持体验。
费用: 必须对应物业名称和类型,并重新确立违约情况。
选择GrapesJS的一个优点是可以通过插件扩展编辑器,而不是从零实现所有功能。这些是GJS.Market目录中的真实列表——名称、价格和图片直接来自市场,因此这里没有任何内容偏离实际销售内容。
这些都是不同的产品,各自有自己的架构指南、权衡和插件。
@grapesjs/react一个官方的软件包组件,或者一个普通的参考和一个效果。Next.jsnext/dynamic仅客户端,加载得很懒,这样就不会进入第一个负载。VueonMounted()把它装进挂钩,拆下后销毁它。AngularngAfterViewInit()在视图初始化后启动它,外部是变更检测。npm install grapesjs @grapesjs/reactimport grapesjs from 'grapesjs';
import GjsEditor from '@grapesjs/react';
import 'grapesjs/dist/css/grapes.min.css';
// GrapesJS owns an iframe canvas and touches `window`, so it mounts on the
// client. In Next.js, load this from a client component.
export default function VisualEditor() {
return (
<GjsEditor
grapesjs={grapesjs}
options={{
height: '100vh',
storageManager: { type: 'remote', autosave: true },
}}
onEditor={(editor) => {
// Register the component types you mapped from your Craft.js
// components here.
}}
/>
);
}集成层故意设计得很薄。你的框架拥有页面,GrapesJS 拥有画布,组件类型在编辑器存在后才注册。
如果你决定GrapesJS适合,还有第二个选择:是带着现有编辑器过去,还是在旁边新建一个。它们的价格不同。
每行一个需求,一个起始点。其中四个指向Craft.js,这也是其他五个值得一读的原因。
| 你的需求 | 更好的起点 |
|---|---|
| 一个运行于 React 组件树上的编辑器 | Craft.js |
| 完整的视觉编辑器基础 | GrapesJS |
| 一个 HTML 和 CSS 页面构建器 | GrapesJS |
| 一个深度以React为先的架构 | Craft.js |
| 内置的视觉编辑工具 | GrapesJS |
| 高度定制的React编辑器界面 | Craft.js |
| 一个插件驱动的编辑器生态系统 | GrapesJS |
| SaaS 页面构建器 | GrapesJS |
| 一个成熟的Craft.js应用 | 先评估迁移成本 |
没有万能的赢家。正确的选择取决于你的编辑器架构和产品需求。
GJS.Market 构建并迁移 GrapesJS 编辑器。如果你不想单独完成清单,这些是参与项目涵盖的阶段——第一个阶段是评估,可能会得出继续使用 Craft.js 是正确答案的结论。
架构评估
我们阅读现有编辑器,计算它与Craft.js的深度耦合,然后直接判断迁移是否值得。
Component 与模板映射
每个组件类型都映射到 GrapesJS 定义,其可编辑属性转换为 traits,现有模板也与其一并映射。
数据和资产迁移
从序列化的Craft.js内容到项目数据的转换过程会被写入并与真实文档进行测试,媒体也会随之移动。
自定义编辑器逻辑与插件
核心中没有对应行为的行为会被重建为命令、面板或插件,从而保持可维护性,而不是被补丁加进去。
存储与发布
Storage Manager是有线连接到你的后端,发布路径是端到端连接的。
并行运行与测试
两位编辑并排对应同一内容,以便新编辑在有人被调往之前进行验证。
这取决于你构建的是什么。对于想要一个包含组件、模块、样式、资源和存储的可视化编辑器基础的团队来说,GrapesJS 是最接近的替代方案。如果你想以 React 为先,Puck 在精神上更接近 Craft.js。没有唯一的最佳答案,这也是为什么这个页面是比较而非推荐。
不是直接插入式的。两者有不同的组件模型,所以迁移意味着重新定义组件并转换存储内容。GrapesJS 是替代品,因为它能完成相同的工作,而不是可以交换依赖。
Craft.js 是一个用于构建页面编辑器的 React 框架:它拥有 React 组件的节点树,样式、资源、存储及相关界面则留给你。GrapesJS 是一个可视化编辑器框架,其核心已经包含画布、块、Style Manager、Asset Manager、Layer Manager、命令、存储和插件系统。
没有。核心是框架无关的,没有 React 依赖。有一个官方的 React 软件包包,但 GrapesJS 内部维护自己的文档模型,而不是 React 树。
是的。官方的软件包器将编辑器挂载为React组件,你同样可以自己从参考和效果启动它。React应用托管编辑器;编辑器仍然拥有自己的画布。
不是自动的。Craft.js 组件是编辑器通过连接器驱动的 React 组件;GrapesJS 组件类型是编辑器自身模型中的定义。大多数团队会重建可视化定义,将业务逻辑放在编辑器外的模块中,并只为真正需要 React 的渲染时组件添加集成层。
是的,通过映射它们而不是转换。每个Craft.js组件都变成注册的组件类型,其可编辑的道具变成traits,渲染的标记会移动到该类型的模型中。这是手写的工作,大致每个组件类型有一个定义。
道具,编辑器用户可以将映射改为traits。只有你的代码集映射到属性或固定模型属性。应用状态中组件在渲染时读取时没有直接对应的,通常需要从组件转移到编辑器周围的代码中。
是的,一旦组件类型存在。模板是存储内容的,所以它会像其他文档一样进行转换:走序列化的节点树,输出等效的 GrapesJS 组件,并将结果保存为模板到新系统中。
是的,写一个变换。Craft.js 坚持一个 JSON 节点映射,每个节点都携带已解析的组件名、其道具和子节点。GrapesJS 以自己的 JSON 形状加载项目数据。两者都是 JSON,迁移是树游历加上每个组件类型一个映射函数。
这是它的常见用途。GrapesJS 提供编辑层;账户、计划、权限、模板、托管和发布仍由你自行构建。SaaS 页面构建指南介绍了这些职责的划分。
是的。GrapesJS 是仅客户端的——启动时会接触浏览器全局——所以加载得很懒,渲染在服务器渲染之外。这也避免了大约 300 KB 的初始负载。
是的。该包自带类型定义,因此不需要单独的类型包。TypeScript 指南涵盖了版本底线和那些声明但未导出的类型。
是的。面板、按钮、图标、标签和翻译都是可替换的,API插件可以完全移除或重建界面的部分内容。这正是它作为嵌入他人品牌产品中的编辑器可行的原因。
是的。插件是一个接收Editor并注册组件类型、模块、命令、面板或存储适配器的功能。这是主要的扩展机制,也是市场列表的基础。
GJS.Market 是一个包含 GrapesJS 插件、模块、预设和集成的目录,按目录部分组织。本页的插件部分显示当前商品列表及实时价格。
当你有很多模板、依赖当前编辑器的用户,或有值得携带的业务逻辑时,就迁移。当Craft.js编辑器还很小、保存的内容很少,或者组件架构本身就在变化时,重新开始。无论哪种情况,现有文档仍然需要转换。
是的。参与从架构评估开始,然后涵盖组件和模板映射、数据和资产迁移、自定义编辑器逻辑、存储与发布,以及一段并行运行期。评估可以得出结论,继续使用Craft.js是正确答案。
如果你现在的编辑器需要太多自定义基础设施,就用实际项目来评估GrapesJS。从编辑器开始,用插件扩展它,并围绕你的产品定制架构。