XMLUI:让React和CSS变得触手可及的现代Web开发框架

XMLUI是一个基于XML标记的现代Web开发框架,它封装了React和CSS,使开发者无需深入React或CSS知识即可构建响应式、主题化的组件应用。文章详细介绍了XMLUI的组件系统、响应式数据绑定、主题机制以及与LLM的协作方式。

介绍XMLUI

在20世纪90年代中期,即使你不是顶尖程序员,也能创建有用的软件。你有Visual Basic,有一个丰富的组件生态系统,你可以将这些组件连接起来创建应用程序,站在构建这些组件的程序员的肩膀上。如果你不到45岁,你可能不知道那是什么样子,也没有意识到Web组件从未以同样的方式工作。我们今天宣布的项目XMLUI将VB模型带到了现代Web及其基于React的组件生态系统。XMLUI封装了React和CSS,并提供了一套可以用XML标记组合的组件。这是一个检查伦敦地铁线路状态的小应用。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<App>
  <Select id="lines" initialValue="bakerloo">
    <Items data="https://api.tfl.gov.uk/line/mode/tube/status">
    </Items>
  </Select>
  <DataSource
    id="tubeStations"
    url="https://api.tfl.gov.uk/Line/{lines.value}/Route/Sequence/inbound"
    resultSelector="stations"/>
  <Table data="{tubeStations}" height="280px">
    <Column bindTo="name" />
    <Column bindTo="modes" />
  </Table>
</App>

十几行XML就足以:

  • 定义一个Select并用API调用的数据填充其Items。
  • 定义一个DataSource从另一个API调用获取数据。
  • 使用Select的值动态形成DataSource的URL。
  • 使用resultSelector深入第二个API调用的结果。
  • 将该结果绑定到Table。
  • 将结果中的字段绑定到Columns。

这是一个干净、现代、基于组件的应用,具有响应式且主题化,无需任何React或CSS知识。这是强大的杠杆作用。而且这是你可以阅读和维护的代码,无论是由你还是LLM助手编写。我为该项目提供咨询,所以你应该自己判断,但对我来说,这感觉像是JavaScript工业综合体的替代方案,满足了所有正确的条件。

组件

我最常被引用的BYTE文章是1994年的封面故事《Componentware》。我们中的许多人曾假设,广泛软件重用的引擎将是低级对象库,由熟练的程序员链接到程序中。实际上获得关注的是由专业开发人员构建并由业务开发人员使用的组件。

有用于图表、网络通信、数据访问、音频/视频播放和图像扫描/编辑的Visual Basic组件。UI控件包括按钮、对话框、滑块、用于显示和编辑表格数据的网格、文本编辑器、树和列表以及标签视图。人们使用这些控件构建销售点系统、调度和项目管理工具、医疗和法律实践管理系统、销售和库存报告等等。

那个组件生产者和消费者的生态系统没有延续到Web。我是Web组件的粉丝,但主导的是React风格,它们对于当年能够高效使用Visual Basic组件的开发人员来说是不可访问的。你不仅需要是一个熟练的程序员来创建React组件,还需要使用它们。XMLUI封装了React组件,以便解决方案构建者可以使用它们。

用户定义组件

XMLUI提供了一个深入的组件目录,包括所有你期望的交互式组件,以及幕后的组件,如DataSource、APICall和Queue。你可以轻松定义自己的组件,与原生集和其他组件互操作。这是一个TubeStops组件的标记。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
<Component name="TubeStops">
  <DataSource
    id="stops"
    url="https://api.tfl.gov.uk/Line/{$props.line}/StopPoints"
    transformResult="{window.transformStops}"
  />
  <Text variant="strong">{$props.line}</Text>
  <Table data="{stops}">
    <Column width="3*" bindTo="name" />
    <Column bindTo="zone" />
    <Column bindTo="wifi" >
      <Fragment when="{$item.wifi === 'yes'}">
        <Icon name="checkmark"/>
      </Fragment>
    </Column>
    <Column bindTo="toilets" >
      <Fragment when="{$item.toilets === 'yes'}">
        <Icon name="checkmark"/>
      </Fragment>
    </Column>
  </Table>
</Component>

这是在并排布局中两次使用该组件的标记。

1
2
3
4
5
6
7
8
<HStack>
  <Stack width="50%">
    <TubeStops line="victoria" />
  </Stack>
  <Stack width="50%">
    <TubeStops line="waterloo-city" />
  </Stack>
</HStack>

阅读和维护简短的XMLUI标记片段很容易。当标记增长到一百行或更多时,就不那么方便了。但我从来不需要看那么多代码;当组件变得太大时,我会重构它们。在任何编程环境中,这种操作都会带来开销:你必须创建和命名文件,确定哪些东西作为属性从一个地方传递,并在另一个地方解包它们。但不断上升的LLM浪潮提升了所有船只。因为我可以将重构委托给我的AI助手团队,所以我能够流畅且持续地进行重构。LLMs并不“知道”XMLUI,但它们知道XML,并且在MCP(见下文)的帮助下,它们可以特别“知道”很多关于XMLUI的知识。

响应式

如果你从未成为React程序员,就像我一样,XMLUI风格响应式的最大挑战不是你需要学习什么,而是你需要忘记什么。让我们再看一下本文顶部显示的应用程序代码。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
<App>
  <Select id="lines" initialValue="bakerloo">
    <Items data="https://api.tfl.gov.uk/line/mode/tube/status">
        <Option value="{$item.id}" label="{$item.name}" />
    </Items>
  </Select>
  <DataSource
    id="tubeStations"
    url="https://api.tfl.gov.uk/Line/{lines.value}/Route/Sequence/inbound"
    resultSelector="stations"/>
  <Table data="{tubeStations}" height="280px">
    <Column bindTo="name" />
    <Column bindTo="modes" />
  </Table>
</App>

注意Select如何声明属性id=“lines”。这使得lines成为一个响应式变量。

现在看DataSource的url属性。它嵌入了对lines.value的引用。更改选择会更改lines.value。DataSource通过获取新的一批详细信息来响应。同样,Table的data属性引用了tubeStations(DataSource),因此它会自动显示新数据。

这种模式有一个名字:响应式数据绑定。这就是电子表格在一个单元格中的更改传播到引用它的其他单元格时所做的事情。这也是React为Web应用程序启用的功能。React是一个复杂的野兽,只有专家程序员才能驯服。幸运的是,构建XMLUI的专家程序员已经为你做到了这一点。作为XMLUI开发人员,你可能需要忘记命令式习惯,以适应声明式流程。这是一种不同的思维方式,但如果你记住电子表格的类比,你很快就会掌握它。在这个过程中,你可能会发现惊喜。例如,这是我们演示应用程序XMLUI Invoice中的搜索功能。

最初我以传统方式编写它,带有一个搜索按钮。然后我意识到不需要按钮。驱动查询的DataSource URL可以响应TextBox中的按键,而Table可以在DataSource刷新时依次响应。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
<Component name="SearchEverything">
    <VStack paddingTop="$space-4">
        <TextBox
            placeholder="Enter search term..."
            width="25rem"
            id="searchTerm"
        />
        <Card when="{searchTerm.value}">
            <DataSource
              id="search"
              url="/api/search/{searchTerm.value}"
            />
            <Text>Found {search.value ? search.value.length : 0} results for
                "{searchTerm.value}":</Text>
            <Table data="{search}">
                <Column  bindTo="table_name" header="Type" width="100px" />
                <Column  bindTo="title" header="Title" width="*" />
                <Column  bindTo="snippet" header="Match Details" width="3*" />
            </Table>
        </Card>
    </VStack>
</Component>

主题

当团队第一次向我展示XMLUI主题系统时,我并不太兴奋。我不是设计师,所以我欣赏一个不错的默认主题,不需要我做出我没有资格做出的颜色选择。切换主题的能力对我来说从来都不那么重要,而且我从未完全理解为什么开发人员如此痴迷于黑暗模式。不过,我曾与CSS搏斗,以实现样式和布局效果,但结果并不令人印象深刻。XMLUI旨在让你构建的一切看起来都很棒,并且行为优雅,无需你编写任何CSS或类似CSS的样式和布局指令。

你可以应用内联样式,但在大多数情况下,你不需要也不应该使用它们。对我来说,这是另一个需要忘记的练习。我知道足够的CSS来造成危险,在早期我滥用了内联样式。这部分是我的错,部分是因为LLMs认为内联样式是猫薄荷,会代表你滥用它们。但如果你看这里的代码片段,你几乎看不到显式的样式或布局指令。每个组件都提供了一套广泛的主题变量,影响其文本颜色和字体、背景颜色、边距、边框、填充等。它们遵循一个命名约定,使设置能够全局或以逐渐更细粒度的方式控制外观。例如,以下是当鼠标悬停在实心按钮上时,可以使用主色控制其边框颜色的变量。

1
2
3
4
5
6
color-primary
backgroundColor-Button
backgroundColor-Button-solid
backgroundColor-Button-primary
backgroundColor-Button-primary-solid
backgroundColor-Button-primary-solid--hover

当渲染按钮时,XMLUI从最具体的设置到最一般的设置向上工作。这种安排为设计师提供了许多自由度来制作极其精细的主题。但几乎所有设置都是可选的,并且默认定义的设置使用逻辑名称而不是硬编码值。例如,backgroundColor-Button-primary的默认设置是$color-primary-500。这是在UI中扮演主要角色的一系列颜色的中点。有一组这样的语义角色,每个都与一个调色板相关联。关键角色是:

  • Surface:创建中性背景和容器。
  • Primary:吸引对重要元素和操作的注意。
  • Secondary:提供视觉支持,而不与主要元素竞争。

更重要的是,你可以从每个的单个中点值生成完整的调色板。

1
2
3
4
5
6
name: Earthtone
id: earthtone
themeVars:
  color-primary: "hsl(30, 50%, 30%)"
  color-secondary: "hsl(120, 40%, 25%)"
  color-surface: "hsl(39, 43%, 97%)"

不过,主题不仅仅是关于颜色。XMLUI组件努力提供默认的布局设置,从而在单个组件内部和组合它们的画布上产生良好的间距、填充和边距。我再次声明,我不是设计师,所以没有资格做出关于这一切如何工作的专业判断。但我能实现的效果对我来说看起来相当不错。

脚本编写

作为Visual Basic开发人员,你不被期望成为顶尖程序员,但被期望能够处理一些脚本编写。XMLUI也是如此。语言是JavaScript,你可以用像TubeStops中的这样小片段走得很远。

1
<Fragment when="{$item.wifi === 'yes'}"></Fragment>

TubeStops还使用其DataSource的transformResult属性来调用更雄心勃勃的代码块。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
function transformStops(stops) {
  return stops.map(stop => {
    // Helper to extract a value from additionalProperties by key
    const getProp = (key) => {
      const prop = stop.additionalProperties && stop.additionalProperties.find(p => p.key === key);
      return prop ? prop.value : '';
    };
    return {
      name: stop.commonName,
      zone: getProp('Zone'),
      wifi: getProp('WiFi'),
      toilets: getProp('Toilets'),
      // A comma-separated list of line names that serve this stop
      lines: stop.lines ? stop.lines.map(line => line.name).join(', ') : ''
    };
  });
}

这并不简单,但也不是火箭科学。当然,你现在不需要写这样的东西,你可以让LLM助手为你做。所以我们不能声称XMLUI是100%声明式的。但我认为公平地说,命令式部分的范围很好,并且对于不知道或不想了解JavaScript工业综合体的解决方案构建者来说是可访问的。

模型上下文协议

在AI时代,当你可以让LLMs为你编写React应用程序时,谁还需要XMLUI?这是一个有效的问题,我认为我有一个相当好的答案。XMLUI Invoice的第一个版本是Claude在30秒内编写的React应用程序。它惊人地完整和功能齐全。但在这个过程中,我并不是一个平等的合作伙伴。我知道React有像useEffect和useContext这样的东西,但我并不真正知道它们是什么或如何正确使用它们,也没有能力审查或维护使用这些模式的JavaScript代码。同样的劣势也适用于Claude编写的CSS。如果你是一个快乐的氛围编码者,从不期望查看或处理LLMs生成的代码,那么XMLUI可能不适合你。

不过,如果你需要能够审查和维护你的应用程序,XMLUI会拉平竞争环境。我可以阅读、评估和有能力地调整LLMs编写的XMLUI代码。在最近的一次演讲中,Andrej Karpathy认为LLMS的最佳点是一种协作伙伴关系,在这种关系中,我们可以动态调整我们给予它们的控制量。他设想的“自主性滑块”要求我们和我们的助手在相同的概念/语义空间中操作。如果空间是React+CSS,那么对于我,以及XMLUI旨在赋能的开发人员来说,这不是真的。如果空间是XMLUI,这可能是真的。

为了增强协作,我们提供了一个MCP服务器,帮助你在与它们合作XMLUI应用程序时引导代理的注意力。在《MCP是AI的RSS》中,我描述了像Claude和Cursor这样的代理可以使用xmlui-mcp提出和回答的问题类型:

  • 有没有一个组件可以做[X]?
  • 关于主题[Y],[X]的文档说了什么?
  • 源代码如何实现[X]?
  • 在其他应用程序中是如何使用的?

你将xmlui-mcp服务器与包含文档和源代码的xmlui仓库放在一起。以及你正在开发XMLUI应用程序的仓库。理想情况下,还有其他包含参考应用程序(如XMLUI Invoice)的仓库。

与LLMs合作

这种安排大多超出了我的预期。随着我构建一套展示最佳实践和模式的应用程序,代理协作得到了改善。当然,这种飞轮效应仍然受到LLM助手特殊习惯的影响,它们 constantly需要被提醒规则。

  1. 未经我的许可,不要编写任何代码,始终预览提议的更改,讨论,并仅在批准后继续。
  2. 不要添加任何xmlui样式,让主题和布局引擎完成它的工作。
  3. 以小增量进行,编写绝对最少的必要xmlui标记,如果可能,不要编写脚本。
  4. 不要发明任何xmlui语法。只使用在文档和示例应用程序中可以找到的构造。引用你的来源。
  5. 永远不要接触dom。我们只使用App领域内的xmlui抽象,借助在index.html的window变量上定义的变量和函数。
  6. 将复杂的函数和表达式排除在xmlui之外,它们可以存在于index.html中,或者(如果作用域需要)在代码背后。
  7. 使用xmlui mcp服务器列出和显示组件文档,但也搜索xmlui源代码、文档和示例。
  8. 总是做最简单的事情。

这就像与2岁的神童一起工作。疯狂,但可能有效!

为了增加你有效协作的几率,我们在文档站点添加了一个How To部分。MCP服务器通过提供列出和搜索这些文章的工具,使代理能够看到这些文章。这灵感来自于一个朋友的问题:“对于一个Select,假设你没有静态的默认第一项,但你想获取数据并选择数据中的第一项作为默认选中,在xmlui中你会怎么做?”我花了几分钟时间整理了一个例子。然后我意识到,这是LLMs应该能够自主提出和回答的问题类型。当代理使用这些工具之一时,它锚定于地面真相:以这种方式找到的文章有一个可引用的URL,指向一个工作示例。

对我来说,用XMLUI做事比用React和CSS容易得多,但我也爬过学习曲线,吸收了很多隐性知识。LLM友好的文档会为新来者及其AI助手拉平学习曲线吗?我渴望找出答案。

内容管理

我们说XMLUI用于构建应用程序,但应用程序到底是什么?如今,网站通常也是应用程序,构建在像Vercel的Next.js这样的框架上。我使用过以这种方式构建的发布系统,我不是粉丝。你不应该需要精通React的前端开发人员来帮助你进行站点的常规更改。而使用XMLUI,你不需要。我们的演示站点、文档站点和登陆页面都是XMLUI应用程序,对我来说,编写和维护它们比我所使用的Next.js站点容易得多。

“吃狗粮”是一个丑陋的名字,代表一个美丽的想法:构建者应该使用和依赖他们构建的东西。我们确实如此,但XMLUI作为CMS的故事还有更多。当你用XMLUI构建一个应用程序时,你会想要记录它。有一个很好的协同作用可用:应用程序及其文档可以由相同的东西制成。你甚至可以在文档中展示应用程序的实时演示,就像我们在组件文档、教程和How To文章中所做的那样。

我是软件演示截屏的早期支持者,展示当然比讲述更好,但搜索做某事的方法却只找到视频是令人愤怒的。理想情况下,你既展示又讲述。用代码、叙述和实时交互的混合来记录软件,将所有模态结合在一起。

可扩展性

开箱即用,XMLUI封装了一堆React组件。当你需要的组件没有被包括时会发生什么?这不是我的第一次竞技表演。在之前的努力中,我 heavily 依赖LLMs挖掘React代码的层次,但仍然无法实现我目标的封装。

对于XMLUI,我最想包括的组件是Tiptap编辑器,它本身是基础ProseMirror工具包的封装。实现这是一个延伸目标,我 honestly 没有期望在发布前实现。但我惊喜地发现,这里有证据。

这个XMLUI TableEditor是我们为想要理解如何创建封装React组件的XMLUI组件的开发人员准备的指南的主题。而且不仅仅是一个玩具示例。当你使用XMLUI进行发布时,基础是Markdown,它对于编写和编辑标题、段落、列表和代码块非常棒,但对于编写和编辑表格却很糟糕。在这种情况下,我总是求助于视觉编辑器来生成Markdown表格语法。现在我有那个视觉编辑器作为一个XMLUI组件,我可以嵌入 anywhere。

该指南中出现的React习语是由LLMs产生的,而不是由我产生的,我不能完全解释它们如何工作,但我现在相信,对于精通React的开发人员来说,扩展XMLUI将是 straightforward 的。更重要的是,我现在可以看到组件构建者和解决方案构建者之间的界限开始模糊。我主要是一个解决方案构建者,总是依赖组件构建者在该级别完成任何有用的事情。我自己能够完成这个有用的事情感觉 significant。

部署

这是TableEditor的最小XMLUI部署占用空间。

1
2
3
4
5
TableEditor
├── Main.xmlui
├── index.html
└── xmlui
    └── 0.9.67.js

index.html只是源最新独立构建的XMLUI。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计