umbraco-example-generator
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseUmbraco Example Generator
Umbraco 示例生成器
Generate complete, testable example extensions for the Umbraco backoffice and run them using the Umbraco source's dev infrastructure.
为Umbraco后台生成完整的可测试示例扩展,并使用Umbraco源码的开发基础设施运行它们。
When to Use
适用场景
- Creating demonstration extensions
- Building testable extension examples
- Rapid development with hot reload
- Testing extensions without .NET backend
- 创建演示扩展
- 构建可测试的扩展示例
- 支持热重载的快速开发
- 无需.NET后端即可测试扩展
Related Skills
相关技能
- umbraco-unit-testing - Add unit tests to examples
- umbraco-mocked-backoffice - E2E testing patterns
- umbraco-backoffice - Extension type blueprints
- umbraco-unit-testing - 为示例添加单元测试
- umbraco-mocked-backoffice - 端到端测试模式
- umbraco-backoffice - 扩展类型蓝图
Quick Start
快速开始
1. Clone Umbraco source (one-time setup)
1. 克隆Umbraco源码(一次性设置)
bash
git clone https://github.com/umbraco/Umbraco-CMS
cd Umbraco-CMS/src/Umbraco.Web.UI.Client
npm installbash
git clone https://github.com/umbraco/Umbraco-CMS
cd Umbraco-CMS/src/Umbraco.Web.UI.Client
npm install2. Create your extension folder
2. 创建你的扩展文件夹
my-extension/
├── index.ts # REQUIRED - exports manifests
└── my-element.ts # Your element(s)my-extension/
├── index.ts # 必填 - 导出清单
└── my-element.ts # 你的自定义元素3. Create index.ts (exports manifests)
3. 创建index.ts(导出清单)
typescript
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' }]
}
];typescript
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. Create your element
4. 创建自定义元素
typescript
// 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>`;
}
}typescript
// 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. Run it
5. 运行扩展
bash
cd Umbraco-CMS/src/Umbraco.Web.UI.Client
VITE_EXTERNAL_EXTENSION=/full/path/to/my-extension npm run dev:externalOpen - your extension appears in the Content section.
http://localhost:5173bash
cd Umbraco-CMS/src/Umbraco.Web.UI.Client
VITE_EXTERNAL_EXTENSION=/full/path/to/my-extension npm run dev:external打开 - 你的扩展将出现在内容板块中。
http://localhost:5173How It Works
工作原理
The Umbraco source () provides two ways to load extensions:
Umbraco-CMS/src/Umbraco.Web.UI.ClientUmbraco源码()提供两种加载扩展的方式:
Umbraco-CMS/src/Umbraco.Web.UI.Client1. Internal Examples (npm run example
)
npm run example1. 内部示例(npm run example
)
npm run exampleExamples placed in the folder inside the Umbraco source.
examples/bash
cd Umbraco-CMS/src/Umbraco.Web.UI.Client
npm run example将示例放在Umbraco源码内的文件夹中。
examples/bash
cd Umbraco-CMS/src/Umbraco.Web.UI.Client
npm run exampleSelect from list of examples
从示例列表中选择
**How it works**: Sets `VITE_EXAMPLE_PATH` and imports `./examples/{name}/index.ts`
**工作机制**:设置`VITE_EXAMPLE_PATH`并导入`./examples/{name}/index.ts`2. External Extensions (npm run dev:external
)
npm run dev:external2. 外部扩展(npm run dev:external
)
npm run dev:externalExtensions from any location on your filesystem - perfect for developing packages.
bash
cd Umbraco-CMS/src/Umbraco.Web.UI.Client
VITE_EXTERNAL_EXTENSION=/path/to/your/extension npm run dev:externalHow it works:
- Sets (mocked APIs)
VITE_UMBRACO_USE_MSW=on - Creates alias pointing to your extension path
@external-extension - Imports and registers exports with
@external-extension/index.tsumbExtensionsRegistry - Resolves imports from the main project (avoids duplicate element registrations)
@umbraco-cms/backoffice/*
可加载文件系统中任意位置的扩展 - 非常适合开发包。
bash
cd Umbraco-CMS/src/Umbraco.Web.UI.Client
VITE_EXTERNAL_EXTENSION=/path/to/your/extension npm run dev:external工作机制:
- 设置(模拟API)
VITE_UMBRACO_USE_MSW=on - 创建别名指向你的扩展路径
@external-extension - 导入并将导出内容注册到
@external-extension/index.tsumbExtensionsRegistry - 从主项目解析导入(避免重复元素注册)
@umbraco-cms/backoffice/*
Extension Loading (index.ts)
扩展加载逻辑(index.ts)
typescript
// From Umbraco-CMS/src/Umbraco.Web.UI.Client/index.ts
if (import.meta.env.VITE_EXTERNAL_EXTENSION) {
const js = await import('@external-extension/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);
}
});
}
}Key point: Your must export manifests (arrays or objects) that get registered automatically.
index.tstypescript
// 来自Umbraco-CMS/src/Umbraco.Web.UI.Client/index.ts
if (import.meta.env.VITE_EXTERNAL_EXTENSION) {
const js = await import('@external-extension/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.tsSetup
配置说明
Prerequisites
前置条件
Clone and set up the Umbraco source:
bash
git clone https://github.com/umbraco/Umbraco-CMS
cd Umbraco-CMS/src/Umbraco.Web.UI.Client
npm install克隆并设置Umbraco源码:
bash
git clone https://github.com/umbraco/Umbraco-CMS
cd Umbraco-CMS/src/Umbraco.Web.UI.Client
npm installExtension Structure
扩展结构
Your extension needs this minimal structure:
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你的扩展需要以下最小结构:
my-extension/
├── index.ts # 导出清单数组(必填)
├── my-element.ts # 自定义元素
├── my-context.ts # 上下文(可选)
├── package.json # 可选 - 用于IDE支持和测试
├── tsconfig.json # 可选 - 用于IDE支持
└── README.md # 文档Required: index.ts
必填项:index.ts
Your must export manifests that will be registered:
index.tstypescript
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'
}
]
}
];你的 必须导出可被注册的清单:
index.tstypescript
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'
}
]
}
];Optional: package.json (for IDE support)
可选:package.json(用于IDE支持)
json
{
"name": "my-extension",
"type": "module",
"devDependencies": {
"@umbraco-cms/backoffice": "^17.0.0",
"typescript": "~5.8.0"
}
}Important: The dependency is only for IDE TypeScript support. At runtime, imports are resolved from the main Umbraco project.
@umbraco-cms/backofficejson
{
"name": "my-extension",
"type": "module",
"devDependencies": {
"@umbraco-cms/backoffice": "^17.0.0",
"typescript": "~5.8.0"
}
}重要提示:依赖仅用于IDE的TypeScript支持。运行时,导入会从主项目解析。
@umbraco-cms/backofficeRunning Your Extension
运行你的扩展
Start the mocked backoffice
启动模拟后台
bash
cd /path/to/Umbraco-CMS/src/Umbraco.Web.UI.Client
VITE_EXTERNAL_EXTENSION=/absolute/path/to/my-extension npm run dev:externalbash
cd /path/to/Umbraco-CMS/src/Umbraco.Web.UI.Client
VITE_EXTERNAL_EXTENSION=/absolute/path/to/my-extension npm run dev:externalOpen in browser
在浏览器中打开
Navigate to - your extension is loaded automatically.
http://localhost:5173访问 - 你的扩展会自动加载。
http://localhost:5173Hot reload
热重载
Changes to your extension files trigger hot reload - no restart needed.
修改扩展文件会触发热重载 - 无需重启服务。
Patterns
开发模式
Basic Element
基础元素
typescript
// 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;
}
}typescript
// 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;
}
}Element with Context
带上下文的元素
typescript
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;
}
}typescript
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;
}
}Context
上下文示例
typescript
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 };typescript
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 };Adding Tests
添加测试
Unit Tests
单元测试
Add unit tests using . See umbraco-unit-testing skill for full setup.
@open-wc/testingbash
npm install --save-dev @open-wc/testing @web/test-runner @web/test-runner-playwright使用添加单元测试。查看umbraco-unit-testing技能获取完整配置。
@open-wc/testingbash
npm install --save-dev @open-wc/testing @web/test-runner @web/test-runner-playwrightE2E Tests (Playwright)
端到端测试(Playwright)
Add E2E tests that run against the mocked backoffice. See umbraco-mocked-backoffice skill for patterns.
bash
npm install --save-dev @playwright/test
npx playwright install chromium添加针对模拟后台的端到端测试。查看umbraco-mocked-backoffice技能获取模式。
bash
npm install --save-dev @playwright/test
npx playwright install chromiumExamples
示例参考
Reference Example
参考示例
Location:
./examples/workspace-feature-toggle/A complete standalone example demonstrating:
- Workspace context with
UmbArrayState - Workspace view consuming context
- Workspace action executing context methods
- Workspace footer app showing summary
- 38 unit tests + 13 E2E tests
bash
cd examples/workspace-feature-toggle
npm install
npm test # Unit tests
npm run test:e2e # E2E tests (requires mocked backoffice running)位置:
./examples/workspace-feature-toggle/一个完整的独立示例,展示:
- 使用的工作区上下文
UmbArrayState - 消费上下文的工作区视图
- 执行上下文方法的工作区操作
- 显示摘要的工作区页脚
- 38个单元测试 + 13个端到端测试
bash
cd examples/workspace-feature-toggle
npm install
npm test # 单元测试
npm run test:e2e # 端到端测试(需要运行模拟后台)Official Umbraco Examples
官方Umbraco示例
Location:
Umbraco-CMS/src/Umbraco.Web.UI.Client/examples/27 official examples covering all extension types. Run any example:
bash
cd Umbraco-CMS/src/Umbraco.Web.UI.Client
npm run example位置:
Umbraco-CMS/src/Umbraco.Web.UI.Client/examples/27个官方示例,覆盖所有扩展类型。运行任意示例:
bash
cd Umbraco-CMS/src/Umbraco.Web.UI.Client
npm run exampleSelect from list
从列表中选择
---
---Naming Conventions
命名规范
| Item | Convention | Example |
|---|---|---|
| Directory | kebab-case describing feature | |
| Alias prefix | | |
| Element prefix | | |
| Context token | | |
| 项 | 规范 | 示例 |
|---|---|---|
| 目录 | 短横线命名描述功能 | |
| 别名前缀 | | |
| 元素前缀 | | |
| 上下文令牌 | | |
Troubleshooting
故障排除
Extension not appearing
扩展未显示
- Check exports a
index.tsarraymanifests - Verify the path in is absolute
VITE_EXTERNAL_EXTENSION - Check browser console for message
📦 Loading external extension from: - Ensure condition matches the section you're viewing
- 检查是否导出
index.ts数组manifests - 验证中的路径是否为绝对路径
VITE_EXTERNAL_EXTENSION - 检查浏览器控制台是否有消息
📦 Loading external extension from: - 确保条件与你查看的板块匹配
Import errors
导入错误
Imports should use . The Vite plugin resolves these from the main project.
@umbraco-cms/backoffice/*导入应使用。Vite插件会从主项目解析这些导入。
@umbraco-cms/backoffice/*"CustomElementRegistry" already defined
"CustomElementRegistry"已定义
Your extension's is being used instead of the main project's. The plugin should handle this, but ensure:
node_modulesexternal-extension-resolver- You're using
npm run dev:external - Imports use not relative paths to node_modules
@umbraco-cms/backoffice/*
你的扩展的被使用而不是主项目的。插件应处理此问题,但请确保:
node_modulesexternal-extension-resolver- 你使用的是
npm run dev:external - 导入使用而不是相对路径到node_modules
@umbraco-cms/backoffice/*
Changes not hot reloading
修改未触发热重载
Ensure the file is within the path specified by . Only files in that directory tree are watched.
VITE_EXTERNAL_EXTENSION确保文件在指定的路径内。只有该目录树中的文件会被监听。
VITE_EXTERNAL_EXTENSION