ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

blocks 编辑器 UI 演进全解:从版本记录看 blocks-ui 25 个迭代背后的功能架构

blocks 编辑器 UI 演进全解:从版本记录看 blocks-ui 25 个迭代背后的功能架构
  • 低代码
  • 前端

【免费下载链接】blocks

A JSX-based page builder for creating beautiful websites without writing code

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

blocks-ui是 blocks(一个基于 JSX 的页面构建器)的编辑器界面包,其 CHANGELOG 以 changesets 风格记录了从 0.0.55 到 0.0.79 共 25 个 patch 版本的每一次提交说明。本文以这份变更记录为主线,逐版本梳理 blocks-ui 从工程脚手架搭建、画布拖拽交互、代码编辑模式,到 sx 样式系统与无障碍优化的完整演进路径,并结合仓库源码为每条关键记录提供实现层面的佐证。

一、版本记录文件的结构:changesets 风格的 changelog

先理解这份 changelog 的书写格式,是读懂后续内容的钥匙。文件 packages/blocks-ui/CHANGELOG.md 中每个版本条目遵循固定结构:

## 0.0.79 ### Patch Changes - c1875b8: Prop label screen reader support - a9ad043: Bump deps - Updated dependencies [a9ad043] - property-controls@0.0.60 - @blocks/react@0.0.64

其含义为:

  • ## 0.0.79:发布版本号。blocks-ui是 0.x 阶段的包,因此所有更新都归类为Patch Changes;
  • - c1875b8: ...:提交哈希(短 SHA)加一句人类可读的变更说明,是 changesets 工具在发布时从CHANGELOG.md汇总的标准格式;
  • Updated dependencies [...]:monorepo 联动升级标记。当依赖的兄弟包(如property-controls、@blocks/react)因某次提交发布新版时,blocks-ui会跟随发布一个 patch 版本以同步依赖。

这种联动关系在 packages/blocks-ui/package.json 中可以得到印证:当前版本0.0.79恰好依赖property-controls@^0.0.60与@blocks/react@^0.0.64,与 changelog 中 0.0.79 条目列出的Updated dependencies完全一致。整个仓库是一个多包 monorepo,四个包分别是:

包路径职责
blocks-uipackages/blocks-ui编辑器 UI 本体(画布、面板、Babel 插件、模式切换)
property-controlspackages/property-controls属性控制的类型与解析(ControlType、ControlMap等)
@blocks/reactpackages/react可拖拽的内置块(header、footer、quote 等)
gatsby-plugin-blockspackages/gatsby-plugin-blocks将编辑器接入 Gatsby 站点的插件

二、0.0.55–0.0.60:工程地基与编辑器重写

0.0.55 —— 新发布工具链

7f40455: New publish tooling

这是本 changelog 记录的最早版本。它引入了新的发布工具(即上文所述 changesets 风格的联动发布),从 0.0.55 起每个包都通过Updated dependencies机制保持依赖同步。

0.0.56 —— 文件名规范化

92a203a: Rename the editor file to be lower case

对应的是编辑器入口文件重命名为小写editor.js,现在位于 packages/blocks-ui/src/editor.js。这类改动在大小写敏感的文件系统(Linux)与不敏感的文件系统(macOS/Windows)之间尤其必要,属于跨平台工程健壮性修复。

0.0.57 —— 引入 ESLint

ea28d49: Add eslint and changes based on linting

加入 lint 并批量修复,为后续 0.0.70 中"Fix lint warnings"这类持续清理打下基础。

0.0.58 —— Safari 标签页一致性修复

9c967f0: Updated tabs to fix inconsistencies in Safari

编辑器使用@reach/tabs实现模式切换(见 packages/blocks-ui/package.json 中的@reach/tabs@^0.10.5),此版本修复了 Safari 下的样式不一致。

0.0.59 —— Layers 图层面板

746a4f9: Add a layers panel

这是编辑器交互体系的重要起点:图层面板(layers panel)以树形结构展示当前 JSX 文档的元素层级。其实现位于 packages/blocks-ui/src/panels/tree.js,核心是一个递归的Tree组件:

  • 缩进由pl: depth * 16按层级深度递增,形成视觉上的层级结构;
  • 选中项(selectedId)与悬停项(hoveredId)分别用primary与border色高亮,并支持onMouseEnter/onMouseLeave/onSelect三个回调与画布联动。

状态管理在 packages/blocks-ui/src/providers/code.js 中:hoverElementId维护了一个悬停栈(currentHoveredElements数组),removeHoveredElementId在移出时将当前悬停 id 回退到栈中上一个元素,保证从子元素移向父元素时高亮连续不跳变。

0.0.60 —— 编辑器内核重写,gatsby 插件开工

09b0ea5: Rewrite editor internals, begin building gatsby-plugin-blocks

这是架构层面的里程碑:编辑器内部状态被重写为基于 Context 的 Provider 体系。从源码结构看,Provider 层由 packages/blocks-ui/src/providers/index.js 组织,包括CodeProvider(代码状态机)、CanvasProvider、EditorProvider、ScopeProvider等。与此同时,同仓库的 packages/gatsby-plugin-blocks 开始承接"把 blocks 编辑器嵌入 Gatsby 站点"的工作,blocks-ui则聚焦为纯编辑器 UI 包——两者的职责切分即在此版本确立。

三、0.0.61–0.0.65:拖拽交互与画布体验

0.0.61 —— 整个画布可投放

dcc372b: Make the entire canvas droppable

"整块画布都可作为拖拽落点"的实现依赖一个 Babel 转换:packages/blocks-ui/src/babel-plugins/drag-and-drop.js。它遍历根元素的子节点,用@babel/template把每个带tuid(uuid 属性,见 packages/blocks-ui/src/constants.js 中的uuidName)的 JSX 元素包裹进BLOCKS_Draggable组件模板中,使每个顶层元素都获得react-beautiful-dnd的 draggable 能力;文本/行内元素则使用buildInlineDraggable生成的span版本模板。

0.0.62 —— Safari Tab 样式修复

b9427db: Fix tab styles for Safari

延续 0.0.58 的浏览器一致性治理。

0.0.63 —— 修复克隆元素时子节点丢失的 Bug

edfc06c: Fix clone element bug with child nodes

对应实现是 packages/blocks-ui/src/babel-plugins/clone.js。该插件定位elementId对应的JSXOpeningElement后执行t.cloneDeep(element.node)深拷贝整棵元素子树,随后手动对新元素内部再跑一次traverse,把克隆子树里继承来的所有旧tuid替换为新生成的uuid()。这一步正是"子节点"修复点:若不做二次遍历,克隆出的子元素会与原件共享同一批 uuid,导致画布上选中、拖拽等操作相互串扰。修复完成后通过element.insertAfter(newElement)把副本插入原件之后,调用方是 packages/blocks-ui/src/providers/code.js 中的cloneCurrentElement。

0.0.64 —— 画布与图层面板悬停同步

05b2c67: Add a hoverstate to elements that's synced with the layers panel

在 0.0.59 图层面板与 0.0.61 可拖拽画布的基础上,这一版本打通了双向悬停高亮:鼠标悬停画布元素时图层面板同步高亮,反之亦然。支撑逻辑即上文providers/code.js中的hoverElementId/removeHoveredElementId悬停栈,渲染侧由Tree组件的hoveredId分支消费。

0.0.65 —— 拖拽时缩放块 + 布局刷新

782d176: Resize blocks when dragging over the canvas782d176: Update editor layout grid782d176: Add title to demo page782d176: Remove pointer events from block listing preview

一次提交带来四项体验改进:拖拽块经过画布上方时的视觉缩放反馈、编辑器网格布局更新、demo 页标题,以及移除块列表预览上的 pointer 事件(避免列表项误触发指针样式)。对应 demo 页即 src/pages/demo.js。

四、0.0.66–0.0.69:代码模式与懒加载

0.0.66 —— 实现代码编辑

2f42c5e: Implement code editing

代码模式让用户直接编辑底层 JSX 源码,而编辑结果必须反向驱动整个画布状态。入口链路是:

  1. packages/blocks-ui/src/modes/code.js 中的CodeMode组件从useCode()取出当前代码,交给编辑器展示,并把用户输入回调给editCode;
  2. packages/blocks-ui/src/providers/code.js 的editCode(rawCode)接收原始源码,先经transforms.addTuid重新注入元素 uuid,再调用updateCode一次性重算四份派生数据:
const updateCode = newCode => { return { code: newCode, transformedCode: transforms.toTransformedJSX(newCode), tree: queries.getElementTree(newCode), blocks: queries.getBlocks(newCode) } }

即"一份源码,四种视图"——画布渲染用的transformedCode、图层面板用的元素树tree、块查询用的blocks全部同源重算,保证代码模式与画布模式永远一致。

0.0.68 —— 引入 Monaco 作为画布代码编辑器

6042b54: Use Monaco for the canvas code editor

即当前 packages/blocks-ui/src/code-editor.js 中的实现:通过@monaco-editor/react封装的Monaco组件,以language="javascript"加载编辑器,并做了针对性配置:

<Monaco height="100%" value={code} language="javascript" editorDidMount={handleEditorDidMount} theme="light" loading={<Loader />} options={{ minimap: { enabled: false }, scrollbar: { vertical: 'hidden' } }} />

内容变更通过editorDidMount里挂载的onDidChangeModelContent监听器转发到onChange。编辑器右上角还配了一个Copy按钮(基于useCopyToClipboardhook 实现),方便一键复制代码。

0.0.67 —— 新增 canvas context,修复视口模式切换 Bug

e5fb954: Added a new 'canvas' context which will provide setters/getters for setting the canvas size. This fixes a bug when switching from viewport mode to canvas mode.e5fb954: Replace @emotion/weak-memoize with useMemo

该版本引入 packages/blocks-ui/src/providers/canvas.js,用 Context 暴露画布尺寸读写接口:

const CanvasProvider = ({ children }) => { const [canvasSize, setCanvasSize] = useState({ width: 0, height: 0 }) return ( <CanvasContext.Provider value={{ canvasSize, setCanvasSize }}> {children} </CanvasContext.Provider> ) }

消费端在 packages/blocks-ui/src/canvas.js 的CanvasWrap中:用useElementSize(canvasRef)持续测量画布容器尺寸,并通过setCanvasSize(elementSize)写回 Context。之前画布尺寸只能局部感知,从 viewport 模式切回 canvas 模式时状态丢失;改为全局 Context 后,尺寸在模式切换间得以保持,这正是 changelog 所说的 bug 修复。同一次提交还把@emotion/weak-memoize替换为标准useMemo,减少一个外部依赖。

0.0.69 —— 模式组件懒加载 + 内化 theme-ui 组件

6c869e3: Lazy loaded mode components6c869e3: Forked @theme-ui/components into blocks-ui

两个改动都发生在 packages/blocks-ui/src/canvas.js 顶部:

const CodeMode = Loadable({ loader: () => import('./modes/code'), loading: Loader })

三个模式(modes/code、modes/viewports、modes/canvas,分别位于 packages/blocks-ui/src/modes/ 下)全部通过react-loadable的Loadable包装为动态import(),切换模式时才按需加载,加载期间显示统一的Loader占位。"Forked @theme-ui/components" 则指把@theme-ui/components的部分组件复制进blocks-ui内部维护,降低对外部组件包行为的依赖——这解释了为何后续版本能对 UI 细节做大量不受上游节奏约束的快速调整。

五、0.0.70–0.0.73:sx 样式系统与主题化控件

0.0.70 —— 花哨加载器 + sx 插件假值处理

475bb3f: Add a fancy loader1ee0279: Fix lint warningse601127: Better handle falsey values in sx prop plugin

第一项是 packages/blocks-ui/src/loader.js:一个用 SVG 路径手绘的方块旋转动画加载器,通过@emotion/core的keyframes定义 7 段关键帧实现 360° 循环转动,用作懒加载模式的占位与 Monaco 的loading插槽。

第三项的实现在 packages/blocks-ui/src/babel-plugins/apply-sx-prop.js:

Object.entries(sx).forEach(([key, value]) => { ... if (typeof value === 'undefined') { return } ... })

注意判空条件是typeof value === 'undefined'而非简单的if (!value):只有undefined会被跳过,而false、0、null这类合法假值会被toLiteral正确序列化后写入 JSX 的sx属性。这是"Better handle falsey values"的准确含义——旧实现若用真值判断,display: 'none'之外的0间距或布尔型样式会被误删。

0.0.71 —— 改进属性应用插件

db0398e: Improve prop apply plugin

对应 packages/blocks-ui/src/babel-plugins/apply-prop.js:定位目标元素后,先findIndex查找同名的既有属性,命中则原地替换(path.node.attributes[existingAttrIndex] = newAttr),未命中才追加新属性。这保证了属性面板的每次修改都是幂等更新而非重复堆积,是图层面板属性编辑体验的基础。

0.0.72 —— margin/padding 主题化滑块

c5bd0d2: Added a slider for editing margin/padding with theme valuesb136932: Fix z-index for typograph dropdown

间距编辑器在 packages/blocks-ui/src/theme-editor/Sx/Space.js。它把主题space刻度(无主题时回退到DEFAULT_SPACE = [0, 4, 8, 16, 32, 64])作为滑块的离散档位,并提供三种编辑模式:

const MODES = [ { label: 'Single', icon: SingleIcon, keys: [{ label: 'All', keys: ['t', 'b', 'r', 'l'] }] }, { label: 'Axis', icon: AxisIcon, keys: [ { label: 'Horizontal', keys: ['l', 'r'] }, { label: 'Vertical', keys: ['t', 'b'] } ] }, { label: 'All', icon: AllIcon, keys: [ { label: 'Top', keys: ['t'] }, { label: 'Right', keys: ['r'] }, { label: 'Bottom', keys: ['b'] }, { label: 'Left', keys: ['l'] } ] } ]

滑块取值是刻度索引而非像素值(min = 0,max = space.length - 1,step = 1),onChange时把索引parseInt后写入pt/pr等键。源码注释特意强调要用四个方向的独立键(t/r/b/l)而非简写键(p/px),以免覆盖已有的独立方向值。

0.0.73 —— 字号受限于主题 + 禁用组件页拖放

4e32769: Add min and max value for font size based on theme0644349: Restrict font sizes to theme-based values. Also fixed some styles in space editor.dc0425f: Disable dropping for components tab

字号编辑在 packages/blocks-ui/src/theme-editor/Sx/Typography.js:FontSizeEditor的min/max同样锚定到主题fontSizes数组长度(无主题时回退DEFAULT_FONT_SIZES = [12, 14, 16, 20, 24, 32, 48, 64, 72]),显示值直接取fontSizes[value],从而把字号选择约束在主题刻度之内——这就是"Restrict font sizes to theme-based values"的落地方式。而"Disable dropping for components tab"限制了拖拽落点:只有画布是合法的投放区域,组件列表页签不再接受投放,避免块被误拖入列表产生脏状态。

六、0.0.74–0.0.79:依赖治理与无障碍收尾

0.0.74 —— 升级 theme-ui preset

2704bd5: Bump theme ui preset version

@theme-ui/presets被多处直接引用作为主题基底,如 packages/blocks-ui/src/theme.js 与 packages/blocks-ui/src/providers/theme-editor.js 均import { system as systemTheme } from '@theme-ui/presets'。当前 package.json 中该依赖被钉在0.3.0,@theme-ui/components为0.3.1,与 changelog 记录的 0.0.69"内化 components + 0.0.74 升级 preset"节奏吻合。

0.0.75 —— 依赖升版与锁定

9cf9ead: Bump and pin deps

将依赖版本提升并精确锁定(pin),是 0.x 阶段保障构建可复现性的常规手段。

0.0.76 —— 编辑器中 null 字面量不显示

c1eb7b0: Remove null literal from showing up in the editor13a4e86: Misc UI updates

属性值为null时不再以字面量形式渲染进编辑器展示区,属于属性面板的显示层修复,与 0.0.70 的假值处理共同构成"非字符串属性值"的完整治理。

0.0.77 —— 升级依赖并为上下文库加 resolutions

776b7c3: Bump deps, add resolutions for context libs

"resolutions for context libs" 指在根依赖中用 resolutions 字段强制锁定 React Context 相关库的版本,规避 peer 依赖冲突导致的版本漂移。

0.0.78 —— 适配 Prettier v2 的解析器导入方式

1411594: Update prettier parser import for v2

这是 packages/blocks-ui/src/modes/code.js 中这段代码的由来:

import prettier from 'prettier/standalone' import parserJS from 'prettier/parser-babel' prettier.format(rawCode, { parser: 'babel', plugins: [parserJS] })

Prettier 2.x 把解析器拆分为独立入口prettier/parser-babel,且format必须通过plugins显式传入解析器插件。CodeMode在挂载时执行transforms.toRawJSX(code)去除内部 uuid 标记,再经 prettier 格式化后展示,使代码模式里看到的是干净、标准缩进的 JSX。package.json 中prettier被精确锁定为2.0.5。

0.0.79 —— 属性标签的读屏支持

c1875b8: Prop label screen reader support

这是 changelog 中最新的无障碍改进:属性标签(Label)补充了 screen reader 语义。以 packages/blocks-ui/src/theme-editor/Sx/Space.js 为例,滑块旁同时渲染两个Label(左侧名称 + 右侧当前值,as="span"的仅用于视觉展示),并配合 packages/blocks-ui/src/ui.js 中IconButton的aria-label={label}模式,保证读屏器能获取控件用途与当前值。

七、如何对照源码验证这份演进史

结合本文内容,可以在仓库中按以下路径逐条核对:

  1. 版本条目 ↔ 发布状态:packages/blocks-ui/CHANGELOG.md 的## 0.0.79顶部版本与 packages/blocks-ui/package.json 的"version": "0.0.79"对应;
  2. Updated dependencies ↔ 实际依赖:changelog 中列出的property-controls@0.0.60、@blocks/react@0.0.64均出现在 package.json 的dependencies字段;
  3. 功能条目 ↔ 源码实现:代码模式看 modes/code.js 与 providers/code.js,画布尺寸 Context 看 providers/canvas.js,懒加载看 canvas.js,拖拽注入看 babel-plugins/drag-and-drop.js,克隆修复看 babel-plugins/clone.js,主题化滑块看 theme-editor/Sx/Space.js 与 theme-editor/Sx/Typography.js;
  4. Babel 插件测试:每个src/babel-plugins/下的插件都配有同名.test.js(如 apply-prop.test.js),可作为对应版本修复的行为级回归证据。

八、适用前提与边界说明

  • 本文所有结论基于当前仓库快照:blocks-ui@0.0.79、prettier@2.0.5、react@16.13.1(peerDependency 要求^16.13.1)、theme-ui@0.3.1。changelog 中提到的版本能力(如 Prettier v2 解析器导入方式)均以此为适用前提;
  • 0.x 阶段所有条目均为 Patch 级别,API 不承诺稳定,文中"从源码结构看"的推断(如 0.0.60 的重写范围、0.0.69 内化 theme-ui 组件的具体动机)以当前代码为限,不能外推到更早或更晚的版本;
  • changelog 每条说明均为上游作者的简要 commit message,本文将其与源码互证后展开,未做超出仓库证据的推测;性能与用户规模类信息仓库中无从确认,本文亦不涉及。
  • 低代码
  • 前端

【免费下载链接】blocks

A JSX-based page builder for creating beautiful websites without writing code

项目地址:https://gitcode.com/gh_mirrors/bl/blocks
点击查看免费下载
上一篇:Starscream文档注释规范:为WebSocket API创建清晰文档
下一篇:Instatic端到端测试:从配置到实践的完整指南

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

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进