在 Bitbucket Server 插件开发中,自定义钩子配置页面经常需要嵌入一些交互式表单或校验逻辑。开发人员可能会顺手把常用的 jQuery 库打包进插件,但页面加载后却发现 AUI 下拉菜单、日期选择器、消息提示等组件集体失灵,控制台报出 $ is not a function 或 aui is not a function。这类问题几乎都指向同一个根源:插件引入的 jQuery 与 Atlassian AUI 所依赖的 jQuery 在全局作用域里互相覆盖。要解决这个冲突,不能简单删掉某一方,需要从资源加载机制、noConflict 调用和模块化隔离三个层面入手。

先说结论:最推荐的做法是让 Atlassian AUI 统一提供 jQuery,插件代码通过依赖声明复用同一份 jQuery,而不是自己再打包一份。如果项目历史包袱较重,必须保留自带 jQuery,则至少要在加载顺序和 noConflict 调用上做好隔离,同时把自有逻辑限制在局部变量中,避免污染全局。
一、冲突的根源:全局 $ 被两份 jQuery 轮流接管
Atlassian AUI 是 Bitbucket Server 前端组件的核心库,它在内部依赖一个特定版本的 jQuery,并通过 com.atlassian.auiplugin:ajs 资源将 jQuery 暴露到页面。很多插件开发者在自定义钩子页面中为了使用 Ajax 或 DOM 操作,会通过 <web-resource> 再引入一份自己下载的 jQuery。问题也正出在这里:jQuery 在加载时会把自己赋值给全局变量 window.jQuery 和 window.$,后加载的会覆盖先加载的。如果插件自己的 jQuery 版本高于 AUI 依赖的版本,AUI 内部代码调用 $(...) 时可能拿到新版本,但 AUI 组件的方法是新版本没有注册的,于是抛出类似 $(...).aui is not a function 的错误。
更隐蔽的情况是,新旧两个版本的 jQuery 虽然都能基本操作 DOM,但事件系统和数据缓存机制并不完全兼容。AUI 组件可能在文档上绑定了旧版 jQuery 的自定义事件,而插件代码使用新版 jQuery 触发事件时,两者的事件队列并不互通,造成交互逻辑看似随机失效。开发者如果只在控制台里关注 $ 是否存在,往往很难第一时间意识到是版本冲突。
因此解决冲突不能只看报错行,而要确认页面中 window.jQuery 的实际版本,以及 AUI 代码期望使用的 jQuery 实例是否一致。可以用浏览器开发者工具执行 jQuery.fn.jquery 查看当前版本,再对比 AUI 资源声明的依赖版本。
二、noConflict 的正确用法:先让出再局部引用
当无法立即移除插件自带的 jQuery 时,可以使用 jQuery.noConflict() 把全局的 $ 和 jQuery 引用归还给 AUI 此前加载的版本。常见的错误是只调用 $.noConflict() 却没有保存返回值,后续代码仍然使用 $,结果反而变回 undefined。正确的做法是在插件脚本执行的最开始,保存当前 jQuery 引用,再调用 noConflict(true) 释放全局变量,并用局部变量承接返回值。
下面是一个典型示例:
(function () {
var pluginJq = window.jQuery.noConflict(true);
// 执行到这里时,window.jQuery 和 window.$ 已经恢复为 AUI 的 jQuery
// 插件自身逻辑全部使用 pluginJq,不要使用全局 $
pluginJq(function () {
pluginJq('#custom-hook-select').on('change', function () {
var value = pluginJq(this).val();
console.log('selected', value);
});
});
})();
这里有两个关键点。第一,noConflict(true) 中的 true 参数表示同时释放 jQuery 和 $ 两个全局变量;如果只调用 noConflict(),则只释放 $,jQuery 仍会指向插件自己的版本,AUI 可能还会访问错误对象。第二,释放动作必须在插件脚本尽早执行,如果页面中已经有一段使用全局 $ 的 AUI 初始化代码执行过了,再释放可能造成已经绑定的事件丢失。因此 noConflict 方案只适合控制脚本加载顺序的场景,不适合异步加载或延迟执行的自定义钩子。
三、从资源声明入手:让 AUI 统一提供 jQuery
更稳妥的解决方式是从 Bitbucket Server 插件的资源声明阶段就避免重复加载。在 atlassian-plugin.xml 中,自定义钩子相关的 <web-resource> 不应再打包自己的 jQuery 文件,而应声明对 AUI 资源的依赖。AUI 提供的 jQuery 会通过依赖传递自动加载,插件脚本可以直接使用全局 $ 或通过 AJS 访问。
配置文件可以这样写:
<web-resource key="hook-config-resources" name="Hook Config Resources">
<dependency>com.atlassian.auiplugin:ajs</dependency>
<resource type="download" name="hook-config.js" location="/js/hook-config.js"/>
</web-resource>
这段配置的含义是,hook-config.js 加载之前,Atlassian 会先确保 com.atlassian.auiplugin:ajs 资源已经就绪。这个资源包含了 AUI 所需的 jQuery,插件代码可以直接使用 AJS.$ 或者全局 $,前提是没有其他资源再覆盖。这样做的好处是版本由 Atlassian 维护,升级 Bitbucket Server 时插件不会因为自带旧 jQuery 而出现兼容问题。
如果插件确实需要使用某些只在新版 jQuery 中可用的 API,而 AUI 提供的版本过旧,也不要轻易引入完整 jQuery 文件。可以考虑引入必要的 polyfill 或使用原生 JavaScript 替代,以免再次触发全局覆盖。对于 Bitbucket Server 插件来说,稳定性通常优先于新特性。
四、模块化隔离与最终验证
对于较大型的自定义钩子前端工程,建议采用 AMD 或 RequireJS 模块化方案。Atlassian 插件框架支持通过 define 声明模块依赖,这样插件代码中的 $ 只在模块作用域内有效,不会污染全局。可以借助 Webpack 的 externals 配置把 jQuery 排除在打包产物之外,运行时从 AUI 提供的全局对象获取。
例如在 Webpack 配置中增加:
module.exports = {
externals: {
jquery: 'jQuery',
aui: 'AJS'
}
};
这样打包后的 require('jquery') 会映射到全局 jQuery,也就是 AUI 提供的同一份实例,不会重复加载。对于未使用构建工具的插件,也可以在脚本开头用 var $ = AJS.$; 显式引用 AUI 的 jQuery,并确保没有其他脚本在之后覆盖全局 $。
最后不要忘记在真实页面中验证:打开浏览器开发者工具,执行 window.jQuery === AJS.$ 和 window.jQuery.fn.jquery,确认两个引用指向同一个 jQuery 对象且版本一致;再逐一测试自定义钩子中的下拉选择、日期选择、异步校验等交互,确保 AUI 组件不再报错。只有全局引用统一且模块隔离到位,Bitbucket Server 自定义钩子才能稳定运行。
Bitbucket Server自定义钩子jQuery冲突修改时间:2026-09-20 20:38:22