营销增长
UI5 (SAPUI5/OpenUI5) 项目迁移到…
适用于把 UI5 (SAPUI5/OpenUI5) 应用或控件库从 JavaScript 转换为 TypeScript 的场景。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 38/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 55/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 60/100 ×0.15
适用场景
UI5 (SAPUI5/OpenUI5)特定
解决思路
- 通用规则:保留所有现有 JSDoc、文档和注释;转换过程中绝不删除 JSDoc 或注释。
- 通用规则:认真遵守本文档所有准则,并在需要时适当调整;每次转换步骤前考虑文档中所有相关细节。
- 通用规则:按步骤转换项目,先完成 TypeScript 项目设置,再转换其他文件依赖的核心文件,使其他文件之后可使用已类型化的核心文件;如有需要,可在 tsconfig.json 的 compilerOptions 中设置 allowJs: true 来运行半转换项目。
- 通用规则:避免 any 类型;不要走捷径,尝试找到正确类型或创建 interface,而不是使用 any。
- 通用规则:避免 unknown 类型转换;导入并使用实际 UI5 控件类型(基础类 sap/ui/core/Control 或更具体的类),检查 XMLView 以确定在控制器中调用 this.byId(...) 实际得到的控件类型;不要忘记使用具体事件类型,例如 Route$PatternMatchedEvent 用于路由事件。
- 通用规则:创建共享类型定义;将多个文件中可复用的类型定义放在集中位置,例如 src/types/ 下的文件。
- 项目设置 package.json:添加以下 dev dependencies(若尚未存在):{{dependencies}}。
- 项目设置 package.json:如果依赖已存在,不要提升其主版本号。
- 项目设置 package.json:不要移除现有依赖,只添加新配置。
- 项目设置 package.json:尽早安装依赖以验证类型可被找到。
- 项目设置 package.json:必须同时添加 @sapui5/types(或 @openui5/types)包作为 dev dependency,且版本与 UI5 项目匹配;框架类型和版本可在 ui5.yaml 中或使用 get_project_info MCP tool 查找。
- 项目设置 package.json:仅在依赖或其版本发生变化时,确保(或告诉用户)执行 npm install / yarn install(使用项目所用的包管理器)以获取变更后的依赖。
- 项目设置 package.json:typescript-eslint 依赖仅在项目已有 eslint 设置时相关。
- 项目设置 package.json:添加 ts-typecheck 脚本,值为 tsc --noEmit,到 package.json,以便检查 TypeScript 错误。
- 项目设置 tsconfig.json:添加 tsconfig.json 文件;使用给定样例作参考,但根据当前项目需求调整,例如调整 paths 映射。
- 项目设置 tsconfig.json:参考配置包括 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];paths 映射 com/myorg/myapp/* 到 ./webapp/*、unit/* 到 ./webapp/test/unit/*、integration/* 到 ./webapp/test/integration/*;exclude 为 [./webapp/test/e2e/**/*];include 为 [./webapp/**/*]。
- 项目设置 ui5.yaml:更新 ui5.yaml 以使用 ui5-tooling-transpile-task 和 ui5-tooling-transpile-middleware,并确保至少存在以下配置:builder.customTasks 中 ui5-tooling-transpile-task 在 afterTask: replaceVersion;server.customMiddleware 中 ui5-tooling-transpile-middleware 在 afterMiddleware: compression;ui5-middleware-livereload 在 afterMiddleware: compression。
- 项目设置 ui5.yaml:确保生成的 ui5.yaml 有效,避免重复条目,每个根配置只能存在一次;如果 server 等配置已存在,必须向其中添加内容,而不是添加第二个条目。
- 项目设置 eslint:仅当项目已设置 eslint 时,才用 TypeScript 特定部分增强 eslint 配置;如果 package.json 中没有 eslint 依赖且没有 eslint 配置,则不要做任何事。
- 项目设置 eslint:eslint v9 兼容的 eslint.config.mjs 可参考:导入 eslint、globals、tseslint;使用 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] } );实际内容取决于具体项目,必须调整。
- 应用代码转换 Step 1:将所有 UI5 类定义(SuperClass.extend(...))转换为标准 JavaScript class。
- 应用代码转换 Step 1:UI5 类配置对象(extend 的第二个参数)中的属性成为标准 JavaScript 类的成员。
- 应用代码转换 Step 1:在 JSDoc 注释中用 @namespace 标注类,以便反向转换重新添加命名空间;@namespace 注释必须紧接在 class 声明之前。
- 应用代码转换 Step 1:命名空间是完整包名加类名(extend 第一个参数)中类名之前的部分。
- 应用代码转换 Step 2:将 TypeScript UI5 应用改为使用现代 ES modules 和 imports。
- 应用代码转换 Step 2:将所有 UI5 模块定义和依赖加载调用(sap.ui.require(...)、sap.ui.define(...))转换为带 imports 的 ES modules;对于 sap.ui.define 还要转换为模块导出。
- 应用代码转换 Step 2:sap.ui.require 应转换为 just imports 且无 export。
- 应用代码转换 Step 2:避免导入模块的名称冲突。
- 应用代码转换 Step 2:提示:导入 sap/ui/core/Core 不提供类(不同于大多数其他 UI5 模块),而是提供 UI5 Core 的单例实例;因此导入的模块可直接用于 byId(...) 等方法,而不必调用返回单例的 sap.ui.getCore()。
- 应用代码转换 Step 2:当 sap.ui.require 动态使用时,例如方法体内 sap.ui.require([sap/m/MessageBox], function(MessageBox) { ... }),转换为动态 import,如 import(sap/m/MessageBox).then((MessageBox) => { ... })。
- 应用代码转换 Step 3:应用将 JavaScript 转换为 TypeScript 的一般知识:在需要处为方法参数和变量添加类型信息。
- 应用代码转换 Step 3:在类定义开头添加缺失的 private 成员类变量(带类型信息);在 JavaScript 中它们常在类实例生命周期中动态创建。
- 应用代码转换 Step 3:当使用 someFunction.bind(...) 时,将常规 function 转换为箭头函数,因为 TypeScript 似乎不会将绑定的 this 上下文类型传播到函数体。
- 应用代码转换 Step 3:如适用,在代码中定义进一步需要的类型和结构。
- 应用代码转换 Step 3:每当使用 UI5 类型(例如注解变量、方法参数/返回类型)时,不要使用带全局命名空间的 UI5 类型(如 sap.m.Button 或 sap.ui.core.Popup);而应从相应模块导入 UI5 类型(如 sap/m/Button 或 sap/ui/core/Popup,需要时添加 import),并使用导入的模块。
- 应用代码转换 Step 3:提示:在 UI5 控件的事件处理器中使用实际 UI5 控件事件,而不是 Event 或 MouseEvent 等浏览器事件;UI5 事件不同。例如处理 sap/m/Button 的 press 事件时使用来自 sap/m/Button 模块的 Button$PressEvent 和 Button$PressEventParameters。
- 应用代码转换 Step 3:提示:对于 UI5 控件 ABC 的任意事件 XYZ,可使用 ABC$XYZEvent 和 ABC$XYZEventParameters 类型。
- 应用代码转换 Step 3:提示:使用能提供所有需要属性的最具体类型。例如浏览器事件使用 KeyboardEvent 或 MouseEvent,而不是仅 Event;使用来自 sap/m/Button 模块的 Button$PressEvent,而不是 sap/ui/base/Event;这适用于所有类型,不只事件。
- 应用代码转换 Step 4:对泛型方法返回值进行类型转换:document.getElementById(...)、someUI5Control.getModel()、控制器内 this.byId() 等泛型 getter 返回所有可能类型的超类型;许多情况下必须将返回值转换为具体类型才能使用。实际类型通常可从上下文推导;如果无法推导,宁可避免转换也不要猜错类型。也不要在返回类型本来就是超类时转换为超类,如 sap.ui.model.Model。
- 应用代码转换 Step 4:对以下 UI5 方法同样处理:core.byId() / view.byId()、control.getBinding()、ownerComponent.getModel()、event.getSource()、component.getRootControl()、this.getOwnerComponent()。
- 应用代码转换 Step 4:类型转换有时还需要额外模块导入,以使类型(如 ODataModel)已知。
- 应用代码转换 Step 4:示例:导入应用的 component(AppComponent),以便将 this.getOwnerComponent() 转换为 AppComponent 并调用 getContentDensityClass。
- 应用代码转换 Step 4:注意:方法的 void 返回类型定义并非 TypeScript 强制要求,但根据 linting 设置可能有益。
- 应用代码转换 Step 5:此时剩余 TypeScript 错误应大幅减少;如果明确识别出一些错误,就修复它们;如有疑问,向开发者提及最后剩余的问题。
- UI5 控件转换:将自定义 UI5 控件从 JavaScript 转换为 TypeScript,适用于应用中的单个自定义控件以及控件库;除一般 TypeScript 转换(专有 UI5 类和语法)外,还需要特定模式。
- UI5 控件转换:理解运行时生成方法问题(关键):UI5 在运行时为所有 properties、aggregations、associations 和 events 生成 getter/setter(及更多)方法,导致 TypeScript 在开发时看不到这些方法并产生类型错误。
- UI5 控件转换:该问题影响属性 getter/setter:getText()、setText()、bindText();聚合方法:addItem()、removeItem()、getItems() 等;关联方法:getLabel()、setLabel();事件方法:attachPress()、detachPress()、firePress();以及构造函数的 settings 对象结构。
- UI5 控件转换:解决方案是安装 @ui5/ts-interface-generator 作为 dev dependency:npm install --save-dev @ui5/ts-interface-generator@{{ts-interface-generator-version}}。
- UI5 控件转换:为方便后续开发,在 package.json 中添加脚本,例如 watch:controls: npx @ui5/ts-interface-generator --watch。
- UI5 控件转换:注意与控件相关的 tsconfig 文件必须位于启动接口生成器的同一目录中;如果在项目根目录启动而覆盖 TypeScript 控件的 tsconfig 在子目录或名称不是 tsconfig.json,则调用 npx @ui5/ts-interface-generator --watch --config path/to/tsconfig.json。
- UI5 控件转换:所有控件 TypeScript 转换完成后,运行一次生成器:npm run watch:controls。
- UI5 控件转换:这会生成 *.gen.d.ts 文件(例如 MyControl.gen.d.ts),包含带所有运行时生成方法的 TypeScript interfaces;TypeScript 会将这些 interfaces 与控件类合并。
- UI5 控件转换:这些生成文件应提交到版本控制,且绝不手动编辑。
- UI5 控件转换:必需构造函数签名(关键手动步骤):运行接口生成器后,必须手动将终端输出中的构造函数签名复制到相应控件类中。
- UI5 控件转换:生成器输出类似三行:constructor(id?: string | $MyControlSettings); constructor(id?: string, settings?: $MyControlSettings); constructor(id?: string, settings?: $MyControlSettings) { super(id, settings); };将这些行复制到类体开头,metadata 定义之前。
- UI5 控件转换:控件 metadata 必须类型化为 MetadataOptions。
- UI5 控件转换:从 sap/ui/core/Element 导入 type { MetadataOptions } 用于控件(或最近的基类——也可用于 sap/ui/core/Object、sap/ui/core/ManagedObject 和 sap/ui/core/Component)。
- UI5 控件转换:使用 import type 而不是 import(仅设计时,无运行时影响)。
- UI5 控件转换:MetadataOptions 自 UI5 1.110 可用;更早版本使用 object。
- UI5 控件转换:类型化可防止从控件继承时的问题(继承的属性不应重复)。
- UI5 控件转换:需要 @namespace JSDoc 注释,以便 transformer 生成正确的 UI5 类名。
- UI5 控件转换:必须使用 export default 并在定义类时立即导出,否则 ts-interface-generator 会失败;正确:export default class MyControl extends Control { ... };错误:class MyControl extends Control { ... } 然后 export default MyControl;。
- UI5 控件转换:metadata 和 renderer 都定义为 static 类成员。
- UI5 控件转换:例如 static readonly metadata: MetadataOptions = { ... }; static renderer = { apiVersion: 2, render: function (rm: RenderManager, control: MyControl): void { ... } };。
- UI5 控件转换:renderer 也可放在单独文件中(在库中常见),转换为 TypeScript 时在这种情况下应保持分离。
- UI5 控件转换:对于单独 renderer,将 JavaScript 的 sap.ui.define([... ./MyControlRenderer], function (Control, MyControlRenderer) { ... return Control.extend(..., { renderer: MyControlRenderer, ... }) }) 转换为 TypeScript:import Control from 'sap/ui/core/Control'; import type { MetadataOptions } from 'sap/ui/core/Element'; import MyControlRenderer from './MyControlRenderer'; export default class MyControl extends Control { ... static renderer = MyControlRenderer; ... }。
- UI5 控件转换:完整控制示例可参考:JavaScript 中定义 metadata.properties.text、metadata.events.press、renderer、onclick 触发 firePress;TypeScript 中导入 Control、MetadataOptions、RenderManager,使用 @namespace,export default class,添加构造函数签名,static readonly metadata,static renderer,onclick(): void { this.firePress(); }。
- UI5 控件库转换:转换整个控件库(不只是应用中的单个控件)时需要额外步骤。
- UI5 控件库转换:库模块中的枚举(关键,避免 XSS 问题!):在 library.ts 中,枚举必须附加到全局库对象以实现 UI5 运行时兼容:import ObjectPath from 'sap/base/util/ObjectPath'; export enum ExampleColor { Red = 'Red', Green = 'Green', Blue = 'Blue' } const thisLib = ObjectPath.get('com.myorg.myui5lib') as {[key: string]: unknown}; thisLib.ExampleColor = ExampleColor;。
- UI5 控件库转换:解释为何对库中每个枚举都关键:控件属性以全局名称引用类型,如 type: 'com.myorg.myui5lib.ExampleColor';UI5 运行时需要通过此全局路径找到枚举;否则 UI5 无法验证属性类型;这会破坏类型检查并可能造成 XSS 漏洞,因为未检查内容可能被意外写入 HTML。
- UI5 控件库转换:在 tsconfig.json 中为库添加路径映射:paths 中 'com/myorg/mylib/*': ['./src/*']。
- 控件转换清单:1. 像常规 UI5 模块一样转换为 ES6 class/module。
- 控件转换清单:2. 添加 @namespace JSDoc 注释。
- 控件转换清单:3. 定义类时立即使用 export default。
- 控件转换清单:4. 将 metadata 类型化为 MetadataOptions(从合适基类导入)。
- 控件转换清单:5. 将 metadata 和 renderer 定义为 static 成员。
- 控件转换清单:6. 安装并运行 @ui5/ts-interface-generator。
- 控件转换清单:7. 将生成器输出中的构造函数签名复制到类中。
- 控件转换清单:8. 如果在库中:手动将枚举附加到全局库对象。
- 控件转换清单:9. 保留所有 JSDoc 注释和文档。
- 测试转换:将 UI5 测试代码从 JavaScript 转换为 TypeScript 有关键且不明显的模式;当需要转换测试时,查看 references/test_conversion.md 了解详情。
适用边界 · 注意事项
- 不适用于非 UI5 (SAPUI5/OpenUI5) 项目。
- 不适用于无法修改 package.json、tsconfig.json、ui5.yaml 或不能安装 devDependencies 的项目。
- 如果项目没有 eslint 设置,不要按本文档配置 eslint TypeScript 部分。