Javascript 模塊?
Odoo支持三種不同類型的JavaScript文件:
plain javascript files (無模塊系統),
Odoo模塊 (使用自定義模塊系統),
如 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需要一種方法來確定模塊是由路徑(如:
:file:
./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
});
如果某些依賴項缺失/未準備好,則該模塊將不會被加載。幾秒鐘后,控制臺會出現警告。
請注意,不支持循環依賴。這很有道理,但這意味著需要小心處理。
定義一個模塊?
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; });
最后,最后一個參數是定義模塊的函數。它的返回值是模塊的值,可以傳遞給需要它的其他模塊。請注意,對于異步模塊,有一個小的例外,請參見下一節。
如果發生錯誤,它將被記錄在控制臺中(在調試模式下):
缺少依賴項
: 這些模塊未出現在頁面中??赡苁?JavaScript 文件未在頁面中,或者模塊名稱錯誤。失敗的模塊
: 檢測到 JavaScript 錯誤Rejected modules
: 該模塊返回一個被拒絕的 Promise。它(以及它所依賴的模塊)不會被加載。被拒絕的鏈接模塊
: 依賴于被拒絕模塊的模塊未加載的模塊
: 依賴于缺失或失敗的模塊的模塊
異步模塊?
有時候模塊需要在準備好之前執行一些工作。例如,它可以執行 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;
});
});