关于 umbraco-example-generator
Umbraco Example Generator 用于生成可测试的 Umbraco backoffice 扩展示例,并借助 Umbraco 源码中的开发环境运行。适合扩展开发者,可从外部目录加载示例,并使用模拟 API 和热重载。
主要功能
- 01生成 backoffice 扩展示例
- 02从源码示例目录或外部路径加载
- 03使用模拟 API 运行扩展
- 04支持开发时热重载
- 05可为示例添加单元及 E2E 测试
适用场景
- 01制作扩展示范和可测试样例
- 02快速开发并查看扩展改动
- 03无 .NET 后端时测试扩展
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ umbraco/ umbraco-cms-backoffice-skills --skill umbraco-example-generator安装后在 Claude Code 中输入/umbraco-example-generator确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
examples/workspace-feature-toggle/feature-toggle-action.tsfeature-toggle-context.test.tsfeature-toggle-context.tsfeature-toggle-footer.element.tsfeature-toggle-view.element.tsindex.tspackage-lock.jsonpackage.jsonREADME.mdtsconfig.jsonweb-test-runner.config.mjsexamples/workspace-feature-toggle/tests/feature-toggle.spec.tsplaywright.config.tswarmup.setup.ts详细介绍
Umbraco 示例生成器
生成完整、可测试的 Umbraco 后台扩展,并使用 Umbraco 源码的开发基础设施运行它们。
适用场景
- 创建演示扩展
- 构建可测试的扩展示例
- 使用热重载快速开发
- 在没有 .NET 后端的情况下测试扩展
相关技能
- umbraco-unit-testing - 为示例添加单元测试
- umbraco-mocked-backoffice - E2E 测试模式
- umbraco-backoffice - 扩展类型蓝图
快速开始
1. 克隆 Umbraco 源码(一次性设置)
git clone https://github.com/umbraco/Umbraco-CMS
cd Umbraco-CMS/src/Umbraco.Web.UI.Client
npm install
2. 创建扩展文件夹
my-extension/
├── index.ts # REQUIRED - exports manifests
└── my-element.ts # Your element(s)
3. 创建 index.ts(导出清单)
import './my-element.js';
export const manifests = [
{
type: 'dashboard',
alias: 'My.Dashboard',
name: 'My Dashboard',
element: 'my-element',
meta: { label: 'My Dashboard', pathname: 'my-dashboard' },
conditions: [{ alias: 'Umb.Condition.SectionAlias', match: 'Umb.Section.Content' }]
}
];
4. 创建元素
// my-element.ts
import { LitElement, html, customElement } from '@umbraco-cms/backoffice/external/lit';
@customElement('my-element')
export class MyElement extends LitElement {
render() {
return html`<uui-box headline="Hello">It works!</uui-box>`;
}
}
5. 运行
cd Umbraco-CMS/src/Umbraco.Web.UI.Client
VITE_EXAMPLE_PATH=/full/path/to/my-extension VITE_UMBRACO_USE_MSW=on npm run dev
打开 http://localhost:5173,你的扩展会显示在 Content 区域中。
工作原理
Umbraco 源码(Umbraco-CMS/src/Umbraco.Web.UI.Client)提供了两种加载扩展的方式:
1. 内部示例(npm run example)
将示例放在 Umbraco 源码中的 examples/ 文件夹内。
cd Umbraco-CMS/src/Umbraco.Web.UI.Client
npm run example
# Select from list of examples
工作原理:设置 VITE_EXAMPLE_PATH,并导入 ./examples/{name}/index.ts
2. 外部扩展(绝对路径 VITE_EXAMPLE_PATH)
来自文件系统任意位置的扩展,非常适合开发包。
cd Umbraco-CMS/src/Umbraco.Web.UI.Client
VITE_EXAMPLE_PATH=/path/to/your/extension VITE_UMBRACO_USE_MSW=on npm run dev
工作原理:
VITE_UMBRACO_USE_MSW=on会模拟 Umbraco 核心 API- 通过 Vite 的
/@fs/前缀提供绝对路径VITE_EXAMPLE_PATH index.ts会导入<VITE_EXAMPLE_PATH>/index.ts,并使用umbExtensionsRegistry注册其导出内容- 共享包解析器会将
@umbraco-cms/backoffice、lit和@umbraco-ui/uui映射到客户端自身的副本(避免重复加载 Lit/UUI)
绝对路径支持(步骤 2 和 4)需要使用构建时启用了外部示例支持的 v18 客户端。运行此仓库的模拟 E2E 套件时,Playwright 测试框架会自动将该支持注入 UMBRACO_CLIENT_PATH 中的客户端,并在之后还原——请参见 umbraco-mocked-backoffice 技能。
扩展加载(index.ts)
// From Umbraco-CMS/src/Umbraco.Web.UI.Client/index.ts
if (import.meta.env.VITE_EXAMPLE_PATH) {
const examplePath = import.meta.env.VITE_EXAMPLE_PATH;
// Absolute paths (external extensions) are served via Vite's /@fs/ prefix; relative paths load from ./
const importPath = examplePath.startsWith('/') ? '/@fs' + examplePath : './' + examplePath;
const js = await import(importPath + '/index.ts');
if (js) {
Object.keys(js).forEach((key) => {
const value = js[key];
if (Array.isArray(value)) {
umbExtensionsRegistry.registerMany(value);
} else if (typeof value === 'object') {
umbExtensionsRegistry.register(value);
}
});
}
}
要点:你的 index.ts 必须导出清单(数组或对象),这些清单会自动注册。
设置
先决条件
克隆并设置 Umbraco 源码:
git clone https://github.com/umbraco/Umbraco-CMS
cd Umbraco-CMS/src/Umbraco.Web.UI.Client
npm install
扩展结构
你的扩展需要以下最简结构:
my-extension/
├── index.ts # Exports manifests array (REQUIRED)
├── my-element.ts # Your element(s)
├── my-context.ts # Context (if needed)
├── package.json # Optional - for IDE support and tests
├── tsconfig.json # Optional - for IDE support
└── README.md # Documentation
必需:index.ts
你的 index.ts 必须导出将要注册的清单:
import './my-dashboard.element.js';
export const manifests = [
{
type: 'dashboard',
alias: 'My.Dashboard',
name: 'My Dashboard',
element: 'my-dashboard',
weight: 100,
meta: {
label: 'My Dashboard',
pathname: 'my-dashboard'
},
conditions: [
{
alias: 'Umb.Condition.SectionAlias',
match: 'Umb.Section.Content'
}
]
}
];
可选:package.json(用于 IDE 支持)
{
"name": "my-extension",
"type": "module",
"devDependencies": {
"@umbraco-cms/backoffice": "^18.0.0",
"typescript": "~5.8.0"
}
}
重要:@umbraco-cms/backoffice 依赖仅用于 IDE 的 TypeScript 支持。运行时,导入内容会从 Umbraco 主项目中解析。
运行扩展
启动模拟后台
cd /path/to/Umbraco-CMS/src/Umbraco.Web.UI.Client
VITE_EXAMPLE_PATH=/absolute/path/to/my-extension VITE_UMBRACO_USE_MSW=on npm run dev
在浏览器中打开
访问 http://localhost:5173,你的扩展会自动加载。
热重载
更改扩展文件会触发热重载,无需重启。
模式
基本元素
// my-dashboard.element.ts
import { LitElement, html, css, customElement } from '@umbraco-cms/backoffice/external/lit';
@customElement('my-dashboard')
export class MyDashboardElement extends LitElement {
static override styles = css`
:host {
display: block;
padding: var(--uui-size-layout-1);
}
`;
override render() {
return html`
<uui-box headline="My Extension">
<p>Running in the mocked backoffice!</p>
</uui-box>
`;
}
}
declare global {
interface HTMLElementTagNameMap {
'my-dashboard': MyDashboardElement;
}
}
带上下文的元素
import { html, customElement, state } from '@umbraco-cms/backoffice/external/lit';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { EXAMPLE_MY_CONTEXT } from './my-context.js';
@customElement('example-my-feature-view')
export class ExampleMyFeatureViewElement extends UmbLitElement {
@state()
private _value?: string;
constructor() {
super();
this.consumeContext(EXAMPLE_MY_CONTEXT, (context) => {
this.observe(context.value, (value) => {
this._value = value;
});
});
}
override render() {
return html`
<uui-box headline="My Feature Example">
<p>Current value: ${this._value ?? 'Loading...'}</p>
</uui-box>
`;
}
}
export default ExampleMyFeatureViewElement;
declare global {
interface HTMLElementTagNameMap {
'example-my-feature-view': ExampleMyFeatureViewElement;
}
}
上下文
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
import { UmbContextBase } from '@umbraco-cms/backoffice/class-api';
import { UmbStringState } from '@umbraco-cms/backoffice/observable-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
export class ExampleMyContext extends UmbContextBase {
#value = new UmbStringState('initial');
readonly value = this.#value.asObservable();
constructor(host: UmbControllerHost) {
super(host, EXAMPLE_MY_CONTEXT);
}
setValue(value: string) {
this.#value.setValue(value);
}
getValue() {
return this.#value.getValue();
}
public override destroy(): void {
this.#value.destroy();
super.destroy();
}
}
export const EXAMPLE_MY_CONTEXT = new UmbContextToken<ExampleMyContext>(
'ExampleMyContext'
);
export { ExampleMyContext as api };
添加测试
单元测试
使用 @open-wc/testing 添加单元测试。完整设置请参见 umbraco-unit-testing 技能。
npm install --save-dev @open-wc/testing @web/test-runner @web/test-runner-playwright
E2E 测试(Playwright)
添加针对模拟后台运行的 E2E 测试。测试模式请参见 umbraco-mocked-backoffice 技能。
npm install --save-dev @playwright/test
npx playwright install chromium
示例
参考示例
位置:./examples/workspace-feature-toggle/
一个完整的独立示例,展示了:
- 使用
UmbArrayState的工作区上下文 - 使用上下文的工作区视图
- 执行上下文方法的工作区操作
- 显示摘要的工作区页脚应用
- 38 个单元测试 + 13 个 E2E 测试
cd examples/workspace-feature-toggle
npm install
npm test # Unit tests
npm run test:e2e # E2E tests (requires mocked backoffice running)
Umbraco 官方示例
位置:Umbraco-CMS/src/Umbraco.Web.UI.Client/examples/
27 个涵盖所有扩展类型的官方示例。运行任意示例:
cd Umbraco-CMS/src/Umbraco.Web.UI.Client
npm run example
# Select from list
命名约定
| 项目 | 约定 | 示例 |
|---|---|---|
| 目录 | 使用 kebab-case 描述功能 | workspace-context-counter |
| 别名前缀 | example. | example.workspaceView.counter |
| 元素前缀 | example- | example-counter-view |
| 上下文令牌 | EXAMPLE_ + SCREAMING_CASE | EXAMPLE_COUNTER_CONTEXT |
故障排除
扩展未显示
- 检查
index.ts是否导出了manifests数组 - 确认
VITE_EXAMPLE_PATH中的路径是绝对路径 - 检查浏览器控制台中是否有
📦 Loading external example from:消息 - 确保条件与当前查看的部分匹配
导入错误
导入应使用 @umbraco-cms/backoffice/*。Vite 插件会从主项目中解析这些内容。
“CustomElementRegistry” 已定义
当前使用的是扩展的 node_modules,而不是主项目的。external-example-resolver 插件应能处理此问题,但请确保:
- 使用
VITE_EXAMPLE_PATH=<abs> VITE_UMBRACO_USE_MSW=on npm run dev - 导入使用
@umbraco-cms/backoffice/*,而不是指向 node_modules 的相对路径
更改未热重载
确保文件位于 VITE_EXAMPLE_PATH 指定的路径下。系统只会监视该目录树中的文件。