JavaScript 模块¶
Odoo 支持三种不同类型的 JavaScript 文件:
普通 JavaScript 文件 (无模块系统),
Odoo 模块 (使用自定义模块系统),
正如 资源管理页面 中所述,所有 JavaScript 文件都会被打包并提供给浏览器。需要注意的是,原生 JavaScript 文件会被 Odoo 服务器处理并转换为 Odoo 自定义模块。
让我们简要说明每种 JavaScript 文件的用途。普通 JavaScript 文件应仅用于外部库和一些小型特定的底层目的。所有新的 JavaScript 文件都应在原生 JavaScript 模块系统中创建。自定义模块系统仅适用于尚未转换的旧文件。
普通 JavaScript 文件¶
普通 JavaScript 文件可以包含任意内容。建议在编写此类文件时使用 iife 立即调用函数执行 风格:
(function () {
// some code here
let a = 1;
console.log(a);
})();
此类文件的优点是避免将局部变量泄露到全局作用域中。
显然,普通 JavaScript 文件不具备模块系统的优势,因此需要注意打包中的顺序(因为浏览器会严格按照该顺序执行它们)。
注解
在 Odoo 中,所有外部库都作为普通 JavaScript 文件加载。
原生 JavaScript 模块¶
Odoo 的 JavaScript 代码使用原生 JavaScript 模块系统。这更简单,并且通过更好的 IDE 集成提升了开发体验。
让我们考虑以下模块,位于 web/static/src/file_a.js
:
import { someFunction } from "./file_b";
export function otherFunction(val) {
return someFunction(val + 3);
}
有一个非常重要的点需要了解:默认情况下,Odoo 会将 /static/src
和 /static/tests
下的文件转译为 Odoo 模块。此文件随后会被转译为如下所示的 Odoo 模块:
odoo.define('@web/file_a', ['@web/file_b'], function (require) {
'use strict';
let __exports = {};
const { someFunction } = require("@web/file_b");
__exports.otherFunction = function otherFunction(val) {
return someFunction(val + 3);
};
return __exports;
)};
因此,如您所见,这种转换基本上是在顶部添加了 odoo.define
并更新了导入/导出语句。这是一个选择退出的系统,您可以告诉转译器忽略该文件。
/** @odoo-module ignore **/
(function () {
const sum = (a, b) => a + b;
console.log(sum(1, 2));
)();
注意第一行的注释:它描述了该文件应被忽略。
在其他文件夹中,默认情况下文件不会被转译,而是选择加入的机制。Odoo 会查看 JS 文件的第一行,并检查是否包含带有 @odoo-module 但不带 ignore 标签的注释。如果是,则会自动将其转换为 Odoo 模块。
/** @odoo-module **/
export function sum(a, b) {
return a + b;
}
另一个重要点是,转译后的模块有一个正式名称:@web/file_a。这是该模块的实际名称。所有相对导入也会被转换。位于 Odoo 插件 some_addon/static/src/path/to/file.js
中的每个文件都会分配一个以插件名称为前缀的名称,例如:@some_addon/path/to/file。
相对导入可以工作,但前提是模块必须位于同一个 Odoo 插件中。因此,假设我们有以下文件结构:
addons/
web/
static/
src/
file_a.js
file_b.js
stock/
static/
src/
file_c.js
文件 file_b
可以像这样导入 file_a
:
import {something} from `./file_a`;
但是 file_c
需要使用全名:
import {something} from `@web/file_a`;
别名模块¶
由于 Odoo 模块 遵循不同的模块命名模式,因此存在一个系统以实现向新系统的平稳过渡。目前,如果某个文件被转换为模块(并因此遵循新的命名约定),项目中尚未转换为 ES6 语法的其他文件将无法正确加载该模块。别名通过创建一个小的代理函数来映射旧名称和新名称,从而使模块可以通过其新名称 和 旧名称调用。
要添加这样的别名,文件顶部的注释标签应如下所示:
/** @odoo-module alias=web.someName**/
import { someFunction } from './file_b';
export default function otherFunction(val) {
return someFunction(val + 3);
}
然后,转译后的模块还将使用请求的名称创建别名:
odoo.define(`web.someName`, ['@web/file_a'], function(require) {
return require('@web/file_a')[Symbol.for("default")];
});
别名的默认行为是重新导出它们所代理模块的 default
值。这是因为“经典”模块通常导出一个单独的值,并且该值会被直接使用,这大致符合默认导出的语义。然而,也可以更直接地委托,并完全遵循被代理模块的行为:
/** @odoo-module alias=web.someName default=0**/
import { someFunction } from './file_b';
export function otherFunction(val) {
return someFunction(val + 3);
}
在这种情况下,这将定义一个包含原始模块导出的所有值的别名:
odoo.define(`web.someName`, ["@web/file_a"], function(require) {
return require('@web/file_a');
});
注解
使用此方法只能定义一个别名。如果您需要另一个别名(例如,为同一个模块提供三个名称),则必须手动添加代理。这不是良好的实践,应尽量避免,除非没有其他选择。
限制¶
出于性能原因,Odoo 不使用完整的 JavaScript 解析器来转换原生模块。因此,存在一些限制,包括但不限于:
import
或export
关键字不能以非空格字符开头,多行注释或字符串不能包含以
import
或export
开头的行。// supported import X from "xxx"; export X; export default X; import X from "xxx"; /* * import X ... */ /* * export X */ // not supported var a= 1;import X from "xxx"; /* import X ... */
当您导出对象时,它不能包含注释。
// supported export { a as b, c, d, } export { a } from "./file_a" // not supported export { a as b, // this is a comment c, d, } export { a /* this is a comment */ } from "./file_a"
Odoo 需要一种方法来确定模块是由路径(如
./views/form_view
)还是名称(如web.FormView
)描述的。为此,它使用一种启发式方法:如果名称中包含/
,则认为它是路径。这意味着 Odoo 不再真正支持带有/
的模块名称。
由于“经典”模块尚未被弃用,目前也没有计划移除它们,因此如果在使用原生模块时遇到问题或受到其限制的影响,您可以并且应该继续使用它们。两种风格可以在同一个 Odoo 插件中共存。
Odoo 模块系统¶
Odoo 定义了一个小型模块系统(位于文件 addons/web/static/src/js/boot.js
中,需要首先加载)。Odoo 模块系统受 AMD 启发,通过在全局 odoo 对象上定义 define
函数来工作。我们通过调用该函数来定义每个 JavaScript 模块。在 Odoo 框架中,模块是一段代码,将在条件允许时尽快执行。它有一个名称,并可能有一些依赖项。当其依赖项加载完成后,模块也会随之加载。模块的值是定义该模块的函数的返回值。
例如,它可能看起来像这样:
// in file a.js
odoo.define('module.A', [], function (require) {
"use strict";
var A = ...;
return A;
});
// in file b.js
odoo.define('module.B', ['module.A'], function (require) {
"use strict";
var A = require('module.A');
var B = ...; // something that involves A
return B;
});
如果某些依赖项缺失或未准备好,则模块将不会被加载。几秒钟后会在控制台中出现警告。
请注意,不支持循环依赖。这是合理的,但意味着需要注意。
定义模块¶
odoo.define
方法接收三个参数:
moduleName
:JavaScript 模块的名称。它应该是一个唯一的字符串。命名约定是以 Odoo 插件名称开头,后跟具体描述。例如,web.Widget
描述了在web
插件中定义的一个模块,该模块导出了一个Widget
类(因为首字母大写)。如果名称不是唯一的,将抛出异常并显示在控制台中。
dependencies
:它应该是一个字符串列表,每个字符串对应一个 JavaScript 模块。这描述了在模块执行之前需要加载的依赖项。最后,最后一个参数是一个定义模块的函数。它的返回值是模块的值,可能会传递给依赖它的其他模块。
odoo.define('module.Something', ['web.ajax'], function (require) { "use strict"; var ajax = require('web.ajax'); // some code here return something; });
如果发生错误,它将在控制台中记录(在调试模式下):
Missing dependencies缺失的依赖项
:这些模块未出现在页面中。可能是 JavaScript 文件未包含在页面中,或者模块名称错误。Failed modules失败的模块
:检测到 JavaScript 错误Rejected modules被拒绝的模块
:模块返回了一个被拒绝的 Promise。它(及其依赖模块)未被加载。Rejected linked modules被拒绝的关联模块
:依赖于被拒绝模块的模块Non loaded modules未加载的模块
:依赖于缺失或失败模块的模块