Javascript 模塊?

Odoo支持三種不同類型的JavaScript文件:

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”關鍵字前不能有非空格字符。

  • 多行注釋或字符串不能以 importexport 開頭的行開頭

    // 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;
    });
});