Javascript 模块¶
Odoo支持三种不同类型的JavaScript文件:
plain javascript files (no module system),
Odoo modules (using a custom module system),
如 assets management page 所述,所有的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更好地集成。
有一个非常重要的要点需要知道:Odoo 需要知道哪些文件应该被翻译成 Odoo 模块,哪些文件不应该被翻译。这是一个选择加入的系统:Odoo 会查看 JS 文件的第一行,检查是否包含字符串 @odoo-module。如果是,它将自动转换为一个 Odoo 模块。
例如,让我们考虑以下模块,位于 web/static/src/file_a.js
:
/** @odoo-module **/
import { someFunction } from './file_b';
export function otherFunction(val) {
return someFunction(val + 3);
}
请注意第一行的注释:它描述了应该将此文件转换。没有此注释的文件将保持不变(这很可能是一个错误)。然后将此文件转换为类似于以下内容的Odoo模块:
odoo.define('@web/file_a', 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
,并更新导入/导出语句。
另一个重要的点是,翻译后的模块有一个官方名称: @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
:
/** @odoo-module **/
import {something} from `./file_a`
但是 file_c
需要使用完整的名称:
/** @odoo-module **/
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`, 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`, 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', function (require) {
"use strict";
var A = require('module.A');
var B = ...; // something that involves A
return B;
});
定义模块的另一种方法是在第二个参数中显式给出依赖项列表。
odoo.define('module.Something', ['module.A', 'module.B'], function (require) {
"use strict";
var A = require('module.A');
var B = require('module.B');
// some code
});
如果某些依赖项缺失/未准备好,则该模块将不会被加载。几秒钟后,控制台会出现警告。
请注意,不支持循环依赖。这很有道理,但这意味着需要小心处理。
定义一个模块¶
The odoo.define
方法接受三个参数:
moduleName
: javascript 模块的名称。它应该是一个唯一的字符串。约定是使用 odoo 插件的名称后跟一个特定的描述。例如,web.Widget
描述了在web
插件中定义的一个模块,它导出了一个Widget
类(因为首字母大写)如果名称不唯一,将抛出异常并在控制台中显示。
dependencies
: 第二个参数是可选的。如果给定,它应该是一个字符串列表,每个字符串对应一个javascript模块。这描述了在模块执行之前需要加载的依赖项。如果这里没有明确给出依赖项,那么模块系统将通过调用toString函数从函数中提取它们,然后使用正则表达式找到所有的`require`语句。odoo.define('module.Something', ['web.ajax'], function (require) { "use strict"; var ajax = require('web.ajax'); // some code here return something; });
最后,最后一个参数是定义模块的函数。它的返回值是模块的值,可以传递给需要它的其他模块。请注意,对于异步模块,有一个小的例外,请参见下一节。
如果发生错误,它将被记录在控制台中(在调试模式下):
Missing dependencies
: These modules do not appear in the page. It is possible that the JavaScript file is not in the page or that the module name is wrongFailed modules
: A javascript error is detectedRejected modules
: The module returns a rejected Promise. It (and its dependent modules) is not loaded.Rejected linked modules
: Modules who depend on a rejected moduleNon loaded modules
: Modules who depend on a missing or a failed module
异步模块¶
有时候模块需要在准备好之前执行一些工作。例如,它可以执行 rpc 来加载一些数据。在这种情况下,模块可以简单地返回一个 promise。模块系统将在注册模块之前等待 promise 完成。
odoo.define('module.Something', function (require) {
"use strict";
var ajax = require('web.ajax');
return ajax.rpc(...).then(function (result) {
// some code here
return something;
});
});