关于 fix-js-globals
fix-js-globals 是面向 UI5 项目的编码技能,用于修复 UI5 linter 检测到但无法自动处理的 JavaScript 全局访问问题。它按不同模式提供模块导入与代码迁移规则,并强调一次性处理相关全局引用。
主要功能
- 01修复 UI5 全局访问问题
- 02替换已弃用的 Core API
- 03迁移 jQuery.sap 工具调用
- 04转换旧式模块与控制器
- 05识别并删除未使用赋值
适用场景
- 01处理 UI5 linter 的 no-globals 报错
- 02将旧式控制器改为 sap.ui.define
- 03替换控制器中的控件 ID 查找
- 04排查 jQuery.sap API 用法
MCP 相关
文档建议在需要额外 Core 方法替换时使用 UI5 MCP Server 的 get_api_reference 工具。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ ui5/ plugins-coding-agents --skill fix-js-globals安装后在 Claude Code 中输入/fix-js-globals确认已加载。
附带文件references/core-api-replacements.mdreferences/example-fix-session.md
详细介绍
修复 JavaScript 全局访问(no-globals)
修复 JavaScript 文件中 UI5 linter 检测到但无法自动修复的 no-globals 错误。运行 npx @ui5/linter --details 获取替换建议和文档链接。
关键规则——应用任何修复前请阅读
-
jQuery/$ 全局变量——保留 jQuery API 调用:修复 jQuery/$ 全局变量时,只添加
sap/ui/thirdparty/jquery依赖,并将$替换为jQuery。不要将标准 jQuery API 调用(jQuery.each、jQuery.extend、jQuery.proxy、jQuery.isEmptyObject等)替换为原生 JavaScript 等效写法。这些是标准 jQuery 方法,不是已弃用的 SAP API。 -
情况 9/10——一次性修复所有全局变量:将文件从
jQuery.sap.declare/require转换为sap.ui.define(情况 9 或 10)时,必须在同一轮中修复文件主体内所有其他全局访问模式。没有第二轮——必须一次性全部处理。请阅读下文“在同一轮中应用所有适用情况”一节。 -
无用代码——删除,不要导入:如果全局赋值存储的值在文件其他位置从未读取(例如,
this.BarColor = sap.ui.core.BarColor,且this.BarColor再未出现),请删除整个语句。不要为其添加导入。 -
控制器中的 byId 不要使用中间形式:控制器内的
sap.ui.getCore().byId("prefix--id")或jQuery("#prefix--id").control(0)应直接改为this.byId("id")。不要将其保留为Element.getElementById("prefix--id")。替换后,移除未使用的Element或jQuery导入。 -
使用 merge,而不是 deepExtend:将
jQuery.sap.extend(true, ...)替换为来自sap/base/util/merge的merge()。模块sap/base/util/deepExtend不存在。
按情况分类的修复策略
1. 赋值到全局命名空间
问题:代码在全局 sap 对象上创建自定义命名空间。
// Before
sap.ui.demo = sap.ui.demo || {};
sap.ui.demo.myApp = { formatter: function() { ... } };
// After — convert to AMD module
sap.ui.define("sap/ui/demo/myApp", [], function() {
"use strict";
return { formatter: function() { ... } };
});
1b. sap.ui.define 内部的全局命名空间赋值
问题:文件已位于 sap.ui.define 中,但仍赋值到全局命名空间并返回全局引用(这是移除 jQuery.sap.declare 后遗留的问题)。
linter 不会报告——请手动搜索:grep -rl "your\.project\.namespace\." webapp/ --include="*.js"
// Before
sap.ui.define([], function() {
"use strict";
com.example.app.utils.MyScripts = { runTests: function() { ... } };
return com.example.app.utils.MyScripts;
});
// After — local variable replaces global assignment
sap.ui.define([], function() {
"use strict";
var MyScripts = { runTests: function() { ... } };
return MyScripts;
});
关键规则:从命名空间末尾提取短名称。使用 var ShortName(作用域至关重要)。替换文件中所有对完整命名空间的引用。
1c. sap.ui.define 中全局命名空间的只读引用
问题:文件通过变量赋值从全局命名空间读取,而不是将模块作为依赖项导入。
不会被代码检查器报告——搜索:grep -rn "var .* = your\.project\.namespace\." webapp/ --include="*.js"
// Before
sap.ui.define(["sap/ui/core/mvc/Controller"], function(Controller) {
var Helper = com.example.app.utils.Helper;
// ...
});
// After — add as dependency, remove local var assignment
sap.ui.define([
"com/example/app/utils/Helper",
"sap/ui/core/mvc/Controller"
], function(Helper, Controller) {
// Helper is now available via dependency parameter
});
关键规则:
- 将点号表示法转换为斜杠表示法:
com.example.app.utils.Helper→"com/example/app/utils/Helper" - 在依赖数组开头添加依赖项(参见说明),并添加对应的参数
- 删除
var X = global.namespace.X;这一行 - 如果存在多个全局读取,一次性将它们全部添加为依赖项
- 确认参数名称与模块的短名称一致
- 原子性:每次全局变量→局部变量的替换都必须配对添加一个
sap.ui.define依赖项。由此引入的循环依赖稍后通过fix-cyclic-deps处理 - 修复后验证:搜索每个新增的变量名,确认其解析为参数、var/let/const,或
sap.ui.require调用
替换前,请先阅读目标模块的 return 语句:
模块的 return | 如何使用依赖参数 |
|---|---|
| 返回一个类 | new MyClass() 或 MyClass.staticMethod() |
| 返回一个包装器 | MyModule.getInstance() |
| 返回一个实例 | 直接调用 myInstance.method() |
| 不返回任何内容(仅产生副作用) | 先修复目标模块——添加 return |
副作用模块:如果目标模块以 }); 结尾且没有 return,请打开该模块,将全局命名空间赋值替换为局部变量,并添加 return varName;。然后在使用该模块的模块中正常导入。
2. sap.ui.getCore() 调用
问题:sap.ui.getCore() 已弃用;其方法已移至专用模块。
// Before — standalone init script (NOT a controller)
sap.ui.getCore().attachInit(function() { ... });
// After
sap.ui.define(["sap/ui/core/Core"], function(Core) {
Core.ready().then(function() { ... });
});
注意:Core.ready() 用于启动阶段的初始化脚本。在控制器中,UI5 已完成初始化,因此不要在此处使用。
主要替换方式(完整表格见 references/core-api-replacements.md):
| 已弃用 | 模块 | 调用 |
|---|---|---|
sap.ui.getCore().attachInit(fn) | sap/ui/core/Core | Core.ready().then(fn) |
sap.ui.getCore().byId(id) | sap/ui/core/Element | Element.getElementById(id)——在控制器中优先使用 this.byId() |
sap.ui.getCore().getEventBus() | sap/ui/core/EventBus | EventBus.getInstance() |
sap.ui.getCore().getLibraryResourceBundle(lib) | sap/ui/core/Lib | Lib.getResourceBundleFor(lib) |
如需替换更多 Core 方法,请使用 UI5 MCP Server 的 get_api_reference 工具。
3. 直接访问 sap.ui.core.Core
将 sap/ui/core/Core 添加到依赖数组,并移除全局访问:
// Before: var Core = sap.ui.core.Core;
// After: add "sap/ui/core/Core" to deps, use Core parameter directly
4. jQuery/$ 全局访问
重要:修复方式是添加导入,而不是替换 jQuery API 调用。jQuery.sap.*(包含 .sap.)已弃用,必须替换(情况 4b)。jQuery.*(不含 .sap.)或 jQuery(...) 是标准 jQuery,应保持不变。
// Before
jQuery("#el").addClass("x");
$(".container").css("display", "block");
jQuery.each(items, function(i, item) { ... });
// After — add dependency, rename $ to jQuery, keep all API calls unchanged
sap.ui.define([..., "sap/ui/thirdparty/jquery"], function(..., jQuery) {
jQuery("#el").addClass("x");
jQuery(".container").css("display", "block");
jQuery.each(items, function(i, item) { ... });
});
绝不要替换以下标准 jQuery 方法——它们在 UI5 中并未弃用:jQuery.each、jQuery.extend、jQuery.proxy、jQuery.isEmptyObject、jQuery.isArray、jQuery.inArray、jQuery.grep、jQuery.map、jQuery.type、jQuery.trim。
4a. 用于查找 UI5 控件的 jQuery DOM 查询 → this.byId()
问题:在控制器中,通过 jQuery("#prefix--id").control(0) 或 Element.closestTo() 获取 UI5 控件。
检测模式——以下写法都应改为 this.byId("<local-id>"):
jQuery("#<anything>--<id>").control(0)Element.closestTo(jQuery("#<anything>--<id>")[0])sap.ui.getCore().byId("<full-id>"),其中 ID 包含视图前缀Element.getElementById("<full-id>"),其中 ID 包含--
局部 ID 是最后一个 -- 之后的部分。替换后,移除未使用的 jQuery/Element 导入。
4b. jQuery.sap.* 工具访问
问题:jQuery.sap.* 调用是已弃用的 UI5 工具,需要使用专用替代模块。
// Before
jQuery.sap.log.info("msg");
var sId = jQuery.sap.uid();
// After
sap.ui.define([..., "sap/base/Log", "sap/base/util/uid"], function(..., Log, uid) {
Log.info("msg");
var sId = uid();
});
运行 npx @ui5/linter --details 查看建议的替代方案。完整表格见 references/core-api-replacements.md。
jQuery.sap.extend 的选择:
- 深拷贝(第一个参数为
true)→sap/base/util/merge→merge({}, obj1, obj2) - 扁平对象(单层属性)→
Object.assign({}, obj1, obj2)(无需导入) - 绝不要转换为
jQuery.extend(...)(会引入不必要的依赖) - 绝不要使用
sap/base/util/deepExtend(该模块不存在)
5. 条件式/探测式全局访问
问题:代码会检查某个全局对象是否存在:if (sap.ui.fl && sap.ui.fl.Utils) { ... }
修复:对于始终可用的模块,将其添加为 sap.ui.define 依赖。对于确实可选的模块,使用同步 sap.ui.require:
var FlUtils = sap.ui.require("sap/ui/fl/Utils");
if (FlUtils) { FlUtils.getComponentClassName(this); }
如需延迟加载,请使用异步方式:sap.ui.require(["module/path"], function(Mod) { ... })。
6. 自定义命名空间定义
与情况 1 的模式相同,但适用于非 SAP 命名空间(window.mycompany.myapp = {...})。将其转换为返回该对象的 sap.ui.define 模块。使用方通过依赖项导入。
7. 未导入的绑定类型字符串
// Before — global reference as string
value: { path: "/amount", type: "sap.ui.model.type.Float" }
// After — import type module, use class reference
value: { path: "/amount", type: new FloatType() } // FloatType from "sap/ui/model/type/Float"
8. delete 表达式
delete sap.ui.core.someTempProperty——这通常意味着代码存在问题。请彻底移除,或改用局部对象。
9. sap.ui.controller()——通过全局工厂定义控制器
范围:普通控制器定义。不适用于 Fiori Elements V2 扩展(请对这些扩展使用 fix-fiori-elements-extensions)。
检测:grep -rn 'sap\.ui\.controller(' webapp/ --include="*.js"
- 两个参数
sap.ui.controller("name", {...})= 定义 → 在此处修复 - 一个参数
sap.ui.controller("name")= 实例查找 → 在MODERNIZATION-ISSUES.md中记录
模式 A:位于现有的 sap.ui.define 中
// Before
sap.ui.define(["sap/m/MessageBox"], function(MessageBox) {
return sap.ui.controller("my.app.controller.Main", { ... });
});
// After — add Controller dep, replace factory with extend
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/m/MessageBox"
], function(Controller, MessageBox) {
return Controller.extend("my.app.controller.Main", { ... });
});
模式 B:不含 sap.ui.define(旧版模块系统)
// Before
jQuery.sap.declare("my.app.controller.Detail");
jQuery.sap.require("sap.ui.core.mvc.Controller");
sap.ui.controller("my.app.controller.Detail", { onInit: function() { ... } });
// After
sap.ui.define(["sap/ui/core/mvc/Controller"], function(Controller) {
"use strict";
return Controller.extend("my.app.controller.Detail", { onInit: function() { ... } });
});
步骤:移除 jQuery.sap.declare/require。用 sap.ui.define 包裹。将点号表示法依赖项转换为斜杠表示法。将 sap.ui.controller 替换为 Controller.extend。添加 return。添加 "use strict"。对文件主体应用所有内联修复(参见“应用所有修复”部分)。
边缘情况
- 缺少
return:Controller.extend()只会返回类——务必在其前添加return - 定义前存在模块级变量:保持原样,只需用
return包裹 extend 调用 - 控制器名称必须与文件路径匹配:保留现有名称,即使不匹配(可能是有意为之)
- 混合文件(定义 + 实例查找):修复定义,并在
MODERNIZATION-ISSUES.md中记录实例查找
10. jQuery.sap.declare/require——旧版模块定义
结构转换方式与情况 9 的模式 B 相同,但适用于非控制器模块:
// Before
jQuery.sap.declare("my.app.util.Formatter");
jQuery.sap.require("sap.ui.core.format.DateFormat");
my.app.util.Formatter = { formatDate: function(oDate) { ... } };
// After
sap.ui.define(["sap/ui/core/format/DateFormat"], function(DateFormat) {
"use strict";
return { formatDate: function(oDate) { ... } };
});
关键规则:移除 jQuery.sap.declare。将 jQuery.sap.require 转换为依赖项。移除全局赋值,改为 return 该对象。如果已有 sap.ui.define,则将剩余的 require 合并到现有依赖数组中。动态/条件式 require → sap.ui.require(["..."], callback)。存在多个 declare 或导出不明确时 → 标记为需要人工审核。应用所有内联修复(参见“应用所有修复”部分)。
11. 将运行时全局对象作为模块导入
问题:通过全局命名空间链访问 sap.ushell.Container 等运行时模块。
// Before
if (sap.ushell && sap.ushell.Container) {
sap.ushell.Container.getService("CrossApplicationNavigation");
}
// After — add as dependency
sap.ui.define(["sap/ushell/Container", ...], function(Container, ...) {
if (Container && Container.getService) {
Container.getService("CrossApplicationNavigation");
}
});
测试侧:使用 sinon 直接替身替换导入的模块。不要设置全局命名空间链(window.sap.ushell = {...})。sinon 和 QUnit 是 Test Starter 全局对象,无需导入。
sap.ui.define(["sap/ushell/Container"], function(Container) {
var oSandbox = sinon.createSandbox();
QUnit.test("...", function(assert) {
oSandbox.stub(Container, "setDirtyFlag");
// ...
});
});
12. jQuery.sap.sjax 现代化后的同步 XHR 防护
将 jQuery.sap.sjax 现代化为原生 XMLHttpRequest 后,务必通过状态检查来保护 xhr.responseText:
var xhr = new XMLHttpRequest();
xhr.open("GET", sUrl, false);
xhr.send();
if (xhr.readyState === 4 && xhr.status === 200) {
var oData = JSON.parse(xhr.responseText);
} else {
Log.error("Failed to load: " + sUrl);
}
| 上下文 | 失败时的回退方式 |
|---|---|
| 模拟服务响应处理程序 | oXhr.respondJSON(200, {}, JSON.stringify({"d": {"results": []}})) |
| 对响应执行 JSON.parse | 返回 {}(这样现有的 if (oResponse.data) 防护就能正常工作) |
| 初始化时加载配置 | 使用 Log.error(...) 提前返回 |
关键:在一次处理中应用所有适用情形
当文件触发 Case 9 或 10 时,在同一次处理中修复所有全局访问模式:
- 有
jQuery("#...")或jQuery(...)调用吗?→ Case 4a 或 4 - 有
sap.ui.getCore().byId(...)调用吗?→ Case 2,然后如果在控制器中则应用 Case 4a - 有
jQuery.sap.*调用吗?→ Case 4b - 有内联的
sap.ui.model.*、sap.m.*、sap.ui.core.*类引用吗?→ 导入依赖 - 有应用命名空间的全局引用吗?→ 导入依赖
- 替换后有未使用的导入吗?→ 从依赖数组和参数中移除
- 有
this.X = importedModule.X,且其他地方不再读取this.X吗?→ 删除(无用代码)
实施步骤
- 运行
npx @ui5/linter --details获取替换建议 - 找出错误模式并确定情形类型
- 应用相应的转换(添加依赖、替换全局引用、删除无用代码)
- 将 jQuery DOM 查找替换为
this.byId()后,移除未使用的导入 - 确认没有其他文件依赖已移除的全局赋值
备注
-
依赖插入位置——关键:始终将新依赖添加到数组开头(并将对应参数添加到函数开头)。许多旧文件中的依赖数量与参数数量不匹配(末尾有无对应参数的副作用导入)。添加到末尾会导致现有映射错位;添加到开头则可保留这些映射。
// Before (3 deps, 2 params — mismatch is common in legacy code) sap.ui.define(["sap/ui/core/mvc/Controller", "sap/m/MessageToast", "some/sideEffect/Module" ], function(Controller, MessageToast) { ... }); // After — new dep added at BEGINNING sap.ui.define(["sap/ui/core/Element", "sap/ui/core/mvc/Controller", "sap/m/MessageToast", "some/sideEffect/Module" ], function(Element, Controller, MessageToast) { ... }); -
参数名称应与模块的默认导出名称一致(例如,
sap/base/Log对应Log) -
测试文件中允许将
QUnit、sinon作为全局变量 -
允许使用全局变量
sap.ui.define、sap.ui.require、sap.ui.loader.config -
对于可选依赖,使用
sap.ui.require("module/path")(同步;如果尚未加载则返回 undefined) -
对于延迟加载,使用
sap.ui.require(["module/path"], callback)(异步)
示例修复流程
如需查看结合多种情形的完整前后示例,请阅读 references/example-fix-session.md。
相关技能
- fix-fiori-elements-extensions:适用于使用
registerControllerExtensions或 manifestsap.ui.controllerExtensions的 Fiori Elements V2 应用中的sap.ui.controller() - fix-pseudo-modules:适用于
no-pseudo-modules和no-implicit-globals错误(枚举导入、DataType 导入、OData 表达式函数) - fix-control-renderer:适用于渲染器相关问题(
no-deprecated-control-renderer-declaration、apiVersion、IconPool、rerender) - fix-xml-globals:适用于 XML 视图/片段中的
no-globals(格式化程序、通过core:require调用事件处理程序) - fix-linter-blind-spots:适用于 linter 无法检测、会导致运行时出错的全局命名空间模式(
sap.*之外的应用专属命名空间)。其中的 Case 1b 和 1c 与该技能中的模式 1-4 有重叠。 - fix-cyclic-deps:如果 Case 1c 的修复会导致循环依赖,请改用延迟
sap.ui.require,而不是常规的sap.ui.define依赖