基于 SOC 职业分类
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/UI5/plugins-coding-agents --skill ui5-typescript-conversion命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
正在显示 SKILL.md
UI5 MDC (sap.ui.mdc) best practices for OData V4 delegate-based controls. Use when creating/troubleshooting MDC FilterBar, Chart, Field, FilterField, ValueHelp, Link, MultiValueField, or Table. Covers delegate pattern, fetchProperties, PropertyInfo, p13nMode, updateBindingInfo, Condition.createCondition, and common errors (delegate not loaded, chart no data, fields not appearing). Also applies to JSON model (non-OData) MDC usage. Triggers: "MDC", "sap.ui.mdc", "delegate pattern", "fetchProperties", "PropertyInfo", "MDC personalization", "p13nMode", "MDC field not rendering", "MDC chart not binding", "MDC conditions", OData V4 freestyle with model-driven controls. Keywords: sap.ui.mdc, FilterBar, Chart, Field, FilterField, ValueHelp, Link, MultiValueField, delegate, fetchProperties, updateBindingInfo, PropertyInfo, OData V4, JSON model, TypeMap, p13nMode, conditions, FilterBarDelegate, ChartDelegate, ValueHelpDelegate, LinkDelegate, Condition.createCondition
UI5 smart controls (sap.ui.comp) best practices for OData V2 annotation-driven controls. Use when creating/troubleshooting SmartField, SmartForm, SmartFilterBar, SmartChart, SmartLink, SmartMultiInput, or SmartTable. Covers control selection, annotations (ValueList, FieldControl, SemanticObject, UI.LineItem, UI.Chart), mandatory rules, common errors, and per-control API reference. Triggers: "SmartField", "SmartForm", "SmartFilterBar", "SmartChart", "SmartLink", "SmartMultiInput", "sap.ui.comp", "smart control annotations", "OData V2 metadata-driven", "value help annotations", "ValueList annotation", "field control annotation", "semantic object navigation". Keywords: sap.ui.comp, SmartField, SmartForm, SmartFilterBar, SmartChart, SmartLink, SmartMultiInput, SmartTable, ValueHelpDialog, OData V2, annotations, ValueList, FieldControl, SemanticObject, entitySet, ControlConfiguration, GroupConfiguration, textArrangement, FilterProvider, initialise, beforeRebindChart, navigationTargetsObtained
UI5 development best practices and coding standards derived exclusively from official SAP UI5 guidelines. Use when writing UI5 applications to ensure modern, maintainable code following SAP standards. Covers: async module loading (sap.ui.define, ES6 imports, core:require), ComponentSupport initialization, data binding with OData types, i18n management, CSP compliance (no inline scripts), TypeScript event types (UI5 >= 1.115.0), MCP tooling (get_api_reference, run_ui5_linter), CAP integration patterns, and form creation rules (never SimpleForm, always Form with ColumnLayout). Keywords: ui5 coding standards, async loading, sap.ui.define, data binding, odata types, i18n translation, CSP no inline scripts, TypeScript event handlers, Button$PressEvent, ui5 linter, API reference, ComponentSupport, form layout, ColumnLayout, CAP integration, cds watch
| name | ui5-typescript-conversion |
| description | A skill for converting UI5 (SAPUI5/OpenUI5) projects to TypeScript. |
How to convert a UI5 (SAPUI5/OpenUI5) project to TypeScript: general rules, project setup changes, code conversion, and test conversion (separate file).
You MUST preserve existing JSDoc, documentation and comments - never remove JSDoc or comments during the conversion. When converting to a class, add @namespace but keep ALL existing JSDoc.
Before:
/**
* My cool controller, it does things.
*/
return Controller.extend("com.myorg.myapp.controller.BaseController", {
/**
* Convenience method for accessing the component of the controller's view.
* @returns {sap.ui.core.Component} The component of the controller's view
*/
getOwnerComponent: function () {
return Controller.prototype.getOwnerComponent.call(this);
},
});
After:
/**
* My cool controller, it does things.
* @namespace com.myorg.myapp.controller
*/
export default class BaseController extends Controller {
/**
* Convenience method for accessing the component of the controller's view.
* @returns {sap.ui.core.Component} The component of the controller's view
*/
public getOwnerComponent(): UIComponent {
return super.getOwnerComponent() as UIComponent;
}
}
Carefully respect all guidelines in this document. Before each conversion step, consider all relevant details.
Convert step by step: TypeScript project setup first, then central files other files depend on, so typed versions are available for consumers. "allowJs": true in tsconfig.json allows semi-converted projects.
any typeFind the proper type or create an interface instead of any:
// BAD: (this.getOwnerComponent() as any).getContentDensityClass();
// GOOD:
(this.getOwnerComponent() as AppComponent).getContentDensityClass()
unknown castsImport and use actual UI5 control types. Inspect the XMLView to find which control type you get from this.byId(...). Use specific event types like Route$PatternMatchedEvent.
// BAD: (this.byId("form") as unknown as {setVisible: (v: boolean) => void}).setVisible(false);
// GOOD:
import SimpleForm from "sap/ui/layout/form/SimpleForm";
(this.byId("form") as SimpleForm).setVisible(false);
Create shared types in a central location like src/types/.
Add the following dev dependencies if not already present:
{{dependencies}}
Do not increase existing major versions. Do not remove existing dependencies.
IMPORTANT: Also add @sapui5/types (or @openui5/types) matching the UI5 project version as dev dependency. Framework type and version from ui5.yaml or get_project_info MCP tool.
If dependencies changed, ensure npm install / yarn install is run. The typescript-eslint dependency is only relevant when the project already has eslint. Also add "ts-typecheck": "tsc --noEmit" script to package.json.
Add a tsconfig.json. Use this as reference, adapt paths to the project:
{
"compilerOptions": {
"target": "es2023",
"module": "es2022",
"moduleResolution": "node",
"skipLibCheck": true,
"allowJs": true,
"strict": true,
"strictNullChecks": false,
"strictPropertyInitialization": false,
"outDir": "./dist",
"rootDir": "./webapp",
"types": ["@sapui5/types", "@types/jquery", "@types/qunit"],
Add ui5-tooling-transpile-task and ui5-tooling-transpile-middleware:
builder:
customTasks:
- name: ui5-tooling-transpile-task
afterTask: replaceVersion
server:
customMiddleware:
- name: ui5-tooling-transpile-middleware
afterMiddleware: compression
- name: ui5-middleware-livereload
afterMiddleware: compression
Avoid duplicate entries — add to existing server/builder sections if they exist.
Only when eslint is already set up, enhance it with TypeScript-specific parts. Example eslint v9 eslint.config.mjs:
import eslint from "@eslint/js";
import globals from "globals";
import tseslint from "typescript-eslint";
export default tseslint.config(
eslint.configs.recommended,
...tseslint.configs.recommended,
...tseslint.configs.recommendedTypeChecked,
{
languageOptions: {
globals: {
...globals.browser,
sap: "readonly"
},
ecmaVersion: 2023,
parserOptions: {
project: true,
tsconfigRootDir: import.meta.dirname
}
}
},
{
ignores: ["eslint.config.mjs"]
}
);
Convert SuperClass.extend(...) to a standard class. Properties in the config object (second extend parameter) become class members. Annotate the class with @namespace in a JSDoc comment (it must immediately precede the class declaration) — the namespace is the part of the full name (first extend parameter) that precedes the class name.
Before:
var App = Controller.extend("ui5tssampleapp.controller.App", {
onInit: function _onInit() {
// apply content density mode to root view
this.getView().addStyleClass(this.getOwnerComponent().getContentDensityClass());
}
});
After:
/**
* @namespace ui5tssampleapp.controller
*/
export default class App extends Controller {
public onInit(): void {
// apply content density mode to root view
this.getView().addStyleClass((this.getOwnerComponent() as AppComponent).getContentDensityClass());
}
}
Convert sap.ui.define(...) to ES imports + export default. Convert sap.ui.require(...) to imports (no export). Avoid name clashes between imported modules.
Before:
sap.ui.define(["sap/ui/core/mvc/Controller"], function (Controller) {
class App extends Controller {
// ... as above
}
return App;
});
After:
import Controller from "sap/ui/core/mvc/Controller";
/**
* @namespace ui5tssampleapp.controller
*/
export default class App extends Controller {
// ... as above
}
Dynamic sap.ui.require inside method bodies → dynamic import:
import("sap/m/MessageBox").then((MessageBox) => { /* ... */ });
Hint: importing
sap/ui/core/Coreprovides the singleton instance, not the class.
someFunction.bind(...) to arrow functions (TypeScript does not propagate the bound this type into the function body).IMPORTANT: Never use a UI5 type with its global namespace (like
sap.m.Button). Always import it from the module (likesap/m/Button) and use the imported name.
Wrong:
const b: sap.m.Button;
function getPopup(): sap.ui.core.Popup { /* ... */ }
Correct:
import Button from "sap/m/Button";
import Popup from "sap/ui/core/Popup";
const b: Button;
function getPopup(): Popup { /* ... */ }
Use UI5 control event types, not browser events like Event or MouseEvent — UI5 events are different:
import Button from "sap/m/Button";
import { Button$PressEvent } from "sap/m/Button";
import { Table$RowSelectionChangeEvent } from "sap/ui/table/Table";
export default class Main extends BaseController {
onPress(oEvent: Button$PressEvent): void {
const button = oEvent.getSource() as Button;
}
onRowSelectionChange(oEvent: Table$RowSelectionChangeEvent): void {
const selectedContext = oEvent.getParameter("rowContext");
}
}
For any event XYZ of a UI5 control ABC, types
ABC$XYZEventandABC$XYZEventParametersare available.
Use the most specific type that provides all needed properties: KeyboardEvent/MouseEvent not Event for browser events; Button$PressEvent not sap/ui/base/Event.
Generic methods return the super-type of all possible types although in practice it will usually be a specific sub-type. Cast the return value to the specific sub-type when needed; derive the actual type from context. This often requires an additional import. Most prominently affected: core.byId()/view.byId(), control.getBinding(), ownerComponent.getModel(), event.getSource(), component.getRootControl(), this.getOwnerComponent().
For the app controller example above, this adds an import of the app's component (AppComponent) so the cast can be done — without it, getOwnerComponent() returns a sap.ui.core.Component, which does not have the getContentDensityClass method.
import Controller from "sap/ui/core/mvc/Controller";
import AppComponent from "../Component";
/**
* @namespace ui5tssampleapp.controller
*/
export default class App extends Controller {
public onInit(): void {
// apply content density mode to root view
this.getView().addStyleClass((this.getOwnerComponent() as AppComponent).getContentDensityClass());
}
}
Do not cast to a superclass when it's already the returned type. Avoid guessing — skip the cast if the actual type isn't clear.
At this point remaining TypeScript errors should be vastly reduced. Fix clearly recognizable ones. In case of doubt, mention the last remaining issues to the developer.
Converting custom UI5 controls requires specific patterns beyond the general conversion. This applies to single custom controls within applications and to control libraries.
This is the most important aspect to understand.
UI5 generates getter/setter (and more) methods for properties, aggregations, associations, and events at runtime. TypeScript cannot see them at development time. A control with a text property in its metadata will have getText()/setText() at runtime, but TypeScript errors on control.getText(). TypeScript also does not know the constructor's settings-object structure. This affects property getters/setters (getText, setText, bindText), aggregation methods (addItem, removeItem, getItems), association methods (getLabel, setLabel), event methods (attachPress, detachPress, firePress), and the constructor settings object.
npm install --save-dev @ui5/ts-interface-generator@{{ts-interface-generator-version}}
Add a script to package.json to make subsequent development easier:
{
"scripts": {
"watch:controls": "npx @ui5/ts-interface-generator --watch"
}
}
NOTE: if the tsconfig covering the controls is in a subdirectory or has a different name, use --config path/to/tsconfig.json.
After converting all controls, run the generator once:
npm run watch:controls
It generates *.gen.d.ts files with interfaces for all runtime-generated methods, which TypeScript merges with the control class. Commit these files; never edit them manually.
Copy the constructor signatures from the generator's terminal output into the beginning of the class body, before the metadata definition:
export default class MyControl extends Control {
// The following three lines were generated and should remain as-is to make TypeScript aware of the constructor signatures
constructor(id?: string | $MyControlSettings);
constructor(id?: string, settings?: $MyControlSettings);
constructor(id?: string, settings?: $MyControlSettings) { super(id, settings); }
static readonly metadata: MetadataOptions = {
// ...
};
}
The control metadata must be typed as MetadataOptions:
import type { MetadataOptions } from "sap/ui/core/Element";
export default class MyControl extends Control {
static readonly metadata: MetadataOptions = {
properties: {
"text": "string"
}
};
}
sap/ui/core/Element (or the closest base class: ManagedObject, Component); use import type (design-time only).object for earlier versions.The @namespace JSDoc annotation is required for the transformer to generate correct UI5 class names:
/**
* @namespace ui5.typescript.helloworld.control
*/
export default class MyControl extends Control {
// ...
}
Must use export default immediately — a separate export breaks ts-interface-generator:
// CORRECT:
export default class MyControl extends Control {
// ...
}
// WRONG:
class MyControl extends Control {
// ...
}
export default MyControl;
Both metadata and renderer are static class members. The renderer can be inline or in a separate file:
import Control from "sap/ui/core/Control";
import type { MetadataOptions } from "sap/ui/core/Element";
import RenderManager from "sap/ui/core/RenderManager";
/**
* @namespace ui5.typescript.helloworld.control
*/
export default class MyControl extends Control {
static readonly metadata: MetadataOptions = {
properties: {
"text": "string"
},
events: {
"press": {}
}
};
static renderer = {
apiVersion: 2,
render: function (rm: RenderManager, control: MyControl): void {
rm.openStart("div", control);
rm.openEnd();
rm.text(control.getText());
rm.close("div");
}
};
onclick(): void {
.();
}
}
When the renderer is in a separate file (common in libraries), it should stay separate — import it (import MyControlRenderer from "./MyControlRenderer";) and assign static renderer = MyControlRenderer;.
When converting entire control libraries (not just single controls in apps), additional steps are required.
In library.ts, enums must be attached to the global library object for UI5 runtime compatibility:
import ObjectPath from "sap/base/util/ObjectPath";
export enum ExampleColor {
Red = "Red",
Green = "Green",
Blue = "Blue"
}
// CRITICAL: Attach to global library object
const thisLib = ObjectPath.get("com.myorg.myui5lib") as {[key: string]: unknown};
thisLib.ExampleColor = ExampleColor;
Why this is critical for every enum in the library:
type: "com.myorg.myui5lib.ExampleColor".For libraries, add path mappings for the library namespace:
{
"compilerOptions": {
"paths": {
"com/myorg/mylib/*": ["./src/*"]
}
}
}
Convert to ES6 class/module with @namespace and immediate export default; type metadata as MetadataOptions; define metadata and renderer as static members; install and run @ui5/ts-interface-generator and copy the constructor signatures from its output; attach enums to the global library object if in a library; preserve all JSDoc.
There are critical, non-obvious patterns for converting UI5 test code from JavaScript to TypeScript. See the test conversion document for details when tests need to be converted.