Модулі Javascript

Odoo підтримує три різні типи файлів JavaScript:

Як описано на сторінка керування активами, усі файли 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.

Є дуже важливий момент, який слід знати: 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, працює шляхом визначення функції define для глобального об’єкта odoo. Потім ми визначаємо кожен модуль 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

  • Відхилені модулі: Модуль повертає відхилений 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;
    });
});