umbraco-example-generator

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Umbraco 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 install
bash
git clone https://github.com/umbraco/Umbraco-CMS
cd Umbraco-CMS/src/Umbraco.Web.UI.Client
npm install

2. 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:external
Open
http://localhost:5173
- your extension appears in the Content section.

bash
cd Umbraco-CMS/src/Umbraco.Web.UI.Client
VITE_EXTERNAL_EXTENSION=/full/path/to/my-extension npm run dev:external
打开
http://localhost:5173
- 你的扩展将出现在内容板块中。

How It Works

工作原理

The Umbraco source (
Umbraco-CMS/src/Umbraco.Web.UI.Client
) provides two ways to load extensions:
Umbraco源码(
Umbraco-CMS/src/Umbraco.Web.UI.Client
)提供两种加载扩展的方式:

1. Internal Examples (
npm run example
)

1. 内部示例(
npm run example

Examples placed in the
examples/
folder inside the Umbraco source.
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 example

Select 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
)

2. 外部扩展(
npm run dev:external

Extensions 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:external
How it works:
  1. Sets
    VITE_UMBRACO_USE_MSW=on
    (mocked APIs)
  2. Creates
    @external-extension
    alias pointing to your extension path
  3. Imports
    @external-extension/index.ts
    and registers exports with
    umbExtensionsRegistry
  4. Resolves
    @umbraco-cms/backoffice/*
    imports from the main project (avoids duplicate element registrations)
可加载文件系统中任意位置的扩展 - 非常适合开发包。
bash
cd Umbraco-CMS/src/Umbraco.Web.UI.Client
VITE_EXTERNAL_EXTENSION=/path/to/your/extension npm run dev:external
工作机制
  1. 设置
    VITE_UMBRACO_USE_MSW=on
    (模拟API)
  2. 创建
    @external-extension
    别名指向你的扩展路径
  3. 导入
    @external-extension/index.ts
    并将导出内容注册到
    umbExtensionsRegistry
  4. 从主项目解析
    @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
index.ts
must export manifests (arrays or objects) that get registered automatically.

typescript
// 来自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.ts
必须导出清单(数组或对象),它们会被自动注册。

Setup

配置说明

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 install

Extension 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
index.ts
must export manifests that will be registered:
typescript
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.ts
必须导出可被注册的清单:
typescript
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
@umbraco-cms/backoffice
dependency is only for IDE TypeScript support. At runtime, imports are resolved from the main Umbraco project.

json
{
  "name": "my-extension",
  "type": "module",
  "devDependencies": {
    "@umbraco-cms/backoffice": "^17.0.0",
    "typescript": "~5.8.0"
  }
}
重要提示
@umbraco-cms/backoffice
依赖仅用于IDE的TypeScript支持。运行时,导入会从主项目解析。

Running 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:external
bash
cd /path/to/Umbraco-CMS/src/Umbraco.Web.UI.Client
VITE_EXTERNAL_EXTENSION=/absolute/path/to/my-extension npm run dev:external

Open in browser

在浏览器中打开

Navigate to
http://localhost:5173
- your extension is loaded automatically.
访问
http://localhost:5173
- 你的扩展会自动加载。

Hot 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
@open-wc/testing
. See umbraco-unit-testing skill for full setup.
bash
npm install --save-dev @open-wc/testing @web/test-runner @web/test-runner-playwright
使用
@open-wc/testing
添加单元测试。查看umbraco-unit-testing技能获取完整配置。
bash
npm install --save-dev @open-wc/testing @web/test-runner @web/test-runner-playwright

E2E 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 chromium

Examples

示例参考

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 example

Select from list

从列表中选择


---

---

Naming Conventions

命名规范

ItemConventionExample
Directorykebab-case describing feature
workspace-context-counter
Alias prefix
example.
example.workspaceView.counter
Element prefix
example-
example-counter-view
Context token
EXAMPLE_
+ SCREAMING_CASE
EXAMPLE_COUNTER_CONTEXT

规范示例
目录短横线命名描述功能
workspace-context-counter
别名前缀
example.
example.workspaceView.counter
元素前缀
example-
example-counter-view
上下文令牌
EXAMPLE_
+ 大写蛇形命名
EXAMPLE_COUNTER_CONTEXT

Troubleshooting

故障排除

Extension not appearing

扩展未显示

  1. Check
    index.ts
    exports a
    manifests
    array
  2. Verify the path in
    VITE_EXTERNAL_EXTENSION
    is absolute
  3. Check browser console for
    📦 Loading external extension from:
    message
  4. Ensure condition matches the section you're viewing
  1. 检查
    index.ts
    是否导出
    manifests
    数组
  2. 验证
    VITE_EXTERNAL_EXTENSION
    中的路径是否为绝对路径
  3. 检查浏览器控制台是否有
    📦 Loading external extension from:
    消息
  4. 确保条件与你查看的板块匹配

Import errors

导入错误

Imports should use
@umbraco-cms/backoffice/*
. The Vite plugin resolves these from the main project.
导入应使用
@umbraco-cms/backoffice/*
。Vite插件会从主项目解析这些导入。

"CustomElementRegistry" already defined

"CustomElementRegistry"已定义

Your extension's
node_modules
is being used instead of the main project's. The
external-extension-resolver
plugin should handle this, but ensure:
  • You're using
    npm run dev:external
  • Imports use
    @umbraco-cms/backoffice/*
    not relative paths to node_modules
你的扩展的
node_modules
被使用而不是主项目的。
external-extension-resolver
插件应处理此问题,但请确保:
  • 你使用的是
    npm run dev:external
  • 导入使用
    @umbraco-cms/backoffice/*
    而不是相对路径到node_modules

Changes not hot reloading

修改未触发热重载

Ensure the file is within the path specified by
VITE_EXTERNAL_EXTENSION
. Only files in that directory tree are watched.
确保文件在
VITE_EXTERNAL_EXTENSION
指定的路径内。只有该目录树中的文件会被监听。