Mọi component, widget, patch, service ta viết suốt loạt này đều phải nằm trong một asset bundle thì web client mới nạp. Ta cứ khai vào web.assets_backend như phản xạ. Bài này giải thích vì sao đúng chỗ đó — và cho thấy hậu quả rất khó chịu khi khai nhầm: code im lặng không chạy, không một dòng lỗi.

Bundle là gì

Odoo không nạp từng file JS/CSS riêng lẻ. Nó gom chúng thành các bundle — mỗi bundle là một gói lớn được biên dịch, nén, và phục vụ như một tài nguyên. Quan trọng: mỗi loại trang chỉ nạp những bundle của nó. Web client (/odoo, sau đăng nhập) nạp web.assets_backend; trang website công khai nạp web.assets_frontend; PDF report nạp web.report_assets_common.

Khai file vào bundle nào là việc của __manifest__.py:

Ảnh chụp đoạn mã nền tối khối assets trong file manifest py của module, có khoá web.report_assets_common chứa file scss report kèm chú thích bundle cho PDF report, khoá web.assets_backend chứa lần lượt the css, the_thanh_vien scss, dashboard js, dashboard xml kèm chú thích bundle cho web client sau đăng nhập nơi đặt OWL và registry, và chú thích cuối rằng thứ tự trong list là thứ tự nạp còn path nhận cả glob dạng src hai sao trên tất cả

Hình 1: Khối assets thật của module demo. Mỗi khoá là tên một bundle, giá trị là danh sách đường dẫn file (nhận cả glob .../src/**/*). File OWL của web client — dashboard.js, dashboard.xml — nằm trong web.assets_backend. Thứ tự trong list chính là thứ tự nạp, quan trọng khi một file phụ thuộc file khác.

Bản đồ các bundle chính

Sơ đồ nền tối liệt kê các bundle asset chính của Odoo, mỗi dòng một khối màu, web.assets_backend là web client sau đăng nhập nơi đặt mọi OWL registry client action widget, web.assets_frontend là trang website công khai không nạp trong web client backend, web.report_assets_common là CSS SCSS cho PDF report không dùng cho màn hình, point_of_sale gạch dưới assets_pos riêng cho giao diện điểm bán hàng POS, web.assets_tests chỉ nạp khi chạy test tour không có ở môi trường thật

Hình 2: Vài bundle hay gặp. Điều cần khắc cốt: mọi thứ của web client (OWL, registry, client action, widget) vào web.assets_backend. web.assets_frontend là cho website công khai — hoàn toàn tách biệt, không nạp trong /odoo.

Đặt nhầm bundle = code chết lặng

Đây là phần đáng nhớ nhất. Mình tạo một file bundletest.js chỉ làm đúng một việc: console.log(...) khi được nạp. Rồi thử khai nó ở hai bundle khác nhau và mở web client /odoo, bắt console:

Ảnh chụp so sánh trước sau trên nền tối, phần một khai file trong web.assets_backend đúng cho web client thì console trả về một mảng chứa dòng BUNDLE bundletest js đã được nạp trong web client màu xanh lá nghĩa là file được nạp và console log chạy, phần hai chuyển file sang web.assets_frontend sai cho web client thì console trả về mảng rỗng màu đỏ nghĩa là file không được nạp trong odoo không log không lỗi không cảnh báo, code biến mất một cách im lặng là bẫy khó chịu nhất khi đặt nhầm bundle

Hình 3: Cùng một file, hai bundle, cùng mở /odoo (bắt console bằng Playwright). Trong web.assets_backend: console in ra dòng log — file được nạp. Chuyển sang web.assets_frontend: console rỗng — file không được nạp trong web client, và không có lỗi, không cảnh báo nào cả. Đây là cái bẫy: code không chạy nhưng mọi thứ trông "bình thường", bạn ngồi debug logic trong khi vấn đề chỉ là sai bundle.

Đúng như các bài OWL trước cảnh báo về "lỗi chỉ ở trình duyệt", nhưng còn tệ hơn: đây không phải lỗi, mà là im lặng. File nằm trong một bundle không được trang này nạp, nên nó đơn giản không tồn tại với web client. Không stack trace để lần.

Vài điều thực dụng

  • OWL/registry của web client → luôn web.assets_backend. Đây là 95% trường hợp bạn gặp khi mở rộng giao diện quản trị.
  • Template OWL (.xml) cũng khai vào bundle, cùng chỗ với JS dùng nó. Quên khai file .xml là component không có template và vỡ khi render (cái này có lỗi, khác với đặt nhầm bundle).
  • Thứ tự khai = thứ tự nạp. File b.js cần cái gì a.js định nghĩa thì a.js phải đứng trước. Có sẵn thao tác ('after', path), ('before', path), ('replace', ...), ('remove', ...) khi bạn cần chèn vào giữa hay gỡ file của module khác.
  • Sửa danh sách asset là phải -u module để manifest được đọc lại; sửa nội dung file thì cũng cần nâng cấp/khởi động lại để bundle biên dịch lại.

Ba ý mang về

  1. Odoo gom JS/CSS/template thành bundle; mỗi trang chỉ nạp bundle của nó. Web client nạp web.assets_backend, website nạp web.assets_frontend, PDF report nạp web.report_assets_common.
  2. Mọi thứ của web client (OWL, registry, client action, widget) khai vào web.assets_backend trong 'assets' của __manifest__.py. Thứ tự trong list là thứ tự nạp.
  3. Đặt nhầm bundle = code chết lặng lẽ — không chạy, không lỗi, không cảnh báo. Khi một mảnh JS "không có tác dụng gì" mà console sạch, hãy kiểm bundle trước khi đi lục logic.

Trong web.assets_backend ta khai cả file .scss. Phần sau đi vào SCSS trong Odoo: dùng biến, ghi đè biến theme của Odoo để đổi màu/khoảng cách toàn hệ thống, và cách SCSS của bạn hoà vào bộ style sẵn có.