JavaScript 模块¶
Odoo 支持三种不同类型的 javascript 文件:
:ref:`plain javascript files <frontend/modules/plain_js>`(无模块系统),
:ref:`Odoo modules <frontend/modules/odoo_module>`(使用自定义模块系统),
如 assets management page 中所述,所有 javascript 文件都捆绑在一起并提供给浏览器。请注意,本机 javascript 文件由 Odoo 服务器处理并转换为 Odoo 自定义模块。
让我们简要解释一下每种 javascript 文件背后的用途。纯 javascript 文件应仅保留用于外部库和一些小型特定低级用途。所有新的 javascript 文件都应在本机 javascript 模块系统中创建。自定义模块系统仅对旧的、尚未转换的文件有用。
纯 JavaScript 文件¶
纯 JavaScript 文件可以包含任意内容。建议在编写此类文件时使用 iife immediately invoked function execution 样式:
(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 modules。然后该文件将被转换为 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 modules 遵循不同的模块命名模式,因此存在一个允许平滑过渡到新系统的系统。目前,如果一个文件被转换为模块(因此遵循新的命名约定),项目中尚未转换为类似 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定义了一个小模块系统(位于文件:file: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:依赖于缺失或故障模块的模块